@pie-project/server 0.5.1

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.
Files changed (4) hide show
  1. package/README.md +62 -0
  2. package/index.d.ts +23 -0
  3. package/index.mjs +107 -0
  4. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # @pie-project/server
2
+
3
+ What `pie serve` boots (controller, gateway, worker, the built-in
4
+ inferlets), inside your Node.js process on the native GPU engines. Clients
5
+ connect as they would to a `pie serve`: over WebSocket with
6
+ `@pie-project/client`, or over the OpenAI / Anthropic / Gemini HTTP routes.
7
+
8
+ ```js
9
+ import { Server } from '@pie-project/server';
10
+
11
+ const server = await Server.start({
12
+ server: { port: 0 }, // 0: a free port; see server.url
13
+ model: { model: 'Qwen/Qwen3.5-0.8B' }, // what `pie model import` fetched
14
+ });
15
+
16
+ const client = await server.connect(); // a PieClient over ws
17
+ const process = await client.launchProcess('text-completion', { prompt: 'Hello' });
18
+ console.log(await process.result());
19
+
20
+ const reply = await fetch(server.httpUrl + '/v1/chat/completions', {
21
+ method: 'POST',
22
+ headers: { 'content-type': 'application/json' },
23
+ body: JSON.stringify({ messages: [{ role: 'user', content: 'Hi' }] }),
24
+ });
25
+ console.log((await reply.json()).choices[0].message.content);
26
+
27
+ await server.shutdown();
28
+ ```
29
+
30
+ `Server.start` takes the config `pie serve --config` reads (what `pie config
31
+ init` writes), as an object or as TOML text, and resolves once the model is
32
+ loaded and the listener is bound. A handle garbage-collected without
33
+ `shutdown()` still stops the engine, on a background thread.
34
+
35
+ The Python twin is the `pie-server` wheel (`python/server`); the browser
36
+ build of pie is `javascript/browser`.
37
+
38
+ ## Installing
39
+
40
+ ```bash
41
+ npm install @pie-project/server
42
+ ```
43
+
44
+ The addon comes as an optional dependency per platform
45
+ (`@pie-project/server-linux-x64`, `-linux-arm64`, `-darwin-arm64`,
46
+ `-win32-x64`), each carrying every engine that platform supports (Linux:
47
+ CUDA, Vulkan, wgpu; macOS: Metal, wgpu; Windows: CUDA, Vulkan); the config's
48
+ `engine.type` picks one at boot.
49
+
50
+ ## Building
51
+
52
+ From source, the addon is built from this directory with the engine features
53
+ for your GPU (`cuda` by default, `metal` on macOS):
54
+
55
+ ```bash
56
+ npm run build # release, default features
57
+ bash build.sh release --features metal
58
+ bash build.sh dev --no-default-features # no engine: loads, refuses to serve
59
+ ```
60
+
61
+ It lands beside `index.mjs` as `pie-server.<platform>-<arch>.node`, which
62
+ the loader prefers over the platform package.
package/index.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { PieClient } from '@pie-project/client';
2
+
3
+ /** The native addon file this platform loads, or null when none is built. */
4
+ export function addonPath(): string | null;
5
+
6
+ /** A running pie engine in this process. */
7
+ export class Server {
8
+ /**
9
+ * Boot the engine from the TOML text `pie serve --config` reads, or an
10
+ * object of the same shape. Resolves once the engine is serving.
11
+ */
12
+ static start(config: string | Record<string, unknown>): Promise<Server>;
13
+ /** `ws://host:port` the engine listens on. */
14
+ readonly url: string;
15
+ /** `http://host:port`, where the OpenAI / Anthropic / Gemini routes live. */
16
+ readonly httpUrl: string;
17
+ /** True until `shutdown()` resolves. */
18
+ readonly running: boolean;
19
+ /** A `PieClient` connected to this engine; closed by `shutdown()`. */
20
+ connect(options?: { identity?: string }): Promise<PieClient>;
21
+ /** Stop the engine and release it. Idempotent. */
22
+ shutdown(): Promise<void>;
23
+ }
package/index.mjs ADDED
@@ -0,0 +1,107 @@
1
+ // `@pie-project/server`: what `pie serve` boots, inside a Node process.
2
+ //
3
+ // import { Server } from '@pie-project/server';
4
+ // const server = await Server.start({ server: { port: 0 }, model: { model: 'Qwen/Qwen3.5-0.8B' } });
5
+ // const client = await server.connect();
6
+ // ...
7
+ // await server.shutdown();
8
+ //
9
+ // The engine is a napi-rs addon: `pie-server.<platform>-<arch>.node` beside
10
+ // this file when built from source (`build.sh`), else the platform package
11
+ // `@pie-project/server-<platform>-<arch>` npm installed as an optional
12
+ // dependency; this wrapper loads it and adds `connect()`.
13
+
14
+ import { createRequire } from 'node:module';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { dirname, join } from 'node:path';
17
+ import { existsSync } from 'node:fs';
18
+ import WebSocket from 'ws';
19
+ import { PieClient } from '@pie-project/client';
20
+
21
+ const here = dirname(fileURLToPath(import.meta.url));
22
+ const require = createRequire(import.meta.url);
23
+ const target = `${process.platform}-${process.arch}`;
24
+
25
+ /** The addon this platform loads: a local build, else the platform package; null when neither is present. */
26
+ export function addonPath() {
27
+ const local = join(here, `pie-server.${target}.node`);
28
+ if (existsSync(local)) return local;
29
+ try {
30
+ return require.resolve(`@pie-project/server-${target}`);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ let native = null;
37
+ function load() {
38
+ if (native) return native;
39
+ const path = addonPath();
40
+ if (!path) {
41
+ throw new Error(
42
+ `@pie-project/server: no native addon for ${target}: npm did not install @pie-project/server-${target} ` +
43
+ '(no build for this platform, or optional dependencies were skipped), and none is built beside ' +
44
+ `${here} (\`npm run build\` builds one; it needs the Rust toolchain)`,
45
+ );
46
+ }
47
+ native = require(path);
48
+ return native;
49
+ }
50
+
51
+ /** A running pie engine in this process. */
52
+ export class Server {
53
+ #handle;
54
+ #clients = [];
55
+
56
+ constructor(handle) {
57
+ this.#handle = handle;
58
+ }
59
+
60
+ /**
61
+ * Boot the engine from the TOML text `pie serve --config` reads, or an
62
+ * object of the same shape (`{server: {port: 0}, model: {…}}`).
63
+ * `server.port = 0` asks the OS for a free port; `url` says which.
64
+ * Resolves once the engine is serving — minutes for a large model.
65
+ */
66
+ static async start(config) {
67
+ const api = load();
68
+ const handle = typeof config === 'string' ? await api.startToml(config) : await api.start(config);
69
+ return new Server(handle);
70
+ }
71
+
72
+ /** `ws://host:port` the engine listens on. */
73
+ get url() {
74
+ return this.#handle.url;
75
+ }
76
+
77
+ /** `http://host:port` — the OpenAI / Anthropic / Gemini routes live here. */
78
+ get httpUrl() {
79
+ return this.url.replace(/^ws(s?):\/\//, 'http$1://');
80
+ }
81
+
82
+ /** True until `shutdown()` resolves. */
83
+ get running() {
84
+ return this.#handle.running;
85
+ }
86
+
87
+ /** A `PieClient` connected to this engine (over Node's `ws`, which can send the identity header). Closed by `shutdown()`. */
88
+ async connect({ identity = 'default/node' } = {}) {
89
+ if (!this.running) throw new Error('the server is shut down');
90
+ const client = new PieClient(this.url, { WebSocket, identity });
91
+ await client.connect();
92
+ this.#clients.push(client);
93
+ return client;
94
+ }
95
+
96
+ /** Close the clients from `connect()`, then stop the engine. Idempotent. */
97
+ async shutdown() {
98
+ for (const client of this.#clients.splice(0)) {
99
+ try {
100
+ await client.close();
101
+ } catch {
102
+ // already gone
103
+ }
104
+ }
105
+ await this.#handle.shutdown();
106
+ }
107
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@pie-project/server",
3
+ "version": "0.5.1",
4
+ "description": "What `pie serve` boots, inside a Node.js process, on the native GPU engines.",
5
+ "type": "module",
6
+ "main": "index.mjs",
7
+ "types": "index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./index.d.ts",
11
+ "import": "./index.mjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "index.mjs",
16
+ "index.d.ts",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "bash build.sh release",
21
+ "build:dev": "bash build.sh dev",
22
+ "test": "node --test test/server.test.mjs"
23
+ },
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "os": [
28
+ "linux",
29
+ "darwin",
30
+ "win32"
31
+ ],
32
+ "dependencies": {
33
+ "@pie-project/client": "^0.5.0",
34
+ "ws": "^8.18.0"
35
+ },
36
+ "optionalDependencies": {
37
+ "@pie-project/server-linux-x64": "0.5.1",
38
+ "@pie-project/server-linux-arm64": "0.5.1",
39
+ "@pie-project/server-darwin-arm64": "0.5.1",
40
+ "@pie-project/server-win32-x64": "0.5.1"
41
+ },
42
+ "license": "Apache-2.0",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/pie-project/pie",
46
+ "directory": "javascript/server"
47
+ }
48
+ }