@pinixai/core 0.6.1 → 0.7.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/src/hub.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { createClient } from "@connectrpc/connect";
2
+ import { createConnectTransport } from "@connectrpc/connect-web";
3
+ import { HubService, type ClipInfo } from "./gen/hub_pb";
4
+
5
+ const PINIX_URL = process.env.PINIX_URL ?? "http://127.0.0.1:9000";
6
+
7
+ function getTransport(hubUrl?: string) {
8
+ return createConnectTransport({
9
+ baseUrl: hubUrl ?? PINIX_URL,
10
+ });
11
+ }
12
+
13
+ function getClient(hubUrl?: string) {
14
+ return createClient(HubService, getTransport(hubUrl));
15
+ }
16
+
17
+ const encoder = new TextEncoder();
18
+ const decoder = new TextDecoder();
19
+
20
+ /**
21
+ * Invoke a command on a remote clip via Hub's Connect-RPC Invoke RPC.
22
+ * The Invoke RPC is server-streaming: we collect all output chunks and return the aggregated result.
23
+ */
24
+ export async function hubInvoke(
25
+ clipName: string,
26
+ command: string,
27
+ input: unknown,
28
+ clipToken?: string,
29
+ hubUrl?: string,
30
+ ): Promise<unknown> {
31
+ const client = getClient(hubUrl);
32
+ const inputBytes = encoder.encode(JSON.stringify(input));
33
+
34
+ let outputBytes = new Uint8Array(0);
35
+
36
+ for await (const response of client.invoke({
37
+ clipName,
38
+ command,
39
+ input: inputBytes,
40
+ clipToken: clipToken ?? "",
41
+ })) {
42
+ if (response.error) {
43
+ throw new Error(response.error.message || response.error.code || "Hub invoke error");
44
+ }
45
+
46
+ if (response.output.length > 0) {
47
+ // Aggregate output chunks
48
+ const merged = new Uint8Array(outputBytes.length + response.output.length);
49
+ merged.set(outputBytes);
50
+ merged.set(response.output, outputBytes.length);
51
+ outputBytes = merged;
52
+ }
53
+ }
54
+
55
+ if (outputBytes.length === 0) {
56
+ return undefined;
57
+ }
58
+
59
+ const outputJson = decoder.decode(outputBytes);
60
+ return JSON.parse(outputJson) as unknown;
61
+ }
62
+
63
+ /**
64
+ * List all clips registered on the Hub.
65
+ */
66
+ export async function hubListClips(hubUrl?: string): Promise<ClipInfo[]> {
67
+ const client = getClient(hubUrl);
68
+ const response = await client.listClips({});
69
+ return response.clips;
70
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export type { Binding, Bindings } from "./bindings";
3
3
  export { command } from "./command";
4
4
  export { handler, type HandlerDef, type Stream } from "./handler";
5
5
  export { serveHTTP } from "./http";
6
+ export { hubListClips } from "./hub";
6
7
  export { serveIPC, invoke, redirectConsoleToStderr } from "./ipc";
7
8
  export type { IPCCommandInfo, IPCManifest } from "./manifest";
8
9
  export { serveMCP } from "./mcp";
package/src/ipc.ts CHANGED
@@ -4,11 +4,12 @@ import { StringDecoder } from "node:string_decoder";
4
4
  import type { Binding, Bindings } from "./bindings";
5
5
  import type { Clip } from "./clip";
6
6
  import type { Stream } from "./handler";
7
+ import { hubInvoke } from "./hub";
7
8
  import { createIPCManifest, type IPCManifest } from "./manifest";
8
9
 
9
10
  // === IPC Protocol Types ===
10
11
 
11
- type MessageType = "register" | "registered" | "invoke" | "result" | "error" | "chunk" | "done";
12
+ type MessageType = "register" | "registered" | "invoke";
12
13
 
13
14
  interface BaseMessage {
14
15
  type: MessageType;
@@ -36,33 +37,8 @@ interface InvokeMessage extends BaseMessage {
36
37
  clip_token?: string;
37
38
  }
38
39
 
39
- interface ResultMessage extends BaseMessage {
40
- type: "result";
41
- id: string;
42
- output: unknown;
43
- }
44
-
45
- interface ErrorMessage extends BaseMessage {
46
- type: "error";
47
- id: string;
48
- error: string;
49
- }
50
-
51
- interface ChunkMessage extends BaseMessage {
52
- type: "chunk";
53
- id: string;
54
- output: unknown;
55
- }
56
-
57
- interface DoneMessage extends BaseMessage {
58
- type: "done";
59
- id: string;
60
- }
61
-
62
40
  // === State ===
63
41
 
64
- const pendingInvokes = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
65
- let idCounter = 0;
66
42
  let registeredAlias: string | undefined;
67
43
  const bindings = loadBindings();
68
44
 
@@ -126,10 +102,6 @@ function asNonEmptyString(value: unknown): string | undefined {
126
102
  return typeof value === "string" && value.length > 0 ? value : undefined;
127
103
  }
128
104
 
129
- function nextId(): string {
130
- return `c${++idCounter}`;
131
- }
132
-
133
105
  function send(msg: Record<string, unknown>): void {
134
106
  process.stdout.write(JSON.stringify(msg) + "\n");
135
107
  }
@@ -138,21 +110,11 @@ function send(msg: Record<string, unknown>): void {
138
110
 
139
111
  export async function invoke(slot: string, command: string, input: unknown): Promise<unknown> {
140
112
  const binding = bindings[slot];
141
- const id = nextId();
142
-
143
- return new Promise((resolve, reject) => {
144
- pendingInvokes.set(id, { resolve, reject });
145
- send({
146
- id,
147
- type: "invoke",
148
- clip: binding?.alias ?? slot,
149
- command,
150
- input,
151
- hub: binding?.hub,
152
- hub_token: binding?.hub_token,
153
- clip_token: binding?.clip_token,
154
- });
155
- });
113
+ const clipName = binding?.alias ?? slot;
114
+ const clipToken = binding?.clip_token;
115
+ const hubUrl = binding?.hub;
116
+
117
+ return hubInvoke(clipName, command, input, clipToken, hubUrl);
156
118
  }
157
119
 
158
120
  // === Stdout Protection ===
@@ -232,53 +194,12 @@ export async function serveIPC(clip: Clip): Promise<void> {
232
194
  break;
233
195
  }
234
196
 
235
- case "result": {
236
- const res = msg as ResultMessage;
237
- const pending = pendingInvokes.get(res.id);
238
- if (pending) {
239
- pendingInvokes.delete(res.id);
240
- pending.resolve(res.output);
241
- }
242
- break;
243
- }
244
-
245
- case "error": {
246
- const err = msg as ErrorMessage;
247
- const pending = pendingInvokes.get(err.id);
248
- if (pending) {
249
- pendingInvokes.delete(err.id);
250
- pending.reject(new Error(err.error));
251
- }
252
- break;
253
- }
254
-
255
- case "chunk": {
256
- // Streaming chunks — currently ignored on client side
257
- // Future: could accumulate or forward to a stream callback
258
- break;
259
- }
260
-
261
- case "done": {
262
- const done = msg as DoneMessage;
263
- const pending = pendingInvokes.get(done.id);
264
- if (pending) {
265
- pendingInvokes.delete(done.id);
266
- pending.resolve(undefined);
267
- }
268
- break;
269
- }
270
-
271
197
  default:
272
198
  process.stderr.write(`[ipc] unknown message type: ${msg.type}\n`);
273
199
  }
274
200
  }
275
201
 
276
- // Clean up pending invokes on EOF
277
202
  clearIdleTimer();
278
- for (const [id, pending] of pendingInvokes) {
279
- pending.reject(new Error("IPC connection closed"));
280
- }
281
- pendingInvokes.clear();
282
203
  }
283
204
 
284
205
  async function handleInvoke(