@pinixai/core 0.2.0 → 0.4.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/package.json +1 -1
- package/src/handler.ts +6 -2
- package/src/index.ts +2 -2
- package/src/ipc.ts +136 -120
- package/src/manifest.ts +20 -0
package/package.json
CHANGED
package/src/handler.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { z, type ZodType } from "zod";
|
|
2
2
|
|
|
3
|
+
export interface Stream {
|
|
4
|
+
chunk(data: unknown): void;
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
export interface HandlerDef<I extends ZodType = ZodType, O extends ZodType = ZodType> {
|
|
4
8
|
input: I;
|
|
5
9
|
output: O;
|
|
6
|
-
fn: (input: z.infer<I
|
|
10
|
+
fn: (input: z.infer<I>, stream?: Stream) => Promise<z.infer<O>>;
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export function handler<I extends ZodType, O extends ZodType>(
|
|
10
14
|
input: I,
|
|
11
15
|
output: O,
|
|
12
|
-
fn: (input: z.infer<I
|
|
16
|
+
fn: (input: z.infer<I>, stream?: Stream) => Promise<z.infer<O>>,
|
|
13
17
|
): HandlerDef<I, O> {
|
|
14
18
|
return { input, output, fn };
|
|
15
19
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { Clip } from "./clip";
|
|
2
2
|
export { command } from "./command";
|
|
3
|
-
export { handler, type HandlerDef } from "./handler";
|
|
3
|
+
export { handler, type HandlerDef, type Stream } from "./handler";
|
|
4
4
|
export { serveHTTP } from "./http";
|
|
5
|
-
export { serveIPC } from "./ipc";
|
|
5
|
+
export { serveIPC, invoke } from "./ipc";
|
|
6
6
|
export { serveMCP } from "./mcp";
|
|
7
7
|
export { z } from "zod";
|
package/src/ipc.ts
CHANGED
|
@@ -1,160 +1,176 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
1
|
import type { Clip } from "./clip";
|
|
2
|
+
import type { Stream } from "./handler";
|
|
3
|
+
import { createIPCManifest } from "./manifest";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
id?: unknown;
|
|
6
|
-
command?: unknown;
|
|
7
|
-
input?: unknown;
|
|
8
|
-
};
|
|
5
|
+
// === IPC Protocol Types ===
|
|
9
6
|
|
|
10
|
-
type
|
|
11
|
-
| {
|
|
12
|
-
id: unknown;
|
|
13
|
-
output: unknown;
|
|
14
|
-
}
|
|
15
|
-
| {
|
|
16
|
-
id: unknown;
|
|
17
|
-
error: {
|
|
18
|
-
message: string;
|
|
19
|
-
code: string;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
7
|
+
type MessageType = "register" | "registered" | "invoke" | "result" | "error" | "chunk" | "done";
|
|
22
8
|
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
interface BaseMessage {
|
|
10
|
+
type: MessageType;
|
|
11
|
+
id?: string;
|
|
25
12
|
}
|
|
26
13
|
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
interface RegisterMessage extends BaseMessage {
|
|
15
|
+
type: "register";
|
|
16
|
+
manifest: { name: string; domain: string; commands: string[]; dependencies: string[] };
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
interface InvokeMessage extends BaseMessage {
|
|
20
|
+
type: "invoke";
|
|
21
|
+
id: string;
|
|
22
|
+
command?: string;
|
|
23
|
+
clip?: string;
|
|
24
|
+
input?: unknown;
|
|
33
25
|
}
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
message,
|
|
40
|
-
code,
|
|
41
|
-
},
|
|
42
|
-
};
|
|
27
|
+
interface ResultMessage extends BaseMessage {
|
|
28
|
+
type: "result";
|
|
29
|
+
id: string;
|
|
30
|
+
output: unknown;
|
|
43
31
|
}
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} catch (error) {
|
|
51
|
-
writeResponse(createErrorResponse(null, `Invalid JSON: ${toErrorMessage(error)}`, "INVALID_JSON"));
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
33
|
+
interface ErrorMessage extends BaseMessage {
|
|
34
|
+
type: "error";
|
|
35
|
+
id: string;
|
|
36
|
+
error: string;
|
|
37
|
+
}
|
|
54
38
|
|
|
55
|
-
|
|
39
|
+
// === State ===
|
|
56
40
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
41
|
+
const pendingInvokes = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
|
|
42
|
+
let idCounter = 0;
|
|
61
43
|
|
|
62
|
-
|
|
44
|
+
function nextId(): string {
|
|
45
|
+
return `c${++idCounter}`;
|
|
46
|
+
}
|
|
63
47
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
48
|
+
function send(msg: Record<string, unknown>): void {
|
|
49
|
+
process.stdout.write(JSON.stringify(msg) + "\n");
|
|
50
|
+
}
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
writeResponse(createErrorResponse(id, "Request input must be an object", "INVALID_INPUT"));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
52
|
+
// === Public API ===
|
|
73
53
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
id,
|
|
81
|
-
output: parsedOutput,
|
|
82
|
-
});
|
|
83
|
-
} catch (error) {
|
|
84
|
-
writeResponse(createErrorResponse(id, toErrorMessage(error), "COMMAND_ERROR"));
|
|
85
|
-
}
|
|
54
|
+
export async function invoke(clip: string, command: string, input: unknown): Promise<unknown> {
|
|
55
|
+
const id = nextId();
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
pendingInvokes.set(id, { resolve, reject });
|
|
58
|
+
send({ id, type: "invoke", clip, command, input });
|
|
59
|
+
});
|
|
86
60
|
}
|
|
87
61
|
|
|
88
|
-
|
|
89
|
-
console.log = (...args: unknown[]) => {
|
|
90
|
-
const serialized = args
|
|
91
|
-
.map((arg) => {
|
|
92
|
-
if (typeof arg === "string") {
|
|
93
|
-
return arg;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
return JSON.stringify(arg);
|
|
98
|
-
} catch {
|
|
99
|
-
return String(arg);
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
.join(" ");
|
|
62
|
+
// === IPC Server ===
|
|
103
63
|
|
|
104
|
-
|
|
64
|
+
export async function serveIPC(clip: Clip): Promise<void> {
|
|
65
|
+
// Redirect console.log to stderr so stdout is reserved for IPC
|
|
66
|
+
const origLog = console.log;
|
|
67
|
+
console.log = (...args: unknown[]) => {
|
|
68
|
+
process.stderr.write(args.map(String).join(" ") + "\n");
|
|
105
69
|
};
|
|
106
|
-
}
|
|
107
70
|
|
|
108
|
-
|
|
109
|
-
|
|
71
|
+
// Register with pinixd
|
|
72
|
+
const manifest = createIPCManifest(clip);
|
|
73
|
+
send({ type: "register", manifest });
|
|
74
|
+
|
|
75
|
+
// Read messages from stdin
|
|
76
|
+
const reader = createLineReader(process.stdin);
|
|
77
|
+
const commands = clip.getCommands();
|
|
78
|
+
|
|
79
|
+
for await (const line of reader) {
|
|
80
|
+
let msg: BaseMessage;
|
|
81
|
+
try {
|
|
82
|
+
msg = JSON.parse(line) as BaseMessage;
|
|
83
|
+
} catch {
|
|
84
|
+
process.stderr.write(`[ipc] invalid JSON: ${line}\n`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
110
87
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
88
|
+
if (!msg.type) {
|
|
89
|
+
process.stderr.write(`[ipc] message missing type: ${line}\n`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
114
92
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
93
|
+
switch (msg.type) {
|
|
94
|
+
case "registered":
|
|
95
|
+
// Registration confirmed
|
|
96
|
+
break;
|
|
118
97
|
|
|
119
|
-
|
|
98
|
+
case "invoke": {
|
|
99
|
+
const inv = msg as InvokeMessage;
|
|
100
|
+
handleInvoke(inv, commands);
|
|
120
101
|
break;
|
|
121
102
|
}
|
|
122
103
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
104
|
+
case "result": {
|
|
105
|
+
const res = msg as ResultMessage;
|
|
106
|
+
const pending = pendingInvokes.get(res.id);
|
|
107
|
+
if (pending) {
|
|
108
|
+
pendingInvokes.delete(res.id);
|
|
109
|
+
pending.resolve(res.output);
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
127
113
|
|
|
128
|
-
|
|
129
|
-
|
|
114
|
+
case "error": {
|
|
115
|
+
const err = msg as ErrorMessage;
|
|
116
|
+
const pending = pendingInvokes.get(err.id);
|
|
117
|
+
if (pending) {
|
|
118
|
+
pendingInvokes.delete(err.id);
|
|
119
|
+
pending.reject(new Error(err.error));
|
|
130
120
|
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
default:
|
|
125
|
+
process.stderr.write(`[ipc] unknown message type: ${msg.type}\n`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
131
129
|
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
async function handleInvoke(
|
|
131
|
+
msg: InvokeMessage,
|
|
132
|
+
commands: ReturnType<Clip["getCommands"]>,
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
const cmd = commands.get(msg.command ?? "");
|
|
135
|
+
if (!cmd) {
|
|
136
|
+
send({ id: msg.id, type: "error", error: `unknown command: ${msg.command}` });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
140
|
+
try {
|
|
141
|
+
const parsed = cmd.input.parse(msg.input ?? {});
|
|
142
|
+
let streamed = false;
|
|
143
|
+
const stream: Stream = {
|
|
144
|
+
chunk(data: unknown): void {
|
|
145
|
+
streamed = true;
|
|
146
|
+
send({ id: msg.id, type: "chunk", output: data });
|
|
147
|
+
},
|
|
148
|
+
};
|
|
138
149
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
150
|
+
const output = await cmd.fn(parsed, stream);
|
|
151
|
+
|
|
152
|
+
if (streamed) {
|
|
153
|
+
send({ id: msg.id, type: "done" });
|
|
154
|
+
return;
|
|
145
155
|
}
|
|
146
156
|
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
send({ id: msg.id, type: "result", output });
|
|
158
|
+
} catch (err) {
|
|
159
|
+
send({ id: msg.id, type: "error", error: err instanceof Error ? err.message : String(err) });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
149
162
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
163
|
+
// === Line Reader ===
|
|
164
|
+
|
|
165
|
+
async function* createLineReader(stream: NodeJS.ReadableStream): AsyncGenerator<string> {
|
|
166
|
+
let buffer = "";
|
|
167
|
+
for await (const chunk of stream) {
|
|
168
|
+
buffer += chunk.toString();
|
|
169
|
+
const lines = buffer.split("\n");
|
|
170
|
+
buffer = lines.pop() ?? "";
|
|
171
|
+
for (const line of lines) {
|
|
172
|
+
if (line.trim()) yield line;
|
|
156
173
|
}
|
|
157
|
-
} finally {
|
|
158
|
-
reader.releaseLock();
|
|
159
174
|
}
|
|
175
|
+
if (buffer.trim()) yield buffer;
|
|
160
176
|
}
|
package/src/manifest.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { z, type ZodType } from "zod";
|
|
2
2
|
import type { Clip } from "./clip";
|
|
3
3
|
|
|
4
|
+
export interface IPCManifest {
|
|
5
|
+
name: string;
|
|
6
|
+
domain: string;
|
|
7
|
+
commands: string[];
|
|
8
|
+
dependencies: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
function formatLiteralValue(value: unknown): string {
|
|
5
12
|
return JSON.stringify(value);
|
|
6
13
|
}
|
|
@@ -139,3 +146,16 @@ export function generateManifest(clip: Clip): string {
|
|
|
139
146
|
|
|
140
147
|
return lines.join("\n");
|
|
141
148
|
}
|
|
149
|
+
|
|
150
|
+
export function createIPCManifest(clip: Clip): IPCManifest {
|
|
151
|
+
const dependencies = (clip as Clip & { dependencies?: unknown }).dependencies;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
name: clip.name,
|
|
155
|
+
domain: clip.domain,
|
|
156
|
+
commands: Array.from(clip.getCommands().keys()),
|
|
157
|
+
dependencies: Array.isArray(dependencies)
|
|
158
|
+
? dependencies.filter((dependency): dependency is string => typeof dependency === "string")
|
|
159
|
+
: [],
|
|
160
|
+
};
|
|
161
|
+
}
|