@perkos/perkos-a2a 0.9.0 → 0.9.2

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.
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Append-only JSONL store for PerkOS chat conversations.
3
+ *
4
+ * Files live at:
5
+ * <storeRoot>/<convId>/messages.jsonl
6
+ * <storeRoot>/<convId>/metadata.json
7
+ *
8
+ * Privacy invariant: this is the canonical record of conversation content.
9
+ * The PerkOS cloud (Firestore) holds only metadata (title, participants,
10
+ * lastMessageAt). Bodies live exclusively here, on the agent's filesystem.
11
+ */
12
+ import { createHash } from "node:crypto";
13
+ import { createReadStream } from "node:fs";
14
+ import { appendFile, mkdir, open, readFile, writeFile, } from "node:fs/promises";
15
+ import { homedir } from "node:os";
16
+ import { join } from "node:path";
17
+ /** Sanitize a convId so it can safely become a directory name. */
18
+ function safeConvId(convId) {
19
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(convId)) {
20
+ throw new Error(`invalid convId: ${convId}`);
21
+ }
22
+ return convId;
23
+ }
24
+ export class ChatStore {
25
+ root;
26
+ constructor(options = {}) {
27
+ this.root = options.storeRoot ?? join(homedir(), ".perkos", "conversations");
28
+ }
29
+ /** Directory for a single conversation. */
30
+ dirFor(convId) {
31
+ return join(this.root, safeConvId(convId));
32
+ }
33
+ /** Path to the JSONL log for a conv. */
34
+ jsonlPath(convId) {
35
+ return join(this.dirFor(convId), "messages.jsonl");
36
+ }
37
+ /** Path to the metadata file for a conv. */
38
+ metaPath(convId) {
39
+ return join(this.dirFor(convId), "metadata.json");
40
+ }
41
+ /** Ensure the conv directory exists. Idempotent. */
42
+ async ensureDir(convId) {
43
+ await mkdir(this.dirFor(convId), { recursive: true });
44
+ }
45
+ /**
46
+ * Write metadata for a conversation. Called on `channel_join` so the agent
47
+ * has a local record of which participants belong to a conv.
48
+ */
49
+ async writeMetadata(meta) {
50
+ await this.ensureDir(meta.convId);
51
+ await writeFile(this.metaPath(meta.convId), JSON.stringify(meta, null, 2));
52
+ }
53
+ async readMetadata(convId) {
54
+ try {
55
+ const raw = await readFile(this.metaPath(convId), "utf8");
56
+ return JSON.parse(raw);
57
+ }
58
+ catch (err) {
59
+ if (isNotFound(err))
60
+ return null;
61
+ throw err;
62
+ }
63
+ }
64
+ /**
65
+ * Append a message to the conversation log. The line written is exactly
66
+ * `JSON.stringify(msg) + "\n"`. The store does not validate ordering; if
67
+ * messages arrive out of timestamp order, that is recorded as-is.
68
+ */
69
+ async append(convId, msg) {
70
+ await this.ensureDir(convId);
71
+ await appendFile(this.jsonlPath(convId), JSON.stringify(msg) + "\n");
72
+ }
73
+ /**
74
+ * Read a history page, reverse-chronological. Returns messages with
75
+ * timestamp strictly less than `before` (if provided), up to `limit`.
76
+ *
77
+ * The output order is chronological ascending — i.e. callers can `concat`
78
+ * pages from oldest to newest without sorting.
79
+ */
80
+ async readPage(convId, opts) {
81
+ const limit = Math.max(1, Math.min(500, opts.limit));
82
+ const before = opts.before ?? null;
83
+ let lines;
84
+ try {
85
+ const raw = await readFile(this.jsonlPath(convId), "utf8");
86
+ lines = raw.split("\n").filter((l) => l.length > 0);
87
+ }
88
+ catch (err) {
89
+ if (isNotFound(err))
90
+ return { messages: [], hasMore: false };
91
+ throw err;
92
+ }
93
+ // Walk lines from newest (end) to oldest (start), collecting up to limit.
94
+ const collected = [];
95
+ let hasMore = false;
96
+ for (let i = lines.length - 1; i >= 0; i--) {
97
+ let msg;
98
+ try {
99
+ msg = JSON.parse(lines[i]);
100
+ }
101
+ catch {
102
+ continue;
103
+ }
104
+ if (before && msg.timestamp >= before)
105
+ continue;
106
+ if (collected.length >= limit) {
107
+ hasMore = true;
108
+ break;
109
+ }
110
+ collected.push(msg);
111
+ }
112
+ return { messages: collected.reverse(), hasMore };
113
+ }
114
+ /**
115
+ * Produce a tamper-evident summary of the conversation log for receipt
116
+ * issuance. Streams `messages.jsonl` through sha256 (followed by a
117
+ * separator + metadata.json content if present), so memory usage stays
118
+ * constant regardless of conv size.
119
+ *
120
+ * The hash is deterministic: same jsonl bytes → same hex string. A
121
+ * downstream verifier with the jsonl can recompute and confirm.
122
+ *
123
+ * Returns counts + first/last timestamps for the receipt manifest.
124
+ */
125
+ async computeReceipt(convId) {
126
+ const path = this.jsonlPath(convId);
127
+ // Probe existence first — createReadStream() defers ENOENT to the
128
+ // 'error' event, which would otherwise reject our Promise.
129
+ try {
130
+ const fd = await open(path, "r");
131
+ await fd.close();
132
+ }
133
+ catch (err) {
134
+ if (isNotFound(err))
135
+ return null;
136
+ throw err;
137
+ }
138
+ const stream = createReadStream(path, { encoding: "utf8", highWaterMark: 64 * 1024 });
139
+ const hash = createHash("sha256");
140
+ let messageCount = 0;
141
+ let firstMessageAt = null;
142
+ let lastMessageAt = null;
143
+ let leftover = "";
144
+ return new Promise((resolve, reject) => {
145
+ stream.on("data", (chunk) => {
146
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
147
+ hash.update(text);
148
+ const combined = leftover + text;
149
+ const lines = combined.split("\n");
150
+ leftover = lines.pop() ?? "";
151
+ for (const line of lines) {
152
+ if (!line)
153
+ continue;
154
+ messageCount++;
155
+ try {
156
+ const parsed = JSON.parse(line);
157
+ const ts = parsed.timestamp;
158
+ if (typeof ts === "string") {
159
+ if (firstMessageAt === null || ts < firstMessageAt)
160
+ firstMessageAt = ts;
161
+ if (lastMessageAt === null || ts > lastMessageAt)
162
+ lastMessageAt = ts;
163
+ }
164
+ }
165
+ catch {
166
+ /* malformed line — still counted toward hash */
167
+ }
168
+ }
169
+ });
170
+ stream.on("end", async () => {
171
+ if (leftover.length > 0) {
172
+ messageCount++;
173
+ try {
174
+ const parsed = JSON.parse(leftover);
175
+ const ts = parsed.timestamp;
176
+ if (typeof ts === "string") {
177
+ if (firstMessageAt === null || ts < firstMessageAt)
178
+ firstMessageAt = ts;
179
+ if (lastMessageAt === null || ts > lastMessageAt)
180
+ lastMessageAt = ts;
181
+ }
182
+ }
183
+ catch { /* ignore */ }
184
+ }
185
+ // Fold metadata.json into the hash if present, so the receipt
186
+ // also commits to participant + historyHost. Use a 0x1E (record
187
+ // separator) byte as an unambiguous boundary.
188
+ try {
189
+ const metaRaw = await readFile(this.metaPath(convId), "utf8");
190
+ hash.update("\x1e");
191
+ hash.update(metaRaw);
192
+ }
193
+ catch {
194
+ /* metadata missing → still produce a receipt over the jsonl alone */
195
+ }
196
+ resolve({
197
+ transcriptHash: hash.digest("hex"),
198
+ hashAlgo: "sha256",
199
+ messageCount,
200
+ firstMessageAt,
201
+ lastMessageAt,
202
+ });
203
+ });
204
+ stream.on("error", (err) => reject(err));
205
+ });
206
+ }
207
+ /**
208
+ * Return the number of messages in the log. Useful for tests and stats.
209
+ * Avoid using this on the hot path — it reads the full file.
210
+ */
211
+ async count(convId) {
212
+ let fd;
213
+ try {
214
+ fd = await open(this.jsonlPath(convId), "r");
215
+ }
216
+ catch (err) {
217
+ if (isNotFound(err))
218
+ return 0;
219
+ throw err;
220
+ }
221
+ try {
222
+ const raw = await fd.readFile({ encoding: "utf8" });
223
+ return raw.split("\n").filter((l) => l.length > 0).length;
224
+ }
225
+ finally {
226
+ await fd.close();
227
+ }
228
+ }
229
+ }
230
+ function isNotFound(err) {
231
+ return !!err && typeof err === "object" && err.code === "ENOENT";
232
+ }
233
+ //# sourceMappingURL=chat-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-store.js","sourceRoot":"","sources":["../src/chat-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * PerkOS-Chat protocol types.
3
+ *
4
+ * Wire format spoken to `wss://chat.perkos.xyz/chat`. The chat server routes
5
+ * frames between users (browser, authenticated via Firebase ID token) and
6
+ * agents (this client, authenticated via the same relay API key issued
7
+ * during PerkOS-Transport pairing).
8
+ *
9
+ * Companion server: github.com/PerkOS-xyz/PerkOS-Chat
10
+ * Full spec: see that repo's docs/protocol.md
11
+ */
12
+ /** Identity strings exchanged on the chat wire. */
13
+ export type ChatIdentity = `user:${string}` | `agent:${string}`;
14
+ /** A single persisted message in a conversation's jsonl. */
15
+ export interface ChatMessage {
16
+ id: string;
17
+ from: ChatIdentity;
18
+ /** Message body. May be markdown. */
19
+ text: string;
20
+ /** ISO 8601 timestamp. */
21
+ timestamp: string;
22
+ /** Optional id of the message this one replies to. */
23
+ replyTo?: string | null;
24
+ }
25
+ /** Sent by this client immediately after the WS opens. */
26
+ export interface AuthFrame {
27
+ type: "auth";
28
+ role: "agent";
29
+ agentName: string;
30
+ apiKey: string;
31
+ }
32
+ export interface AuthOkFrame {
33
+ type: "auth_ok";
34
+ session: {
35
+ agentName: string;
36
+ scopes: string[];
37
+ };
38
+ }
39
+ export interface AuthErrorFrame {
40
+ type: "auth_error";
41
+ code: string;
42
+ message: string;
43
+ }
44
+ /** Server → agent: a new user (or another agent) sent a message in this conv. */
45
+ export interface ChatDeliverFrame {
46
+ type: "chat_deliver";
47
+ id: string;
48
+ convId: string;
49
+ from: ChatIdentity;
50
+ text: string;
51
+ timestamp: string;
52
+ }
53
+ /** Server → agent: another agent posted in a multi-participant channel. */
54
+ export interface ChatMessageInboundFrame {
55
+ type: "chat_message";
56
+ id: string;
57
+ convId: string;
58
+ from: ChatIdentity;
59
+ text: string;
60
+ replyTo?: string | null;
61
+ timestamp: string;
62
+ }
63
+ /** Agent → server: reply to a conv. The server broadcasts as `chat_message`. */
64
+ export interface ChatReplyFrame {
65
+ type: "chat_reply";
66
+ id?: string;
67
+ convId: string;
68
+ /** Required: tells the server which wallet's conv tree to look up. */
69
+ walletAddress: string;
70
+ text: string;
71
+ replyTo?: string | null;
72
+ }
73
+ /** Server → agent: please serve a history page. */
74
+ export interface HistoryRequestFrame {
75
+ type: "history_request";
76
+ id: string;
77
+ convId: string;
78
+ forWallet: string;
79
+ /** ISO timestamp — return messages strictly older than this. */
80
+ before?: string | null;
81
+ limit: number;
82
+ }
83
+ /** Agent → server: history chunk response. */
84
+ export interface HistoryChunkFrame {
85
+ type: "history_chunk";
86
+ id: string;
87
+ convId: string;
88
+ forWallet: string;
89
+ messages: ChatMessage[];
90
+ hasMore: boolean;
91
+ }
92
+ /** Server → agent: heads-up that this agent is now a participant in a conv. */
93
+ export interface ChannelJoinFrame {
94
+ type: "channel_join";
95
+ convId: string;
96
+ participants: ChatIdentity[];
97
+ historyHost: ChatIdentity;
98
+ }
99
+ export interface TypingFrame {
100
+ type: "typing";
101
+ convId: string;
102
+ /** When echoed by the server, this is populated. */
103
+ from?: ChatIdentity;
104
+ state: "start" | "stop";
105
+ }
106
+ export interface AckFrame {
107
+ type: "ack";
108
+ id: string;
109
+ convId: string;
110
+ delivered: number;
111
+ timestamp: string;
112
+ }
113
+ /** Server → agent: please compute a tamper-evident hash of this conv. */
114
+ export interface ReceiptRequestFrame {
115
+ type: "receipt_request";
116
+ id: string;
117
+ convId: string;
118
+ /** Wallet that the receipt is being issued for. */
119
+ forWallet: string;
120
+ }
121
+ /** Agent → server: hash + metadata. Server routes back to the wallet. */
122
+ export interface ReceiptResponseFrame {
123
+ type: "receipt_response";
124
+ id: string;
125
+ convId: string;
126
+ forWallet: string;
127
+ /** sha256 hex of the canonical jsonl + metadata.json. */
128
+ transcriptHash: string;
129
+ hashAlgo: "sha256";
130
+ messageCount: number;
131
+ firstMessageAt: string | null;
132
+ lastMessageAt: string | null;
133
+ /** ISO timestamp the agent produced the hash. */
134
+ generatedAt: string;
135
+ }
136
+ export interface ChatErrorFrame {
137
+ type: "error";
138
+ code: string;
139
+ message: string;
140
+ }
141
+ export interface PingFrame {
142
+ type: "ping";
143
+ }
144
+ export interface PongFrame {
145
+ type: "pong";
146
+ ts: string;
147
+ }
148
+ export type ChatFrame = AuthFrame | AuthOkFrame | AuthErrorFrame | ChatDeliverFrame | ChatMessageInboundFrame | ChatReplyFrame | HistoryRequestFrame | HistoryChunkFrame | ChannelJoinFrame | TypingFrame | AckFrame | ChatErrorFrame | ReceiptRequestFrame | ReceiptResponseFrame | PingFrame | PongFrame;
149
+ export interface ChatConfig {
150
+ /** Whether the chat client should connect at startup. Default: false. */
151
+ enabled: boolean;
152
+ /** WebSocket URL. Default: wss://chat.perkos.xyz/chat. */
153
+ url: string;
154
+ /** Relay API key for this agent (same key used for PerkOS-Transport pairing). */
155
+ apiKey: string;
156
+ /** Root directory for the JSONL stores. Default: ~/.perkos/conversations. */
157
+ storeRoot?: string;
158
+ /** Maximum messages returned per history page. Default: 50. */
159
+ defaultHistoryLimit?: number;
160
+ /** Reconnect floor/ceiling in ms. Defaults: 1000 / 60000. */
161
+ minReconnectMs?: number;
162
+ maxReconnectMs?: number;
163
+ /** Heartbeat interval ms. Default: 25000. */
164
+ heartbeatIntervalMs?: number;
165
+ }
166
+ //# sourceMappingURL=chat-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-types.d.ts","sourceRoot":"","sources":["../src/chat-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * PerkOS-Chat protocol types.
3
+ *
4
+ * Wire format spoken to `wss://chat.perkos.xyz/chat`. The chat server routes
5
+ * frames between users (browser, authenticated via Firebase ID token) and
6
+ * agents (this client, authenticated via the same relay API key issued
7
+ * during PerkOS-Transport pairing).
8
+ *
9
+ * Companion server: github.com/PerkOS-xyz/PerkOS-Chat
10
+ * Full spec: see that repo's docs/protocol.md
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=chat-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-types.js","sourceRoot":"","sources":["../src/chat-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,66 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { dirname, join } from "path";
4
+ import { generateKeyPairSync } from "crypto";
5
+ export function defaultPairingPath(agentName) {
6
+ const safeName = agentName.replace(/[^a-zA-Z0-9_.-]/g, "_");
7
+ return join(homedir(), ".perkos", "a2a", "agents", `${safeName}.json`);
8
+ }
9
+ export function loadOrCreateIdentity(agentName, path = defaultPairingPath(agentName)) {
10
+ if (existsSync(path)) {
11
+ const raw = JSON.parse(readFileSync(path, "utf8"));
12
+ if (raw.identity?.agentName && raw.identity?.publicKey && raw.identity?.privateKey)
13
+ return raw.identity;
14
+ }
15
+ const pair = generateKeyPairSync("ed25519", {
16
+ publicKeyEncoding: { type: "spki", format: "pem" },
17
+ privateKeyEncoding: { type: "pkcs8", format: "pem" },
18
+ });
19
+ return {
20
+ agentName,
21
+ publicKey: pair.publicKey,
22
+ privateKey: pair.privateKey,
23
+ keyType: "ed25519",
24
+ createdAt: new Date().toISOString(),
25
+ };
26
+ }
27
+ export function savePairingProfile(profile, path = defaultPairingPath(profile.identity.agentName)) {
28
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
29
+ writeFileSync(path, `${JSON.stringify(profile, null, 2)}\n`, { mode: 0o600 });
30
+ }
31
+ export function buildClaimRequest(input) {
32
+ return {
33
+ agentName: input.identity.agentName,
34
+ publicKey: input.identity.publicKey,
35
+ keyType: input.identity.keyType,
36
+ runtime: input.runtime,
37
+ capabilities: input.capabilities?.length ? input.capabilities : ["chat", "tasks:receive", "messages:send"],
38
+ transport: { relay: true, publicUrl: input.publicUrl },
39
+ metadata: input.metadata,
40
+ };
41
+ }
42
+ export function claimUrlFromInvite(inviteUrl) {
43
+ const trimmed = inviteUrl.replace(/\/+$/, "");
44
+ return `${trimmed}/claim`;
45
+ }
46
+ export async function claimPairingInvite(inviteUrl, request) {
47
+ const response = await fetch(claimUrlFromInvite(inviteUrl), {
48
+ method: "POST",
49
+ headers: { "content-type": "application/json" },
50
+ body: JSON.stringify(request),
51
+ });
52
+ const body = await response.text();
53
+ let parsed;
54
+ try {
55
+ parsed = body ? JSON.parse(body) : {};
56
+ }
57
+ catch {
58
+ throw new Error(`Pairing server returned non-JSON response (${response.status}): ${body}`);
59
+ }
60
+ if (!response.ok) {
61
+ const message = typeof parsed === "object" && parsed && "error" in parsed ? String(parsed.error) : body;
62
+ throw new Error(`Pairing claim failed (${response.status}): ${message}`);
63
+ }
64
+ return parsed;
65
+ }
66
+ //# sourceMappingURL=pairing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing.js","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"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"}