@averyyy/pi-server 0.80.3-piclient.2
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/CHANGELOG.md +16 -0
- package/README.md +46 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +40 -0
- package/dist/config.js.map +1 -0
- package/dist/event-encoding.d.ts +5 -0
- package/dist/event-encoding.d.ts.map +1 -0
- package/dist/event-encoding.js +15 -0
- package/dist/event-encoding.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/request-chunks.d.ts +27 -0
- package/dist/request-chunks.d.ts.map +1 -0
- package/dist/request-chunks.js +71 -0
- package/dist/request-chunks.js.map +1 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +504 -0
- package/dist/server.js.map +1 -0
- package/dist/session-persistence.d.ts +5 -0
- package/dist/session-persistence.d.ts.map +1 -0
- package/dist/session-persistence.js +158 -0
- package/dist/session-persistence.js.map +1 -0
- package/dist/session-store.d.ts +65 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +259 -0
- package/dist/session-store.js.map +1 -0
- package/package.json +54 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial pi-server package: HTTP proxy server that stores session state and forwards incremental LLM requests to upstream providers.
|
|
8
|
+
- `/api/session/init` endpoint for initializing session static context.
|
|
9
|
+
- `/api/session/update` endpoint for updating session static context.
|
|
10
|
+
- `GET /api/session/:id/history` endpoint for reading full server-side session history without a request body.
|
|
11
|
+
- `/api/stream` endpoint for streaming incremental LLM requests with delta messages.
|
|
12
|
+
- `/api/request/chunk` endpoint for reassembling oversized client requests before dispatch.
|
|
13
|
+
- `DELETE /api/session/:id` endpoint for removing one server-side session.
|
|
14
|
+
- `/health` endpoint for health checks.
|
|
15
|
+
- Configurable via `PI_SERVER_CONFIG` or environment variables: `PI_SERVER_HOST`, `PI_SERVER_PORT`, `PI_SERVER_AUTH_TOKEN`.
|
|
16
|
+
- Persistent session tree storage under `PI_SERVER_SESSION_STORE_DIR`, including exact tree hashes in session responses.
|
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @averyyy/pi-server
|
|
2
|
+
|
|
3
|
+
Server for `@averyyy/pi-client`. It stores session state and forwards client requests to upstream model providers.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g @averyyy/pi-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pi-server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
By default it listens on `http://127.0.0.1:4217` and stores sessions under `.pi/pi-server/sessions` in the current directory.
|
|
18
|
+
|
|
19
|
+
## Configure
|
|
20
|
+
|
|
21
|
+
Set provider keys in the server environment, then start the server:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
OPENAI_API_KEY=your-key pi-server
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Useful server settings:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
PI_SERVER_HOST=127.0.0.1
|
|
31
|
+
PI_SERVER_PORT=4217
|
|
32
|
+
PI_SERVER_AUTH_TOKEN=your-token
|
|
33
|
+
PI_SERVER_SESSION_STORE_DIR=/path/to/sessions
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Connect A Client
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
PI_SERVER_URL=http://127.0.0.1:4217 pi-client
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
With auth:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
PI_SERVER_AUTH_TOKEN=your-token PI_SERVER_URL=http://127.0.0.1:4217 pi-client
|
|
46
|
+
```
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\nimport { startServer } from \"./server.ts\";\n\nstartServer();\n"]}
|
package/dist/cli.js
ADDED
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,WAAW,EAAE,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { startServer } from \"./server.ts\";\n\nstartServer();\n"]}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;CACxB;AAwBD,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAmB1E","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface ServerConfig {\n\thost: string;\n\tport: number;\n\tauthToken: string | undefined;\n\tsessionStoreDir: string;\n}\n\nconst DEFAULT_CONFIG: ServerConfig = {\n\thost: \"127.0.0.1\",\n\tport: 4217,\n\tauthToken: undefined,\n\tsessionStoreDir: resolve(\".pi\", \"pi-server\", \"sessions\"),\n};\n\nfunction loadConfigFile(): Partial<ServerConfig> {\n\tconst configPath = process.env.PI_SERVER_CONFIG;\n\tif (!configPath) return {};\n\tconst resolved = resolve(configPath);\n\tif (!existsSync(resolved)) {\n\t\tthrow new Error(`PI_SERVER_CONFIG file not found: ${resolved}`);\n\t}\n\ttry {\n\t\tconst content = readFileSync(resolved, \"utf-8\");\n\t\treturn JSON.parse(content) as Partial<ServerConfig>;\n\t} catch (err) {\n\t\tthrow new Error(`PI_SERVER_CONFIG file contains invalid JSON: ${resolved}`, { cause: err });\n\t}\n}\n\nexport function loadConfig(overrides?: Partial<ServerConfig>): ServerConfig {\n\tconst fileConfig = loadConfigFile();\n\n\treturn {\n\t\thost: overrides?.host ?? process.env.PI_SERVER_HOST ?? fileConfig.host ?? DEFAULT_CONFIG.host,\n\t\tport:\n\t\t\toverrides?.port ??\n\t\t\t(process.env.PI_SERVER_PORT ? parseInt(process.env.PI_SERVER_PORT, 10) : undefined) ??\n\t\t\tfileConfig.port ??\n\t\t\tDEFAULT_CONFIG.port,\n\t\tauthToken:\n\t\t\toverrides?.authToken ?? process.env.PI_SERVER_AUTH_TOKEN ?? fileConfig.authToken ?? DEFAULT_CONFIG.authToken,\n\t\tsessionStoreDir: resolve(\n\t\t\toverrides?.sessionStoreDir ??\n\t\t\t\tprocess.env.PI_SERVER_SESSION_STORE_DIR ??\n\t\t\t\tfileConfig.sessionStoreDir ??\n\t\t\t\tDEFAULT_CONFIG.sessionStoreDir,\n\t\t),\n\t};\n}\n"]}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
const DEFAULT_CONFIG = {
|
|
4
|
+
host: "127.0.0.1",
|
|
5
|
+
port: 4217,
|
|
6
|
+
authToken: undefined,
|
|
7
|
+
sessionStoreDir: resolve(".pi", "pi-server", "sessions"),
|
|
8
|
+
};
|
|
9
|
+
function loadConfigFile() {
|
|
10
|
+
const configPath = process.env.PI_SERVER_CONFIG;
|
|
11
|
+
if (!configPath)
|
|
12
|
+
return {};
|
|
13
|
+
const resolved = resolve(configPath);
|
|
14
|
+
if (!existsSync(resolved)) {
|
|
15
|
+
throw new Error(`PI_SERVER_CONFIG file not found: ${resolved}`);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(resolved, "utf-8");
|
|
19
|
+
return JSON.parse(content);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
throw new Error(`PI_SERVER_CONFIG file contains invalid JSON: ${resolved}`, { cause: err });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function loadConfig(overrides) {
|
|
26
|
+
const fileConfig = loadConfigFile();
|
|
27
|
+
return {
|
|
28
|
+
host: overrides?.host ?? process.env.PI_SERVER_HOST ?? fileConfig.host ?? DEFAULT_CONFIG.host,
|
|
29
|
+
port: overrides?.port ??
|
|
30
|
+
(process.env.PI_SERVER_PORT ? parseInt(process.env.PI_SERVER_PORT, 10) : undefined) ??
|
|
31
|
+
fileConfig.port ??
|
|
32
|
+
DEFAULT_CONFIG.port,
|
|
33
|
+
authToken: overrides?.authToken ?? process.env.PI_SERVER_AUTH_TOKEN ?? fileConfig.authToken ?? DEFAULT_CONFIG.authToken,
|
|
34
|
+
sessionStoreDir: resolve(overrides?.sessionStoreDir ??
|
|
35
|
+
process.env.PI_SERVER_SESSION_STORE_DIR ??
|
|
36
|
+
fileConfig.sessionStoreDir ??
|
|
37
|
+
DEFAULT_CONFIG.sessionStoreDir),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,MAAM,cAAc,GAAiB;IACpC,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,IAAI;IACV,SAAS,EAAE,SAAS;IACpB,eAAe,EAAE,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,CAAC;CACxD,CAAC;AAEF,SAAS,cAAc,GAA0B;IAChD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAChD,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,oCAAoC,QAAQ,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAA0B,CAAC;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,gDAAgD,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC7F,CAAC;AAAA,CACD;AAED,MAAM,UAAU,UAAU,CAAC,SAAiC,EAAgB;IAC3E,MAAM,UAAU,GAAG,cAAc,EAAE,CAAC;IAEpC,OAAO;QACN,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,UAAU,CAAC,IAAI,IAAI,cAAc,CAAC,IAAI;QAC7F,IAAI,EACH,SAAS,EAAE,IAAI;YACf,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACnF,UAAU,CAAC,IAAI;YACf,cAAc,CAAC,IAAI;QACpB,SAAS,EACR,SAAS,EAAE,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,UAAU,CAAC,SAAS,IAAI,cAAc,CAAC,SAAS;QAC7G,eAAe,EAAE,OAAO,CACvB,SAAS,EAAE,eAAe;YACzB,OAAO,CAAC,GAAG,CAAC,2BAA2B;YACvC,UAAU,CAAC,eAAe;YAC1B,cAAc,CAAC,eAAe,CAC/B;KACD,CAAC;AAAA,CACF","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface ServerConfig {\n\thost: string;\n\tport: number;\n\tauthToken: string | undefined;\n\tsessionStoreDir: string;\n}\n\nconst DEFAULT_CONFIG: ServerConfig = {\n\thost: \"127.0.0.1\",\n\tport: 4217,\n\tauthToken: undefined,\n\tsessionStoreDir: resolve(\".pi\", \"pi-server\", \"sessions\"),\n};\n\nfunction loadConfigFile(): Partial<ServerConfig> {\n\tconst configPath = process.env.PI_SERVER_CONFIG;\n\tif (!configPath) return {};\n\tconst resolved = resolve(configPath);\n\tif (!existsSync(resolved)) {\n\t\tthrow new Error(`PI_SERVER_CONFIG file not found: ${resolved}`);\n\t}\n\ttry {\n\t\tconst content = readFileSync(resolved, \"utf-8\");\n\t\treturn JSON.parse(content) as Partial<ServerConfig>;\n\t} catch (err) {\n\t\tthrow new Error(`PI_SERVER_CONFIG file contains invalid JSON: ${resolved}`, { cause: err });\n\t}\n}\n\nexport function loadConfig(overrides?: Partial<ServerConfig>): ServerConfig {\n\tconst fileConfig = loadConfigFile();\n\n\treturn {\n\t\thost: overrides?.host ?? process.env.PI_SERVER_HOST ?? fileConfig.host ?? DEFAULT_CONFIG.host,\n\t\tport:\n\t\t\toverrides?.port ??\n\t\t\t(process.env.PI_SERVER_PORT ? parseInt(process.env.PI_SERVER_PORT, 10) : undefined) ??\n\t\t\tfileConfig.port ??\n\t\t\tDEFAULT_CONFIG.port,\n\t\tauthToken:\n\t\t\toverrides?.authToken ?? process.env.PI_SERVER_AUTH_TOKEN ?? fileConfig.authToken ?? DEFAULT_CONFIG.authToken,\n\t\tsessionStoreDir: resolve(\n\t\t\toverrides?.sessionStoreDir ??\n\t\t\t\tprocess.env.PI_SERVER_SESSION_STORE_DIR ??\n\t\t\t\tfileConfig.sessionStoreDir ??\n\t\t\t\tDEFAULT_CONFIG.sessionStoreDir,\n\t\t),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ProxyAssistantMessageEvent } from "@earendil-works/pi-agent-core";
|
|
2
|
+
export declare function encodeProxyEvent(event: ProxyAssistantMessageEvent): string;
|
|
3
|
+
export declare function encodeErrorEvent(message: string): string;
|
|
4
|
+
export declare function parseProxyEvent(line: string): ProxyAssistantMessageEvent | undefined;
|
|
5
|
+
//# sourceMappingURL=event-encoding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-encoding.d.ts","sourceRoot":"","sources":["../src/event-encoding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAE1E;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAKpF","sourcesContent":["import type { ProxyAssistantMessageEvent } from \"@earendil-works/pi-agent-core\";\n\nexport function encodeProxyEvent(event: ProxyAssistantMessageEvent): string {\n\treturn `data: ${JSON.stringify(event)}\\n\\n`;\n}\n\nexport function encodeErrorEvent(message: string): string {\n\treturn `data: ${JSON.stringify({ type: \"error\", reason: \"error\", errorMessage: message, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } } })}\\n\\n`;\n}\n\nexport function parseProxyEvent(line: string): ProxyAssistantMessageEvent | undefined {\n\tif (!line.startsWith(\"data: \")) return undefined;\n\tconst data = line.slice(6).trim();\n\tif (!data) return undefined;\n\treturn JSON.parse(data) as ProxyAssistantMessageEvent;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function encodeProxyEvent(event) {
|
|
2
|
+
return `data: ${JSON.stringify(event)}\n\n`;
|
|
3
|
+
}
|
|
4
|
+
export function encodeErrorEvent(message) {
|
|
5
|
+
return `data: ${JSON.stringify({ type: "error", reason: "error", errorMessage: message, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } } })}\n\n`;
|
|
6
|
+
}
|
|
7
|
+
export function parseProxyEvent(line) {
|
|
8
|
+
if (!line.startsWith("data: "))
|
|
9
|
+
return undefined;
|
|
10
|
+
const data = line.slice(6).trim();
|
|
11
|
+
if (!data)
|
|
12
|
+
return undefined;
|
|
13
|
+
return JSON.parse(data);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=event-encoding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-encoding.js","sourceRoot":"","sources":["../src/event-encoding.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,KAAiC,EAAU;IAC3E,OAAO,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;AAAA,CAC5C;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAU;IACzD,OAAO,SAAS,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC;AAAA,CACnP;AAED,MAAM,UAAU,eAAe,CAAC,IAAY,EAA0C;IACrF,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAA+B,CAAC;AAAA,CACtD","sourcesContent":["import type { ProxyAssistantMessageEvent } from \"@earendil-works/pi-agent-core\";\n\nexport function encodeProxyEvent(event: ProxyAssistantMessageEvent): string {\n\treturn `data: ${JSON.stringify(event)}\\n\\n`;\n}\n\nexport function encodeErrorEvent(message: string): string {\n\treturn `data: ${JSON.stringify({ type: \"error\", reason: \"error\", errorMessage: message, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } } })}\\n\\n`;\n}\n\nexport function parseProxyEvent(line: string): ProxyAssistantMessageEvent | undefined {\n\tif (!line.startsWith(\"data: \")) return undefined;\n\tconst data = line.slice(6).trim();\n\tif (!data) return undefined;\n\treturn JSON.parse(data) as ProxyAssistantMessageEvent;\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { loadConfig, type ServerConfig } from "./config.ts";
|
|
2
|
+
export { encodeErrorEvent, encodeProxyEvent, parseProxyEvent } from "./event-encoding.ts";
|
|
3
|
+
export { createPiServer, startServer } from "./server.ts";
|
|
4
|
+
export { appendAssistantResponse, appendMessages, clearAllSessions, deleteSession, exportSessionState, getOrCreateSession, getSession, hashSessionEntries, listSessions, type PersistedSessionState, restoreSessionState, type SessionState, type SessionStaticContext, type SessionSummary, setStaticContext, } from "./session-store.ts";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EACN,uBAAuB,EACvB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,gBAAgB,GAChB,MAAM,oBAAoB,CAAC","sourcesContent":["export { loadConfig, type ServerConfig } from \"./config.ts\";\nexport { encodeErrorEvent, encodeProxyEvent, parseProxyEvent } from \"./event-encoding.ts\";\nexport { createPiServer, startServer } from \"./server.ts\";\nexport {\n\tappendAssistantResponse,\n\tappendMessages,\n\tclearAllSessions,\n\tdeleteSession,\n\texportSessionState,\n\tgetOrCreateSession,\n\tgetSession,\n\thashSessionEntries,\n\tlistSessions,\n\ttype PersistedSessionState,\n\trestoreSessionState,\n\ttype SessionState,\n\ttype SessionStaticContext,\n\ttype SessionSummary,\n\tsetStaticContext,\n} from \"./session-store.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { loadConfig } from "./config.js";
|
|
2
|
+
export { encodeErrorEvent, encodeProxyEvent, parseProxyEvent } from "./event-encoding.js";
|
|
3
|
+
export { createPiServer, startServer } from "./server.js";
|
|
4
|
+
export { appendAssistantResponse, appendMessages, clearAllSessions, deleteSession, exportSessionState, getOrCreateSession, getSession, hashSessionEntries, listSessions, restoreSessionState, setStaticContext, } from "./session-store.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EACN,uBAAuB,EACvB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,kBAAkB,EAClB,YAAY,EAEZ,mBAAmB,EAInB,gBAAgB,GAChB,MAAM,oBAAoB,CAAC","sourcesContent":["export { loadConfig, type ServerConfig } from \"./config.ts\";\nexport { encodeErrorEvent, encodeProxyEvent, parseProxyEvent } from \"./event-encoding.ts\";\nexport { createPiServer, startServer } from \"./server.ts\";\nexport {\n\tappendAssistantResponse,\n\tappendMessages,\n\tclearAllSessions,\n\tdeleteSession,\n\texportSessionState,\n\tgetOrCreateSession,\n\tgetSession,\n\thashSessionEntries,\n\tlistSessions,\n\ttype PersistedSessionState,\n\trestoreSessionState,\n\ttype SessionState,\n\ttype SessionStaticContext,\n\ttype SessionSummary,\n\tsetStaticContext,\n} from \"./session-store.ts\";\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const CHUNK_ENDPOINT = "/api/request/chunk";
|
|
2
|
+
export interface RequestChunkBody {
|
|
3
|
+
requestId: string;
|
|
4
|
+
target: string;
|
|
5
|
+
index: number;
|
|
6
|
+
total: number;
|
|
7
|
+
chunk: string;
|
|
8
|
+
}
|
|
9
|
+
interface ChunkAck {
|
|
10
|
+
received: true;
|
|
11
|
+
requestId: string;
|
|
12
|
+
index: number;
|
|
13
|
+
total: number;
|
|
14
|
+
}
|
|
15
|
+
interface PendingChunkResult {
|
|
16
|
+
complete: false;
|
|
17
|
+
ack: ChunkAck;
|
|
18
|
+
}
|
|
19
|
+
interface CompleteChunkResult {
|
|
20
|
+
complete: true;
|
|
21
|
+
target: string;
|
|
22
|
+
bodyJson: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function receiveRequestChunk(body: RequestChunkBody): PendingChunkResult | CompleteChunkResult;
|
|
25
|
+
export declare function clearAllRequestChunks(): void;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=request-chunks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-chunks.d.ts","sourceRoot":"","sources":["../src/request-chunks.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,uBAAuB,CAAC;AAenD,MAAM,WAAW,gBAAgB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAQD,UAAU,QAAQ;IACjB,QAAQ,EAAE,IAAI,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAED,UAAU,kBAAkB;IAC3B,QAAQ,EAAE,KAAK,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACd;AAED,UAAU,mBAAmB;IAC5B,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CACjB;AAcD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,kBAAkB,GAAG,mBAAmB,CA2CpG;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C","sourcesContent":["import { Buffer } from \"node:buffer\";\n\nexport const CHUNK_ENDPOINT = \"/api/request/chunk\";\n\nconst ALLOWED_TARGETS = new Set([\n\t\"/api/session/init\",\n\t\"/api/session/update\",\n\t\"/api/session/sync\",\n\t\"/api/session/append\",\n\t\"/api/session/tree/sync\",\n\t\"/api/session/tree/append\",\n\t\"/api/session/tree/switch\",\n\t\"/api/session/drop-last-assistant-error\",\n\t\"/api/session/compact\",\n\t\"/api/stream\",\n]);\n\nexport interface RequestChunkBody {\n\trequestId: string;\n\ttarget: string;\n\tindex: number;\n\ttotal: number;\n\tchunk: string;\n}\n\ninterface PendingRequest {\n\ttarget: string;\n\ttotal: number;\n\tchunks: Map<number, string>;\n}\n\ninterface ChunkAck {\n\treceived: true;\n\trequestId: string;\n\tindex: number;\n\ttotal: number;\n}\n\ninterface PendingChunkResult {\n\tcomplete: false;\n\tack: ChunkAck;\n}\n\ninterface CompleteChunkResult {\n\tcomplete: true;\n\ttarget: string;\n\tbodyJson: string;\n}\n\nconst pendingRequests = new Map<string, PendingRequest>();\n\nfunction assertValidChunk(body: RequestChunkBody): void {\n\tif (!body.requestId) throw new Error(\"requestId is required\");\n\tif (!ALLOWED_TARGETS.has(body.target)) throw new Error(`Unsupported chunk target: ${body.target}`);\n\tif (!Number.isInteger(body.total) || body.total <= 0) throw new Error(\"total must be a positive integer\");\n\tif (!Number.isInteger(body.index) || body.index < 0 || body.index >= body.total) {\n\t\tthrow new Error(\"index must be an integer within the chunk range\");\n\t}\n\tif (typeof body.chunk !== \"string\") throw new Error(\"chunk must be a string\");\n}\n\nexport function receiveRequestChunk(body: RequestChunkBody): PendingChunkResult | CompleteChunkResult {\n\tassertValidChunk(body);\n\n\tlet pending = pendingRequests.get(body.requestId);\n\tif (!pending) {\n\t\tpending = { target: body.target, total: body.total, chunks: new Map() };\n\t\tpendingRequests.set(body.requestId, pending);\n\t}\n\n\tif (pending.target !== body.target || pending.total !== body.total) {\n\t\tthrow new Error(\"Chunk metadata does not match the pending request\");\n\t}\n\tif (pending.chunks.has(body.index)) {\n\t\tthrow new Error(`Duplicate chunk index: ${body.index}`);\n\t}\n\n\tpending.chunks.set(body.index, body.chunk);\n\n\tif (pending.chunks.size !== pending.total) {\n\t\treturn {\n\t\t\tcomplete: false,\n\t\t\tack: {\n\t\t\t\treceived: true,\n\t\t\t\trequestId: body.requestId,\n\t\t\t\tindex: body.index,\n\t\t\t\ttotal: body.total,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst encodedChunks: string[] = [];\n\tfor (let index = 0; index < pending.total; index++) {\n\t\tconst chunk = pending.chunks.get(index);\n\t\tif (chunk === undefined) throw new Error(`Missing chunk index: ${index}`);\n\t\tencodedChunks.push(chunk);\n\t}\n\n\tpendingRequests.delete(body.requestId);\n\treturn {\n\t\tcomplete: true,\n\t\ttarget: pending.target,\n\t\tbodyJson: Buffer.from(encodedChunks.join(\"\"), \"base64\").toString(\"utf-8\"),\n\t};\n}\n\nexport function clearAllRequestChunks(): void {\n\tpendingRequests.clear();\n}\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
export const CHUNK_ENDPOINT = "/api/request/chunk";
|
|
3
|
+
const ALLOWED_TARGETS = new Set([
|
|
4
|
+
"/api/session/init",
|
|
5
|
+
"/api/session/update",
|
|
6
|
+
"/api/session/sync",
|
|
7
|
+
"/api/session/append",
|
|
8
|
+
"/api/session/tree/sync",
|
|
9
|
+
"/api/session/tree/append",
|
|
10
|
+
"/api/session/tree/switch",
|
|
11
|
+
"/api/session/drop-last-assistant-error",
|
|
12
|
+
"/api/session/compact",
|
|
13
|
+
"/api/stream",
|
|
14
|
+
]);
|
|
15
|
+
const pendingRequests = new Map();
|
|
16
|
+
function assertValidChunk(body) {
|
|
17
|
+
if (!body.requestId)
|
|
18
|
+
throw new Error("requestId is required");
|
|
19
|
+
if (!ALLOWED_TARGETS.has(body.target))
|
|
20
|
+
throw new Error(`Unsupported chunk target: ${body.target}`);
|
|
21
|
+
if (!Number.isInteger(body.total) || body.total <= 0)
|
|
22
|
+
throw new Error("total must be a positive integer");
|
|
23
|
+
if (!Number.isInteger(body.index) || body.index < 0 || body.index >= body.total) {
|
|
24
|
+
throw new Error("index must be an integer within the chunk range");
|
|
25
|
+
}
|
|
26
|
+
if (typeof body.chunk !== "string")
|
|
27
|
+
throw new Error("chunk must be a string");
|
|
28
|
+
}
|
|
29
|
+
export function receiveRequestChunk(body) {
|
|
30
|
+
assertValidChunk(body);
|
|
31
|
+
let pending = pendingRequests.get(body.requestId);
|
|
32
|
+
if (!pending) {
|
|
33
|
+
pending = { target: body.target, total: body.total, chunks: new Map() };
|
|
34
|
+
pendingRequests.set(body.requestId, pending);
|
|
35
|
+
}
|
|
36
|
+
if (pending.target !== body.target || pending.total !== body.total) {
|
|
37
|
+
throw new Error("Chunk metadata does not match the pending request");
|
|
38
|
+
}
|
|
39
|
+
if (pending.chunks.has(body.index)) {
|
|
40
|
+
throw new Error(`Duplicate chunk index: ${body.index}`);
|
|
41
|
+
}
|
|
42
|
+
pending.chunks.set(body.index, body.chunk);
|
|
43
|
+
if (pending.chunks.size !== pending.total) {
|
|
44
|
+
return {
|
|
45
|
+
complete: false,
|
|
46
|
+
ack: {
|
|
47
|
+
received: true,
|
|
48
|
+
requestId: body.requestId,
|
|
49
|
+
index: body.index,
|
|
50
|
+
total: body.total,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const encodedChunks = [];
|
|
55
|
+
for (let index = 0; index < pending.total; index++) {
|
|
56
|
+
const chunk = pending.chunks.get(index);
|
|
57
|
+
if (chunk === undefined)
|
|
58
|
+
throw new Error(`Missing chunk index: ${index}`);
|
|
59
|
+
encodedChunks.push(chunk);
|
|
60
|
+
}
|
|
61
|
+
pendingRequests.delete(body.requestId);
|
|
62
|
+
return {
|
|
63
|
+
complete: true,
|
|
64
|
+
target: pending.target,
|
|
65
|
+
bodyJson: Buffer.from(encodedChunks.join(""), "base64").toString("utf-8"),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function clearAllRequestChunks() {
|
|
69
|
+
pendingRequests.clear();
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=request-chunks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-chunks.js","sourceRoot":"","sources":["../src/request-chunks.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from \"node:buffer\";\n\nexport const CHUNK_ENDPOINT = \"/api/request/chunk\";\n\nconst ALLOWED_TARGETS = new Set([\n\t\"/api/session/init\",\n\t\"/api/session/update\",\n\t\"/api/session/sync\",\n\t\"/api/session/append\",\n\t\"/api/session/tree/sync\",\n\t\"/api/session/tree/append\",\n\t\"/api/session/tree/switch\",\n\t\"/api/session/drop-last-assistant-error\",\n\t\"/api/session/compact\",\n\t\"/api/stream\",\n]);\n\nexport interface RequestChunkBody {\n\trequestId: string;\n\ttarget: string;\n\tindex: number;\n\ttotal: number;\n\tchunk: string;\n}\n\ninterface PendingRequest {\n\ttarget: string;\n\ttotal: number;\n\tchunks: Map<number, string>;\n}\n\ninterface ChunkAck {\n\treceived: true;\n\trequestId: string;\n\tindex: number;\n\ttotal: number;\n}\n\ninterface PendingChunkResult {\n\tcomplete: false;\n\tack: ChunkAck;\n}\n\ninterface CompleteChunkResult {\n\tcomplete: true;\n\ttarget: string;\n\tbodyJson: string;\n}\n\nconst pendingRequests = new Map<string, PendingRequest>();\n\nfunction assertValidChunk(body: RequestChunkBody): void {\n\tif (!body.requestId) throw new Error(\"requestId is required\");\n\tif (!ALLOWED_TARGETS.has(body.target)) throw new Error(`Unsupported chunk target: ${body.target}`);\n\tif (!Number.isInteger(body.total) || body.total <= 0) throw new Error(\"total must be a positive integer\");\n\tif (!Number.isInteger(body.index) || body.index < 0 || body.index >= body.total) {\n\t\tthrow new Error(\"index must be an integer within the chunk range\");\n\t}\n\tif (typeof body.chunk !== \"string\") throw new Error(\"chunk must be a string\");\n}\n\nexport function receiveRequestChunk(body: RequestChunkBody): PendingChunkResult | CompleteChunkResult {\n\tassertValidChunk(body);\n\n\tlet pending = pendingRequests.get(body.requestId);\n\tif (!pending) {\n\t\tpending = { target: body.target, total: body.total, chunks: new Map() };\n\t\tpendingRequests.set(body.requestId, pending);\n\t}\n\n\tif (pending.target !== body.target || pending.total !== body.total) {\n\t\tthrow new Error(\"Chunk metadata does not match the pending request\");\n\t}\n\tif (pending.chunks.has(body.index)) {\n\t\tthrow new Error(`Duplicate chunk index: ${body.index}`);\n\t}\n\n\tpending.chunks.set(body.index, body.chunk);\n\n\tif (pending.chunks.size !== pending.total) {\n\t\treturn {\n\t\t\tcomplete: false,\n\t\t\tack: {\n\t\t\t\treceived: true,\n\t\t\t\trequestId: body.requestId,\n\t\t\t\tindex: body.index,\n\t\t\t\ttotal: body.total,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst encodedChunks: string[] = [];\n\tfor (let index = 0; index < pending.total; index++) {\n\t\tconst chunk = pending.chunks.get(index);\n\t\tif (chunk === undefined) throw new Error(`Missing chunk index: ${index}`);\n\t\tencodedChunks.push(chunk);\n\t}\n\n\tpendingRequests.delete(body.requestId);\n\treturn {\n\t\tcomplete: true,\n\t\ttarget: pending.target,\n\t\tbodyJson: Buffer.from(encodedChunks.join(\"\"), \"base64\").toString(\"utf-8\"),\n\t};\n}\n\nexport function clearAllRequestChunks(): void {\n\tpendingRequests.clear();\n}\n"]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Server as HttpServer } from "node:http";
|
|
2
|
+
import { type Model, type SimpleStreamOptions } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { ServerConfig } from "./config.ts";
|
|
4
|
+
import { type SessionStaticContext } from "./session-store.ts";
|
|
5
|
+
export { loadConfig, type ServerConfig } from "./config.ts";
|
|
6
|
+
interface StreamRequestBody {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
model: Model<any>;
|
|
9
|
+
options?: SimpleStreamOptions;
|
|
10
|
+
staticContext?: SessionStaticContext;
|
|
11
|
+
}
|
|
12
|
+
interface ResolvedStream {
|
|
13
|
+
model: Model<any>;
|
|
14
|
+
options: SimpleStreamOptions;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolveStreamOptions(_config: ServerConfig, model: Model<any>, body: StreamRequestBody): ResolvedStream;
|
|
17
|
+
export declare function createPiServer(configOverride?: Partial<ServerConfig>): HttpServer;
|
|
18
|
+
export declare function startServer(configOverride?: Partial<ServerConfig>): HttpServer;
|
|
19
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,IAAI,UAAU,EAA6C,MAAM,WAAW,CAAC;AAU/G,OAAO,EAMN,KAAK,KAAK,EACV,KAAK,mBAAmB,EACxB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,OAAO,EAcN,KAAK,oBAAoB,EAGzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAO5D,UAAU,iBAAiB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACrC;AA2FD,UAAU,cAAc;IACvB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,OAAO,EAAE,mBAAmB,CAAC;CAC7B;AAkBD,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,IAAI,EAAE,iBAAiB,GACrB,cAAc,CAEhB;AAqVD,wBAAgB,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,UAAU,CAmFjF;AA+DD,wBAAgB,WAAW,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,UAAU,CAO9E","sourcesContent":["import { createServer, type Server as HttpServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport {\n\ttype CompactionSettings,\n\ttype CompactResult,\n\tcompact,\n\tDEFAULT_COMPACTION_SETTINGS,\n\ttype ProxyAssistantMessageEvent,\n\tprepareCompaction,\n\ttype SessionTreeEntry,\n} from \"@earendil-works/pi-agent-core\";\nimport {\n\ttype AssistantMessageEvent,\n\ttype Context,\n\tcreateModels,\n\tcreateProvider,\n\ttype Message,\n\ttype Model,\n\ttype SimpleStreamOptions,\n} from \"@earendil-works/pi-ai\";\nimport { streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport type { ServerConfig } from \"./config.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { encodeErrorEvent, encodeProxyEvent } from \"./event-encoding.ts\";\nimport { CHUNK_ENDPOINT, type RequestChunkBody, receiveRequestChunk } from \"./request-chunks.ts\";\nimport { deletePersistedSession, loadPersistedSessions, savePersistedSession } from \"./session-persistence.ts\";\nimport {\n\tappendCompactionEntry,\n\tappendMessages,\n\tappendSessionEntries,\n\tdeleteSession as deleteSessionFromStore,\n\tdropLastAssistantError,\n\tgetOrCreateSession,\n\tgetSession,\n\tgetSessionBranch,\n\thashSessionEntries,\n\tlistSessions,\n\treplaceMessages,\n\treplaceSessionTree,\n\ttype SessionState,\n\ttype SessionStaticContext,\n\tsetStaticContext,\n\tswitchSessionLeaf,\n} from \"./session-store.ts\";\n\nexport { loadConfig, type ServerConfig } from \"./config.ts\";\n\ninterface SessionInitBody {\n\tsessionId: string;\n\tstaticContext?: SessionStaticContext;\n}\n\ninterface StreamRequestBody {\n\tsessionId: string;\n\tmodel: Model<any>;\n\toptions?: SimpleStreamOptions;\n\tstaticContext?: SessionStaticContext;\n}\n\ninterface SessionSyncBody {\n\tsessionId: string;\n\tmessages: Message[];\n\tstaticContext?: SessionStaticContext;\n}\n\ninterface SessionAppendBody {\n\tsessionId: string;\n\tmessages: Message[];\n\tstaticContext?: SessionStaticContext;\n}\n\ninterface SessionTreeSyncBody {\n\tsessionId: string;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n\tstaticContext?: SessionStaticContext;\n}\n\ninterface SessionTreeSwitchBody {\n\tsessionId: string;\n\tleafId: string | null;\n}\n\ninterface SessionCompactBody {\n\tsessionId: string;\n\tmodel: Model<any>;\n\toptions?: SimpleStreamOptions;\n\tsettings?: CompactionSettings;\n\tcustomInstructions?: string;\n}\n\nfunction createRequestModels(model: Model<any>, options: SimpleStreamOptions) {\n\tconst models = createModels();\n\tmodels.setProvider(\n\t\tcreateProvider({\n\t\t\tid: model.provider,\n\t\t\tname: model.provider,\n\t\t\tmodels: [model],\n\t\t\tauth: {\n\t\t\t\tapiKey: {\n\t\t\t\t\tname: \"pi-server request auth\",\n\t\t\t\t\tresolve: async () => ({ auth: { apiKey: options.apiKey, headers: options.headers } }),\n\t\t\t\t},\n\t\t\t},\n\t\t\tapi: {\n\t\t\t\tstream: (requestModel, context, streamOptions) => streamSimple(requestModel, context, streamOptions),\n\t\t\t\tstreamSimple: (requestModel, context, streamOptions) => streamSimple(requestModel, context, streamOptions),\n\t\t\t},\n\t\t}),\n\t);\n\treturn models;\n}\n\ninterface SessionIdBody {\n\tsessionId: string;\n}\n\nconst STREAM_HEARTBEAT = \": keep-alive\\n\\n\";\nconst STREAM_HEARTBEAT_INTERVAL_MS = 25_000;\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst chunks: Buffer[] = [];\n\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\treq.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n\t\treq.on(\"error\", reject);\n\t});\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n\tconst data = JSON.stringify(body);\n\tres.writeHead(status, { \"Content-Type\": \"application/json\", \"Content-Length\": Buffer.byteLength(data) });\n\tres.end(data);\n}\n\nfunction logRequestError(req: IncomingMessage, error: unknown): void {\n\tconst message = error instanceof Error ? error.stack || error.message : String(error);\n\tconsole.error(`${req.method ?? \"UNKNOWN\"} ${req.url ?? \"/\"} failed: ${message}`);\n}\n\nfunction authenticate(config: ServerConfig, req: IncomingMessage): boolean {\n\tif (!config.authToken) return true;\n\tconst header = req.headers.authorization;\n\tif (!header) return false;\n\tconst token = header.startsWith(\"Bearer \") ? header.slice(7) : header;\n\treturn token === config.authToken;\n}\n\ninterface ResolvedStream {\n\tmodel: Model<any>;\n\toptions: SimpleStreamOptions;\n}\n\nfunction sessionResponseBody(session: SessionState) {\n\treturn {\n\t\tsessionId: session.sessionId,\n\t\tstaticContextHash: session.staticContextHash,\n\t\ttreeHash: hashSessionEntries(session.entries),\n\t\tmessageCount: session.messages.length,\n\t\tentryCount: session.entries.length,\n\t\tleafId: session.leafId,\n\t\trevision: session.revision,\n\t};\n}\n\nfunction persistSession(config: ServerConfig, session: SessionState): void {\n\tsavePersistedSession(config.sessionStoreDir, session);\n}\n\nexport function resolveStreamOptions(\n\t_config: ServerConfig,\n\tmodel: Model<any>,\n\tbody: StreamRequestBody,\n): ResolvedStream {\n\treturn { model, options: { ...(body.options ?? {}) } };\n}\n\nfunction handleSessionInit(config: ServerConfig, body: SessionInitBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tif (body.staticContext) {\n\t\tsetStaticContext(body.sessionId, body.staticContext);\n\t} else {\n\t\tgetOrCreateSession(body.sessionId);\n\t}\n\tconst session = getSession(body.sessionId)!;\n\tpersistSession(config, session);\n\tsendJson(res, 200, sessionResponseBody(session));\n}\n\nfunction handleSessionUpdate(\n\tconfig: ServerConfig,\n\tbody: SessionInitBody & { staticContext: SessionStaticContext },\n\tres: ServerResponse,\n): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tif (!body.staticContext) {\n\t\tsendJson(res, 400, { error: \"staticContext is required for update\" });\n\t\treturn;\n\t}\n\tsetStaticContext(body.sessionId, body.staticContext);\n\tconst session = getSession(body.sessionId)!;\n\tpersistSession(config, session);\n\tsendJson(res, 200, sessionResponseBody(session));\n}\n\nfunction handleSessionSync(config: ServerConfig, body: SessionSyncBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tif (!Array.isArray(body.messages)) {\n\t\tsendJson(res, 400, { error: \"messages is required\" });\n\t\treturn;\n\t}\n\tif (body.staticContext) {\n\t\tsetStaticContext(body.sessionId, body.staticContext);\n\t}\n\tconst session = replaceMessages(body.sessionId, body.messages);\n\tpersistSession(config, session);\n\tsendJson(res, 200, sessionResponseBody(session));\n}\n\nfunction handleSessionAppend(config: ServerConfig, body: SessionAppendBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tif (!Array.isArray(body.messages)) {\n\t\tsendJson(res, 400, { error: \"messages is required\" });\n\t\treturn;\n\t}\n\tif (body.staticContext) {\n\t\tsetStaticContext(body.sessionId, body.staticContext);\n\t}\n\tconst session = appendMessages(body.sessionId, body.messages);\n\tpersistSession(config, session);\n\tsendJson(res, 200, sessionResponseBody(session));\n}\n\nfunction handleSessionTreeSync(config: ServerConfig, body: SessionTreeSyncBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tif (!Array.isArray(body.entries)) {\n\t\tsendJson(res, 400, { error: \"entries is required\" });\n\t\treturn;\n\t}\n\tif (body.staticContext) {\n\t\tsetStaticContext(body.sessionId, body.staticContext);\n\t}\n\tconst session = replaceSessionTree(body.sessionId, body.entries, body.leafId ?? null);\n\tpersistSession(config, session);\n\tsendJson(res, 200, sessionResponseBody(session));\n}\n\nfunction handleSessionTreeAppend(config: ServerConfig, body: SessionTreeSyncBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tif (!Array.isArray(body.entries)) {\n\t\tsendJson(res, 400, { error: \"entries is required\" });\n\t\treturn;\n\t}\n\tif (body.staticContext) {\n\t\tsetStaticContext(body.sessionId, body.staticContext);\n\t}\n\tconst session = appendSessionEntries(body.sessionId, body.entries, body.leafId ?? null);\n\tpersistSession(config, session);\n\tsendJson(res, 200, sessionResponseBody(session));\n}\n\nfunction handleSessionTreeSwitch(config: ServerConfig, body: SessionTreeSwitchBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tconst session = switchSessionLeaf(body.sessionId, body.leafId ?? null);\n\tpersistSession(config, session);\n\tsendJson(res, 200, sessionResponseBody(session));\n}\n\nasync function handleSessionCompact(\n\tconfig: ServerConfig,\n\tbody: SessionCompactBody,\n\tres: ServerResponse,\n): Promise<void> {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tif (!body.model) {\n\t\tsendJson(res, 400, { error: \"model is required\" });\n\t\treturn;\n\t}\n\n\tconst session = getSession(body.sessionId);\n\tif (!session) {\n\t\tsendJson(res, 404, { error: \"session not found\" });\n\t\treturn;\n\t}\n\n\tconst entries = getSessionBranch(session);\n\tconst preparationResult = prepareCompaction(entries, body.settings ?? DEFAULT_COMPACTION_SETTINGS);\n\tif (!preparationResult.ok) {\n\t\tsendJson(res, 400, { error: preparationResult.error.message });\n\t\treturn;\n\t}\n\tif (!preparationResult.value) {\n\t\tsendJson(res, 400, { error: \"Nothing to compact\" });\n\t\treturn;\n\t}\n\n\tconst options = body.options ?? {};\n\tconst result = await compact(\n\t\tpreparationResult.value,\n\t\tcreateRequestModels(body.model, options),\n\t\tbody.model,\n\t\tbody.customInstructions,\n\t\tundefined,\n\t\toptions.reasoning,\n\t);\n\tif (!result.ok) {\n\t\tsendJson(res, 500, { error: result.error.message });\n\t\treturn;\n\t}\n\n\tconst { session: updatedSession, entry: compactionEntry } = appendCompactionEntry(body.sessionId, result.value);\n\tpersistSession(config, updatedSession);\n\tsendJson(res, 200, {\n\t\tsuccess: true,\n\t\tcompaction: result.value satisfies CompactResult,\n\t\tcompactionEntry,\n\t\t...sessionResponseBody(updatedSession),\n\t\tstaticContext: updatedSession.staticContext,\n\t\tentries: updatedSession.entries,\n\t\tmessages: updatedSession.messages,\n\t});\n}\n\nfunction handleDropLastAssistantError(config: ServerConfig, body: SessionIdBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tconst dropped = dropLastAssistantError(body.sessionId);\n\tconst session = getSession(body.sessionId);\n\tif (session) {\n\t\tpersistSession(config, session);\n\t}\n\tconst messageCount = session?.messages.length ?? 0;\n\tsendJson(res, 200, { success: true, dropped, messageCount });\n}\n\nfunction handleSessionHistory(sessionId: string, from: number | undefined, res: ServerResponse): void {\n\tconst session = getSession(sessionId);\n\tif (!session) {\n\t\tsendJson(res, 404, { error: \"session not found\" });\n\t\treturn;\n\t}\n\tconst baseMessageCount = from ?? 0;\n\tsendJson(res, 200, {\n\t\tsessionId: session.sessionId,\n\t\tstaticContext: session.staticContext,\n\t\tstaticContextHash: session.staticContextHash,\n\t\ttreeHash: hashSessionEntries(session.entries),\n\t\tmessageCount: session.messages.length,\n\t\tentryCount: session.entries.length,\n\t\tleafId: session.leafId,\n\t\tentries: session.entries,\n\t\tbaseMessageCount,\n\t\tmessages: session.messages.slice(baseMessageCount),\n\t});\n}\n\nfunction handleStream(config: ServerConfig, body: StreamRequestBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendJson(res, 400, { error: \"sessionId is required\" });\n\t\treturn;\n\t}\n\tif (!body.model) {\n\t\tsendJson(res, 400, { error: \"model is required\" });\n\t\treturn;\n\t}\n\n\tconst session = getOrCreateSession(body.sessionId);\n\n\tif (body.staticContext) {\n\t\tsetStaticContext(body.sessionId, body.staticContext);\n\t\tpersistSession(config, session);\n\t}\n\n\tif (!session.staticContext && !body.staticContext) {\n\t\tsendJson(res, 400, { error: \"Session has no static context. Initialize with /api/session/init first.\" });\n\t\treturn;\n\t}\n\n\tconst context: Context = {\n\t\tsystemPrompt: session.staticContext?.systemPrompt,\n\t\tmessages: session.messages,\n\t\ttools: session.staticContext?.tools,\n\t};\n\n\tconst { model: resolvedModel, options: streamOptions } = resolveStreamOptions(config, body.model, body);\n\n\tres.writeHead(200, {\n\t\t\"Content-Type\": \"text/event-stream\",\n\t\t\"Cache-Control\": \"no-cache\",\n\t\tConnection: \"keep-alive\",\n\t});\n\tres.flushHeaders();\n\tres.write(STREAM_HEARTBEAT);\n\n\tconst heartbeat = setInterval(() => {\n\t\tif (!res.writableEnded) {\n\t\t\tres.write(STREAM_HEARTBEAT);\n\t\t}\n\t}, STREAM_HEARTBEAT_INTERVAL_MS);\n\theartbeat.unref();\n\n\tlet stream: AsyncIterable<AssistantMessageEvent>;\n\ttry {\n\t\tstream = streamSimple(resolvedModel, context, streamOptions);\n\t} catch (err) {\n\t\tclearInterval(heartbeat);\n\t\tres.write(encodeErrorEvent(err instanceof Error ? err.message : String(err)));\n\t\tres.end();\n\t\treturn;\n\t}\n\n\t(async () => {\n\t\ttry {\n\t\t\tfor await (const event of stream) {\n\t\t\t\tconst proxyEvent = toProxyEvent(event);\n\t\t\t\tif (proxyEvent) {\n\t\t\t\t\tres.write(encodeProxyEvent(proxyEvent));\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tclearInterval(heartbeat);\n\t\t}\n\n\t\tres.end();\n\t})().catch((err) => {\n\t\tclearInterval(heartbeat);\n\t\tres.write(encodeErrorEvent(err instanceof Error ? err.message : String(err)));\n\t\tres.end();\n\t});\n}\n\nasync function handlePostRequest(\n\tconfig: ServerConfig,\n\tpathname: string,\n\tbody: unknown,\n\tres: ServerResponse,\n): Promise<boolean> {\n\tif (pathname === \"/api/session/init\") {\n\t\thandleSessionInit(config, body as SessionInitBody, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/session/update\") {\n\t\thandleSessionUpdate(config, body as SessionInitBody & { staticContext: SessionStaticContext }, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/session/sync\") {\n\t\thandleSessionSync(config, body as SessionSyncBody, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/session/append\") {\n\t\thandleSessionAppend(config, body as SessionAppendBody, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/session/tree/sync\") {\n\t\thandleSessionTreeSync(config, body as SessionTreeSyncBody, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/session/tree/append\") {\n\t\thandleSessionTreeAppend(config, body as SessionTreeSyncBody, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/session/tree/switch\") {\n\t\thandleSessionTreeSwitch(config, body as SessionTreeSwitchBody, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/session/drop-last-assistant-error\") {\n\t\thandleDropLastAssistantError(config, body as SessionIdBody, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/session/compact\") {\n\t\tawait handleSessionCompact(config, body as SessionCompactBody, res);\n\t\treturn true;\n\t}\n\n\tif (pathname === \"/api/stream\") {\n\t\thandleStream(config, body as StreamRequestBody, res);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nexport function createPiServer(configOverride?: Partial<ServerConfig>): HttpServer {\n\tconst config = loadConfig(configOverride);\n\tloadPersistedSessions(config.sessionStoreDir);\n\n\tconst server = createServer(async (req, res) => {\n\t\tconst url = new URL(req.url ?? \"/\", `http://${config.host}:${config.port}`);\n\n\t\tif (req.method === \"GET\" && url.pathname === \"/health\") {\n\t\t\tsendJson(res, 200, { status: \"ok\" });\n\t\t\treturn;\n\t\t}\n\n\t\tif (!authenticate(config, req)) {\n\t\t\tsendJson(res, 401, { error: \"Unauthorized\" });\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url.pathname === \"/api/sessions\") {\n\t\t\tsendJson(res, 200, { sessions: listSessions() });\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url.pathname.startsWith(\"/api/session/\") && url.pathname.endsWith(\"/history\")) {\n\t\t\tconst encodedSessionId = url.pathname.slice(\"/api/session/\".length, -\"/history\".length);\n\t\t\tconst fromParam = url.searchParams.get(\"from\");\n\t\t\tconst from = fromParam === null ? undefined : Number(fromParam);\n\t\t\tif (from !== undefined && (!Number.isInteger(from) || from < 0)) {\n\t\t\t\tsendJson(res, 400, { error: \"from must be a non-negative integer\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thandleSessionHistory(decodeURIComponent(encodedSessionId), from, res);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"POST\" && url.pathname === CHUNK_ENDPOINT) {\n\t\t\ttry {\n\t\t\t\tconst body = JSON.parse(await readBody(req)) as RequestChunkBody;\n\t\t\t\tconst chunkResult = receiveRequestChunk(body);\n\t\t\t\tif (!chunkResult.complete) {\n\t\t\t\t\tsendJson(res, 200, chunkResult.ack);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait handlePostRequest(config, chunkResult.target, JSON.parse(chunkResult.bodyJson) as unknown, res);\n\t\t\t} catch (err) {\n\t\t\t\tlogRequestError(req, err);\n\t\t\t\tif (!res.headersSent) {\n\t\t\t\t\tsendJson(res, 400, { error: err instanceof Error ? err.message : String(err) });\n\t\t\t\t} else {\n\t\t\t\t\tres.write(encodeErrorEvent(err instanceof Error ? err.message : String(err)));\n\t\t\t\t\tres.end();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"POST\") {\n\t\t\ttry {\n\t\t\t\tconst body = JSON.parse(await readBody(req)) as unknown;\n\t\t\t\tif (await handlePostRequest(config, url.pathname, body, res)) return;\n\t\t\t} catch (err) {\n\t\t\t\tlogRequestError(req, err);\n\t\t\t\tif (!res.headersSent) {\n\t\t\t\t\tsendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n\t\t\t\t} else {\n\t\t\t\t\tres.write(encodeErrorEvent(err instanceof Error ? err.message : String(err)));\n\t\t\t\t\tres.end();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (req.method === \"DELETE\" && url.pathname.startsWith(\"/api/session/\")) {\n\t\t\tconst sessionId = decodeURIComponent(url.pathname.slice(\"/api/session/\".length));\n\t\t\tdeleteSessionFromStore(sessionId);\n\t\t\tdeletePersistedSession(config.sessionStoreDir, sessionId);\n\t\t\tsendJson(res, 200, { deleted: sessionId });\n\t\t\treturn;\n\t\t}\n\n\t\tsendJson(res, 404, { error: \"Not found\" });\n\t});\n\n\treturn server;\n}\n\nfunction toProxyEvent(event: AssistantMessageEvent): ProxyAssistantMessageEvent | undefined {\n\tswitch (event.type) {\n\t\tcase \"start\":\n\t\t\treturn { type: \"start\" };\n\t\tcase \"text_start\":\n\t\t\treturn { type: \"text_start\", contentIndex: event.contentIndex };\n\t\tcase \"text_delta\":\n\t\t\treturn { type: \"text_delta\", contentIndex: event.contentIndex, delta: event.delta };\n\t\tcase \"text_end\":\n\t\t\treturn {\n\t\t\t\ttype: \"text_end\",\n\t\t\t\tcontentIndex: event.contentIndex,\n\t\t\t\tcontentSignature:\n\t\t\t\t\tevent.partial.content[event.contentIndex]?.type === \"text\"\n\t\t\t\t\t\t? (event.partial.content[event.contentIndex] as { textSignature?: string }).textSignature\n\t\t\t\t\t\t: undefined,\n\t\t\t};\n\t\tcase \"thinking_start\":\n\t\t\treturn { type: \"thinking_start\", contentIndex: event.contentIndex };\n\t\tcase \"thinking_delta\":\n\t\t\treturn { type: \"thinking_delta\", contentIndex: event.contentIndex, delta: event.delta };\n\t\tcase \"thinking_end\":\n\t\t\treturn {\n\t\t\t\ttype: \"thinking_end\",\n\t\t\t\tcontentIndex: event.contentIndex,\n\t\t\t\tcontentSignature:\n\t\t\t\t\tevent.partial.content[event.contentIndex]?.type === \"thinking\"\n\t\t\t\t\t\t? (event.partial.content[event.contentIndex] as { thinkingSignature?: string }).thinkingSignature\n\t\t\t\t\t\t: undefined,\n\t\t\t};\n\t\tcase \"toolcall_start\":\n\t\t\treturn {\n\t\t\t\ttype: \"toolcall_start\",\n\t\t\t\tcontentIndex: event.contentIndex,\n\t\t\t\tid:\n\t\t\t\t\tevent.partial.content[event.contentIndex]?.type === \"toolCall\"\n\t\t\t\t\t\t? (event.partial.content[event.contentIndex] as { id: string }).id\n\t\t\t\t\t\t: \"\",\n\t\t\t\ttoolName:\n\t\t\t\t\tevent.partial.content[event.contentIndex]?.type === \"toolCall\"\n\t\t\t\t\t\t? (event.partial.content[event.contentIndex] as { name: string }).name\n\t\t\t\t\t\t: \"\",\n\t\t\t};\n\t\tcase \"toolcall_delta\":\n\t\t\treturn { type: \"toolcall_delta\", contentIndex: event.contentIndex, delta: event.delta };\n\t\tcase \"toolcall_end\":\n\t\t\treturn { type: \"toolcall_end\", contentIndex: event.contentIndex };\n\t\tcase \"done\":\n\t\t\treturn { type: \"done\", reason: event.reason, usage: event.message.usage };\n\t\tcase \"error\":\n\t\t\treturn {\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: event.reason,\n\t\t\t\terrorMessage: event.error.errorMessage,\n\t\t\t\tusage: event.error.usage,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function startServer(configOverride?: Partial<ServerConfig>): HttpServer {\n\tconst config = loadConfig(configOverride);\n\tconst server = createPiServer(configOverride);\n\tserver.listen(config.port, config.host, () => {\n\t\tconsole.log(`pi-server listening on ${config.host}:${config.port}`);\n\t});\n\treturn server;\n}\n"]}
|