@relaymessenger/pi 0.1.0-staging.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 ADDED
@@ -0,0 +1,3 @@
1
+ MIT License
2
+
3
+ Copyright (c) Relay Messenger
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # @relaymessenger/pi
2
+
3
+ Relay channel for Pi. It consumes Relay's acknowledged Agent WebSocket, runs
4
+ each inbound Message through Pi RPC mode, and sends one final text reply.
5
+
6
+ `relay connect pi` configures and runs this path for you. For a direct
7
+ integration, pass `agentToken`, optionally `baseURL` and `piCommand`, then use
8
+ `runPiChannel()`.
@@ -0,0 +1,23 @@
1
+ import Relay from "@relaymessenger/sdk";
2
+ export interface PiChannelOptions {
3
+ readonly agentToken: string;
4
+ readonly baseURL?: string;
5
+ readonly piCommand?: string;
6
+ readonly piArgs?: readonly string[];
7
+ readonly spawnPi?: (command: string, args: readonly string[]) => PiProcess;
8
+ readonly relay?: Relay;
9
+ }
10
+ export interface PiProcess {
11
+ readonly stdin: {
12
+ write(data: string): void;
13
+ end(): void;
14
+ };
15
+ readonly stdout: AsyncIterable<string>;
16
+ readonly kill: () => void;
17
+ }
18
+ export declare class PiChannel {
19
+ #private;
20
+ constructor(options: PiChannelOptions);
21
+ run(signal?: AbortSignal): Promise<void>;
22
+ }
23
+ export declare const runPiChannel: (options: PiChannelOptions, signal?: AbortSignal) => Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+ import Relay, {} from "@relaymessenger/sdk";
4
+ class ChildPiProcess {
5
+ #child;
6
+ constructor(command, args) {
7
+ this.#child = spawn(command, [...args], { stdio: ["pipe", "pipe", "pipe"] });
8
+ this.#child.stderr.resume();
9
+ }
10
+ get stdin() { return this.#child.stdin; }
11
+ get stdout() { return createInterface({ input: this.#child.stdout }); }
12
+ kill = () => { this.#child.kill(); };
13
+ }
14
+ const textFromEvent = (event) => {
15
+ if (event.event_type !== "message.received" || event.data.direction !== "inbound")
16
+ return null;
17
+ return event.data.parts.filter((part) => part.type === "text" || part.type === "link").map((part) => part.value).join("\n").trim() || null;
18
+ };
19
+ export class PiChannel {
20
+ #relay;
21
+ #options;
22
+ #spawnPi;
23
+ constructor(options) {
24
+ if (!options.agentToken.trim())
25
+ throw new Error("Relay Agent Token is required");
26
+ this.#options = options;
27
+ this.#relay = options.relay ?? new Relay({
28
+ apiKey: options.agentToken,
29
+ ...(options.baseURL ? { baseURL: options.baseURL } : {}),
30
+ });
31
+ this.#spawnPi = options.spawnPi ?? ((command, args) => new ChildPiProcess(command, args));
32
+ }
33
+ async run(signal) {
34
+ await this.#relay.websocket.run({
35
+ ...(signal ? { signal } : {}),
36
+ onEvent: async (event) => { await this.#handle(event); },
37
+ onFullSync: async () => { },
38
+ });
39
+ }
40
+ async #handle(event) {
41
+ const message = textFromEvent(event);
42
+ if (!message)
43
+ return;
44
+ const pi = this.#spawnPi(this.#options.piCommand ?? "pi", ["--mode", "rpc", ...(this.#options.piArgs ?? [])]);
45
+ try {
46
+ const lines = pi.stdout[Symbol.asyncIterator]();
47
+ let id = 0;
48
+ const readResponse = async (requestId, command) => {
49
+ for (;;) {
50
+ const item = await lines.next();
51
+ if (item.done)
52
+ throw new Error("Pi RPC process exited before responding");
53
+ const record = JSON.parse(item.value);
54
+ if (record.type === "response" && record.id === requestId) {
55
+ if (record.success === false)
56
+ throw new Error(record.error ?? `Pi RPC ${command} failed`);
57
+ return record;
58
+ }
59
+ }
60
+ };
61
+ const promptId = String(++id);
62
+ pi.stdin.write(JSON.stringify({ id: promptId, type: "prompt", message }) + "\n");
63
+ await readResponse(promptId, "prompt");
64
+ let settled = false;
65
+ while (!settled) {
66
+ const item = await lines.next();
67
+ if (item.done)
68
+ throw new Error("Pi RPC process exited before settling");
69
+ const record = JSON.parse(item.value);
70
+ settled = record.type === "agent_settled";
71
+ }
72
+ const textId = String(++id);
73
+ pi.stdin.write(JSON.stringify({ id: textId, type: "get_last_assistant_text" }) + "\n");
74
+ const response = await readResponse(textId, "get_last_assistant_text");
75
+ const answer = response.data?.text?.trim();
76
+ if (!answer)
77
+ throw new Error("Pi returned no final text answer");
78
+ const data = event.data;
79
+ await this.#relay.chats.messages.send(data.chat.id, {
80
+ message: {
81
+ parts: [{ type: "text", value: answer }],
82
+ idempotency_key: `pi-${event.event_id}`,
83
+ },
84
+ });
85
+ }
86
+ finally {
87
+ pi.stdin.end();
88
+ pi.kill();
89
+ }
90
+ }
91
+ }
92
+ export const runPiChannel = (options, signal) => new PiChannel(options).run(signal);
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@relaymessenger/pi",
3
+ "version": "0.1.0-staging.0",
4
+ "description": "Relay Messenger channel for Pi over Relay v1 WebSocket delivery.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "license": "MIT",
17
+ "packageManager": "npm@12.0.2",
18
+ "engines": { "node": ">=22.22.3" },
19
+ "files": ["dist/", "README.md", "LICENSE"],
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.build.json",
22
+ "check": "tsc -p tsconfig.json --noEmit",
23
+ "test": "vitest run"
24
+ },
25
+ "dependencies": { "@relaymessenger/sdk": "0.3.3-staging.7" },
26
+ "devDependencies": {
27
+ "@types/node": "26.0.0",
28
+ "typescript": "7.0.2",
29
+ "vitest": "4.1.10"
30
+ },
31
+ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/", "tag": "staging", "provenance": true },
32
+ "repository": { "type": "git", "url": "git+https://github.com/RelayMessenger/Relay-SDK.git", "directory": "packages/pi" },
33
+ "homepage": "https://github.com/RelayMessenger/Relay-SDK/tree/main/packages/pi#readme",
34
+ "keywords": ["relay", "pi", "channel", "messaging"]
35
+ }