@tpsdev-ai/adk-flair 0.44.2 → 0.44.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -9
- package/dist/signing.d.ts +25 -5
- package/dist/signing.d.ts.map +1 -1
- package/dist/signing.js +92 -27
- package/dist/signing.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,32 +5,68 @@
|
|
|
5
5
|
[Flair](https://github.com/tpsdev-ai/flair) — a self-hosted, federated
|
|
6
6
|
semantic memory engine. Keep your agent memory yours.
|
|
7
7
|
|
|
8
|
+
## Quickstart
|
|
9
|
+
|
|
8
10
|
```bash
|
|
11
|
+
# 1. Install Flair and initialise it
|
|
12
|
+
npm i -g @tpsdev-ai/flair
|
|
13
|
+
flair init
|
|
14
|
+
|
|
15
|
+
# 2. Provision an Ed25519 identity for your ADK app
|
|
16
|
+
flair agent add my-adk-app
|
|
17
|
+
# → writes ~/.flair/keys/my-adk-app.key
|
|
18
|
+
|
|
19
|
+
# 3. Install adk-flair
|
|
9
20
|
npm install @tpsdev-ai/adk-flair
|
|
10
|
-
```
|
|
11
21
|
|
|
12
|
-
|
|
22
|
+
# 4. Set environment variables
|
|
23
|
+
export FLAIR_URL=http://localhost:19926
|
|
24
|
+
export FLAIR_AGENT_ID=my-adk-app
|
|
25
|
+
export FLAIR_KEYFILE=~/.flair/keys/my-adk-app.key
|
|
26
|
+
export GOOGLE_API_KEY=... # or GEMINI_API_KEY, to run the agent
|
|
27
|
+
```
|
|
13
28
|
|
|
14
|
-
|
|
29
|
+
Then swap the memory service in your ADK agent (illustrative — `agent` and
|
|
30
|
+
`sessionService` are your own, see the runnable example below for the full
|
|
31
|
+
wiring):
|
|
15
32
|
|
|
16
33
|
```typescript
|
|
17
34
|
import { FlairMemoryService } from "@tpsdev-ai/adk-flair";
|
|
18
|
-
import { Runner } from "@google/adk";
|
|
35
|
+
import { Agent, Runner, InMemorySessionService, PRELOAD_MEMORY } from "@google/adk";
|
|
19
36
|
|
|
20
37
|
const memoryService = new FlairMemoryService({
|
|
21
|
-
url:
|
|
22
|
-
agentId:
|
|
23
|
-
keyfile:
|
|
38
|
+
url: process.env.FLAIR_URL, // http://localhost:19926
|
|
39
|
+
agentId: process.env.FLAIR_AGENT_ID, // my-adk-app
|
|
40
|
+
keyfile: process.env.FLAIR_KEYFILE, // ~/.flair/keys/my-adk-app.key (~ is expanded)
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const agent = new Agent({
|
|
44
|
+
model: "gemini-2.5-flash",
|
|
45
|
+
name: "my_agent",
|
|
46
|
+
instruction: "You are a helpful assistant with memory.",
|
|
47
|
+
tools: [PRELOAD_MEMORY], // pulls past memories into the prompt
|
|
24
48
|
});
|
|
25
49
|
|
|
26
50
|
const runner = new Runner({
|
|
27
51
|
agent,
|
|
28
52
|
appName: "my-app",
|
|
29
|
-
sessionService,
|
|
53
|
+
sessionService: new InMemorySessionService(),
|
|
30
54
|
memoryService,
|
|
31
55
|
});
|
|
32
56
|
```
|
|
33
57
|
|
|
58
|
+
### Run it end to end
|
|
59
|
+
|
|
60
|
+
A complete, copy-paste-runnable demo lives at
|
|
61
|
+
[`examples/quickstart.ts`](./examples/quickstart.ts). After the steps above:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bun run examples/quickstart.ts
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
It plants a fact in session 1, waits for Flair to make it searchable, then
|
|
68
|
+
asks for it back in a fresh session 2 and prints whether the fact was recalled.
|
|
69
|
+
|
|
34
70
|
### Dev UI (AdkApiServer)
|
|
35
71
|
|
|
36
72
|
```typescript
|
|
@@ -48,7 +84,7 @@ server.start();
|
|
|
48
84
|
|---|---|---|---|
|
|
49
85
|
| `FLAIR_URL` | No | `http://localhost:19926` | Flair HTTP endpoint |
|
|
50
86
|
| `FLAIR_AGENT_ID` | Yes | — | Flair agent identity |
|
|
51
|
-
| `FLAIR_KEYFILE` | Yes | — | Path to Ed25519
|
|
87
|
+
| `FLAIR_KEYFILE` | Yes | — | Path to the Ed25519 keyfile from `flair agent add` (raw seed; base64/PEM also accepted). A leading `~` is expanded. |
|
|
52
88
|
| `FLAIR_ALLOW_REMOTE_URL` | No | — | Set to `1` to allow non-localhost URLs |
|
|
53
89
|
|
|
54
90
|
All values can also be passed directly to the constructor.
|
package/dist/signing.d.ts
CHANGED
|
@@ -7,13 +7,33 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import * as crypto from "node:crypto";
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Expand a leading `~` / `~/` to the current user's home directory.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* `flair agent add <id>` and the README both hand users paths like
|
|
13
|
+
* `~/.flair/keys/<id>.key`. Node's `fs` does not expand `~` — a shell does —
|
|
14
|
+
* so a raw `readFileSync("~/...")` throws ENOENT for a cold user copying the
|
|
15
|
+
* quickstart verbatim. Expand here so the documented path just works.
|
|
16
|
+
*/
|
|
17
|
+
export declare function expandHome(p: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Load and validate an Ed25519 private key from a Flair keyfile.
|
|
20
|
+
*
|
|
21
|
+
* Accepts every on-disk encoding Flair itself produces or consumes (mirrors
|
|
22
|
+
* src/lib/auth-resolve.ts), so the keyfile written by `flair agent add`
|
|
23
|
+
* (a raw 32-byte seed) loads without a conversion step:
|
|
24
|
+
*
|
|
25
|
+
* - raw 32-byte Ed25519 seed (binary) — what `flair agent add` writes
|
|
26
|
+
* - base64-encoded raw 32-byte seed
|
|
27
|
+
* - base64-encoded PKCS8 DER (the historical adk-flair format)
|
|
28
|
+
* - PEM (`-----BEGIN PRIVATE KEY-----`)
|
|
29
|
+
*
|
|
30
|
+
* Parses the key material eagerly (in the constructor path) so that a bad
|
|
31
|
+
* keyfile fails immediately — before ADK's exception-swallowing search path
|
|
32
|
+
* can turn it into permanent silent empty recall.
|
|
33
|
+
*
|
|
34
|
+
* A leading `~`/`~/` in the path is expanded to the home directory.
|
|
15
35
|
*
|
|
16
|
-
* @param keyfilePath - Path to the keyfile (
|
|
36
|
+
* @param keyfilePath - Path to the keyfile (`~` accepted)
|
|
17
37
|
* @returns A Node.js KeyObject for the Ed25519 private key
|
|
18
38
|
* @throws If the keyfile is missing, unreadable, or contains invalid key material
|
|
19
39
|
*/
|
package/dist/signing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AActC;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,CA6CpE;AA0CD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,CAAC,SAAS,EAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,MAAM,CAOR"}
|
package/dist/signing.js
CHANGED
|
@@ -7,57 +7,122 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import * as crypto from "node:crypto";
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
// PKCS8 DER prefix for an Ed25519 private key. Prepending this to a raw
|
|
13
|
+
// 32-byte seed yields a full 48-byte PKCS8 DER that `createPrivateKey` accepts.
|
|
14
|
+
// Mirrors src/lib/auth-resolve.ts:buildEd25519Auth so the adapter reads exactly
|
|
15
|
+
// the keyfiles Flair itself writes and signs with.
|
|
16
|
+
const ED25519_PKCS8_HEADER = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
10
17
|
/**
|
|
11
|
-
*
|
|
18
|
+
* Expand a leading `~` / `~/` to the current user's home directory.
|
|
12
19
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
20
|
+
* `flair agent add <id>` and the README both hand users paths like
|
|
21
|
+
* `~/.flair/keys/<id>.key`. Node's `fs` does not expand `~` — a shell does —
|
|
22
|
+
* so a raw `readFileSync("~/...")` throws ENOENT for a cold user copying the
|
|
23
|
+
* quickstart verbatim. Expand here so the documented path just works.
|
|
24
|
+
*/
|
|
25
|
+
export function expandHome(p) {
|
|
26
|
+
if (p === "~")
|
|
27
|
+
return os.homedir();
|
|
28
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
29
|
+
return path.join(os.homedir(), p.slice(2));
|
|
30
|
+
}
|
|
31
|
+
return p;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Load and validate an Ed25519 private key from a Flair keyfile.
|
|
35
|
+
*
|
|
36
|
+
* Accepts every on-disk encoding Flair itself produces or consumes (mirrors
|
|
37
|
+
* src/lib/auth-resolve.ts), so the keyfile written by `flair agent add`
|
|
38
|
+
* (a raw 32-byte seed) loads without a conversion step:
|
|
39
|
+
*
|
|
40
|
+
* - raw 32-byte Ed25519 seed (binary) — what `flair agent add` writes
|
|
41
|
+
* - base64-encoded raw 32-byte seed
|
|
42
|
+
* - base64-encoded PKCS8 DER (the historical adk-flair format)
|
|
43
|
+
* - PEM (`-----BEGIN PRIVATE KEY-----`)
|
|
44
|
+
*
|
|
45
|
+
* Parses the key material eagerly (in the constructor path) so that a bad
|
|
46
|
+
* keyfile fails immediately — before ADK's exception-swallowing search path
|
|
47
|
+
* can turn it into permanent silent empty recall.
|
|
16
48
|
*
|
|
17
|
-
*
|
|
49
|
+
* A leading `~`/`~/` in the path is expanded to the home directory.
|
|
50
|
+
*
|
|
51
|
+
* @param keyfilePath - Path to the keyfile (`~` accepted)
|
|
18
52
|
* @returns A Node.js KeyObject for the Ed25519 private key
|
|
19
53
|
* @throws If the keyfile is missing, unreadable, or contains invalid key material
|
|
20
54
|
*/
|
|
21
55
|
export function loadEd25519Key(keyfilePath) {
|
|
22
|
-
|
|
56
|
+
const resolvedPath = expandHome(keyfilePath);
|
|
57
|
+
let raw;
|
|
23
58
|
try {
|
|
24
|
-
|
|
59
|
+
raw = fs.readFileSync(resolvedPath);
|
|
25
60
|
}
|
|
26
61
|
catch (err) {
|
|
62
|
+
const code = err?.code;
|
|
63
|
+
if (code === "ENOENT") {
|
|
64
|
+
throw new Error(`FLAIR_KEYFILE: keyfile not found at "${resolvedPath}"` +
|
|
65
|
+
(resolvedPath !== keyfilePath ? ` (expanded from "${keyfilePath}")` : "") +
|
|
66
|
+
`. Provision one with: flair agent add <agent-id> ` +
|
|
67
|
+
`(writes ~/.flair/keys/<agent-id>.key), then point FLAIR_KEYFILE at it.`);
|
|
68
|
+
}
|
|
27
69
|
const msg = err instanceof Error ? err.message : String(err);
|
|
28
|
-
throw new Error(`FLAIR_KEYFILE: cannot read keyfile: ${msg}`);
|
|
29
|
-
}
|
|
30
|
-
if (b64.length === 0) {
|
|
31
|
-
throw new Error("FLAIR_KEYFILE: keyfile is empty");
|
|
32
|
-
}
|
|
33
|
-
let der;
|
|
34
|
-
try {
|
|
35
|
-
der = Buffer.from(b64, "base64");
|
|
70
|
+
throw new Error(`FLAIR_KEYFILE: cannot read keyfile at "${resolvedPath}": ${msg}`);
|
|
36
71
|
}
|
|
37
|
-
|
|
38
|
-
throw new Error(
|
|
39
|
-
}
|
|
40
|
-
if (der.length === 0) {
|
|
41
|
-
throw new Error("FLAIR_KEYFILE: keyfile decoded to empty key material");
|
|
72
|
+
if (raw.length === 0) {
|
|
73
|
+
throw new Error(`FLAIR_KEYFILE: keyfile at "${resolvedPath}" is empty`);
|
|
42
74
|
}
|
|
43
75
|
let key;
|
|
44
76
|
try {
|
|
45
|
-
key =
|
|
46
|
-
key: der,
|
|
47
|
-
format: "der",
|
|
48
|
-
type: "pkcs8",
|
|
49
|
-
});
|
|
77
|
+
key = parseEd25519(raw);
|
|
50
78
|
}
|
|
51
79
|
catch (err) {
|
|
52
80
|
const msg = err instanceof Error ? err.message : String(err);
|
|
53
|
-
throw new Error(`FLAIR_KEYFILE: invalid Ed25519 key material: ${msg}`);
|
|
81
|
+
throw new Error(`FLAIR_KEYFILE: invalid Ed25519 key material in "${resolvedPath}": ${msg}`);
|
|
54
82
|
}
|
|
55
83
|
// Verify it's actually an Ed25519 key
|
|
56
84
|
if (key.asymmetricKeyType !== "ed25519") {
|
|
57
|
-
throw new Error(`FLAIR_KEYFILE: expected Ed25519 key
|
|
85
|
+
throw new Error(`FLAIR_KEYFILE: expected Ed25519 key in "${resolvedPath}", ` +
|
|
86
|
+
`got ${key.asymmetricKeyType ?? "unknown"}`);
|
|
58
87
|
}
|
|
59
88
|
return key;
|
|
60
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Turn raw keyfile bytes into an Ed25519 KeyObject, trying each format Flair
|
|
92
|
+
* emits. Throws if none parse.
|
|
93
|
+
*/
|
|
94
|
+
function parseEd25519(raw) {
|
|
95
|
+
// 1) Raw 32-byte seed on disk (binary) — `flair agent add` format.
|
|
96
|
+
if (raw.length === 32) {
|
|
97
|
+
return crypto.createPrivateKey({
|
|
98
|
+
key: Buffer.concat([ED25519_PKCS8_HEADER, raw]),
|
|
99
|
+
format: "der",
|
|
100
|
+
type: "pkcs8",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const text = raw.toString("utf-8").trim();
|
|
104
|
+
// 2) PEM.
|
|
105
|
+
if (text.includes("-----BEGIN")) {
|
|
106
|
+
return crypto.createPrivateKey(text);
|
|
107
|
+
}
|
|
108
|
+
// 3/4) base64 — either a raw 32-byte seed or a full PKCS8 DER.
|
|
109
|
+
const decoded = Buffer.from(text, "base64");
|
|
110
|
+
if (decoded.length === 0) {
|
|
111
|
+
throw new Error("keyfile did not decode to any key material");
|
|
112
|
+
}
|
|
113
|
+
if (decoded.length === 32) {
|
|
114
|
+
return crypto.createPrivateKey({
|
|
115
|
+
key: Buffer.concat([ED25519_PKCS8_HEADER, decoded]),
|
|
116
|
+
format: "der",
|
|
117
|
+
type: "pkcs8",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return crypto.createPrivateKey({
|
|
121
|
+
key: decoded,
|
|
122
|
+
format: "der",
|
|
123
|
+
type: "pkcs8",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
61
126
|
/**
|
|
62
127
|
* Build the TPS-Ed25519 Authorization header value.
|
|
63
128
|
*
|
package/dist/signing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"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"}
|