@sesamespace/hivemind 0.5.3 → 0.5.5

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 (96) hide show
  1. package/.github/workflows/memory-release.yml +89 -0
  2. package/config/default.toml +12 -0
  3. package/data/lancedb/contexts.lance/_transactions/0-c4755ab9-b604-4d90-851f-0491f3cbcbce.txn +2 -0
  4. package/data/lancedb/contexts.lance/_versions/1.manifest +0 -0
  5. package/data/lancedb/episode_access.lance/_transactions/0-407a6366-0dca-490a-868b-ea63bee3b40c.txn +2 -0
  6. package/data/lancedb/episode_access.lance/_versions/1.manifest +0 -0
  7. package/data/lancedb/episode_cooccurrence.lance/_transactions/0-0e103c7f-29d7-4f09-8100-505c076f01ae.txn +1 -0
  8. package/data/lancedb/episode_cooccurrence.lance/_versions/1.manifest +0 -0
  9. package/data/lancedb/episodes.lance/_transactions/0-e678cbac-792b-4a9d-a457-17b0d4d23607.txn +1 -0
  10. package/data/lancedb/episodes.lance/_versions/1.manifest +0 -0
  11. package/data/lancedb/l3_knowledge.lance/_transactions/0-cdb3561f-3a59-4e15-bded-e93c5f9a50e3.txn +1 -0
  12. package/data/lancedb/l3_knowledge.lance/_versions/1.manifest +0 -0
  13. package/data/lancedb/tasks.lance/_transactions/0-d1cf10ec-1eb8-48b4-bbbe-34b3a1083664.txn +4 -0
  14. package/data/lancedb/tasks.lance/_versions/1.manifest +0 -0
  15. package/dist/{chunk-HGNVCCYG.js → chunk-2OIRJFI5.js} +14 -31
  16. package/dist/chunk-2OIRJFI5.js.map +1 -0
  17. package/dist/chunk-7D4SUZUM.js +38 -0
  18. package/dist/chunk-7D4SUZUM.js.map +1 -0
  19. package/dist/chunk-LRK64BAK.js +3601 -0
  20. package/dist/chunk-LRK64BAK.js.map +1 -0
  21. package/dist/chunk-MBS5A6BZ.js +132 -0
  22. package/dist/chunk-MBS5A6BZ.js.map +1 -0
  23. package/dist/{chunk-LNV373IF.js → chunk-OQ272HKA.js} +3 -28
  24. package/dist/chunk-OQ272HKA.js.map +1 -0
  25. package/dist/{chunk-CGSXJVSS.js → chunk-RXCV57H3.js} +2 -2
  26. package/dist/{chunk-S3RVZBPZ.js → chunk-YEOAEJ62.js} +2 -2
  27. package/dist/commands/fleet.js +4 -3
  28. package/dist/commands/init.js +2 -1
  29. package/dist/commands/service.js +2 -1
  30. package/dist/commands/start.js +4 -3
  31. package/dist/commands/upgrade.js +1 -0
  32. package/dist/index.js +3 -2
  33. package/dist/main.js +7 -6
  34. package/dist/main.js.map +1 -1
  35. package/dist/start.js +2 -1
  36. package/dist/start.js.map +1 -1
  37. package/package.json +1 -1
  38. package/PLANNING.md +0 -383
  39. package/TASKS.md +0 -60
  40. package/dist/chunk-HGNVCCYG.js.map +0 -1
  41. package/dist/chunk-LNV373IF.js.map +0 -1
  42. package/dist/chunk-PPQGQHXJ.js +0 -151
  43. package/dist/chunk-PPQGQHXJ.js.map +0 -1
  44. package/dist/chunk-YHRGEWAZ.js +0 -2326
  45. package/dist/chunk-YHRGEWAZ.js.map +0 -1
  46. package/install.sh +0 -120
  47. package/npm-package.json +0 -26
  48. package/packages/cli/package.json +0 -23
  49. package/packages/cli/src/commands/fleet.ts +0 -206
  50. package/packages/cli/src/commands/init.ts +0 -253
  51. package/packages/cli/src/commands/service.ts +0 -159
  52. package/packages/cli/src/commands/start.ts +0 -78
  53. package/packages/cli/src/commands/upgrade.ts +0 -158
  54. package/packages/cli/src/main.ts +0 -64
  55. package/packages/cli/tsconfig.json +0 -8
  56. package/packages/memory/Cargo.lock +0 -6480
  57. package/packages/memory/Cargo.toml +0 -21
  58. package/packages/memory/src/context.rs +0 -179
  59. package/packages/memory/src/embeddings.rs +0 -51
  60. package/packages/memory/src/main.rs +0 -626
  61. package/packages/memory/src/promotion.rs +0 -637
  62. package/packages/memory/src/scoring.rs +0 -131
  63. package/packages/memory/src/store.rs +0 -460
  64. package/packages/memory/src/tasks.rs +0 -321
  65. package/packages/runtime/package.json +0 -24
  66. package/packages/runtime/src/__tests__/fleet-integration.test.ts +0 -235
  67. package/packages/runtime/src/__tests__/fleet.test.ts +0 -207
  68. package/packages/runtime/src/__tests__/integration.test.ts +0 -434
  69. package/packages/runtime/src/agent.ts +0 -255
  70. package/packages/runtime/src/config.ts +0 -130
  71. package/packages/runtime/src/context.ts +0 -192
  72. package/packages/runtime/src/fleet/fleet-manager.ts +0 -399
  73. package/packages/runtime/src/fleet/memory-sync.ts +0 -362
  74. package/packages/runtime/src/fleet/primary-client.ts +0 -285
  75. package/packages/runtime/src/fleet/worker-protocol.ts +0 -158
  76. package/packages/runtime/src/fleet/worker-server.ts +0 -246
  77. package/packages/runtime/src/index.ts +0 -57
  78. package/packages/runtime/src/llm-client.ts +0 -65
  79. package/packages/runtime/src/memory-client.ts +0 -309
  80. package/packages/runtime/src/pipeline.ts +0 -187
  81. package/packages/runtime/src/prompt.ts +0 -173
  82. package/packages/runtime/src/sesame.ts +0 -226
  83. package/packages/runtime/src/start.ts +0 -20
  84. package/packages/runtime/src/task-engine.ts +0 -113
  85. package/packages/runtime/src/worker.ts +0 -339
  86. package/packages/runtime/tsconfig.json +0 -8
  87. package/pnpm-workspace.yaml +0 -2
  88. package/run-aidan.sh +0 -23
  89. package/scripts/bootstrap.sh +0 -196
  90. package/scripts/build-npm.sh +0 -92
  91. package/scripts/com.hivemind.agent.plist +0 -44
  92. package/scripts/com.hivemind.memory.plist +0 -31
  93. package/tsconfig.json +0 -22
  94. package/tsup.config.ts +0 -27
  95. /package/dist/{chunk-CGSXJVSS.js.map → chunk-RXCV57H3.js.map} +0 -0
  96. /package/dist/{chunk-S3RVZBPZ.js.map → chunk-YEOAEJ62.js.map} +0 -0
