@decido/kernel-bridge 1.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # 🚀 @decido/kernel-bridge
2
+
3
+ > Tauri v2 IPC Bridge for Decido OS
4
+
5
+ Bienvenido a la documentación oficial de **@decido/kernel-bridge**, un componente integral del ecosistema **Decido OS**.
6
+
7
+ ## 📦 Instalación
8
+
9
+ Para aprovisionar este módulo dentro de otra área del monorepo o consumirlo remotamente:
10
+
11
+ ```bash
12
+ npm install @decido/kernel-bridge
13
+ # o mediante el gestor oficial del monorepo
14
+ pnpm add @decido/kernel-bridge
15
+ ```
16
+
17
+ ## 🔧 Estructura y Dependencias
18
+
19
+ Este paquete está diseñado para interoperar de forma nativa con la infraestructura central.
20
+ Para su correcto funcionamiento en un entorno aislado (Sandboxed), se apoya en los siguientes cimientos tecnológicos:
21
+
22
+ - `@anthropic-ai/sdk`
23
+ - `@decido/sdk`
24
+ - `@google/genai`
25
+ - `@tauri-apps/api`
26
+ - `@tauri-apps/plugin-haptics`
27
+
28
+ ## 🔐 Licencia y Privacidad
29
+ El código de este componente se encuentra auditado y restringido (Sin Sourcemaps).
30
+ Propiedad Intelectual Protegida - Framework Decido OS.
31
+ Distribuido bajo licencia **UNLICENSED**.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decido/kernel-bridge",
3
- "version": "1.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Tauri v2 IPC Bridge for Decido OS",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -19,7 +19,7 @@
19
19
  "openai": "^6.24.0",
20
20
  "socket.io-client": "^4.8.3",
21
21
  "uuid": "^13.0.0",
22
- "@decido/sdk": "1.0.0"
22
+ "@decido/sdk": "4.0.1"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/react": "^19.0.0",
@@ -30,6 +30,9 @@
30
30
  "access": "public"
31
31
  },
32
32
  "license": "UNLICENSED",
