@rine-network/eve 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.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The 5 messaging tool factories: `rine_send`, `rine_send_and_wait`,
3
+ * `rine_check_inbox`, `rine_read`, `rine_reply`. Each returns an Eve `defineTool`
4
+ * descriptor — one `AsyncRineClient` call rendered to a string, wrapped by
5
+ * `makeExecute` (lazy env client + formatError). The runtime tool NAME comes from
6
+ * the filename slug of the scaffolded `agent/tools/<name>.ts`, not from here.
7
+ *
8
+ * R4 holds throughout: renderers read only plaintext/decrypt_error/verification,
9
+ * never the ciphertext envelope. `read`/`check_inbox` add a `toModelOutput`
10
+ * redactor as defence-in-depth.
11
+ */
12
+ import { type RineToolOpts } from "../tool.js";
13
+ /** `rine_send` — send a 1:1 or `#`-group message (the SDK auto-routes groups). */
14
+ export declare function rineSendTool(opts?: RineToolOpts): import("eve/tools").ToolDefinition<any, any>;
15
+ /** `rine_send_and_wait` — 1:1 send that blocks for a reply (ms timeout). */
16
+ export declare function rineSendAndWaitTool(opts?: RineToolOpts): import("eve/tools").ToolDefinition<any, any>;
17
+ /**
18
+ * `rine_check_inbox` — poll the newest new messages, decrypt them, then
19
+ * best-effort `markDelivered` the decryptable ids so a later check returns only
20
+ * newer mail. On ack failure: warn but still return the reads.
21
+ */
22
+ export declare function rineCheckInboxTool(opts?: RineToolOpts): import("eve/tools").ToolDefinition<any, any>;
23
+ /** `rine_read` — fetch + decrypt one message by id. */
24
+ export declare function rineReadTool(opts?: RineToolOpts): import("eve/tools").ToolDefinition<any, any>;
25
+ /** `rine_thread` — fetch the both-sided, decrypted transcript of a conversation. */
26
+ export declare function rineThreadTool(opts?: RineToolOpts): import("eve/tools").ToolDefinition<any, any>;
27
+ /** `rine_reply` — reply to a message, threading into the same conversation. */
28
+ export declare function rineReplyTool(opts?: RineToolOpts): import("eve/tools").ToolDefinition<any, any>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Pure tool metadata (name / export name / domain) — NO `eve` import, NO factory
3
+ * references. The scaffolder and CLI depend only on this, so `init` runs without
4
+ * resolving the `eve` peer dependency (the eve-importing factories load only when
5
+ * the Eve runtime imports the scaffolded `agent/tools/*.ts` files). `tools/index.ts`
6
+ * attaches the live factories to this metadata for programmatic use.
7
+ */
8
+ /** The canonical domains a rine tool belongs to (drives `init --tools`). */
9
+ export type RineToolDomain = "messaging" | "discovery" | "groups";
10
+ /** Scaffold metadata for one tool: canonical name, export name, domain. */
11
+ export interface RineToolMeta {
12
+ /** Filename slug + runtime tool name (e.g. `rine_send`). */
13
+ readonly name: string;
14
+ /** Named export in `@rine-network/eve/tools` that builds the descriptor. */
15
+ readonly factoryName: string;
16
+ readonly domain: RineToolDomain;
17
+ }
18
+ /**
19
+ * The registry the scaffolder + docs read. Order is the canonical surface order
20
+ * (messaging → discovery → groups). `name` === scaffolded filename === tool name.
21
+ */
22
+ export declare const RINE_TOOL_META: readonly RineToolMeta[];
@@ -0,0 +1,369 @@
1
+ import { a as GROUP_ON_WAIT_MESSAGE, c as groupIsMls, d as renderInbox, f as renderMessageBody, h as renderThread, i as redactToText, l as renderDiscover, m as renderSingleMessage, n as asRecipient, p as renderProfile, r as makeExecute, s as isGroupUnsupportedOnWait, t as approvalGate, u as renderGroup, v as verifiedNote } from "./tool-BC49DldZ.js";
2
+ import { t as RINE_TOOL_META } from "./registry-BG7S2XJg.js";
3
+ import { NotFoundError, asAgentUuid, asGroupUuid, asMessageUuid } from "@rine-network/sdk";
4
+ import { UUID_RE, normalizeHandle, resolveToUuid } from "@rine-network/core";
5
+ import { z } from "zod";
6
+ import { defineTool } from "eve/tools";
7
+ import { zodToJsonSchema } from "zod-to-json-schema";
8
+ //#region src/_zod.ts
9
+ /**
10
+ * Convert a zod schema to a plain JSON Schema for `defineTool`'s `inputSchema`.
11
+ * Returns `any` deliberately: it bridges into Eve's internal `JsonObject` type,
12
+ * which is not exported for us to name.
13
+ */
14
+ function jsonSchema(schema) {
15
+ return zodToJsonSchema(schema, { $refStrategy: "none" });
16
+ }
17
+ /** The shared `outputSchema` for every rine tool: a JSON-Schema string. */
18
+ const STRING_OUTPUT = { type: "string" };
19
+ //#endregion
20
+ //#region src/schemas-groups.ts
21
+ /**
22
+ * Zod input schemas for the 4 group tools (split out of `schemas.ts` to hold the
23
+ * ~200-LOC budget — one file per domain). Same authoring rules: rich `.describe()`
24
+ * on every field, NO identity/credentials in the schema (env-injected, R3).
25
+ *
26
+ * Groups are MLS-capable by default — `enableMls` (default true) on create.
27
+ */
28
+ const groupCreateInput = z.object({
29
+ name: z.string().describe("Human-readable group name. A group handle is derived from it."),
30
+ enrollment: z.enum([
31
+ "open",
32
+ "closed",
33
+ "majority",
34
+ "unanimity"
35
+ ]).default("closed").describe("Who may join and how: `open` (anyone), `closed` (invite-only, default), `majority` (member vote), `unanimity` (all must approve)."),
36
+ visibility: z.enum(["public", "private"]).default("private").describe("`public` (listed in discovery) or `private` (hidden, default)."),
37
+ description: z.string().optional().describe("Optional group description."),
38
+ enableMls: z.boolean().default(true).describe("Create the group with MLS (RFC 9420) end-to-end encryption (default true). MLS gives forward secrecy and post-compromise security; disable only to force the legacy sender-key scheme.")
39
+ });
40
+ const groupInviteInput = z.object({
41
+ group: z.string().describe("The target group: a handle (`#name@org`) or a UUID. Resolved to a UUID before inviting."),
42
+ agentToInvite: z.string().describe("The agent to invite: a handle (`name@org`) or a UUID. Resolved to a UUID before inviting."),
43
+ message: z.string().optional().describe("Optional message included with the invite.")
44
+ });
45
+ const groupRemoveInput = z.object({
46
+ group: z.string().describe("The target group: a handle (`#name@org`) or a UUID. Resolved to a UUID before removal."),
47
+ agentId: z.string().describe("The member to remove: a handle (`name@org`) or a UUID. Group keys are rotated after removal.")
48
+ });
49
+ const groupInspectInput = z.object({ group: z.string().describe("The group to inspect: a handle (`#name@org`) or a UUID. Reports its E2EE mode and policy.") });
50
+ //#endregion
51
+ //#region src/schemas.ts
52
+ /**
53
+ * Shared Zod input schemas for the 12 rine tools.
54
+ *
55
+ * Rich `.describe()` on EVERY field: the field descriptions are the #1 lever on
56
+ * tool-call accuracy (the AI-DX). Identity/credentials NEVER appear here — the
57
+ * acting agent + config dir come from `process.env` (R3), never the model-visible
58
+ * input schema.
59
+ */
60
+ const DEFAULT_MESSAGE_TYPE = "rine.v1.task_request";
61
+ const DEFAULT_REPLY_TYPE = "rine.v1.task_response";
62
+ const sendInput = z.object({
63
+ to: z.string().describe("Recipient address: a 1:1 agent handle `name@org`, a UUID, or a group handle prefixed with `#` (e.g. `#ops@acme`). A `#` target sends to the whole group over its E2EE channel (MLS or sender-key, chosen automatically)."),
64
+ body: z.string().describe("The plaintext message text to send. It is end-to-end encrypted before transmission."),
65
+ messageType: z.string().default(DEFAULT_MESSAGE_TYPE).describe("The rine message type, e.g. `rine.v1.task_request` (default) or `rine.v1.text`. Leave as the default unless you know the recipient expects a specific type."),
66
+ idempotencyKey: z.string().optional().describe("Optional idempotency key. Reusing the same key for a retry prevents a duplicate send if the first attempt's response was lost.")
67
+ });
68
+ const sendAndWaitInput = z.object({
69
+ to: z.string().describe("Recipient agent address (1:1 ONLY): a handle `name@org` or a UUID. Group handles (`#…`) are NOT supported here — use rine_send for groups."),
70
+ body: z.string().describe("The plaintext message text to send. End-to-end encrypted before transmission."),
71
+ waitSeconds: z.number().int().min(1).max(300).default(30).describe("How long to block waiting for a reply, in SECONDS (1–300, default 30). Returns the reply if one arrives in time, otherwise a 'no reply' note."),
72
+ messageType: z.string().default(DEFAULT_MESSAGE_TYPE).describe(`The rine message type (default \`${DEFAULT_MESSAGE_TYPE}\`).`)
73
+ });
74
+ const checkInboxInput = z.object({ limit: z.number().int().min(1).max(100).default(20).describe("Maximum number of new messages to fetch and decrypt (1–100, default 20). Fetched messages are marked delivered so a later check returns only newer mail.") });
75
+ const readInput = z.object({ messageId: z.string().describe("The UUID of the message to fetch and decrypt. Returns the decrypted body and signature status.") });
76
+ const replyInput = z.object({
77
+ messageId: z.string().describe("The UUID of the message you are replying to. The reply threads into the same conversation."),
78
+ body: z.string().describe("The plaintext reply text. End-to-end encrypted before transmission."),
79
+ messageType: z.string().default(DEFAULT_REPLY_TYPE).describe(`The rine message type for the reply (default \`${DEFAULT_REPLY_TYPE}\`).`)
80
+ });
81
+ const threadInput = z.object({
82
+ conversationId: z.string().describe("The UUID of the conversation to fetch the transcript of. Returns every turn (yours and the peer's) decrypted and role-tagged, oldest→newest."),
83
+ limit: z.number().int().min(1).max(100).optional().describe("Optional max number of most-recent turns to return (1–100). Omit for the server default.")
84
+ });
85
+ const discoverInput = z.object({
86
+ q: z.string().optional().describe("Free-text search query matched against agent names, handles, and descriptions."),
87
+ category: z.string().optional().describe("Filter by agent category (e.g. `research`, `support`)."),
88
+ language: z.string().optional().describe("Filter by the agent's working language (e.g. `en`, `de`)."),
89
+ verified: z.boolean().optional().describe("When true, return only verified agents; when false, only unverified; omit for both."),
90
+ limit: z.number().int().min(1).max(100).default(10).describe("Maximum number of agents to return (1–100, default 10).")
91
+ });
92
+ const inspectInput = z.object({ handleOrId: z.string().describe("An agent handle (`name@org`, resolved via WebFinger) or a UUID. Returns the full public profile.") });
93
+ //#endregion
94
+ //#region src/tools/discovery.ts
95
+ /**
96
+ * The 2 discovery tool factories: `rine_discover`, `rine_inspect`. Both are
97
+ * unauthenticated directory reads.
98
+ */
99
+ /** `rine_discover` — search the public agent directory. */
100
+ function rineDiscoverTool(opts = {}) {
101
+ return defineTool({
102
+ description: "Search the public rine agent directory by free text, category, language, and/or verification status. Returns a numbered list of matching agents with their handles. Use this to find an agent's handle before messaging it.",
103
+ inputSchema: jsonSchema(discoverInput),
104
+ outputSchema: STRING_OUTPUT,
105
+ execute: makeExecute(discoverInput, opts, async (client, i) => {
106
+ return renderDiscover((await client.discover({
107
+ q: i.q,
108
+ category: i.category,
109
+ language: i.language,
110
+ verified: i.verified,
111
+ limit: i.limit
112
+ })).items);
113
+ })
114
+ });
115
+ }
116
+ /** `rine_inspect` — fetch one agent's full public profile. */
117
+ function rineInspectTool(opts = {}) {
118
+ return defineTool({
119
+ description: "Fetch the full public profile of an agent by its handle (`name@org`) or UUID — name, description, category, verification, and human-oversight status.",
120
+ inputSchema: jsonSchema(inspectInput),
121
+ outputSchema: STRING_OUTPUT,
122
+ execute: makeExecute(inspectInput, opts, async (client, i) => {
123
+ return renderProfile(await client.inspect(i.handleOrId));
124
+ })
125
+ });
126
+ }
127
+ //#endregion
128
+ //#region src/tools/groups.ts
129
+ /**
130
+ * The 4 group tool factories: `rine_group_create`, `rine_group_invite`,
131
+ * `rine_group_remove`, `rine_group_inspect`. Groups are MLS-by-default.
132
+ *
133
+ * The TS SDK's `groups.invite`/`removeMember` take BRANDED UUIDs positionally, so
134
+ * invite/remove/inspect PRE-RESOLVE handle→UUID here:
135
+ * - a group → `groups.list()` + local match (`findGroup`),
136
+ * - an agent → `resolveToUuid` (WebFinger) → its UUID (or pass a UUID through),
137
+ * so unlisted agents resolve too.
138
+ */
139
+ /** True for a bare UUID string (no `@`, matches the rine UUID shape). */
140
+ function isUuid(s) {
141
+ return UUID_RE.test(s);
142
+ }
143
+ /** A handle's bare local name: `#ops@acme` / `ops` → `ops`. */
144
+ function bareLocalName(handle) {
145
+ return handle.replace(/^#/, "").split("@")[0] ?? handle;
146
+ }
147
+ /** Match a group among the caller's groups by UUID, full handle, or bare name. */
148
+ function findGroup(groups, target) {
149
+ if (isUuid(target)) return groups.find((g) => g.id === target);
150
+ if (!target.includes("@")) {
151
+ const want = bareLocalName(target);
152
+ return groups.find((g) => bareLocalName(g.handle) === want);
153
+ }
154
+ const want = normalizeHandle(target.startsWith("#") ? target : `#${target}`);
155
+ return groups.find((g) => normalizeHandle(g.handle) === want);
156
+ }
157
+ /** Resolve a group handle/UUID to its `GroupUuid` via the caller's group list. */
158
+ async function resolveGroupUuid(client, target) {
159
+ if (isUuid(target)) return asGroupUuid(target);
160
+ const match = findGroup((await client.groups.list()).items, target);
161
+ if (!match) throw new NotFoundError(`No group '${target}' among the groups this agent belongs to`);
162
+ return asGroupUuid(match.id);
163
+ }
164
+ /** Resolve an agent handle/UUID to its `AgentUuid` (UUIDs pass through). */
165
+ async function resolveAgentUuid(apiUrl, target) {
166
+ if (isUuid(target)) return asAgentUuid(target);
167
+ return asAgentUuid(await resolveToUuid(apiUrl, target));
168
+ }
169
+ /** `rine_group_create` — create an MLS-by-default coordination group. */
170
+ function rineGroupCreateTool(opts = {}) {
171
+ return defineTool({
172
+ description: "Create a new end-to-end-encrypted coordination group. By default the group uses MLS (RFC 9420) encryption with forward secrecy. This is a real, irreversible network action. Returns the new group handle, id, E2EE mode, and enrollment policy.",
173
+ inputSchema: jsonSchema(groupCreateInput),
174
+ outputSchema: STRING_OUTPUT,
175
+ needsApproval: approvalGate(opts),
176
+ execute: makeExecute(groupCreateInput, opts, async (client, i) => {
177
+ const g = await client.groups.create(i.name, {
178
+ description: i.description,
179
+ enrollment: i.enrollment,
180
+ visibility: i.visibility,
181
+ enableMls: i.enableMls
182
+ });
183
+ const mode = groupIsMls(g) ? "MLS" : "sender-key";
184
+ return `Created group ${g.handle} (id ${g.id}, ${mode} E2EE, enrollment ${g.enrollment_policy}).`;
185
+ })
186
+ });
187
+ }
188
+ /** `rine_group_invite` — invite an agent into a group (handle→UUID pre-resolved). */
189
+ function rineGroupInviteTool(opts = {}) {
190
+ return defineTool({
191
+ description: "Invite an agent into a group your agent administers (group and agent may be handles or UUIDs). This is a real, irreversible network action. Returns the invite status.",
192
+ inputSchema: jsonSchema(groupInviteInput),
193
+ outputSchema: STRING_OUTPUT,
194
+ needsApproval: approvalGate(opts),
195
+ execute: makeExecute(groupInviteInput, opts, async (client, i, apiUrl) => {
196
+ const groupId = await resolveGroupUuid(client, i.group);
197
+ const agentId = await resolveAgentUuid(apiUrl, i.agentToInvite);
198
+ const result = await client.groups.invite(groupId, agentId, { message: i.message });
199
+ return `Invited ${i.agentToInvite} to ${i.group} (status ${result.status}).`;
200
+ })
201
+ });
202
+ }
203
+ /** `rine_group_remove` — remove a member; group keys rotate (handle→UUID pre-resolved). */
204
+ function rineGroupRemoveTool(opts = {}) {
205
+ return defineTool({
206
+ description: "Remove a member from a group your agent administers (group and agent may be handles or UUIDs). Group keys are rotated for forward secrecy. This is a real, irreversible network action.",
207
+ inputSchema: jsonSchema(groupRemoveInput),
208
+ outputSchema: STRING_OUTPUT,
209
+ needsApproval: approvalGate(opts),
210
+ execute: makeExecute(groupRemoveInput, opts, async (client, i, apiUrl) => {
211
+ const groupId = await resolveGroupUuid(client, i.group);
212
+ const agentId = await resolveAgentUuid(apiUrl, i.agentId);
213
+ await client.groups.removeMember(groupId, agentId);
214
+ return `Removed ${i.agentId} from ${i.group}; keys rotated.`;
215
+ })
216
+ });
217
+ }
218
+ /** `rine_group_inspect` — report a group's E2EE mode + policy. */
219
+ function rineGroupInspectTool(opts = {}) {
220
+ return defineTool({
221
+ description: "Show a group's details and encryption status (MLS vs sender-key) so you can confirm your agent can read and post to it. Accepts a group handle or UUID.",
222
+ inputSchema: jsonSchema(groupInspectInput),
223
+ outputSchema: STRING_OUTPUT,
224
+ execute: makeExecute(groupInspectInput, opts, async (client, i) => {
225
+ const match = findGroup((await client.groups.list()).items, i.group);
226
+ if (!match) throw new NotFoundError(`No group '${i.group}' among the groups this agent belongs to`);
227
+ return renderGroup(match);
228
+ })
229
+ });
230
+ }
231
+ //#endregion
232
+ //#region src/tools/messaging.ts
233
+ /**
234
+ * The 5 messaging tool factories: `rine_send`, `rine_send_and_wait`,
235
+ * `rine_check_inbox`, `rine_read`, `rine_reply`. Each returns an Eve `defineTool`
236
+ * descriptor — one `AsyncRineClient` call rendered to a string, wrapped by
237
+ * `makeExecute` (lazy env client + formatError). The runtime tool NAME comes from
238
+ * the filename slug of the scaffolded `agent/tools/<name>.ts`, not from here.
239
+ *
240
+ * R4 holds throughout: renderers read only plaintext/decrypt_error/verification,
241
+ * never the ciphertext envelope. `read`/`check_inbox` add a `toModelOutput`
242
+ * redactor as defence-in-depth.
243
+ */
244
+ /** `rine_send` — send a 1:1 or `#`-group message (the SDK auto-routes groups). */
245
+ function rineSendTool(opts = {}) {
246
+ return defineTool({
247
+ description: "Send an end-to-end-encrypted message to another agent (`name@org` / UUID) or, with a `#`-prefixed handle, to a whole group over its E2EE channel. This is a real, irreversible network action: the message is delivered to a live recipient. Returns the new message id and conversation id.",
248
+ inputSchema: jsonSchema(sendInput),
249
+ outputSchema: STRING_OUTPUT,
250
+ needsApproval: approvalGate(opts),
251
+ execute: makeExecute(sendInput, opts, async (client, i) => {
252
+ const msg = await client.send(asRecipient(i.to), { text: i.body }, {
253
+ type: i.messageType,
254
+ idempotencyKey: i.idempotencyKey
255
+ });
256
+ return `Sent message ${msg.id} to ${i.to} (conversation ${msg.conversation_id}).`;
257
+ })
258
+ });
259
+ }
260
+ /** `rine_send_and_wait` — 1:1 send that blocks for a reply (ms timeout). */
261
+ function rineSendAndWaitTool(opts = {}) {
262
+ return defineTool({
263
+ description: "Send an end-to-end-encrypted message to a single agent (`name@org` / UUID) and block up to `waitSeconds` for a reply, returning the decrypted reply if one arrives. 1:1 only — use rine_send for groups. This is a real, irreversible network action.",
264
+ inputSchema: jsonSchema(sendAndWaitInput),
265
+ outputSchema: STRING_OUTPUT,
266
+ needsApproval: approvalGate(opts),
267
+ execute: makeExecute(sendAndWaitInput, opts, async (client, i) => {
268
+ try {
269
+ const { sent, reply } = await client.sendAndWait(asRecipient(i.to), { text: i.body }, {
270
+ timeout: i.waitSeconds * 1e3,
271
+ type: i.messageType
272
+ });
273
+ if (!reply) return `Sent ${sent.id}; no reply within ${i.waitSeconds}s.`;
274
+ return `Reply: ${renderMessageBody(reply)} (${verifiedNote(reply)})`;
275
+ } catch (err) {
276
+ if (isGroupUnsupportedOnWait(err)) return GROUP_ON_WAIT_MESSAGE;
277
+ throw err;
278
+ }
279
+ })
280
+ });
281
+ }
282
+ /**
283
+ * `rine_check_inbox` — poll the newest new messages, decrypt them, then
284
+ * best-effort `markDelivered` the decryptable ids so a later check returns only
285
+ * newer mail. On ack failure: warn but still return the reads.
286
+ */
287
+ function rineCheckInboxTool(opts = {}) {
288
+ return defineTool({
289
+ description: "Fetch and decrypt your newest unread messages (1:1 and group), then mark them delivered so a later check returns only newer mail. Returns a numbered list of decrypted messages, or 'No new messages.'.",
290
+ inputSchema: jsonSchema(checkInboxInput),
291
+ outputSchema: STRING_OUTPUT,
292
+ toModelOutput: redactToText,
293
+ execute: makeExecute(checkInboxInput, opts, async (client, i) => {
294
+ const items = (await client.inbox({
295
+ status: "new",
296
+ limit: i.limit
297
+ })).items;
298
+ const rendered = renderInbox(items);
299
+ const decryptableIds = items.filter((m) => !m.decrypt_error).map((m) => m.id);
300
+ if (decryptableIds.length === 0) return rendered;
301
+ try {
302
+ await client.markDelivered(decryptableIds);
303
+ return rendered;
304
+ } catch {
305
+ return `${rendered}\n[WARN] could not mark messages delivered; they may reappear on the next check.`;
306
+ }
307
+ })
308
+ });
309
+ }
310
+ /** `rine_read` — fetch + decrypt one message by id. */
311
+ function rineReadTool(opts = {}) {
312
+ return defineTool({
313
+ description: "Fetch and decrypt a single message by its UUID. Returns the sender, type, decrypted body, and signature status.",
314
+ inputSchema: jsonSchema(readInput),
315
+ outputSchema: STRING_OUTPUT,
316
+ toModelOutput: redactToText,
317
+ execute: makeExecute(readInput, opts, async (client, i) => {
318
+ return renderSingleMessage(await client.read(asMessageUuid(i.messageId)));
319
+ })
320
+ });
321
+ }
322
+ /** `rine_thread` — fetch the both-sided, decrypted transcript of a conversation. */
323
+ function rineThreadTool(opts = {}) {
324
+ return defineTool({
325
+ description: "Fetch the both-sided, decrypted transcript of a conversation by its UUID. Returns every turn (yours and the peer's) ordered oldest→newest, each role-tagged (`[sent] you:` / `[received] handle:`). A turn you cannot decrypt renders `[unavailable]`. Use to recover the full context of a conversation on demand.",
326
+ inputSchema: jsonSchema(threadInput),
327
+ outputSchema: STRING_OUTPUT,
328
+ toModelOutput: redactToText,
329
+ execute: makeExecute(threadInput, opts, async (client, i) => {
330
+ return renderThread(await client.thread(i.conversationId, { limit: i.limit }));
331
+ })
332
+ });
333
+ }
334
+ /** `rine_reply` — reply to a message, threading into the same conversation. */
335
+ function rineReplyTool(opts = {}) {
336
+ return defineTool({
337
+ description: "Send an end-to-end-encrypted reply to a message (by its UUID), threading into the same conversation. This is a real, irreversible network action. Returns the reply's message id.",
338
+ inputSchema: jsonSchema(replyInput),
339
+ outputSchema: STRING_OUTPUT,
340
+ needsApproval: approvalGate(opts),
341
+ execute: makeExecute(replyInput, opts, async (client, i) => {
342
+ const reply = await client.reply(asMessageUuid(i.messageId), { text: i.body }, { type: i.messageType });
343
+ return `Replied to ${i.messageId} -> ${reply.id}.`;
344
+ })
345
+ });
346
+ }
347
+ //#endregion
348
+ //#region src/tools/index.ts
349
+ const FACTORIES = {
350
+ rineSendTool,
351
+ rineSendAndWaitTool,
352
+ rineCheckInboxTool,
353
+ rineReadTool,
354
+ rineReplyTool,
355
+ rineThreadTool,
356
+ rineDiscoverTool,
357
+ rineInspectTool,
358
+ rineGroupCreateTool,
359
+ rineGroupInviteTool,
360
+ rineGroupRemoveTool,
361
+ rineGroupInspectTool
362
+ };
363
+ /** The metadata registry with live factories attached (programmatic use). */
364
+ const RINE_TOOLS = RINE_TOOL_META.map((m) => ({
365
+ ...m,
366
+ factory: FACTORIES[m.factoryName]
367
+ }));
368
+ //#endregion
369
+ export { rineSendAndWaitTool as a, rineGroupCreateTool as c, rineGroupRemoveTool as d, rineDiscoverTool as f, rineReplyTool as i, rineGroupInspectTool as l, rineCheckInboxTool as n, rineSendTool as o, rineInspectTool as p, rineReadTool as r, rineThreadTool as s, RINE_TOOLS as t, rineGroupInviteTool as u };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Token-budgeted, role-tagged transcript rendering for the Eve channel's
3
+ * push-injection (REQ-CTX-01). The Eve session is STATELESS per inbound message,
4
+ * so cross-turn memory comes from injecting the recent thread into the session
5
+ * `context[]` on each inbound. This module renders that transcript within a
6
+ * char-approximated token budget, truncating the OLDEST turns first.
7
+ *
8
+ * R4 holds: it reads only `ThreadEntry.text` (already decrypted + rendered by the
9
+ * SDK), never any ciphertext field.
10
+ */
11
+ import type { ThreadEntry } from "@rine-network/sdk";
12
+ /** Marker prepended when older turns were dropped to fit the budget. */
13
+ export declare const OMITTED_MARKER = "[\u2026earlier turns omitted]";
14
+ /**
15
+ * Render the most-recent turns of a thread as role-tagged `context[]` lines within
16
+ * a token budget. Turns are accumulated from the NEWEST end backwards until the
17
+ * char budget (`tokenBudget * 4`) is hit; if any older turns were dropped, a single
18
+ * {@link OMITTED_MARKER} line is prepended. Returns lines oldest→newest, ready to
19
+ * slot into the Eve session `context` array.
20
+ *
21
+ * @param entries Thread entries, ordered oldest→newest (SDK `client.thread()`).
22
+ * @param tokenBudget Approximate token budget for the transcript.
23
+ */
24
+ export declare function renderTranscriptContext(entries: readonly ThreadEntry[], tokenBudget: number): string[];
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SDK return-type aliases for the renderers + tools + channel.
3
+ *
4
+ * The SDK exports the `*Schema` Zod schemas from its root but NOT the inferred
5
+ * type aliases (`GroupRead`, `AgentSummary`, …). We re-derive the handful we need
6
+ * via the schemas' `_output` projection — a thin, drift-proof bridge (if the SDK
7
+ * schema changes, these follow). These are type-only re-derivations; runtime tool
8
+ * schemas are authored with the host `zod` in `schemas.ts`.
9
+ */
10
+ import type { AgentProfileSchema, AgentSummarySchema, DecryptedMessageSchema, GroupReadSchema, InviteResultSchema, MessageReadSchema } from "@rine-network/sdk";
11
+ /** Project a Zod schema's output type without importing the SDK's `z` instance. */
12
+ type Infer<S> = S extends {
13
+ _output: infer O;
14
+ } ? O : never;
15
+ export type DecryptedMessage = Infer<typeof DecryptedMessageSchema>;
16
+ export type MessageRead = Infer<typeof MessageReadSchema>;
17
+ export type GroupRead = Infer<typeof GroupReadSchema>;
18
+ export type InviteResult = Infer<typeof InviteResultSchema>;
19
+ export type AgentSummary = Infer<typeof AgentSummarySchema>;
20
+ export type AgentProfile = Infer<typeof AgentProfileSchema>;
21
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Prod inbound wiring — `npx @rine-network/eve webhook --url <publicBase>`.
3
+ *
4
+ * A Vercel-deployed Eve agent has a public URL, so inbound rine messages arrive
5
+ * by webhook (no daemon, serverless-native). This registers a rine webhook that
6
+ * POSTs new-message notifications to the channel route, and returns the HMAC
7
+ * `secret` the channel verifies with (`RINE_WEBHOOK_SECRET`). `--delete` tears it
8
+ * down. R1: the client is built lazily inside the call.
9
+ */
10
+ /** Options shared by register/delete. */
11
+ export interface WebhookOptions {
12
+ /** Public base URL of the deployed Eve agent (e.g. `https://x.vercel.app`). */
13
+ baseUrl: string;
14
+ /** Acting agent handle/UUID; defaults to `process.env.RINE_AGENT`. */
15
+ agent?: string;
16
+ apiUrl?: string;
17
+ configDir?: string;
18
+ /** Inbound path; defaults to `RINE_INBOUND_PATH` env or `/rine/v1/inbound`. */
19
+ path?: string;
20
+ }
21
+ /** The persisted result of registering a webhook. */
22
+ export interface RegisteredWebhook {
23
+ readonly id: string;
24
+ readonly secret: string;
25
+ readonly url: string;
26
+ }
27
+ /**
28
+ * Register a rine webhook pointing at the deployed agent's inbound route. Returns
29
+ * the webhook id + the HMAC secret (shown once) for `RINE_WEBHOOK_SECRET`.
30
+ */
31
+ export declare function registerRineWebhook(opts: WebhookOptions): Promise<RegisteredWebhook>;
32
+ /** Delete a previously-registered webhook by id (best-effort teardown). */
33
+ export declare function deleteRineWebhook(id: string, opts?: Pick<WebhookOptions, "agent" | "apiUrl" | "configDir">): Promise<void>;
@@ -0,0 +1,54 @@
1
+ import { t as getRineClient } from "./client-X_-9CpQT.js";
2
+ import { asAgentUuid, asWebhookUuid } from "@rine-network/sdk";
3
+ import { UUID_RE, resolveApiUrl, resolveToUuid } from "@rine-network/core";
4
+ //#region src/webhook.ts
5
+ /**
6
+ * Prod inbound wiring — `npx @rine-network/eve webhook --url <publicBase>`.
7
+ *
8
+ * A Vercel-deployed Eve agent has a public URL, so inbound rine messages arrive
9
+ * by webhook (no daemon, serverless-native). This registers a rine webhook that
10
+ * POSTs new-message notifications to the channel route, and returns the HMAC
11
+ * `secret` the channel verifies with (`RINE_WEBHOOK_SECRET`). `--delete` tears it
12
+ * down. R1: the client is built lazily inside the call.
13
+ */
14
+ const DEFAULT_INBOUND_PATH = "/rine/v1/inbound";
15
+ function inboundUrl(opts) {
16
+ const path = opts.path ?? process.env.RINE_INBOUND_PATH ?? DEFAULT_INBOUND_PATH;
17
+ return `${opts.baseUrl.replace(/\/$/, "")}${path}`;
18
+ }
19
+ async function resolveAgentUuid(apiUrl, agent) {
20
+ if (UUID_RE.test(agent)) return asAgentUuid(agent);
21
+ return asAgentUuid(await resolveToUuid(apiUrl, agent));
22
+ }
23
+ /**
24
+ * Register a rine webhook pointing at the deployed agent's inbound route. Returns
25
+ * the webhook id + the HMAC secret (shown once) for `RINE_WEBHOOK_SECRET`.
26
+ */
27
+ async function registerRineWebhook(opts) {
28
+ const agent = opts.agent ?? process.env.RINE_AGENT;
29
+ if (!agent) throw new Error("no acting agent — set RINE_AGENT or pass --agent <handle>");
30
+ const apiUrl = opts.apiUrl ?? resolveApiUrl();
31
+ const client = getRineClient({
32
+ agent,
33
+ apiUrl: opts.apiUrl,
34
+ configDir: opts.configDir
35
+ });
36
+ const agentUuid = await resolveAgentUuid(apiUrl, agent);
37
+ const url = inboundUrl(opts);
38
+ const created = await client.webhooks.create(agentUuid, url);
39
+ return {
40
+ id: created.id,
41
+ secret: created.secret,
42
+ url
43
+ };
44
+ }
45
+ /** Delete a previously-registered webhook by id (best-effort teardown). */
46
+ async function deleteRineWebhook(id, opts = {}) {
47
+ await getRineClient({
48
+ agent: opts.agent ?? process.env.RINE_AGENT,
49
+ apiUrl: opts.apiUrl,
50
+ configDir: opts.configDir
51
+ }).webhooks.delete(asWebhookUuid(id));
52
+ }
53
+ //#endregion
54
+ export { deleteRineWebhook, registerRineWebhook };
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "@rine-network/eve",
3
+ "version": "0.1.0",
4
+ "description": "Native Vercel Eve connector for the rine network \u2014 a custom channel that makes an Eve agent reachable over E2E-encrypted (HPKE 1:1, MLS groups RFC 9420, PQ-hybrid) agent-to-agent messaging, plus file-discovered rine tools, a skill, and an init/onboard/relay CLI.",
5
+ "author": "mmmbs <mmmbs@proton.me>",
6
+ "license": "EUPL-1.2",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=20"
10
+ },
11
+ "bin": {
12
+ "rine-eve": "bin/cli.js"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./channel": {
21
+ "types": "./dist/channel.d.ts",
22
+ "import": "./dist/channel.js",
23
+ "default": "./dist/channel.js"
24
+ },
25
+ "./tools": {
26
+ "types": "./dist/tools/index.d.ts",
27
+ "import": "./dist/tools/index.js",
28
+ "default": "./dist/tools/index.js"
29
+ },
30
+ "./onboard": {
31
+ "types": "./dist/onboard.d.ts",
32
+ "import": "./dist/onboard.js",
33
+ "default": "./dist/onboard.js"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "files": [
38
+ "dist/",
39
+ "bin/",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsdown && tsc -p tsconfig.build.json --emitDeclarationOnly",
45
+ "typecheck": "tsc --noEmit",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "prepublishOnly": "node scripts/check-no-file-deps.mjs"
49
+ },
50
+ "dependencies": {
51
+ "@rine-network/core": "^0.7.0",
52
+ "@rine-network/sdk": "^0.4.0",
53
+ "zod": "^3.25.0",
54
+ "zod-to-json-schema": "^3.24.1"
55
+ },
56
+ "peerDependencies": {
57
+ "eve": ">=0.11.0"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^22.0.0",
61
+ "eve": "^0.11.6",
62
+ "tsdown": "^0.12.0",
63
+ "typescript": "^5.7.0",
64
+ "vitest": "^3.0.0"
65
+ },
66
+ "homepage": "https://rine.network",
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "https://codeberg.org/rine/rine-eve"
70
+ },
71
+ "bugs": {
72
+ "url": "https://codeberg.org/rine/rine-eve/issues"
73
+ },
74
+ "publishConfig": {
75
+ "access": "public"
76
+ },
77
+ "keywords": [
78
+ "eve",
79
+ "vercel",
80
+ "rine",
81
+ "ai-agents",
82
+ "agent-framework",
83
+ "channel",
84
+ "tools",
85
+ "a2a",
86
+ "e2ee",
87
+ "mls",
88
+ "messaging"
89
+ ]
90
+ }