@acidkill/surreal-memory-client 2.6.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/LICENSE +22 -0
- package/README.md +90 -0
- package/dist/index.cjs +171 -0
- package/dist/index.d.cts +177 -0
- package/dist/index.d.ts +177 -0
- package/dist/index.js +143 -0
- package/package.json +61 -0
- package/src/client.ts +234 -0
- package/src/index.ts +28 -0
- package/src/types.ts +136 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Surreal-Memory Contributors
|
|
4
|
+
Copyright (c) 2026 Toni Nowak / AI-Flow NOWAK
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @acidkill/surreal-memory-client
|
|
2
|
+
|
|
3
|
+
TypeScript client for the [Surreal-Memory](https://github.com/acidkill/surreal-memory) REST API.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @acidkill/surreal-memory-client
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { SurrealMemoryClient } from "@acidkill/surreal-memory-client"
|
|
13
|
+
|
|
14
|
+
const client = new SurrealMemoryClient({
|
|
15
|
+
baseUrl: "http://localhost:8000",
|
|
16
|
+
brain: "myproject",
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// Save a memory
|
|
20
|
+
const { fiber_id } = await client.remember({
|
|
21
|
+
content: "Fixed auth bug with null check in login.py:42",
|
|
22
|
+
type: "fix",
|
|
23
|
+
priority: 7,
|
|
24
|
+
tags: ["auth"],
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Recall related memories
|
|
28
|
+
const { results } = await client.recall({ query: "auth bug", limit: 5 })
|
|
29
|
+
|
|
30
|
+
for (const { fiber, score } of results) {
|
|
31
|
+
console.log(score, fiber.content)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Pull recent context
|
|
35
|
+
const { fibers } = await client.context({ limit: 20 })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
|
|
40
|
+
| Method | Endpoint | Description |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `remember(req)` | `POST /api/remember` | Save a memory. |
|
|
43
|
+
| `recall(req)` | `POST /api/recall` | Recall by query (spreading activation + optional vector). |
|
|
44
|
+
| `context(req?)` | `GET /api/context` | Recent fibers in the current brain. |
|
|
45
|
+
| `getFiber(id)` | `GET /api/fibers/:id` | Fetch one fiber. |
|
|
46
|
+
| `forget(id)` | `DELETE /api/fibers/:id` | Hard-delete a fiber. |
|
|
47
|
+
| `listBrains()` | `GET /api/brains` | List all brains. |
|
|
48
|
+
| `getBrainStats()` | `GET /api/stats` | Neuron / synapse / fiber counts. |
|
|
49
|
+
| `health()` | `GET /api/health` | Health probe. |
|
|
50
|
+
|
|
51
|
+
Every method accepts an optional `RequestOptions` argument (`brain` override, `timeoutMs`, `AbortSignal`, extra `headers`).
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
new SurrealMemoryClient({
|
|
57
|
+
baseUrl: "https://memory.example.com", // required, no trailing slash
|
|
58
|
+
brain: "default", // optional default brain
|
|
59
|
+
apiKey: process.env.SURREAL_MEMORY_API_KEY, // optional bearer token
|
|
60
|
+
timeoutMs: 30_000, // default 30s
|
|
61
|
+
fetch: customFetch, // optional fetch impl
|
|
62
|
+
headers: { "X-Tenant": "acme" }, // optional default headers
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Error handling
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { ApiError, SurrealMemoryClient } from "@acidkill/surreal-memory-client"
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await client.recall({ query: "..." })
|
|
73
|
+
} catch (err: unknown) {
|
|
74
|
+
if (err instanceof ApiError) {
|
|
75
|
+
console.error(err.status, err.message, err.payload)
|
|
76
|
+
} else {
|
|
77
|
+
throw err
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Server compatibility
|
|
83
|
+
|
|
84
|
+
| Client | Server |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `2.x` | Surreal-Memory `2.x` (REST API at `/api/*`) |
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT — see [LICENSE](https://github.com/acidkill/surreal-memory/blob/main/LICENSE).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ApiError: () => ApiError,
|
|
24
|
+
SurrealMemoryClient: () => SurrealMemoryClient
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/client.ts
|
|
29
|
+
var ApiError = class extends Error {
|
|
30
|
+
status;
|
|
31
|
+
payload;
|
|
32
|
+
constructor(status, message, payload) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "ApiError";
|
|
35
|
+
this.status = status;
|
|
36
|
+
this.payload = payload;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var SurrealMemoryClient = class {
|
|
40
|
+
baseUrl;
|
|
41
|
+
defaultBrain;
|
|
42
|
+
apiKey;
|
|
43
|
+
fetchImpl;
|
|
44
|
+
defaultTimeoutMs;
|
|
45
|
+
defaultHeaders;
|
|
46
|
+
constructor(options) {
|
|
47
|
+
if (!options.baseUrl) {
|
|
48
|
+
throw new Error("SurrealMemoryClient: baseUrl is required");
|
|
49
|
+
}
|
|
50
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
51
|
+
this.defaultBrain = options.brain;
|
|
52
|
+
this.apiKey = options.apiKey;
|
|
53
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
54
|
+
this.defaultTimeoutMs = options.timeoutMs ?? 3e4;
|
|
55
|
+
this.defaultHeaders = options.headers ?? {};
|
|
56
|
+
}
|
|
57
|
+
// ── Memory operations ─────────────────────────────────────
|
|
58
|
+
async remember(req, options) {
|
|
59
|
+
return this.request("POST", "/api/remember", req, options);
|
|
60
|
+
}
|
|
61
|
+
async recall(req, options) {
|
|
62
|
+
return this.request("POST", "/api/recall", req, options);
|
|
63
|
+
}
|
|
64
|
+
async context(req, options) {
|
|
65
|
+
const query = req ? this.buildQuery(req) : "";
|
|
66
|
+
return this.request("GET", `/api/context${query}`, void 0, options);
|
|
67
|
+
}
|
|
68
|
+
async getFiber(fiberId, options) {
|
|
69
|
+
return this.request("GET", `/api/fibers/${encodeURIComponent(fiberId)}`, void 0, options);
|
|
70
|
+
}
|
|
71
|
+
async forget(fiberId, options) {
|
|
72
|
+
return this.request("DELETE", `/api/fibers/${encodeURIComponent(fiberId)}`, void 0, options);
|
|
73
|
+
}
|
|
74
|
+
// ── Brain operations ──────────────────────────────────────
|
|
75
|
+
async listBrains(options) {
|
|
76
|
+
return this.request("GET", "/api/brains", void 0, options);
|
|
77
|
+
}
|
|
78
|
+
async getBrainStats(options) {
|
|
79
|
+
return this.request("GET", "/api/stats", void 0, options);
|
|
80
|
+
}
|
|
81
|
+
// ── Health / diagnostics ─────────────────────────────────
|
|
82
|
+
async health(options) {
|
|
83
|
+
return this.request("GET", "/api/health", void 0, options);
|
|
84
|
+
}
|
|
85
|
+
// ── Internal request plumbing ────────────────────────────
|
|
86
|
+
async request(method, path, body, options) {
|
|
87
|
+
const brain = options?.brain ?? this.defaultBrain;
|
|
88
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
89
|
+
const headers = new Headers(this.defaultHeaders);
|
|
90
|
+
for (const [k, v] of Object.entries(options?.headers ?? {})) {
|
|
91
|
+
headers.set(k, v);
|
|
92
|
+
}
|
|
93
|
+
if (brain) {
|
|
94
|
+
headers.set("X-Brain-ID", brain);
|
|
95
|
+
}
|
|
96
|
+
if (this.apiKey) {
|
|
97
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
98
|
+
}
|
|
99
|
+
if (body !== void 0 && !headers.has("Content-Type")) {
|
|
100
|
+
headers.set("Content-Type", "application/json");
|
|
101
|
+
}
|
|
102
|
+
if (!headers.has("Accept")) {
|
|
103
|
+
headers.set("Accept", "application/json");
|
|
104
|
+
}
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timeoutHandle = timeoutMs > 0 ? setTimeout(() => controller.abort(new Error("Request timeout")), timeoutMs) : void 0;
|
|
107
|
+
const externalSignal = options?.signal;
|
|
108
|
+
if (externalSignal) {
|
|
109
|
+
if (externalSignal.aborted) {
|
|
110
|
+
controller.abort(externalSignal.reason);
|
|
111
|
+
} else {
|
|
112
|
+
externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), {
|
|
113
|
+
once: true
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
119
|
+
method,
|
|
120
|
+
headers,
|
|
121
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
122
|
+
signal: controller.signal
|
|
123
|
+
});
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const payload = await this.safeReadErrorPayload(response);
|
|
126
|
+
const message = payload?.detail ?? payload?.error ?? `HTTP ${response.status}`;
|
|
127
|
+
throw new ApiError(response.status, message, payload);
|
|
128
|
+
}
|
|
129
|
+
if (response.status === 204) {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
return await response.json();
|
|
133
|
+
} finally {
|
|
134
|
+
if (timeoutHandle) {
|
|
135
|
+
clearTimeout(timeoutHandle);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async safeReadErrorPayload(response) {
|
|
140
|
+
try {
|
|
141
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
142
|
+
if (contentType.includes("application/json")) {
|
|
143
|
+
return await response.json();
|
|
144
|
+
}
|
|
145
|
+
const text = await response.text();
|
|
146
|
+
return { detail: text || void 0 };
|
|
147
|
+
} catch {
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
buildQuery(params) {
|
|
152
|
+
const search = new URLSearchParams();
|
|
153
|
+
for (const [key, value] of Object.entries(params)) {
|
|
154
|
+
if (value === void 0 || value === null) continue;
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
for (const v of value) {
|
|
157
|
+
search.append(key, String(v));
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
search.set(key, String(value));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const qs = search.toString();
|
|
164
|
+
return qs ? `?${qs}` : "";
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
168
|
+
0 && (module.exports = {
|
|
169
|
+
ApiError,
|
|
170
|
+
SurrealMemoryClient
|
|
171
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Surreal-Memory REST API.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the server response shapes from `src/surreal_memory/server/`.
|
|
5
|
+
* Only public-API fields are typed here; internal fields are passed through
|
|
6
|
+
* as `unknown` so the SDK does not break when the server adds new fields.
|
|
7
|
+
*/
|
|
8
|
+
type MemoryType = "fact" | "decision" | "error" | "insight" | "preference" | "workflow" | "instruction" | "concept" | "context" | "todo";
|
|
9
|
+
type SynapseType = "CAUSED_BY" | "LEADS_TO" | "CONTRADICTS" | "SIMILAR_TO" | "PART_OF" | "USED_BY" | "DEPENDS_ON" | string;
|
|
10
|
+
type NeuronType = "entity" | "concept" | "time" | "action" | "intent" | "state";
|
|
11
|
+
type LifecycleStage = "full" | "summary" | "essence" | "ghost" | "metadata";
|
|
12
|
+
interface Neuron {
|
|
13
|
+
id: string;
|
|
14
|
+
type: NeuronType;
|
|
15
|
+
content: string;
|
|
16
|
+
content_hash?: number;
|
|
17
|
+
created_at: string;
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
interface Synapse {
|
|
21
|
+
id: string;
|
|
22
|
+
type: SynapseType;
|
|
23
|
+
source_id: string;
|
|
24
|
+
target_id: string;
|
|
25
|
+
weight?: number;
|
|
26
|
+
created_at: string;
|
|
27
|
+
}
|
|
28
|
+
interface Fiber {
|
|
29
|
+
id: string;
|
|
30
|
+
content: string;
|
|
31
|
+
type?: MemoryType;
|
|
32
|
+
priority?: number;
|
|
33
|
+
tags?: string[];
|
|
34
|
+
stage?: LifecycleStage;
|
|
35
|
+
created_at: string;
|
|
36
|
+
updated_at?: string;
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
interface Brain {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
created_at: string;
|
|
43
|
+
stats?: BrainStats;
|
|
44
|
+
}
|
|
45
|
+
interface BrainStats {
|
|
46
|
+
neurons: number;
|
|
47
|
+
synapses: number;
|
|
48
|
+
fibers: number;
|
|
49
|
+
active_neurons?: number;
|
|
50
|
+
}
|
|
51
|
+
interface RememberRequest {
|
|
52
|
+
content: string;
|
|
53
|
+
type?: MemoryType;
|
|
54
|
+
priority?: number;
|
|
55
|
+
tags?: string[];
|
|
56
|
+
ephemeral?: boolean;
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
interface RememberResponse {
|
|
60
|
+
fiber_id: string;
|
|
61
|
+
type: MemoryType;
|
|
62
|
+
saved: true;
|
|
63
|
+
}
|
|
64
|
+
interface RecallRequest {
|
|
65
|
+
query: string;
|
|
66
|
+
limit?: number;
|
|
67
|
+
type?: MemoryType;
|
|
68
|
+
tags?: string[];
|
|
69
|
+
min_priority?: number;
|
|
70
|
+
}
|
|
71
|
+
interface RecallResult {
|
|
72
|
+
fiber: Fiber;
|
|
73
|
+
score: number;
|
|
74
|
+
activation?: number;
|
|
75
|
+
}
|
|
76
|
+
interface RecallResponse {
|
|
77
|
+
results: RecallResult[];
|
|
78
|
+
query: string;
|
|
79
|
+
brain: string;
|
|
80
|
+
}
|
|
81
|
+
interface ContextRequest {
|
|
82
|
+
limit?: number;
|
|
83
|
+
since?: string;
|
|
84
|
+
}
|
|
85
|
+
interface ContextResponse {
|
|
86
|
+
fibers: Fiber[];
|
|
87
|
+
brain: string;
|
|
88
|
+
count: number;
|
|
89
|
+
}
|
|
90
|
+
interface HealthResponse {
|
|
91
|
+
status: "ok" | "degraded" | "down";
|
|
92
|
+
version: string;
|
|
93
|
+
storage: string;
|
|
94
|
+
brain?: string;
|
|
95
|
+
}
|
|
96
|
+
interface ApiErrorPayload {
|
|
97
|
+
detail?: string;
|
|
98
|
+
error?: string;
|
|
99
|
+
status?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* SurrealMemoryClient — typed REST client for a running Surreal-Memory server.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* import { SurrealMemoryClient } from "@acidkill/surreal-memory-client"
|
|
108
|
+
*
|
|
109
|
+
* const client = new SurrealMemoryClient({
|
|
110
|
+
* baseUrl: "http://localhost:8000",
|
|
111
|
+
* brain: "myproject",
|
|
112
|
+
* })
|
|
113
|
+
*
|
|
114
|
+
* const { fiber_id } = await client.remember({
|
|
115
|
+
* content: "Fixed auth bug with null check in login.py:42",
|
|
116
|
+
* type: "fix",
|
|
117
|
+
* priority: 7,
|
|
118
|
+
* tags: ["auth"],
|
|
119
|
+
* })
|
|
120
|
+
*
|
|
121
|
+
* const { results } = await client.recall({ query: "auth bug", limit: 5 })
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
interface ClientOptions {
|
|
126
|
+
/** REST server URL, e.g. `http://localhost:8000`. No trailing slash. */
|
|
127
|
+
baseUrl: string;
|
|
128
|
+
/** Default brain name. Can be overridden per-request. Optional. */
|
|
129
|
+
brain?: string;
|
|
130
|
+
/** Optional bearer token sent as `Authorization: Bearer <token>`. */
|
|
131
|
+
apiKey?: string;
|
|
132
|
+
/** Custom `fetch` implementation (defaults to global `fetch`). */
|
|
133
|
+
fetch?: typeof fetch;
|
|
134
|
+
/** Default timeout in ms (defaults to 30000). */
|
|
135
|
+
timeoutMs?: number;
|
|
136
|
+
/** Extra headers attached to every request. */
|
|
137
|
+
headers?: Record<string, string>;
|
|
138
|
+
}
|
|
139
|
+
interface RequestOptions {
|
|
140
|
+
/** Override the brain for this single call. */
|
|
141
|
+
brain?: string;
|
|
142
|
+
/** Override the timeout for this single call. */
|
|
143
|
+
timeoutMs?: number;
|
|
144
|
+
/** AbortSignal for cancellation. */
|
|
145
|
+
signal?: AbortSignal;
|
|
146
|
+
/** Additional headers merged on top of client defaults. */
|
|
147
|
+
headers?: Record<string, string>;
|
|
148
|
+
}
|
|
149
|
+
declare class ApiError extends Error {
|
|
150
|
+
readonly status: number;
|
|
151
|
+
readonly payload: ApiErrorPayload | undefined;
|
|
152
|
+
constructor(status: number, message: string, payload?: ApiErrorPayload);
|
|
153
|
+
}
|
|
154
|
+
declare class SurrealMemoryClient {
|
|
155
|
+
private readonly baseUrl;
|
|
156
|
+
private readonly defaultBrain;
|
|
157
|
+
private readonly apiKey;
|
|
158
|
+
private readonly fetchImpl;
|
|
159
|
+
private readonly defaultTimeoutMs;
|
|
160
|
+
private readonly defaultHeaders;
|
|
161
|
+
constructor(options: ClientOptions);
|
|
162
|
+
remember(req: RememberRequest, options?: RequestOptions): Promise<RememberResponse>;
|
|
163
|
+
recall(req: RecallRequest, options?: RequestOptions): Promise<RecallResponse>;
|
|
164
|
+
context(req?: ContextRequest, options?: RequestOptions): Promise<ContextResponse>;
|
|
165
|
+
getFiber(fiberId: string, options?: RequestOptions): Promise<Fiber>;
|
|
166
|
+
forget(fiberId: string, options?: RequestOptions): Promise<{
|
|
167
|
+
deleted: true;
|
|
168
|
+
}>;
|
|
169
|
+
listBrains(options?: RequestOptions): Promise<Brain[]>;
|
|
170
|
+
getBrainStats(options?: RequestOptions): Promise<BrainStats>;
|
|
171
|
+
health(options?: RequestOptions): Promise<HealthResponse>;
|
|
172
|
+
private request;
|
|
173
|
+
private safeReadErrorPayload;
|
|
174
|
+
private buildQuery;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export { ApiError, type ApiErrorPayload, type Brain, type BrainStats, type ClientOptions, type ContextRequest, type ContextResponse, type Fiber, type HealthResponse, type LifecycleStage, type MemoryType, type Neuron, type NeuronType, type RecallRequest, type RecallResponse, type RecallResult, type RememberRequest, type RememberResponse, type RequestOptions, SurrealMemoryClient, type Synapse, type SynapseType };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Surreal-Memory REST API.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the server response shapes from `src/surreal_memory/server/`.
|
|
5
|
+
* Only public-API fields are typed here; internal fields are passed through
|
|
6
|
+
* as `unknown` so the SDK does not break when the server adds new fields.
|
|
7
|
+
*/
|
|
8
|
+
type MemoryType = "fact" | "decision" | "error" | "insight" | "preference" | "workflow" | "instruction" | "concept" | "context" | "todo";
|
|
9
|
+
type SynapseType = "CAUSED_BY" | "LEADS_TO" | "CONTRADICTS" | "SIMILAR_TO" | "PART_OF" | "USED_BY" | "DEPENDS_ON" | string;
|
|
10
|
+
type NeuronType = "entity" | "concept" | "time" | "action" | "intent" | "state";
|
|
11
|
+
type LifecycleStage = "full" | "summary" | "essence" | "ghost" | "metadata";
|
|
12
|
+
interface Neuron {
|
|
13
|
+
id: string;
|
|
14
|
+
type: NeuronType;
|
|
15
|
+
content: string;
|
|
16
|
+
content_hash?: number;
|
|
17
|
+
created_at: string;
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
interface Synapse {
|
|
21
|
+
id: string;
|
|
22
|
+
type: SynapseType;
|
|
23
|
+
source_id: string;
|
|
24
|
+
target_id: string;
|
|
25
|
+
weight?: number;
|
|
26
|
+
created_at: string;
|
|
27
|
+
}
|
|
28
|
+
interface Fiber {
|
|
29
|
+
id: string;
|
|
30
|
+
content: string;
|
|
31
|
+
type?: MemoryType;
|
|
32
|
+
priority?: number;
|
|
33
|
+
tags?: string[];
|
|
34
|
+
stage?: LifecycleStage;
|
|
35
|
+
created_at: string;
|
|
36
|
+
updated_at?: string;
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
interface Brain {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
created_at: string;
|
|
43
|
+
stats?: BrainStats;
|
|
44
|
+
}
|
|
45
|
+
interface BrainStats {
|
|
46
|
+
neurons: number;
|
|
47
|
+
synapses: number;
|
|
48
|
+
fibers: number;
|
|
49
|
+
active_neurons?: number;
|
|
50
|
+
}
|
|
51
|
+
interface RememberRequest {
|
|
52
|
+
content: string;
|
|
53
|
+
type?: MemoryType;
|
|
54
|
+
priority?: number;
|
|
55
|
+
tags?: string[];
|
|
56
|
+
ephemeral?: boolean;
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
interface RememberResponse {
|
|
60
|
+
fiber_id: string;
|
|
61
|
+
type: MemoryType;
|
|
62
|
+
saved: true;
|
|
63
|
+
}
|
|
64
|
+
interface RecallRequest {
|
|
65
|
+
query: string;
|
|
66
|
+
limit?: number;
|
|
67
|
+
type?: MemoryType;
|
|
68
|
+
tags?: string[];
|
|
69
|
+
min_priority?: number;
|
|
70
|
+
}
|
|
71
|
+
interface RecallResult {
|
|
72
|
+
fiber: Fiber;
|
|
73
|
+
score: number;
|
|
74
|
+
activation?: number;
|
|
75
|
+
}
|
|
76
|
+
interface RecallResponse {
|
|
77
|
+
results: RecallResult[];
|
|
78
|
+
query: string;
|
|
79
|
+
brain: string;
|
|
80
|
+
}
|
|
81
|
+
interface ContextRequest {
|
|
82
|
+
limit?: number;
|
|
83
|
+
since?: string;
|
|
84
|
+
}
|
|
85
|
+
interface ContextResponse {
|
|
86
|
+
fibers: Fiber[];
|
|
87
|
+
brain: string;
|
|
88
|
+
count: number;
|
|
89
|
+
}
|
|
90
|
+
interface HealthResponse {
|
|
91
|
+
status: "ok" | "degraded" | "down";
|
|
92
|
+
version: string;
|
|
93
|
+
storage: string;
|
|
94
|
+
brain?: string;
|
|
95
|
+
}
|
|
96
|
+
interface ApiErrorPayload {
|
|
97
|
+
detail?: string;
|
|
98
|
+
error?: string;
|
|
99
|
+
status?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* SurrealMemoryClient — typed REST client for a running Surreal-Memory server.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* import { SurrealMemoryClient } from "@acidkill/surreal-memory-client"
|
|
108
|
+
*
|
|
109
|
+
* const client = new SurrealMemoryClient({
|
|
110
|
+
* baseUrl: "http://localhost:8000",
|
|
111
|
+
* brain: "myproject",
|
|
112
|
+
* })
|
|
113
|
+
*
|
|
114
|
+
* const { fiber_id } = await client.remember({
|
|
115
|
+
* content: "Fixed auth bug with null check in login.py:42",
|
|
116
|
+
* type: "fix",
|
|
117
|
+
* priority: 7,
|
|
118
|
+
* tags: ["auth"],
|
|
119
|
+
* })
|
|
120
|
+
*
|
|
121
|
+
* const { results } = await client.recall({ query: "auth bug", limit: 5 })
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
interface ClientOptions {
|
|
126
|
+
/** REST server URL, e.g. `http://localhost:8000`. No trailing slash. */
|
|
127
|
+
baseUrl: string;
|
|
128
|
+
/** Default brain name. Can be overridden per-request. Optional. */
|
|
129
|
+
brain?: string;
|
|
130
|
+
/** Optional bearer token sent as `Authorization: Bearer <token>`. */
|
|
131
|
+
apiKey?: string;
|
|
132
|
+
/** Custom `fetch` implementation (defaults to global `fetch`). */
|
|
133
|
+
fetch?: typeof fetch;
|
|
134
|
+
/** Default timeout in ms (defaults to 30000). */
|
|
135
|
+
timeoutMs?: number;
|
|
136
|
+
/** Extra headers attached to every request. */
|
|
137
|
+
headers?: Record<string, string>;
|
|
138
|
+
}
|
|
139
|
+
interface RequestOptions {
|
|
140
|
+
/** Override the brain for this single call. */
|
|
141
|
+
brain?: string;
|
|
142
|
+
/** Override the timeout for this single call. */
|
|
143
|
+
timeoutMs?: number;
|
|
144
|
+
/** AbortSignal for cancellation. */
|
|
145
|
+
signal?: AbortSignal;
|
|
146
|
+
/** Additional headers merged on top of client defaults. */
|
|
147
|
+
headers?: Record<string, string>;
|
|
148
|
+
}
|
|
149
|
+
declare class ApiError extends Error {
|
|
150
|
+
readonly status: number;
|
|
151
|
+
readonly payload: ApiErrorPayload | undefined;
|
|
152
|
+
constructor(status: number, message: string, payload?: ApiErrorPayload);
|
|
153
|
+
}
|
|
154
|
+
declare class SurrealMemoryClient {
|
|
155
|
+
private readonly baseUrl;
|
|
156
|
+
private readonly defaultBrain;
|
|
157
|
+
private readonly apiKey;
|
|
158
|
+
private readonly fetchImpl;
|
|
159
|
+
private readonly defaultTimeoutMs;
|
|
160
|
+
private readonly defaultHeaders;
|
|
161
|
+
constructor(options: ClientOptions);
|
|
162
|
+
remember(req: RememberRequest, options?: RequestOptions): Promise<RememberResponse>;
|
|
163
|
+
recall(req: RecallRequest, options?: RequestOptions): Promise<RecallResponse>;
|
|
164
|
+
context(req?: ContextRequest, options?: RequestOptions): Promise<ContextResponse>;
|
|
165
|
+
getFiber(fiberId: string, options?: RequestOptions): Promise<Fiber>;
|
|
166
|
+
forget(fiberId: string, options?: RequestOptions): Promise<{
|
|
167
|
+
deleted: true;
|
|
168
|
+
}>;
|
|
169
|
+
listBrains(options?: RequestOptions): Promise<Brain[]>;
|
|
170
|
+
getBrainStats(options?: RequestOptions): Promise<BrainStats>;
|
|
171
|
+
health(options?: RequestOptions): Promise<HealthResponse>;
|
|
172
|
+
private request;
|
|
173
|
+
private safeReadErrorPayload;
|
|
174
|
+
private buildQuery;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export { ApiError, type ApiErrorPayload, type Brain, type BrainStats, type ClientOptions, type ContextRequest, type ContextResponse, type Fiber, type HealthResponse, type LifecycleStage, type MemoryType, type Neuron, type NeuronType, type RecallRequest, type RecallResponse, type RecallResult, type RememberRequest, type RememberResponse, type RequestOptions, SurrealMemoryClient, type Synapse, type SynapseType };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var ApiError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
payload;
|
|
5
|
+
constructor(status, message, payload) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "ApiError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.payload = payload;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var SurrealMemoryClient = class {
|
|
13
|
+
baseUrl;
|
|
14
|
+
defaultBrain;
|
|
15
|
+
apiKey;
|
|
16
|
+
fetchImpl;
|
|
17
|
+
defaultTimeoutMs;
|
|
18
|
+
defaultHeaders;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
if (!options.baseUrl) {
|
|
21
|
+
throw new Error("SurrealMemoryClient: baseUrl is required");
|
|
22
|
+
}
|
|
23
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
24
|
+
this.defaultBrain = options.brain;
|
|
25
|
+
this.apiKey = options.apiKey;
|
|
26
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
27
|
+
this.defaultTimeoutMs = options.timeoutMs ?? 3e4;
|
|
28
|
+
this.defaultHeaders = options.headers ?? {};
|
|
29
|
+
}
|
|
30
|
+
// ── Memory operations ─────────────────────────────────────
|
|
31
|
+
async remember(req, options) {
|
|
32
|
+
return this.request("POST", "/api/remember", req, options);
|
|
33
|
+
}
|
|
34
|
+
async recall(req, options) {
|
|
35
|
+
return this.request("POST", "/api/recall", req, options);
|
|
36
|
+
}
|
|
37
|
+
async context(req, options) {
|
|
38
|
+
const query = req ? this.buildQuery(req) : "";
|
|
39
|
+
return this.request("GET", `/api/context${query}`, void 0, options);
|
|
40
|
+
}
|
|
41
|
+
async getFiber(fiberId, options) {
|
|
42
|
+
return this.request("GET", `/api/fibers/${encodeURIComponent(fiberId)}`, void 0, options);
|
|
43
|
+
}
|
|
44
|
+
async forget(fiberId, options) {
|
|
45
|
+
return this.request("DELETE", `/api/fibers/${encodeURIComponent(fiberId)}`, void 0, options);
|
|
46
|
+
}
|
|
47
|
+
// ── Brain operations ──────────────────────────────────────
|
|
48
|
+
async listBrains(options) {
|
|
49
|
+
return this.request("GET", "/api/brains", void 0, options);
|
|
50
|
+
}
|
|
51
|
+
async getBrainStats(options) {
|
|
52
|
+
return this.request("GET", "/api/stats", void 0, options);
|
|
53
|
+
}
|
|
54
|
+
// ── Health / diagnostics ─────────────────────────────────
|
|
55
|
+
async health(options) {
|
|
56
|
+
return this.request("GET", "/api/health", void 0, options);
|
|
57
|
+
}
|
|
58
|
+
// ── Internal request plumbing ────────────────────────────
|
|
59
|
+
async request(method, path, body, options) {
|
|
60
|
+
const brain = options?.brain ?? this.defaultBrain;
|
|
61
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
62
|
+
const headers = new Headers(this.defaultHeaders);
|
|
63
|
+
for (const [k, v] of Object.entries(options?.headers ?? {})) {
|
|
64
|
+
headers.set(k, v);
|
|
65
|
+
}
|
|
66
|
+
if (brain) {
|
|
67
|
+
headers.set("X-Brain-ID", brain);
|
|
68
|
+
}
|
|
69
|
+
if (this.apiKey) {
|
|
70
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
71
|
+
}
|
|
72
|
+
if (body !== void 0 && !headers.has("Content-Type")) {
|
|
73
|
+
headers.set("Content-Type", "application/json");
|
|
74
|
+
}
|
|
75
|
+
if (!headers.has("Accept")) {
|
|
76
|
+
headers.set("Accept", "application/json");
|
|
77
|
+
}
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
const timeoutHandle = timeoutMs > 0 ? setTimeout(() => controller.abort(new Error("Request timeout")), timeoutMs) : void 0;
|
|
80
|
+
const externalSignal = options?.signal;
|
|
81
|
+
if (externalSignal) {
|
|
82
|
+
if (externalSignal.aborted) {
|
|
83
|
+
controller.abort(externalSignal.reason);
|
|
84
|
+
} else {
|
|
85
|
+
externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), {
|
|
86
|
+
once: true
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
92
|
+
method,
|
|
93
|
+
headers,
|
|
94
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
95
|
+
signal: controller.signal
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const payload = await this.safeReadErrorPayload(response);
|
|
99
|
+
const message = payload?.detail ?? payload?.error ?? `HTTP ${response.status}`;
|
|
100
|
+
throw new ApiError(response.status, message, payload);
|
|
101
|
+
}
|
|
102
|
+
if (response.status === 204) {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
return await response.json();
|
|
106
|
+
} finally {
|
|
107
|
+
if (timeoutHandle) {
|
|
108
|
+
clearTimeout(timeoutHandle);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async safeReadErrorPayload(response) {
|
|
113
|
+
try {
|
|
114
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
115
|
+
if (contentType.includes("application/json")) {
|
|
116
|
+
return await response.json();
|
|
117
|
+
}
|
|
118
|
+
const text = await response.text();
|
|
119
|
+
return { detail: text || void 0 };
|
|
120
|
+
} catch {
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
buildQuery(params) {
|
|
125
|
+
const search = new URLSearchParams();
|
|
126
|
+
for (const [key, value] of Object.entries(params)) {
|
|
127
|
+
if (value === void 0 || value === null) continue;
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
for (const v of value) {
|
|
130
|
+
search.append(key, String(v));
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
search.set(key, String(value));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const qs = search.toString();
|
|
137
|
+
return qs ? `?${qs}` : "";
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
export {
|
|
141
|
+
ApiError,
|
|
142
|
+
SurrealMemoryClient
|
|
143
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@acidkill/surreal-memory-client",
|
|
3
|
+
"version": "2.6.0",
|
|
4
|
+
"description": "TypeScript client for the Surreal-Memory REST API — typed access to brains, neurons, synapses, fibers, recall, and sync.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/",
|
|
18
|
+
"src/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"surreal-memory",
|
|
30
|
+
"surrealmemory",
|
|
31
|
+
"surrealdb",
|
|
32
|
+
"memory",
|
|
33
|
+
"ai-agent",
|
|
34
|
+
"client",
|
|
35
|
+
"sdk",
|
|
36
|
+
"rest"
|
|
37
|
+
],
|
|
38
|
+
"author": "Toni Nowak / AI-Flow NOWAK",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"homepage": "https://github.com/acidkill/surreal-memory#readme",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/acidkill/surreal-memory.git",
|
|
44
|
+
"directory": "integrations/surreal-memory-client"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/acidkill/surreal-memory/issues"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.0.0",
|
|
57
|
+
"tsup": "^8.0.0",
|
|
58
|
+
"typescript": "^5.4.0",
|
|
59
|
+
"vitest": "^1.6.0"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SurrealMemoryClient — typed REST client for a running Surreal-Memory server.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { SurrealMemoryClient } from "@acidkill/surreal-memory-client"
|
|
7
|
+
*
|
|
8
|
+
* const client = new SurrealMemoryClient({
|
|
9
|
+
* baseUrl: "http://localhost:8000",
|
|
10
|
+
* brain: "myproject",
|
|
11
|
+
* })
|
|
12
|
+
*
|
|
13
|
+
* const { fiber_id } = await client.remember({
|
|
14
|
+
* content: "Fixed auth bug with null check in login.py:42",
|
|
15
|
+
* type: "fix",
|
|
16
|
+
* priority: 7,
|
|
17
|
+
* tags: ["auth"],
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* const { results } = await client.recall({ query: "auth bug", limit: 5 })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
ApiErrorPayload,
|
|
26
|
+
Brain,
|
|
27
|
+
BrainStats,
|
|
28
|
+
ContextRequest,
|
|
29
|
+
ContextResponse,
|
|
30
|
+
Fiber,
|
|
31
|
+
HealthResponse,
|
|
32
|
+
RecallRequest,
|
|
33
|
+
RecallResponse,
|
|
34
|
+
RememberRequest,
|
|
35
|
+
RememberResponse,
|
|
36
|
+
} from "./types"
|
|
37
|
+
|
|
38
|
+
export interface ClientOptions {
|
|
39
|
+
/** REST server URL, e.g. `http://localhost:8000`. No trailing slash. */
|
|
40
|
+
baseUrl: string
|
|
41
|
+
/** Default brain name. Can be overridden per-request. Optional. */
|
|
42
|
+
brain?: string
|
|
43
|
+
/** Optional bearer token sent as `Authorization: Bearer <token>`. */
|
|
44
|
+
apiKey?: string
|
|
45
|
+
/** Custom `fetch` implementation (defaults to global `fetch`). */
|
|
46
|
+
fetch?: typeof fetch
|
|
47
|
+
/** Default timeout in ms (defaults to 30000). */
|
|
48
|
+
timeoutMs?: number
|
|
49
|
+
/** Extra headers attached to every request. */
|
|
50
|
+
headers?: Record<string, string>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RequestOptions {
|
|
54
|
+
/** Override the brain for this single call. */
|
|
55
|
+
brain?: string
|
|
56
|
+
/** Override the timeout for this single call. */
|
|
57
|
+
timeoutMs?: number
|
|
58
|
+
/** AbortSignal for cancellation. */
|
|
59
|
+
signal?: AbortSignal
|
|
60
|
+
/** Additional headers merged on top of client defaults. */
|
|
61
|
+
headers?: Record<string, string>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class ApiError extends Error {
|
|
65
|
+
public readonly status: number
|
|
66
|
+
public readonly payload: ApiErrorPayload | undefined
|
|
67
|
+
|
|
68
|
+
constructor(status: number, message: string, payload?: ApiErrorPayload) {
|
|
69
|
+
super(message)
|
|
70
|
+
this.name = "ApiError"
|
|
71
|
+
this.status = status
|
|
72
|
+
this.payload = payload
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class SurrealMemoryClient {
|
|
77
|
+
private readonly baseUrl: string
|
|
78
|
+
private readonly defaultBrain: string | undefined
|
|
79
|
+
private readonly apiKey: string | undefined
|
|
80
|
+
private readonly fetchImpl: typeof fetch
|
|
81
|
+
private readonly defaultTimeoutMs: number
|
|
82
|
+
private readonly defaultHeaders: Record<string, string>
|
|
83
|
+
|
|
84
|
+
constructor(options: ClientOptions) {
|
|
85
|
+
if (!options.baseUrl) {
|
|
86
|
+
throw new Error("SurrealMemoryClient: baseUrl is required")
|
|
87
|
+
}
|
|
88
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "")
|
|
89
|
+
this.defaultBrain = options.brain
|
|
90
|
+
this.apiKey = options.apiKey
|
|
91
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch
|
|
92
|
+
this.defaultTimeoutMs = options.timeoutMs ?? 30_000
|
|
93
|
+
this.defaultHeaders = options.headers ?? {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Memory operations ─────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
async remember(req: RememberRequest, options?: RequestOptions): Promise<RememberResponse> {
|
|
99
|
+
return this.request<RememberResponse>("POST", "/api/remember", req, options)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async recall(req: RecallRequest, options?: RequestOptions): Promise<RecallResponse> {
|
|
103
|
+
return this.request<RecallResponse>("POST", "/api/recall", req, options)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async context(req?: ContextRequest, options?: RequestOptions): Promise<ContextResponse> {
|
|
107
|
+
const query = req ? this.buildQuery(req as Record<string, unknown>) : ""
|
|
108
|
+
return this.request<ContextResponse>("GET", `/api/context${query}`, undefined, options)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async getFiber(fiberId: string, options?: RequestOptions): Promise<Fiber> {
|
|
112
|
+
return this.request<Fiber>("GET", `/api/fibers/${encodeURIComponent(fiberId)}`, undefined, options)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async forget(fiberId: string, options?: RequestOptions): Promise<{ deleted: true }> {
|
|
116
|
+
return this.request("DELETE", `/api/fibers/${encodeURIComponent(fiberId)}`, undefined, options)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Brain operations ──────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
async listBrains(options?: RequestOptions): Promise<Brain[]> {
|
|
122
|
+
return this.request<Brain[]>("GET", "/api/brains", undefined, options)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getBrainStats(options?: RequestOptions): Promise<BrainStats> {
|
|
126
|
+
return this.request<BrainStats>("GET", "/api/stats", undefined, options)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Health / diagnostics ─────────────────────────────────
|
|
130
|
+
|
|
131
|
+
async health(options?: RequestOptions): Promise<HealthResponse> {
|
|
132
|
+
return this.request<HealthResponse>("GET", "/api/health", undefined, options)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── Internal request plumbing ────────────────────────────
|
|
136
|
+
|
|
137
|
+
private async request<T>(
|
|
138
|
+
method: string,
|
|
139
|
+
path: string,
|
|
140
|
+
body: unknown,
|
|
141
|
+
options?: RequestOptions,
|
|
142
|
+
): Promise<T> {
|
|
143
|
+
const brain = options?.brain ?? this.defaultBrain
|
|
144
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs
|
|
145
|
+
|
|
146
|
+
const headers = new Headers(this.defaultHeaders)
|
|
147
|
+
for (const [k, v] of Object.entries(options?.headers ?? {})) {
|
|
148
|
+
headers.set(k, v)
|
|
149
|
+
}
|
|
150
|
+
if (brain) {
|
|
151
|
+
headers.set("X-Brain-ID", brain)
|
|
152
|
+
}
|
|
153
|
+
if (this.apiKey) {
|
|
154
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`)
|
|
155
|
+
}
|
|
156
|
+
if (body !== undefined && !headers.has("Content-Type")) {
|
|
157
|
+
headers.set("Content-Type", "application/json")
|
|
158
|
+
}
|
|
159
|
+
if (!headers.has("Accept")) {
|
|
160
|
+
headers.set("Accept", "application/json")
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const controller = new AbortController()
|
|
164
|
+
const timeoutHandle =
|
|
165
|
+
timeoutMs > 0
|
|
166
|
+
? setTimeout(() => controller.abort(new Error("Request timeout")), timeoutMs)
|
|
167
|
+
: undefined
|
|
168
|
+
|
|
169
|
+
const externalSignal = options?.signal
|
|
170
|
+
if (externalSignal) {
|
|
171
|
+
if (externalSignal.aborted) {
|
|
172
|
+
controller.abort(externalSignal.reason)
|
|
173
|
+
} else {
|
|
174
|
+
externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), {
|
|
175
|
+
once: true,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
182
|
+
method,
|
|
183
|
+
headers,
|
|
184
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
185
|
+
signal: controller.signal,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
const payload = await this.safeReadErrorPayload(response)
|
|
190
|
+
const message = payload?.detail ?? payload?.error ?? `HTTP ${response.status}`
|
|
191
|
+
throw new ApiError(response.status, message, payload)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (response.status === 204) {
|
|
195
|
+
return undefined as T
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return (await response.json()) as T
|
|
199
|
+
} finally {
|
|
200
|
+
if (timeoutHandle) {
|
|
201
|
+
clearTimeout(timeoutHandle)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async safeReadErrorPayload(response: Response): Promise<ApiErrorPayload | undefined> {
|
|
207
|
+
try {
|
|
208
|
+
const contentType = response.headers.get("Content-Type") ?? ""
|
|
209
|
+
if (contentType.includes("application/json")) {
|
|
210
|
+
return (await response.json()) as ApiErrorPayload
|
|
211
|
+
}
|
|
212
|
+
const text = await response.text()
|
|
213
|
+
return { detail: text || undefined }
|
|
214
|
+
} catch {
|
|
215
|
+
return undefined
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private buildQuery(params: Record<string, unknown>): string {
|
|
220
|
+
const search = new URLSearchParams()
|
|
221
|
+
for (const [key, value] of Object.entries(params)) {
|
|
222
|
+
if (value === undefined || value === null) continue
|
|
223
|
+
if (Array.isArray(value)) {
|
|
224
|
+
for (const v of value) {
|
|
225
|
+
search.append(key, String(v))
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
search.set(key, String(value))
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const qs = search.toString()
|
|
232
|
+
return qs ? `?${qs}` : ""
|
|
233
|
+
}
|
|
234
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @acidkill/surreal-memory-client
|
|
3
|
+
*
|
|
4
|
+
* Typed REST client for Surreal-Memory. See README.md for usage examples.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { SurrealMemoryClient, ApiError } from "./client"
|
|
8
|
+
export type { ClientOptions, RequestOptions } from "./client"
|
|
9
|
+
export type {
|
|
10
|
+
ApiErrorPayload,
|
|
11
|
+
Brain,
|
|
12
|
+
BrainStats,
|
|
13
|
+
ContextRequest,
|
|
14
|
+
ContextResponse,
|
|
15
|
+
Fiber,
|
|
16
|
+
HealthResponse,
|
|
17
|
+
LifecycleStage,
|
|
18
|
+
MemoryType,
|
|
19
|
+
Neuron,
|
|
20
|
+
NeuronType,
|
|
21
|
+
RecallRequest,
|
|
22
|
+
RecallResponse,
|
|
23
|
+
RecallResult,
|
|
24
|
+
RememberRequest,
|
|
25
|
+
RememberResponse,
|
|
26
|
+
Synapse,
|
|
27
|
+
SynapseType,
|
|
28
|
+
} from "./types"
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Surreal-Memory REST API.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the server response shapes from `src/surreal_memory/server/`.
|
|
5
|
+
* Only public-API fields are typed here; internal fields are passed through
|
|
6
|
+
* as `unknown` so the SDK does not break when the server adds new fields.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type MemoryType =
|
|
10
|
+
| "fact"
|
|
11
|
+
| "decision"
|
|
12
|
+
| "error"
|
|
13
|
+
| "insight"
|
|
14
|
+
| "preference"
|
|
15
|
+
| "workflow"
|
|
16
|
+
| "instruction"
|
|
17
|
+
| "concept"
|
|
18
|
+
| "context"
|
|
19
|
+
| "todo"
|
|
20
|
+
|
|
21
|
+
export type SynapseType =
|
|
22
|
+
| "CAUSED_BY"
|
|
23
|
+
| "LEADS_TO"
|
|
24
|
+
| "CONTRADICTS"
|
|
25
|
+
| "SIMILAR_TO"
|
|
26
|
+
| "PART_OF"
|
|
27
|
+
| "USED_BY"
|
|
28
|
+
| "DEPENDS_ON"
|
|
29
|
+
| string
|
|
30
|
+
|
|
31
|
+
export type NeuronType = "entity" | "concept" | "time" | "action" | "intent" | "state"
|
|
32
|
+
|
|
33
|
+
export type LifecycleStage = "full" | "summary" | "essence" | "ghost" | "metadata"
|
|
34
|
+
|
|
35
|
+
export interface Neuron {
|
|
36
|
+
id: string
|
|
37
|
+
type: NeuronType
|
|
38
|
+
content: string
|
|
39
|
+
content_hash?: number
|
|
40
|
+
created_at: string
|
|
41
|
+
metadata?: Record<string, unknown>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Synapse {
|
|
45
|
+
id: string
|
|
46
|
+
type: SynapseType
|
|
47
|
+
source_id: string
|
|
48
|
+
target_id: string
|
|
49
|
+
weight?: number
|
|
50
|
+
created_at: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface Fiber {
|
|
54
|
+
id: string
|
|
55
|
+
content: string
|
|
56
|
+
type?: MemoryType
|
|
57
|
+
priority?: number
|
|
58
|
+
tags?: string[]
|
|
59
|
+
stage?: LifecycleStage
|
|
60
|
+
created_at: string
|
|
61
|
+
updated_at?: string
|
|
62
|
+
metadata?: Record<string, unknown>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Brain {
|
|
66
|
+
id: string
|
|
67
|
+
name: string
|
|
68
|
+
created_at: string
|
|
69
|
+
stats?: BrainStats
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface BrainStats {
|
|
73
|
+
neurons: number
|
|
74
|
+
synapses: number
|
|
75
|
+
fibers: number
|
|
76
|
+
active_neurons?: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface RememberRequest {
|
|
80
|
+
content: string
|
|
81
|
+
type?: MemoryType
|
|
82
|
+
priority?: number
|
|
83
|
+
tags?: string[]
|
|
84
|
+
ephemeral?: boolean
|
|
85
|
+
metadata?: Record<string, unknown>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface RememberResponse {
|
|
89
|
+
fiber_id: string
|
|
90
|
+
type: MemoryType
|
|
91
|
+
saved: true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface RecallRequest {
|
|
95
|
+
query: string
|
|
96
|
+
limit?: number
|
|
97
|
+
type?: MemoryType
|
|
98
|
+
tags?: string[]
|
|
99
|
+
min_priority?: number
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface RecallResult {
|
|
103
|
+
fiber: Fiber
|
|
104
|
+
score: number
|
|
105
|
+
activation?: number
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface RecallResponse {
|
|
109
|
+
results: RecallResult[]
|
|
110
|
+
query: string
|
|
111
|
+
brain: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ContextRequest {
|
|
115
|
+
limit?: number
|
|
116
|
+
since?: string
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ContextResponse {
|
|
120
|
+
fibers: Fiber[]
|
|
121
|
+
brain: string
|
|
122
|
+
count: number
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface HealthResponse {
|
|
126
|
+
status: "ok" | "degraded" | "down"
|
|
127
|
+
version: string
|
|
128
|
+
storage: string
|
|
129
|
+
brain?: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ApiErrorPayload {
|
|
133
|
+
detail?: string
|
|
134
|
+
error?: string
|
|
135
|
+
status?: number
|
|
136
|
+
}
|