@hexis-ai/engram-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/client.d.ts +97 -0
- package/dist/client.js +184 -0
- package/dist/extract.d.ts +22 -0
- package/dist/extract.js +137 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/tool-name.d.ts +12 -0
- package/dist/tool-name.js +10 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.js +7 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hexis ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @hexis-ai/engram-sdk
|
|
2
|
+
|
|
3
|
+
Host-side SDK for [engram](https://github.com/hexis-ai/engram). Records agent session steps, buffers them with auto-flush, and exposes a typed search client.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @hexis-ai/engram-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Engram } from "@hexis-ai/engram-sdk";
|
|
15
|
+
|
|
16
|
+
const engram = new Engram({
|
|
17
|
+
apiKey: process.env.ENGRAM_API_KEY!,
|
|
18
|
+
baseUrl: process.env.ENGRAM_BASE_URL!,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// 1. Begin a session — id can be your own conversation id.
|
|
22
|
+
const session = await engram.startSession({ id: conversationId, channel: "chat_ui" });
|
|
23
|
+
|
|
24
|
+
// 2. Record steps as the agent runs. The SDK extracts canonical resource
|
|
25
|
+
// references from common MCP / built-in tools (WebFetch, WebSearch,
|
|
26
|
+
// Linear, Notion, ...) when you pass `input` + `result`.
|
|
27
|
+
session.recordStep({ tool: "WebFetch", input: { url }, result });
|
|
28
|
+
|
|
29
|
+
// Or pass resources directly:
|
|
30
|
+
session.recordStep({ tool: "Read", resources: ["file:src/x.ts"] });
|
|
31
|
+
|
|
32
|
+
// 3. Optional metadata.
|
|
33
|
+
session.addParticipant("slack:U001");
|
|
34
|
+
session.setTitle("debug auth flake");
|
|
35
|
+
|
|
36
|
+
// 4. Flush at session end.
|
|
37
|
+
await session.end();
|
|
38
|
+
|
|
39
|
+
// 5. Search later.
|
|
40
|
+
const { results } = await engram.search({ query: { sessionId: conversationId } });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
| Option | Default | Notes |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `apiKey` | required | sent as `Authorization: Bearer <key>` |
|
|
48
|
+
| `baseUrl` | required | engram-server URL |
|
|
49
|
+
| `flushIntervalMs` | 2000 | 0 to disable auto-flush |
|
|
50
|
+
| `batchSize` | 32 | flushes immediately when buffer reaches this |
|
|
51
|
+
| `onError` | `console.error` | called on transport errors |
|
|
52
|
+
| `fetch` | `globalThis.fetch` | inject for testing |
|
|
53
|
+
|
|
54
|
+
## Resource normalization
|
|
55
|
+
|
|
56
|
+
Tool names like `mcp__linear__list_issues` are stripped to bare `list_issues`. Resources are encoded as `service:resource_id` (e.g. `linear:HEX-1`, `web:https://...`, `notion:<pageId>`).
|
|
57
|
+
|
|
58
|
+
The extraction logic is exposed via `@hexis-ai/engram-sdk/extract` for hosts that want to derive resources without going through the SDK.
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ScoredSession, SearchOptions, Session, SessionStep } from "@hexis-ai/engram-core";
|
|
2
|
+
import { type RefCandidate } from "./extract";
|
|
3
|
+
import type { EventBatch, SessionInit } from "./types";
|
|
4
|
+
export interface EngramOptions {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
/**
|
|
9
|
+
* Auto-flush events at most this many ms after the first buffered event.
|
|
10
|
+
* Default 2000. Set to 0 to disable auto-flush (manual `flush()` only).
|
|
11
|
+
*/
|
|
12
|
+
flushIntervalMs?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Auto-flush when the buffer reaches this size. Default 32.
|
|
15
|
+
*/
|
|
16
|
+
batchSize?: number;
|
|
17
|
+
/** Hook invoked on transport errors. Default: console.error. */
|
|
18
|
+
onError?: (err: unknown) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface RecordStepInput {
|
|
21
|
+
tool: string;
|
|
22
|
+
/** Engram-format resources (`service:id`). Used as-is when provided. */
|
|
23
|
+
resources?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* If `resources` is omitted, the SDK runs `extractReferences()` over
|
|
26
|
+
* `input` + `result` to derive resources automatically.
|
|
27
|
+
*/
|
|
28
|
+
input?: Record<string, unknown>;
|
|
29
|
+
result?: unknown;
|
|
30
|
+
/** Override vendor extraction (otherwise inferred via `parseToolName`). */
|
|
31
|
+
vendor?: string;
|
|
32
|
+
/** Pre-resolved bare tool name (otherwise inferred via `parseToolName`). */
|
|
33
|
+
bareName?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SearchRequest {
|
|
36
|
+
/**
|
|
37
|
+
* Either an existing session id (server-side fetch + use as query) or an
|
|
38
|
+
* inline `Session`. Inline is convenient for one-off / synthetic queries.
|
|
39
|
+
*/
|
|
40
|
+
query: {
|
|
41
|
+
sessionId: string;
|
|
42
|
+
} | {
|
|
43
|
+
session: Pick<Session, "steps" | "participants">;
|
|
44
|
+
};
|
|
45
|
+
options?: SearchOptions;
|
|
46
|
+
}
|
|
47
|
+
export interface SearchResponse {
|
|
48
|
+
results: ScoredSession[];
|
|
49
|
+
}
|
|
50
|
+
export declare class Engram {
|
|
51
|
+
private readonly apiKey;
|
|
52
|
+
private readonly baseUrl;
|
|
53
|
+
private readonly fetchImpl;
|
|
54
|
+
private readonly flushIntervalMs;
|
|
55
|
+
private readonly batchSize;
|
|
56
|
+
private readonly onError;
|
|
57
|
+
constructor(opts: EngramOptions);
|
|
58
|
+
/** Begin a new session. Returns a handle for buffering events. */
|
|
59
|
+
startSession(init?: SessionInit): Promise<EngramSession>;
|
|
60
|
+
/** Fetch a single Session by id. */
|
|
61
|
+
getSession(id: string): Promise<Session>;
|
|
62
|
+
/** List recent sessions (server defines defaults & limits). */
|
|
63
|
+
listSessions(opts?: {
|
|
64
|
+
limit?: number;
|
|
65
|
+
channel?: string;
|
|
66
|
+
}): Promise<Session[]>;
|
|
67
|
+
/** Run a search. */
|
|
68
|
+
search(req: SearchRequest): Promise<SearchResponse>;
|
|
69
|
+
/** Internal: ship an event batch to the server. */
|
|
70
|
+
sendBatch(sessionId: string, batch: EventBatch): Promise<void>;
|
|
71
|
+
/** Internal accessor used by EngramSession to honor SDK config. */
|
|
72
|
+
get config(): {
|
|
73
|
+
flushIntervalMs: number;
|
|
74
|
+
batchSize: number;
|
|
75
|
+
onError: (err: unknown) => void;
|
|
76
|
+
};
|
|
77
|
+
private request;
|
|
78
|
+
}
|
|
79
|
+
export declare class EngramSession {
|
|
80
|
+
readonly id: string;
|
|
81
|
+
private readonly engram;
|
|
82
|
+
private readonly buffer;
|
|
83
|
+
private seq;
|
|
84
|
+
private flushTimer;
|
|
85
|
+
private inFlight;
|
|
86
|
+
private ended;
|
|
87
|
+
constructor(engram: Engram, id: string);
|
|
88
|
+
recordStep(input: RecordStepInput): SessionStep;
|
|
89
|
+
addParticipant(identityRef: string): void;
|
|
90
|
+
setTitle(title: string): void;
|
|
91
|
+
/** Mark the session ended and flush buffered events. */
|
|
92
|
+
end(): Promise<void>;
|
|
93
|
+
/** Force a flush regardless of batch size / timer. */
|
|
94
|
+
flush(): Promise<void>;
|
|
95
|
+
private enqueue;
|
|
96
|
+
}
|
|
97
|
+
export type { RefCandidate };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { encodeResourceId, extractReferences } from "./extract";
|
|
2
|
+
import { parseToolName } from "./tool-name";
|
|
3
|
+
export class Engram {
|
|
4
|
+
apiKey;
|
|
5
|
+
baseUrl;
|
|
6
|
+
fetchImpl;
|
|
7
|
+
flushIntervalMs;
|
|
8
|
+
batchSize;
|
|
9
|
+
onError;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
if (!opts.apiKey)
|
|
12
|
+
throw new Error("Engram: apiKey is required");
|
|
13
|
+
if (!opts.baseUrl)
|
|
14
|
+
throw new Error("Engram: baseUrl is required");
|
|
15
|
+
this.apiKey = opts.apiKey;
|
|
16
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
17
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
18
|
+
this.flushIntervalMs = opts.flushIntervalMs ?? 2000;
|
|
19
|
+
this.batchSize = opts.batchSize ?? 32;
|
|
20
|
+
this.onError = opts.onError ?? ((e) => console.error("[engram]", e));
|
|
21
|
+
}
|
|
22
|
+
/** Begin a new session. Returns a handle for buffering events. */
|
|
23
|
+
async startSession(init = {}) {
|
|
24
|
+
const ack = await this.request("POST", "/v1/sessions", init);
|
|
25
|
+
return new EngramSession(this, ack.id);
|
|
26
|
+
}
|
|
27
|
+
/** Fetch a single Session by id. */
|
|
28
|
+
async getSession(id) {
|
|
29
|
+
return this.request("GET", `/v1/sessions/${encodeURIComponent(id)}`);
|
|
30
|
+
}
|
|
31
|
+
/** List recent sessions (server defines defaults & limits). */
|
|
32
|
+
async listSessions(opts = {}) {
|
|
33
|
+
const qs = new URLSearchParams();
|
|
34
|
+
if (opts.limit !== undefined)
|
|
35
|
+
qs.set("limit", String(opts.limit));
|
|
36
|
+
if (opts.channel)
|
|
37
|
+
qs.set("channel", opts.channel);
|
|
38
|
+
const tail = qs.toString();
|
|
39
|
+
return this.request("GET", `/v1/sessions${tail ? `?${tail}` : ""}`);
|
|
40
|
+
}
|
|
41
|
+
/** Run a search. */
|
|
42
|
+
async search(req) {
|
|
43
|
+
return this.request("POST", "/v1/search", req);
|
|
44
|
+
}
|
|
45
|
+
/** Internal: ship an event batch to the server. */
|
|
46
|
+
async sendBatch(sessionId, batch) {
|
|
47
|
+
if (batch.events.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
await this.request("POST", `/v1/sessions/${encodeURIComponent(sessionId)}/events`, batch);
|
|
50
|
+
}
|
|
51
|
+
/** Internal accessor used by EngramSession to honor SDK config. */
|
|
52
|
+
get config() {
|
|
53
|
+
return { flushIntervalMs: this.flushIntervalMs, batchSize: this.batchSize, onError: this.onError };
|
|
54
|
+
}
|
|
55
|
+
async request(method, path, body) {
|
|
56
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
57
|
+
method,
|
|
58
|
+
headers: {
|
|
59
|
+
"content-type": "application/json",
|
|
60
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
61
|
+
},
|
|
62
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
const text = await res.text().catch(() => "");
|
|
66
|
+
throw new Error(`engram ${method} ${path} ${res.status}: ${text}`);
|
|
67
|
+
}
|
|
68
|
+
if (res.status === 204)
|
|
69
|
+
return undefined;
|
|
70
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
71
|
+
if (!ct.includes("application/json"))
|
|
72
|
+
return undefined;
|
|
73
|
+
return (await res.json());
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export class EngramSession {
|
|
77
|
+
id;
|
|
78
|
+
engram;
|
|
79
|
+
buffer = [];
|
|
80
|
+
seq = 0;
|
|
81
|
+
flushTimer = null;
|
|
82
|
+
inFlight = null;
|
|
83
|
+
ended = false;
|
|
84
|
+
constructor(engram, id) {
|
|
85
|
+
this.engram = engram;
|
|
86
|
+
this.id = id;
|
|
87
|
+
}
|
|
88
|
+
recordStep(input) {
|
|
89
|
+
const resources = resolveResources(input);
|
|
90
|
+
const tool = input.bareName ?? parseToolName(input.tool).bareName;
|
|
91
|
+
const ev = {
|
|
92
|
+
type: "step",
|
|
93
|
+
seq: this.seq++,
|
|
94
|
+
at: new Date().toISOString(),
|
|
95
|
+
tool,
|
|
96
|
+
resources,
|
|
97
|
+
};
|
|
98
|
+
this.enqueue(ev);
|
|
99
|
+
return { tool, resources };
|
|
100
|
+
}
|
|
101
|
+
addParticipant(identityRef) {
|
|
102
|
+
const ev = {
|
|
103
|
+
type: "participant",
|
|
104
|
+
seq: this.seq++,
|
|
105
|
+
at: new Date().toISOString(),
|
|
106
|
+
identityRef,
|
|
107
|
+
};
|
|
108
|
+
this.enqueue(ev);
|
|
109
|
+
}
|
|
110
|
+
setTitle(title) {
|
|
111
|
+
const ev = {
|
|
112
|
+
type: "title",
|
|
113
|
+
seq: this.seq++,
|
|
114
|
+
at: new Date().toISOString(),
|
|
115
|
+
title,
|
|
116
|
+
};
|
|
117
|
+
this.enqueue(ev);
|
|
118
|
+
}
|
|
119
|
+
/** Mark the session ended and flush buffered events. */
|
|
120
|
+
async end() {
|
|
121
|
+
if (this.ended)
|
|
122
|
+
return;
|
|
123
|
+
this.ended = true;
|
|
124
|
+
this.enqueue({
|
|
125
|
+
type: "end",
|
|
126
|
+
seq: this.seq++,
|
|
127
|
+
at: new Date().toISOString(),
|
|
128
|
+
});
|
|
129
|
+
await this.flush();
|
|
130
|
+
}
|
|
131
|
+
/** Force a flush regardless of batch size / timer. */
|
|
132
|
+
async flush() {
|
|
133
|
+
if (this.flushTimer) {
|
|
134
|
+
clearTimeout(this.flushTimer);
|
|
135
|
+
this.flushTimer = null;
|
|
136
|
+
}
|
|
137
|
+
if (this.inFlight)
|
|
138
|
+
await this.inFlight;
|
|
139
|
+
if (this.buffer.length === 0)
|
|
140
|
+
return;
|
|
141
|
+
const events = this.buffer.splice(0);
|
|
142
|
+
this.inFlight = this.engram
|
|
143
|
+
.sendBatch(this.id, { events })
|
|
144
|
+
.catch((e) => {
|
|
145
|
+
this.engram.config.onError(e);
|
|
146
|
+
this.buffer.unshift(...events);
|
|
147
|
+
throw e;
|
|
148
|
+
})
|
|
149
|
+
.finally(() => {
|
|
150
|
+
this.inFlight = null;
|
|
151
|
+
});
|
|
152
|
+
await this.inFlight;
|
|
153
|
+
}
|
|
154
|
+
enqueue(ev) {
|
|
155
|
+
this.buffer.push(ev);
|
|
156
|
+
const { batchSize, flushIntervalMs } = this.engram.config;
|
|
157
|
+
if (this.buffer.length >= batchSize) {
|
|
158
|
+
void this.flush().catch(() => { });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (flushIntervalMs > 0 && !this.flushTimer) {
|
|
162
|
+
this.flushTimer = setTimeout(() => {
|
|
163
|
+
this.flushTimer = null;
|
|
164
|
+
void this.flush().catch(() => { });
|
|
165
|
+
}, flushIntervalMs);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function resolveResources(input) {
|
|
170
|
+
if (input.resources)
|
|
171
|
+
return [...input.resources];
|
|
172
|
+
if (input.input === undefined && input.result === undefined)
|
|
173
|
+
return [];
|
|
174
|
+
const { vendor, bareName } = (() => {
|
|
175
|
+
if (input.bareName !== undefined)
|
|
176
|
+
return { vendor: input.vendor, bareName: input.bareName };
|
|
177
|
+
return parseToolName(input.tool);
|
|
178
|
+
})();
|
|
179
|
+
const refs = extractReferences(vendor, bareName, input.input ?? {}, input.result);
|
|
180
|
+
return dedupe(refs.map(encodeResourceId));
|
|
181
|
+
}
|
|
182
|
+
function dedupe(xs) {
|
|
183
|
+
return [...new Set(xs)];
|
|
184
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource extraction: derives engram-shaped resource references from a
|
|
3
|
+
* single tool call's `(name, input, result)` triple.
|
|
4
|
+
*
|
|
5
|
+
* Migrated from monet's `agent/reference-extractor.ts` so that any host that
|
|
6
|
+
* speaks Claude Agent SDK / vendored MCP can normalize tool calls without
|
|
7
|
+
* shipping its own copy.
|
|
8
|
+
*
|
|
9
|
+
* Resource id format follows engram convention: `${service}:${id-or-url}`.
|
|
10
|
+
*/
|
|
11
|
+
export type ReferenceService = "linear" | "notion" | "web" | "github";
|
|
12
|
+
export type ReferenceAction = "read" | "write";
|
|
13
|
+
export interface RefCandidate {
|
|
14
|
+
service: ReferenceService;
|
|
15
|
+
action: ReferenceAction;
|
|
16
|
+
resource_id: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function extractReferences(vendor: string | undefined, toolName: string, input: Record<string, unknown>, result: unknown): RefCandidate[];
|
|
21
|
+
/** Convenience: encode RefCandidate to engram resource id (`service:resource_id`). */
|
|
22
|
+
export declare function encodeResourceId(ref: Pick<RefCandidate, "service" | "resource_id">): string;
|
package/dist/extract.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource extraction: derives engram-shaped resource references from a
|
|
3
|
+
* single tool call's `(name, input, result)` triple.
|
|
4
|
+
*
|
|
5
|
+
* Migrated from monet's `agent/reference-extractor.ts` so that any host that
|
|
6
|
+
* speaks Claude Agent SDK / vendored MCP can normalize tool calls without
|
|
7
|
+
* shipping its own copy.
|
|
8
|
+
*
|
|
9
|
+
* Resource id format follows engram convention: `${service}:${id-or-url}`.
|
|
10
|
+
*/
|
|
11
|
+
const isWebSearch = (n) => n === "WebSearch" || n === "web_search";
|
|
12
|
+
const isWebFetch = (n) => n === "WebFetch" || n === "web_fetch";
|
|
13
|
+
const WRITE_TOOLS = {
|
|
14
|
+
create_issue: "write",
|
|
15
|
+
update_issue: "write",
|
|
16
|
+
"notion-create-pages": "write",
|
|
17
|
+
"notion-update-page": "write",
|
|
18
|
+
"notion-create-database": "write",
|
|
19
|
+
"notion-create-comment": "write",
|
|
20
|
+
};
|
|
21
|
+
function getAction(toolName) {
|
|
22
|
+
return WRITE_TOOLS[toolName] ?? "read";
|
|
23
|
+
}
|
|
24
|
+
function extractMcpText(result) {
|
|
25
|
+
const r = result;
|
|
26
|
+
if (!r?.content)
|
|
27
|
+
return "";
|
|
28
|
+
return r.content
|
|
29
|
+
.filter((c) => c.type === "text" && c.text)
|
|
30
|
+
.map((c) => c.text)
|
|
31
|
+
.join("\n");
|
|
32
|
+
}
|
|
33
|
+
function tryParseJson(text) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(text);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function extractReferences(vendor, toolName, input, result) {
|
|
42
|
+
if (vendor === undefined) {
|
|
43
|
+
if (toolName === "create_issue" || toolName === "update_issue") {
|
|
44
|
+
return extractNativeLinearIssueRef(toolName, result);
|
|
45
|
+
}
|
|
46
|
+
if (isWebSearch(toolName) || isWebFetch(toolName))
|
|
47
|
+
return extractWebRefs(toolName, input);
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
if (vendor === "linear")
|
|
51
|
+
return extractLinearRefs(toolName, input, result);
|
|
52
|
+
if (vendor === "notion")
|
|
53
|
+
return extractNotionRefs(toolName, input, result);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
/** Convenience: encode RefCandidate to engram resource id (`service:resource_id`). */
|
|
57
|
+
export function encodeResourceId(ref) {
|
|
58
|
+
return `${ref.service}:${ref.resource_id}`;
|
|
59
|
+
}
|
|
60
|
+
function extractNativeLinearIssueRef(toolName, result) {
|
|
61
|
+
if (!result || typeof result !== "object")
|
|
62
|
+
return [];
|
|
63
|
+
const obj = result;
|
|
64
|
+
if (typeof obj.id !== "string" && typeof obj.identifier !== "string")
|
|
65
|
+
return [];
|
|
66
|
+
return [{
|
|
67
|
+
service: "linear",
|
|
68
|
+
action: getAction(toolName),
|
|
69
|
+
resource_id: String(obj.identifier ?? obj.id),
|
|
70
|
+
title: obj.title,
|
|
71
|
+
url: obj.url,
|
|
72
|
+
}];
|
|
73
|
+
}
|
|
74
|
+
function extractLinearRefs(toolName, input, result) {
|
|
75
|
+
const action = getAction(toolName);
|
|
76
|
+
const text = extractMcpText(result);
|
|
77
|
+
const parsed = tryParseJson(text);
|
|
78
|
+
if (parsed && typeof parsed === "object" && "id" in parsed) {
|
|
79
|
+
const obj = parsed;
|
|
80
|
+
return [{
|
|
81
|
+
service: "linear",
|
|
82
|
+
action,
|
|
83
|
+
resource_id: String(obj.identifier ?? obj.id),
|
|
84
|
+
title: obj.title,
|
|
85
|
+
url: obj.url,
|
|
86
|
+
}];
|
|
87
|
+
}
|
|
88
|
+
if (parsed && typeof parsed === "object" && "issues" in parsed) {
|
|
89
|
+
const issues = parsed.issues;
|
|
90
|
+
return issues.map((issue) => ({
|
|
91
|
+
service: "linear",
|
|
92
|
+
action: "read",
|
|
93
|
+
resource_id: String(issue.identifier ?? issue.id),
|
|
94
|
+
title: issue.title,
|
|
95
|
+
url: issue.url,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
if (input.issue_id) {
|
|
99
|
+
return [{ service: "linear", action, resource_id: String(input.issue_id) }];
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
function extractNotionRefs(toolName, input, result) {
|
|
104
|
+
const action = getAction(toolName);
|
|
105
|
+
if (input.pageId) {
|
|
106
|
+
return [{ service: "notion", action, resource_id: String(input.pageId) }];
|
|
107
|
+
}
|
|
108
|
+
const text = extractMcpText(result);
|
|
109
|
+
const urlMatches = text.match(/https:\/\/www\.notion\.so\/[^\s)">]+/g);
|
|
110
|
+
if (urlMatches) {
|
|
111
|
+
return urlMatches.slice(0, 5).map((url) => ({
|
|
112
|
+
service: "notion",
|
|
113
|
+
action,
|
|
114
|
+
resource_id: url,
|
|
115
|
+
url,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
function extractWebRefs(toolName, input) {
|
|
121
|
+
if (isWebFetch(toolName) && input.url) {
|
|
122
|
+
return [{
|
|
123
|
+
service: "web",
|
|
124
|
+
action: "read",
|
|
125
|
+
resource_id: String(input.url),
|
|
126
|
+
url: String(input.url),
|
|
127
|
+
}];
|
|
128
|
+
}
|
|
129
|
+
if (isWebSearch(toolName) && input.query) {
|
|
130
|
+
return [{
|
|
131
|
+
service: "web",
|
|
132
|
+
action: "read",
|
|
133
|
+
resource_id: `search:${String(input.query)}`,
|
|
134
|
+
}];
|
|
135
|
+
}
|
|
136
|
+
return [];
|
|
137
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { Engram, EngramSession, type EngramOptions, type RecordStepInput, type SearchRequest, type SearchResponse, } from "./client";
|
|
2
|
+
export { extractReferences, encodeResourceId, type RefCandidate, type ReferenceService, type ReferenceAction, } from "./extract";
|
|
3
|
+
export { parseToolName, type ParsedToolName } from "./tool-name";
|
|
4
|
+
export type { SessionInit, SessionAck, SessionEvent, StepEvent, ParticipantEvent, TitleEvent, EndEvent, EventBatch, } from "./types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool names follow the convention `mcp__<server>__<bare>` when invoked
|
|
3
|
+
* via the Claude Agent SDK / Anthropic SDK. The "in-process engram MCP server"
|
|
4
|
+
* (server name "engram") is collapsed to no vendor since it is conceptually
|
|
5
|
+
* native to the host. Built-in SDK tools (Read, Edit, WebSearch, ...) come in
|
|
6
|
+
* with no prefix at all.
|
|
7
|
+
*/
|
|
8
|
+
export interface ParsedToolName {
|
|
9
|
+
vendor?: string;
|
|
10
|
+
bareName: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseToolName(name: string): ParsedToolName;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const MCP_RE = /^mcp__([^_]+)__(.+)$/;
|
|
2
|
+
export function parseToolName(name) {
|
|
3
|
+
const m = MCP_RE.exec(name);
|
|
4
|
+
if (!m)
|
|
5
|
+
return { bareName: name };
|
|
6
|
+
const [, server, bare] = m;
|
|
7
|
+
if (server === "engram")
|
|
8
|
+
return { bareName: bare };
|
|
9
|
+
return { vendor: server, bareName: bare };
|
|
10
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types shared between @hexis-ai/engram-sdk and @hexis-ai/engram-server.
|
|
3
|
+
*
|
|
4
|
+
* Keep this file dependency-free (no @hexis-ai/engram-core import) so it can be reused
|
|
5
|
+
* by language ports later. Algorithm types live in @hexis-ai/engram-core.
|
|
6
|
+
*/
|
|
7
|
+
export interface SessionInit {
|
|
8
|
+
/** Optional client-supplied id; server may accept or override. */
|
|
9
|
+
id?: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Free-form classification of where this session originated
|
|
13
|
+
* (e.g. `chat_ui`, `slack_dm`, `cron:dailyDigest`).
|
|
14
|
+
*/
|
|
15
|
+
channel?: string;
|
|
16
|
+
participants?: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface SessionAck {
|
|
19
|
+
id: string;
|
|
20
|
+
}
|
|
21
|
+
export interface StepEvent {
|
|
22
|
+
type: "step";
|
|
23
|
+
seq: number;
|
|
24
|
+
at: string;
|
|
25
|
+
tool: string;
|
|
26
|
+
resources: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface ParticipantEvent {
|
|
29
|
+
type: "participant";
|
|
30
|
+
seq: number;
|
|
31
|
+
at: string;
|
|
32
|
+
identityRef: string;
|
|
33
|
+
}
|
|
34
|
+
export interface TitleEvent {
|
|
35
|
+
type: "title";
|
|
36
|
+
seq: number;
|
|
37
|
+
at: string;
|
|
38
|
+
title: string;
|
|
39
|
+
}
|
|
40
|
+
export interface EndEvent {
|
|
41
|
+
type: "end";
|
|
42
|
+
seq: number;
|
|
43
|
+
at: string;
|
|
44
|
+
}
|
|
45
|
+
export type SessionEvent = StepEvent | ParticipantEvent | TitleEvent | EndEvent;
|
|
46
|
+
export interface EventBatch {
|
|
47
|
+
events: SessionEvent[];
|
|
48
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types shared between @hexis-ai/engram-sdk and @hexis-ai/engram-server.
|
|
3
|
+
*
|
|
4
|
+
* Keep this file dependency-free (no @hexis-ai/engram-core import) so it can be reused
|
|
5
|
+
* by language ports later. Algorithm types live in @hexis-ai/engram-core.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hexis-ai/engram-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Host SDK for engram. Records agent session steps and ships them to an engram server.",
|
|
5
|
+
"keywords": ["engram", "agents", "claude", "anthropic", "sdk", "observability"],
|
|
6
|
+
"homepage": "https://github.com/hexis-ai/engram#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/hexis-ai/engram.git",
|
|
10
|
+
"directory": "packages/sdk"
|
|
11
|
+
},
|
|
12
|
+
"bugs": "https://github.com/hexis-ai/engram/issues",
|
|
13
|
+
"author": "hexis ltd.",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./extract": {
|
|
24
|
+
"types": "./dist/extract.d.ts",
|
|
25
|
+
"default": "./dist/extract.js"
|
|
26
|
+
},
|
|
27
|
+
"./tool-name": {
|
|
28
|
+
"types": "./dist/tool-name.d.ts",
|
|
29
|
+
"default": "./dist/tool-name.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
37
|
+
"prepack": "bun run build",
|
|
38
|
+
"pack": "bun run build && bun pm pack",
|
|
39
|
+
"test": "bun test",
|
|
40
|
+
"type-check": "tsc --noEmit"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@hexis-ai/engram-core": "workspace:*"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|