33
+ "files": [
34
+ "dist"
35
+ ],
33
36
  "scripts": {
34
37
  "lint": "eslint \"src/**/*.ts*\"",
35
38
  "build": "tsup src/index.ts --format esm --dts --minify --clean"
@@ -1,13 +0,0 @@
1
-
2
- Debugger listening on ws://127.0.0.1:58975/0314506c-e1de-4a58-be5e-b4ac774586f4
3
- For help, see: https://nodejs.org/en/docs/inspector
4
- Debugger attached.
5
-
6
- > @decido/kernel-bridge@1.0.0 build /Users/julioramirez/dev/active/OnBoardingDecido/packages/kernel-bridge
7
- > tsc
8
-
9
- Debugger listening on ws://127.0.0.1:58979/db35dc6b-ec5f-499c-b2bb-a281886a7590
10
- For help, see: https://nodejs.org/en/docs/inspector
11
- Debugger attached.
12
- Waiting for the debugger to disconnect...
13
- Waiting for the debugger to disconnect...
@@ -1,30 +0,0 @@
1
-
2
- Debugger listening on ws://127.0.0.1:60130/80df8f38-b3d4-4cfb-a50d-56cde2cc32c8
3
- For help, see: https://nodejs.org/en/docs/inspector
4
- Debugger attached.
5
-
6
- > @macia/kernel-bridge@1.0.0 lint /Users/julioramirez/dev/active/Decido/packages/core-decido/kernel-bridge
7
- > eslint "src/**/*.ts*"
8
-
9
- Debugger listening on ws://127.0.0.1:60166/f14ee9d9-5a8a-4ad3-a957-a0f39414225c
10
- For help, see: https://nodejs.org/en/docs/inspector
11
- Debugger attached.
12
-
13
- Oops! Something went wrong! :(
14
-
15
- ESLint: 9.39.3
16
-
17
- ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
18
-
19
- From ESLint v9.0.0, the default configuration file is now eslint.config.js.
20
- If you are using a .eslintrc.* file, please follow the migration guide
21
- to update your configuration file to the new format:
22
-
23
- https://eslint.org/docs/latest/use/configure/migration-guide
24
-
25
- If you still have problems after following the migration guide, please stop by
26
- https://eslint.org/chat/help to chat with the team.
27
-
28
- Waiting for the debugger to disconnect...
29
-  ELIFECYCLE  Command failed with exit code 2.
30
- Waiting for the debugger to disconnect...
@@ -1,219 +0,0 @@
1
- /**
2
- * PeerNetworkPanel — P2P mesh control panel
3
- *
4
- * Shows connected peers, hosting controls, and shared results.
5
- * Uses Decido Aura design tokens for full theme compatibility.
6
- */
7
-
8
- import React, { useState, useCallback } from 'react';
9
- import { usePeerMesh } from '../hooks/usePeerMesh';
10
- import type { PeerInfo } from '../services/PeerMesh';
11
-
12
- // ─── Helpers ────────────────────────────────────────────────
13
-
14
- function timeAgo(ts: number): string {
15
- const diff = Date.now() - ts;
16
- if (diff < 60_000) return 'just now';
17
- if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
18
- return `${Math.floor(diff / 3_600_000)}h ago`;
19
- }
20
-
21
- // ─── Component ──────────────────────────────────────────────
22
-
23
- export function PeerNetworkPanel() {
24
- const {
25
- peers, isHosting, isConnected, sharedResults, peerCount,
26
- startHost, stopHost, connectToPeer, disconnect,
27
- } = usePeerMesh();
28
-
29
- const [peerAddress, setPeerAddress] = useState('');
30
- const [hostPort, setHostPort] = useState('9876');
31
-
32
- const handleConnect = useCallback(() => {
33
- if (!peerAddress.trim()) return;
34
- connectToPeer(peerAddress.trim());
35
- setPeerAddress('');
36
- }, [peerAddress, connectToPeer]);
37
-
38
- const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
39
- if (e.key === 'Enter') handleConnect();
40
- }, [handleConnect]);
41
-
42
- return (
43
- <div className="flex flex-col gap-3 p-4 bg-surface-secondary rounded-2xl border border-border-subtle">
44
- {/* Header */}
45
- <div className="flex items-center justify-between">
46
- <div className="flex items-center gap-2">
47
- <span className="text-lg">🌐</span>
48
- <h3 className="text-sm font-semibold text-text-primary">Peer Network</h3>
49
- {peerCount > 0 && (
50
- <span className="px-1.5 py-0.5 text-[10px] font-bold rounded-full bg-emerald-500/20 text-accent-green">
51
- {peerCount}
52
- </span>
53
- )}
54
- </div>
55
- <StatusBadge isHosting={isHosting} isConnected={isConnected} />
56
- </div>
57
-
58
- {/* Connection Controls */}
59
- <div className="flex flex-col gap-2 p-3 rounded-xl bg-surface-tertiary/50">
60
- {/* Host Toggle */}
61
- <div className="flex items-center justify-between">
62
- <div className="flex flex-col">
63
- <span className="text-[11px] font-medium text-text-secondary">Host a Group</span>
64
- <span className="text-[9px] text-text-muted">Others can connect to you</span>
65
- </div>
66
- <div className="flex items-center gap-2">
67
- <input
68
- className="w-16 px-2 py-1 text-[11px] font-mono text-text-primary bg-surface-primary
69
- border border-border-subtle rounded-lg text-center
70
- focus:border-border-strong focus:outline-none transition-colors"
71
- value={hostPort}
72
- onChange={(e) => setHostPort(e.target.value)}
73
- placeholder="Port"
74
- disabled={isHosting}
75
- />
76
- <button
77
- onClick={() => isHosting ? stopHost() : startHost(Number(hostPort))}
78
- className={`px-3 py-1 text-[11px] font-medium rounded-lg transition-all
79
- ${isHosting
80
- ? 'bg-red-500/15 text-accent-red hover:bg-red-500/25'
81
- : 'bg-emerald-500/15 text-accent-green hover:bg-emerald-500/25'
82
- }`}
83
- >
84
- {isHosting ? '⏹ Stop' : '▶ Start'}
85
- </button>
86
- </div>
87
- </div>
88
-
89
- {/* Divider */}
90
- <div className="h-px bg-border-subtle" />
91
-
92
- {/* Connect to Peer */}
93
- <div className="flex flex-col gap-1.5">
94
- <span className="text-[11px] font-medium text-text-secondary">Join a Group</span>
95
- <div className="flex gap-2">
96
- <input
97
- className="flex-1 px-3 py-1.5 text-[11px] font-mono text-text-primary bg-surface-primary
98
- border border-border-subtle rounded-lg placeholder:text-text-muted
99
- focus:border-border-strong focus:outline-none transition-colors"
100
- value={peerAddress}
101
- onChange={(e) => setPeerAddress(e.target.value)}
102
- onKeyDown={handleKeyDown}
103
- placeholder="192.168.1.10:9876"
104
- disabled={isConnected}
105
- />
106
- {isConnected ? (
107
- <button
108
- onClick={disconnect}
109
- className="px-3 py-1.5 text-[11px] font-medium rounded-lg bg-red-500/15 text-accent-red
110
- hover:bg-red-500/25 transition-all"
111
- >
112
- Disconnect
113
- </button>
114
- ) : (
115
- <button
116
- onClick={handleConnect}
117
- className="px-3 py-1.5 text-[11px] font-medium rounded-lg bg-cyan-500/15 text-accent-cyan
118
- hover:bg-cyan-500/25 transition-all"
119
- >
120
- Connect
121
- </button>
122
- )}
123
- </div>
124
- </div>
125
- </div>
126
-
127
- {/* Connected Peers */}
128
- {peers.length > 0 && (
129
- <div className="flex flex-col gap-1.5">
130
- <span className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
131
- Connected Peers
132
- </span>
133
- {peers.map((peer) => (
134
- <PeerRow key={peer.id} peer={peer} />
135
- ))}
136
- </div>
137
- )}
138
-
139
- {/* Empty State */}
140
- {!isHosting && !isConnected && peers.length === 0 && (
141
- <div className="flex flex-col items-center justify-center py-5 text-text-muted gap-1.5">
142
- <span className="text-2xl opacity-40">🔗</span>
143
- <span className="text-xs text-center">
144
- Host a group or connect to a peer<br />
145
- to share AI results
146
- </span>
147
- </div>
148
- )}
149
-
150
- {/* Shared Results */}
151
- {sharedResults.length > 0 && (
152
- <div className="flex flex-col gap-1.5 mt-1">
153
- <span className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
154
- Shared Results ({sharedResults.length})
155
- </span>
156
- <div className="flex flex-col gap-1 max-h-40 overflow-y-auto">
157
- {sharedResults.slice(-8).reverse().map((r, i) => (
158
- <div
159
- key={`${r.timestamp}-${i}`}
160
- className="px-3 py-2 rounded-xl bg-surface-glass text-[10px]"
161
- >
162
- <div className="flex items-center justify-between mb-1">
163
- <span className="font-medium text-accent-purple">
164
- {r.peerId.slice(0, 8)}
165
- </span>
166
- <span className="text-text-muted font-mono">{r.model}</span>
167
- </div>
168
- <p className="text-text-secondary line-clamp-2">{r.response.slice(0, 120)}...</p>
169
- </div>
170
- ))}
171
- </div>
172
- </div>
173
- )}
174
- </div>
175
- );
176
- }
177
-
178
- // ─── Subcomponents ──────────────────────────────────────────
179
-
180
- function StatusBadge({ isHosting, isConnected }: { isHosting: boolean; isConnected: boolean }) {
181
- if (isHosting) {
182
- return (
183
- <span className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-emerald-500/15 text-[10px] font-medium text-accent-green">
184
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
185
- Hosting
186
- </span>
187
- );
188
- }
189
- if (isConnected) {
190
- return (
191
- <span className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-cyan-500/15 text-[10px] font-medium text-accent-cyan">
192
- <span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
193
- Connected
194
- </span>
195
- );
196
- }
197
- return (
198
- <span className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-surface-glass text-[10px] text-text-muted">
199
- <span className="w-1.5 h-1.5 rounded-full bg-surface-tertiary" />
200
- Offline
201
- </span>
202
- );
203
- }
204
-
205
- function PeerRow({ peer }: { peer: PeerInfo }) {
206
- return (
207
- <div className="flex items-center justify-between px-3 py-2 rounded-xl bg-surface-glass transition-colors hover:bg-surface-tertiary/50">
208
- <div className="flex items-center gap-2">
209
- <span className="w-2 h-2 rounded-full bg-emerald-400" />
210
- <span className="text-xs font-medium text-text-primary">{peer.name}</span>
211
- </div>
212
- <div className="flex items-center gap-2 text-[10px] text-text-muted">
213
- <span className="font-mono">{peer.address}</span>
214
- <span>·</span>
215
- <span>{timeAgo(peer.lastSeen)}</span>
216
- </div>
217
- </div>
218
- );
219
- }
@@ -1,172 +0,0 @@
1
- /**
2
- * TokenWalletPanel — Visual spending dashboard for AI providers
3
- *
4
- * Shows total tokens, estimated cost, and per-provider breakdown.
5
- * Uses Decido Aura design tokens for full theme compatibility.
6
- */
7
-
8
- import React, { useState } from 'react';
9
- import { useTokenWallet } from '../hooks/useTokenWallet';
10
- import type { TokenUsageEntry } from '../services/TokenWallet';
11
-
12
- // ─── Provider colors (accent palette) ───────────────────────
13
-
14
- const PROVIDER_COLORS: Record<string, string> = {
15
- ollama: 'text-accent-green',
16
- gemini: 'text-accent-blue',
17
- anthropic: 'text-accent-purple',
18
- openai: 'text-accent-cyan',
19
- mlx: 'text-accent-amber',
20
- };
21
-
22
- const PROVIDER_BG: Record<string, string> = {
23
- ollama: 'bg-emerald-500/10',
24
- gemini: 'bg-blue-500/10',
25
- anthropic: 'bg-purple-500/10',
26
- openai: 'bg-cyan-500/10',
27
- mlx: 'bg-amber-500/10',
28
- };
29
-
30
- const PROVIDER_ICONS: Record<string, string> = {
31
- ollama: '🦙',
32
- gemini: '✨',
33
- anthropic: '🧠',
34
- openai: '⚡',
35
- mlx: '🍎',
36
- };
37
-
38
- // ─── Helpers ────────────────────────────────────────────────
39
-
40
- function formatCost(usd: number): string {
41
- if (usd === 0) return 'FREE';
42
- if (usd < 0.01) return `$${usd.toFixed(6)}`;
43
- if (usd < 1) return `$${usd.toFixed(4)}`;
44
- return `$${usd.toFixed(2)}`;
45
- }
46
-
47
- function formatTokens(n: number): string {
48
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
49
- if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
50
- return String(n);
51
- }
52
-
53
- function timeAgo(ts: number): string {
54
- const diff = Date.now() - ts;
55
- if (diff < 60_000) return 'just now';
56
- if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
57
- if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
58
- return `${Math.floor(diff / 86_400_000)}d ago`;
59
- }
60
-
61
- // ─── Component ──────────────────────────────────────────────
62
-
63
- export function TokenWalletPanel() {
64
- const { summary, totalTokens, totalCost, totalCalls, byProvider, clearHistory, getRecentHistory } = useTokenWallet();
65
- const [showHistory, setShowHistory] = useState(false);
66
-
67
- const providers = Object.entries(byProvider).sort((a, b) => b[1].cost - a[1].cost);
68
- const history = showHistory ? getRecentHistory(15) : [];
69
-
70
- return (
71
- <div className="flex flex-col gap-3 p-4 bg-surface-secondary rounded-2xl border border-border-subtle">
72
- {/* Header */}
73
- <div className="flex items-center justify-between">
74
- <div className="flex items-center gap-2">
75
- <span className="text-lg">💰</span>
76
- <h3 className="text-sm font-semibold text-text-primary">Token Wallet</h3>
77
- </div>
78
- <button
79
- onClick={clearHistory}
80
- className="text-[10px] px-2 py-0.5 rounded-md bg-surface-glass text-text-muted
81
- hover:bg-surface-tertiary hover:text-text-secondary transition-colors"
82
- >
83
- Clear
84
- </button>
85
- </div>
86
-
87
- {/* Total Stats */}
88
- <div className="grid grid-cols-3 gap-2">
89
- <StatCard label="Tokens" value={formatTokens(totalTokens)} accent="text-accent-cyan" />
90
- <StatCard label="Cost" value={formatCost(totalCost)} accent={totalCost === 0 ? 'text-accent-green' : 'text-accent-amber'} />
91
- <StatCard label="Calls" value={String(totalCalls)} accent="text-accent-blue" />
92
- </div>
93
-
94
- {/* Per-Provider Breakdown */}
95
- {providers.length > 0 && (
96
- <div className="flex flex-col gap-1.5 mt-1">
97
- <span className="text-[10px] font-medium text-text-muted uppercase tracking-wider">By Provider</span>
98
- {providers.map(([id, data]) => (
99
- <div
100
- key={id}
101
- className={`flex items-center justify-between px-3 py-2 rounded-xl ${PROVIDER_BG[id] || 'bg-surface-glass'} transition-colors`}
102
- >
103
- <div className="flex items-center gap-2">
104
- <span className="text-sm">{PROVIDER_ICONS[id] || '🤖'}</span>
105
- <span className={`text-xs font-medium ${PROVIDER_COLORS[id] || 'text-text-primary'}`}>
106
- {id.charAt(0).toUpperCase() + id.slice(1)}
107
- </span>
108
- </div>
109
- <div className="flex items-center gap-3 text-[11px]">
110
- <span className="text-text-muted">{data.calls} calls</span>
111
- <span className="text-text-secondary font-mono">{formatTokens(data.tokens)}</span>
112
- <span className="font-semibold text-text-primary font-mono">{formatCost(data.cost)}</span>
113
- </div>
114
- </div>
115
- ))}
116
- </div>
117
- )}
118
-
119
- {/* Empty State */}
120
- {providers.length === 0 && (
121
- <div className="flex flex-col items-center justify-center py-6 text-text-muted gap-1">
122
- <span className="text-2xl opacity-50">📊</span>
123
- <span className="text-xs">No AI calls recorded yet</span>
124
- </div>
125
- )}
126
-
127
- {/* History Toggle */}
128
- <button
129
- onClick={() => setShowHistory(!showHistory)}
130
- className="text-[10px] text-text-muted hover:text-text-secondary transition-colors self-center"
131
- >
132
- {showHistory ? '▲ Hide recent' : '▼ Show recent calls'}
133
- </button>
134
-
135
- {/* History List */}
136
- {showHistory && history.length > 0 && (
137
- <div className="flex flex-col gap-1 max-h-48 overflow-y-auto">
138
- {history.reverse().map((entry, i) => (
139
- <HistoryRow key={`${entry.timestamp}-${i}`} entry={entry} />
140
- ))}
141
- </div>
142
- )}
143
- </div>
144
- );
145
- }
146
-
147
- // ─── Subcomponents ──────────────────────────────────────────
148
-
149
- function StatCard({ label, value, accent }: { label: string; value: string; accent: string }) {
150
- return (
151
- <div className="flex flex-col items-center p-2 rounded-xl bg-surface-tertiary/50">
152
- <span className="text-[9px] text-text-muted uppercase tracking-wider">{label}</span>
153
- <span className={`text-base font-bold font-mono ${accent}`}>{value}</span>
154
- </div>
155
- );
156
- }
157
-
158
- function HistoryRow({ entry }: { entry: TokenUsageEntry }) {
159
- return (
160
- <div className="flex items-center justify-between px-2 py-1.5 rounded-lg bg-surface-glass text-[10px]">
161
- <div className="flex items-center gap-1.5">
162
- <span>{PROVIDER_ICONS[entry.provider] || '🤖'}</span>
163
- <span className="text-text-secondary font-mono">{entry.model.split('/').pop()?.slice(0, 18)}</span>
164
- </div>
165
- <div className="flex items-center gap-2">
166
- <span className="text-text-muted">{formatTokens(entry.totalTokens)}</span>
167
- <span className="text-text-primary font-mono">{formatCost(entry.estimatedCostUsd)}</span>
168
- <span className="text-text-muted">{timeAgo(entry.timestamp)}</span>
169
- </div>
170
- </div>
171
- );
172
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * usePeerMesh — React hook for P2P group mesh
3
- *
4
- * Exposes peer list, hosting controls, and context sharing.
5
- */
6
-
7
- import { useState, useEffect, useCallback, useRef } from 'react';
8
- import {
9
- peerMesh,
10
- type PeerInfo,
11
- type SharedResult,
12
- } from '../services/PeerMesh';
13
-
14
- export function usePeerMesh() {
15
- const [peers, setPeers] = useState<PeerInfo[]>([]);
16
- const [isHosting, setIsHosting] = useState(false);
17
- const [isConnected, setIsConnected] = useState(false);
18
- const [sharedResults, setSharedResults] = useState<SharedResult[]>([]);
19
- const resultsRef = useRef(sharedResults);
20
- resultsRef.current = sharedResults;
21
-
22
- useEffect(() => {
23
- const unsubPeers = peerMesh.onPeersChanged((newPeers) => {
24
- setPeers([...newPeers]);
25
- });
26
-
27
- const unsubHosting = peerMesh.onHostingChanged((hosting) => {
28
- setIsHosting(hosting);
29
- });
30
-
31
- const unsubConnected = peerMesh.onConnectionChanged((connected) => {
32
- setIsConnected(connected);
33
- });
34
-
35
- const unsubResult = peerMesh.onSharedResult((result) => {
36
- setSharedResults(prev => [...prev.slice(-49), result]);
37
- });
38
-
39
- return () => {
40
- unsubPeers();
41
- unsubHosting();
42
- unsubConnected();
43
- unsubResult();
44
- };
45
- }, []);
46
-
47
- const startHost = useCallback((port?: number) => {
48
- peerMesh.startHost(port);
49
- }, []);
50
-
51
- const stopHost = useCallback(() => {
52
- peerMesh.stopHost();
53
- }, []);
54
-
55
- const connectToPeer = useCallback((address: string) => {
56
- peerMesh.connectToPeer(address);
57
- }, []);
58
-
59
- const disconnect = useCallback(() => {
60
- peerMesh.disconnect();
61
- }, []);
62
-
63
- const shareResult = useCallback((result: Omit<SharedResult, 'peerId' | 'timestamp'>) => {
64
- peerMesh.shareResult(result);
65
- }, []);
66
-
67
- return {
68
- peers,
69
- isHosting,
70
- isConnected,
71
- sharedResults,
72
- peerCount: peers.length,
73
- startHost,
74
- stopHost,
75
- connectToPeer,
76
- disconnect,
77
- shareResult,
78
- };
79
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * useTokenWallet — React hook for the Token Wallet
3
- *
4
- * Subscribes to wallet changes and exposes a reactive summary.
5
- */
6
-
7
- import { useState, useEffect, useCallback } from 'react';
8
- import { tokenWallet, type WalletSummary, type TokenUsageEntry } from '../services/TokenWallet';
9
-
10
- export function useTokenWallet() {
11
- const [summary, setSummary] = useState<WalletSummary>(tokenWallet.getSummary());
12
-
13
- useEffect(() => {
14
- const unsubscribe = tokenWallet.subscribe(setSummary);
15
- return unsubscribe;
16
- }, []);
17
-
18
- const clearHistory = useCallback(() => {
19
- tokenWallet.clearHistory();
20
- }, []);
21
-
22
- const getRecentHistory = useCallback((count = 20): TokenUsageEntry[] => {
23
- return tokenWallet.getRecentHistory(count);
24
- }, []);
25
-
26
- return {
27
- summary,
28
- clearHistory,
29
- getRecentHistory,
30
- totalTokens: summary.totalTokens,
31
- totalCost: summary.totalCostUsd,
32
- totalCalls: summary.totalCalls,
33
- byProvider: summary.byProvider,
34
- };
35
- }