@@ -1,158 +0,0 @@
1
- /**
2
- * Shared types for Primary <-> Worker communication protocol.
3
- *
4
- * Communication is HTTP-based:
5
- * - Primary calls Worker endpoints to assign contexts, check health
6
- * - Worker calls Primary endpoints to register, report status
7
- */
8
-
9
- // --- Worker Identity & Registration ---
10
-
11
- export interface WorkerInfo {
12
- id: string;
13
- url: string;
14
- capabilities: WorkerCapabilities;
15
- assigned_contexts: string[];
16
- registered_at: string;
17
- last_heartbeat: string;
18
- }
19
-
20
- export interface WorkerCapabilities {
21
- max_contexts: number;
22
- has_ollama: boolean;
23
- has_memory_daemon: boolean;
24
- available_models: string[];
25
- }
26
-
27
- export interface WorkerRegistrationRequest {
28
- url: string;
29
- capabilities: WorkerCapabilities;
30
- }
31
-
32
- export interface WorkerRegistrationResponse {
33
- worker_id: string;
34
- registered_at: string;
35
- }
36
-
37
- // --- Health Checks ---
38
-
39
- export type WorkerHealthStatus = "healthy" | "degraded" | "unreachable";
40
-
41
- export interface WorkerHealthResponse {
42
- worker_id: string;
43
- status: WorkerHealthStatus;
44
- uptime_seconds: number;
45
- assigned_contexts: string[];
46
- active_context: string | null;
47
- memory_daemon_ok: boolean;
48
- ollama_ok: boolean;
49
- }
50
-
51
- // --- Context Assignment ---
52
-
53
- export interface ContextAssignment {
54
- context_name: string;
55
- context_description: string;
56
- assigned_at: string;
57
- }
58
-
59
- export interface ContextAssignRequest {
60
- context_name: string;
61
- context_description: string;
62
- }
63
-
64
- export interface ContextAssignResponse {
65
- context_name: string;
66
- accepted: boolean;
67
- reason?: string;
68
- }
69
-
70
- // --- Worker Status Reporting ---
71
-
72
- export type WorkerActivity = "idle" | "working" | "error";
73
-
74
- export interface WorkerStatus {
75
- worker_id: string;
76
- activity: WorkerActivity;
77
- current_context: string | null;
78
- current_task: string | null;
79
- error?: string;
80
- reported_at: string;
81
- }
82
-
83
- export interface WorkerStatusReport {
84
- activity: WorkerActivity;
85
- current_context: string | null;
86
- current_task: string | null;
87
- error?: string;
88
- }
89
-
90
- // --- Memory Sync ---
91
-
92
- /** L3 knowledge entries synced from Worker to Primary. */
93
- export interface SyncPullRequest {
94
- worker_id: string;
95
- context_name: string;
96
- entries: SyncL3Entry[];
97
- }
98
-
99
- /** An L3 entry with a last-modified timestamp for conflict resolution. */
100
- export interface SyncL3Entry {
101
- id: string;
102
- source_episode_id: string;
103
- context_name: string;
104
- content: string;
105
- promoted_at: string;
106
- access_count: number;
107
- connection_density: number;
108
- updated_at: string;
109
- }
110
-
111
- export interface SyncPullResponse {
112
- accepted: number;
113
- rejected: number;
114
- }
115
-
116
- /** Global context updates pushed from Primary to Worker. */
117
- export interface SyncPushRequest {
118
- entries: SyncL3Entry[];
119
- episodes: SyncL2Episode[];
120
- }
121
-
122
- /** L2 episodes synced as append-only (no conflict resolution needed). */
123
- export interface SyncL2Episode {
124
- id: string;
125
- timestamp: string;
126
- context_name: string;
127
- role: string;
128
- content: string;
129
- }
130
-
131
- export interface SyncPushResponse {
132
- l3_accepted: number;
133
- l2_appended: number;
134
- }
135
-
136
- // --- Protocol Constants ---
137
-
138
- export const WORKER_API_PREFIX = "/workers";
139
-
140
- /** Primary-side routes (Worker calls these) */
141
- export const PRIMARY_ROUTES = {
142
- register: `${WORKER_API_PREFIX}/register`,
143
- status: (workerId: string) => `${WORKER_API_PREFIX}/${encodeURIComponent(workerId)}/status`,
144
- syncPull: `${WORKER_API_PREFIX}/sync/pull`,
145
- } as const;
146
-
147
- /** Worker-side routes (Primary calls these) */
148
- export const WORKER_ROUTES = {
149
- health: "/health",
150
- assign: "/assign",
151
- unassign: (contextName: string) => `/assign/${encodeURIComponent(contextName)}`,
152
- status: "/status",
153
- syncPush: "/sync/push",
154
- } as const;
155
-
156
- export const DEFAULT_HEALTH_INTERVAL_MS = 30_000;
157
- export const HEALTH_TIMEOUT_MS = 5_000;
158
- export const DEFAULT_SYNC_INTERVAL_MS = 60_000;
@@ -1,246 +0,0 @@
1
- /**
2
- * Worker-side HTTP server that exposes endpoints for the Primary to call.
3
- *
4
- * Uses Node's built-in http module — no external framework needed for these few routes.
5
- *
6
- * Endpoints:
7
- * GET /health — Health check
8
- * POST /assign — Accept a context assignment
9
- * DELETE /assign/:contextName — Release a context
10
- * GET /status — Current activity status
11
- */
12
-
13
- import { createServer, type Server, type IncomingMessage, type ServerResponse } from "http";
14
- import type {
15
- WorkerHealthResponse,
16
- WorkerStatusReport,
17
- ContextAssignRequest,
18
- ContextAssignResponse,
19
- ContextAssignment,
20
- SyncPushRequest,
21
- SyncPushResponse,
22
- } from "./worker-protocol.js";
23
-
24
- export interface WorkerServerOptions {
25
- workerId: string;
26
- port: number;
27
- maxContexts?: number;
28
- memoryDaemonUrl?: string;
29
- ollamaUrl?: string;
30
- }
31
-
32
- export class WorkerServer {
33
- private server: Server | null = null;
34
- private workerId: string;
35
- private port: number;
36
- private startTime: number;
37
- private assignedContexts: Map<string, ContextAssignment> = new Map();
38
- private activeContext: string | null = null;
39
- private currentTask: string | null = null;
40
- private maxContexts: number;
41
- private memoryDaemonUrl: string | null;
42
- private ollamaUrl: string | null;
43
- private onAssignCallback: ((contextName: string, description: string) => void) | null = null;
44
- private onSyncPushCallback: ((req: SyncPushRequest) => Promise<SyncPushResponse>) | null = null;
45
-
46
- constructor(opts: WorkerServerOptions) {
47
- this.workerId = opts.workerId;
48
- this.port = opts.port;
49
- this.startTime = Date.now();
50
- this.maxContexts = opts.maxContexts ?? 4;
51
- this.memoryDaemonUrl = opts.memoryDaemonUrl ?? null;
52
- this.ollamaUrl = opts.ollamaUrl ?? null;
53
- }
54
-
55
- /** Start listening. */
56
- async start(): Promise<void> {
57
- return new Promise((resolve, reject) => {
58
- this.server = createServer((req, res) => this.handleRequest(req, res));
59
-
60
- this.server.on("error", reject);
61
- this.server.listen(this.port, () => resolve());
62
- });
63
- }
64
-
65
- /** Stop the server. */
66
- async stop(): Promise<void> {
67
- return new Promise((resolve) => {
68
- if (!this.server) {
69
- resolve();
70
- return;
71
- }
72
- this.server.close(() => resolve());
73
- });
74
- }
75
-
76
- getPort(): number {
77
- return this.port;
78
- }
79
-
80
- getAssignedContexts(): string[] {
81
- return Array.from(this.assignedContexts.keys());
82
- }
83
-
84
- setActiveContext(name: string | null): void {
85
- this.activeContext = name;
86
- }
87
-
88
- setCurrentTask(taskId: string | null): void {
89
- this.currentTask = taskId;
90
- }
91
-
92
- /** Register a callback for when a new context is assigned. */
93
- onContextAssigned(cb: (contextName: string, description: string) => void): void {
94
- this.onAssignCallback = cb;
95
- }
96
-
97
- /** Register a handler for incoming sync push requests from Primary. */
98
- onSyncPush(cb: (req: SyncPushRequest) => Promise<SyncPushResponse>): void {
99
- this.onSyncPushCallback = cb;
100
- }
101
-
102
- // --- Request Router ---
103
-
104
- private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
105
- const url = new URL(req.url ?? "/", `http://localhost:${this.port}`);
106
- const path = url.pathname;
107
- const method = req.method ?? "GET";
108
-
109
- try {
110
- if (method === "GET" && path === "/health") {
111
- return this.handleHealth(res);
112
- }
113
-
114
- if (method === "POST" && path === "/assign") {
115
- const body = await readBody(req);
116
- return this.handleAssign(body, res);
117
- }
118
-
119
- // DELETE /assign/:contextName
120
- if (method === "DELETE" && path.startsWith("/assign/")) {
121
- const contextName = decodeURIComponent(path.slice("/assign/".length));
122
- return this.handleUnassign(contextName, res);
123
- }
124
-
125
- if (method === "GET" && path === "/status") {
126
- return this.handleStatus(res);
127
- }
128
-
129
- if (method === "POST" && path === "/sync/push") {
130
- const body = await readBody(req);
131
- return this.handleSyncPush(body, res);
132
- }
133
-
134
- sendJson(res, 404, { error: "Not found" });
135
- } catch (err) {
136
- const msg = err instanceof Error ? err.message : String(err);
137
- sendJson(res, 500, { error: msg });
138
- }
139
- }
140
-
141
- // --- Handlers ---
142
-
143
- private handleHealth(res: ServerResponse): void {
144
- const uptimeMs = Date.now() - this.startTime;
145
-
146
- const body: WorkerHealthResponse = {
147
- worker_id: this.workerId,
148
- status: "healthy",
149
- uptime_seconds: Math.floor(uptimeMs / 1000),
150
- assigned_contexts: Array.from(this.assignedContexts.keys()),
151
- active_context: this.activeContext,
152
- memory_daemon_ok: this.memoryDaemonUrl !== null,
153
- ollama_ok: this.ollamaUrl !== null,
154
- };
155
-
156
- sendJson(res, 200, body);
157
- }
158
-
159
- private handleAssign(raw: string, res: ServerResponse): void {
160
- const req = JSON.parse(raw) as ContextAssignRequest;
161
-
162
- if (this.assignedContexts.size >= this.maxContexts) {
163
- const body: ContextAssignResponse = {
164
- context_name: req.context_name,
165
- accepted: false,
166
- reason: "Worker at max context capacity",
167
- };
168
- sendJson(res, 200, body);
169
- return;
170
- }
171
-
172
- const assignment: ContextAssignment = {
173
- context_name: req.context_name,
174
- context_description: req.context_description,
175
- assigned_at: new Date().toISOString(),
176
- };
177
-
178
- this.assignedContexts.set(req.context_name, assignment);
179
-
180
- // Notify the worker runtime about the new context
181
- if (this.onAssignCallback) {
182
- this.onAssignCallback(req.context_name, req.context_description);
183
- }
184
-
185
- const body: ContextAssignResponse = {
186
- context_name: req.context_name,
187
- accepted: true,
188
- };
189
- sendJson(res, 200, body);
190
- }
191
-
192
- private handleUnassign(contextName: string, res: ServerResponse): void {
193
- const existed = this.assignedContexts.delete(contextName);
194
-
195
- if (this.activeContext === contextName) {
196
- this.activeContext = null;
197
- this.currentTask = null;
198
- }
199
-
200
- sendJson(res, existed ? 200 : 404, {
201
- context_name: contextName,
202
- removed: existed,
203
- });
204
- }
205
-
206
- private handleStatus(res: ServerResponse): void {
207
- const report: WorkerStatusReport = {
208
- activity: this.currentTask ? "working" : "idle",
209
- current_context: this.activeContext,
210
- current_task: this.currentTask,
211
- };
212
-
213
- sendJson(res, 200, report);
214
- }
215
-
216
- private async handleSyncPush(raw: string, res: ServerResponse): Promise<void> {
217
- if (!this.onSyncPushCallback) {
218
- sendJson(res, 501, { error: "Sync push handler not registered" });
219
- return;
220
- }
221
-
222
- const req = JSON.parse(raw) as SyncPushRequest;
223
- const result = await this.onSyncPushCallback(req);
224
- sendJson(res, 200, result);
225
- }
226
- }
227
-
228
- // --- Helpers ---
229
-
230
- function readBody(req: IncomingMessage): Promise<string> {
231
- return new Promise((resolve, reject) => {
232
- const chunks: Buffer[] = [];
233
- req.on("data", (chunk: Buffer) => chunks.push(chunk));
234
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
235
- req.on("error", reject);
236
- });
237
- }
238
-
239
- function sendJson(res: ServerResponse, status: number, body: unknown): void {
240
- const payload = JSON.stringify(body);
241
- res.writeHead(status, {
242
- "Content-Type": "application/json",
243
- "Content-Length": Buffer.byteLength(payload),
244
- });
245
- res.end(payload);
246
- }
@@ -1,57 +0,0 @@
1
- export { Agent } from "./agent.js";
2
- export { MemoryClient } from "./memory-client.js";
3
- export { LLMClient } from "./llm-client.js";
4
- export { ContextManager } from "./context.js";
5
- export { TaskEngine } from "./task-engine.js";
6
- export { loadConfig } from "./config.js";
7
- export { buildSystemPrompt, buildMessages } from "./prompt.js";
8
- export { SesameClient } from "./sesame.js";
9
- export { startPipeline } from "./pipeline.js";
10
-
11
- export type { HivemindConfig, AgentConfig, LLMConfig, MemoryConfig, SesameConfig, WorkerModeConfig } from "./config.js";
12
- export type { ChatMessage, ChatResponse } from "./llm-client.js";
13
- export type {
14
- Episode,
15
- ScoredEpisode,
16
- EpisodeInput,
17
- ContextInfo,
18
- CrossContextResult,
19
- PromotionResult,
20
- L3Entry,
21
- TaskRecord,
22
- TaskInput,
23
- } from "./memory-client.js";
24
- export type { AgentResponse } from "./agent.js";
25
- export type { SesameMessage } from "./sesame.js";
26
- export type { ContextMetadata, ContextSwitchResult } from "./context.js";
27
- export type { TaskCommand, TaskStatus } from "./task-engine.js";
28
-
29
- // Fleet (Phase 3)
30
- export { PrimaryClient } from "./fleet/primary-client.js";
31
- export { WorkerServer } from "./fleet/worker-server.js";
32
- export { WorkerRuntime, startWorker } from "./worker.js";
33
- export { FleetManager } from "./fleet/fleet-manager.js";
34
- export { WorkerMemorySync, PrimaryMemorySync } from "./fleet/memory-sync.js";
35
- export type { FleetDashboard, WorkerSummary, MigrationResult } from "./fleet/fleet-manager.js";
36
- export type { WorkerServerOptions } from "./fleet/worker-server.js";
37
- export type { WorkerSyncOptions, PrimarySyncOptions } from "./fleet/memory-sync.js";
38
- export type {
39
- WorkerInfo,
40
- WorkerCapabilities,
41
- WorkerRegistrationRequest,
42
- WorkerRegistrationResponse,
43
- WorkerHealthResponse,
44
- WorkerHealthStatus,
45
- WorkerStatus,
46
- WorkerStatusReport,
47
- WorkerActivity,
48
- ContextAssignment,
49
- ContextAssignRequest,
50
- ContextAssignResponse,
51
- SyncPullRequest,
52
- SyncPullResponse,
53
- SyncPushRequest,
54
- SyncPushResponse,
55
- SyncL3Entry,
56
- SyncL2Episode,
57
- } from "./fleet/worker-protocol.js";
@@ -1,65 +0,0 @@
1
- import type { LLMConfig } from "./config.js";
2
-
3
- export interface ChatMessage {
4
- role: "system" | "user" | "assistant";
5
- content: string;
6
- }
7
-
8
- export interface ChatResponse {
9
- content: string;
10
- model: string;
11
- usage?: {
12
- prompt_tokens: number;
13
- completion_tokens: number;
14
- total_tokens: number;
15
- };
16
- }
17
-
18
- export class LLMClient {
19
- private baseUrl: string;
20
- private model: string;
21
- private maxTokens: number;
22
- private temperature: number;
23
- private apiKey: string;
24
-
25
- constructor(config: LLMConfig) {
26
- this.baseUrl = config.base_url;
27
- this.model = config.model;
28
- this.maxTokens = config.max_tokens;
29
- this.temperature = config.temperature;
30
- this.apiKey = config.api_key ?? "";
31
- }
32
-
33
- async chat(messages: ChatMessage[]): Promise<ChatResponse> {
34
- const resp = await fetch(`${this.baseUrl}/chat/completions`, {
35
- method: "POST",
36
- headers: {
37
- "Content-Type": "application/json",
38
- ...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}),
39
- },
40
- body: JSON.stringify({
41
- model: this.model,
42
- messages,
43
- max_tokens: this.maxTokens,
44
- temperature: this.temperature,
45
- }),
46
- });
47
-
48
- if (!resp.ok) {
49
- const body = await resp.text();
50
- throw new Error(`LLM request failed: ${resp.status} ${body}`);
51
- }
52
-
53
- const data = (await resp.json()) as {
54
- choices: Array<{ message: { content: string } }>;
55
- model: string;
56
- usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
57
- };
58
-
59
- return {
60
- content: data.choices[0].message.content,
61
- model: data.model,
62
- usage: data.usage,
63
- };
64
- }
65
- }