@averyyy/pi-server 0.87.1-piclient.2 → 0.99.1-piclient.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.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js.map +1 -1
- package/dist/event-encoding.d.ts.map +1 -1
- package/dist/event-encoding.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/pi-server-protocol.d.ts.map +1 -1
- package/dist/pi-server-protocol.js.map +1 -1
- package/dist/receive-upload.d.ts.map +1 -1
- package/dist/receive-upload.js.map +1 -1
- package/dist/request-chunks.d.ts.map +1 -1
- package/dist/request-chunks.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js.map +1 -1
- package/dist/session-persistence.d.ts.map +1 -1
- package/dist/session-persistence.js.map +1 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js.map +1 -1
- package/package.json +4 -4
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM1C,MAAM,eAAe,GAAG,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM1C,MAAM,eAAe,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAoB,CAAC;AAC7F,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,MAAM,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC;AACxH,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;IACvC,OAAO,CAAC,KAAK,CAAC,mBAAmB,mBAAmB,sCAAsC,CAAC,CAAC;IAC5F,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACtB,CAAC;KAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IAC3D,SAAS,EAAE,CAAC;AACb,CAAC;KAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC;KAAM,CAAC;IACP,WAAW,EAAE,CAAC;AACf,CAAC;AAED,SAAS,SAAS;IACjB,OAAO,CAAC,GAAG,CAAC;;;;;;;8CAOiC,CAAC,CAAC;AAChD,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { createRequire } from \"node:module\";\nimport { startServer } from \"./server.ts\";\n\ninterface PackageMetadata {\n\tversion: string;\n}\n\nconst packageMetadata = createRequire(import.meta.url)(\"../package.json\") as PackageMetadata;\nconst args = process.argv.slice(2);\n\nconst unsupportedArgument = args.find((arg) => arg !== \"--help\" && arg !== \"-h\" && arg !== \"--version\" && arg !== \"-v\");\nif (unsupportedArgument !== undefined) {\n\tconsole.error(`Unknown option \"${unsupportedArgument}\". Use \"pi-server --help\" for usage.`);\n\tprocess.exitCode = 1;\n} else if (args.includes(\"--help\") || args.includes(\"-h\")) {\n\tprintHelp();\n} else if (args.includes(\"--version\") || args.includes(\"-v\")) {\n\tconsole.log(packageMetadata.version);\n} else {\n\tstartServer();\n}\n\nfunction printHelp(): void {\n\tconsole.log(`pi-server - HTTP proxy for pi-client\n\nUsage:\n pi-server\n\nOptions:\n --help, -h Show this help\n --version, -v Show the installed version`);\n}\n"]}
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,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;IACxB,SAAS,EAAE,MAAM,CAAC;CAClB;AAyBD,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAsB1E"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,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;IACxB,SAAS,EAAE,MAAM,CAAC;CAClB;AAyBD,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAsB1E"}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +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,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAU1C,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;IACxD,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC;CACjD,CAAC;AAEF,SAAS,cAAc
|
|
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,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAU1C,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;IACxD,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC;CACjD,CAAC;AAEF,SAAS,cAAc;IACtB,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;AACF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiC;IAC3D,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;QACD,SAAS,EAAE,OAAO,CACjB,SAAS,EAAE,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,UAAU,CAAC,SAAS,IAAI,cAAc,CAAC,SAAS,CAC5G;KACD,CAAC;AACH,CAAC","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\n\nexport interface ServerConfig {\n\thost: string;\n\tport: number;\n\tauthToken: string | undefined;\n\tsessionStoreDir: string;\n\tuploadDir: 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\tuploadDir: join(homedir(), \".pi\", \"upload_files\"),\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\tuploadDir: resolve(\n\t\t\toverrides?.uploadDir ?? process.env.PI_SERVER_UPLOAD_DIR ?? fileConfig.uploadDir ?? DEFAULT_CONFIG.uploadDir,\n\t\t),\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAE9D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;CAYpB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAE3F,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,qBAAa,aAAc,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,iBAAiB,CAAC;IACxC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAE9D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;CAYpB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAE3F,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,qBAAa,aAAc,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,iBAAiB,CAAC;IACxC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAEtC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAMvF;AAED,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,iBAAiB,GAAG,SAAS,GAAG,OAAO,CAM5F;AAED,wBAAgB,mCAAmC,CAAC,IAAI,EAAE,iBAAiB,GAAG,SAAS,GAAG,OAAO,CAQhG"}
|
package/dist/error-codes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-codes.js","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAE9D,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAChC,iBAAiB,EAAE,mBAAmB;IACtC,yBAAyB,EAAE,2BAA2B;IACtD,sBAAsB,EAAE,wBAAwB;IAChD,iBAAiB,EAAE,mBAAmB;IACtC,oBAAoB,EAAE,sBAAsB;IAC5C,eAAe,EAAE,iBAAiB;IAClC,sBAAsB,EAAE,wBAAwB;IAChD,aAAa,EAAE,eAAe;IAC9B,eAAe,EAAE,iBAAiB;IAClC,sBAAsB,EAAE,wBAAwB;IAChD,cAAc,EAAE,gBAAgB;CACvB,CAAC;AAUX,MAAM,OAAO,aAAc,SAAQ,KAAK;IACvB,IAAI,CAAoB;IACxB,OAAO,CAA2B;IAElD,YAAY,OAAe,EAAE,IAAuB,EAAE,OAAiC
|
|
1
|
+
{"version":3,"file":"error-codes.js","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAE9D,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAChC,iBAAiB,EAAE,mBAAmB;IACtC,yBAAyB,EAAE,2BAA2B;IACtD,sBAAsB,EAAE,wBAAwB;IAChD,iBAAiB,EAAE,mBAAmB;IACtC,oBAAoB,EAAE,sBAAsB;IAC5C,eAAe,EAAE,iBAAiB;IAClC,sBAAsB,EAAE,wBAAwB;IAChD,aAAa,EAAE,eAAe;IAC9B,eAAe,EAAE,iBAAiB;IAClC,sBAAsB,EAAE,wBAAwB;IAChD,cAAc,EAAE,gBAAgB;CACvB,CAAC;AAUX,MAAM,OAAO,aAAc,SAAQ,KAAK;IACvB,IAAI,CAAoB;IACxB,OAAO,CAA2B;IAElD,YAAY,OAAe,EAAE,IAAuB,EAAE,OAAiC;QACtF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AAED,MAAM,UAAU,+BAA+B,CAAC,IAAmC;IAClF,OAAO,CACN,IAAI,KAAK,iBAAiB,CAAC,sBAAsB;QACjD,IAAI,KAAK,iBAAiB,CAAC,iBAAiB;QAC5C,IAAI,KAAK,iBAAiB,CAAC,oBAAoB,CAC/C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mCAAmC,CAAC,IAAmC;IACtF,OAAO,CACN,IAAI,KAAK,iBAAiB,CAAC,iBAAiB;QAC5C,IAAI,KAAK,iBAAiB,CAAC,yBAAyB;QACpD,IAAI,KAAK,iBAAiB,CAAC,sBAAsB;QACjD,IAAI,KAAK,iBAAiB,CAAC,iBAAiB;QAC5C,IAAI,KAAK,iBAAiB,CAAC,oBAAoB,CAC/C,CAAC;AACH,CAAC","sourcesContent":["/** Structured error codes for the pi-server HTTP protocol. */\n\nexport const PiServerErrorCode = {\n\tSESSION_NOT_FOUND: \"SESSION_NOT_FOUND\",\n\tSESSION_NO_STATIC_CONTEXT: \"SESSION_NO_STATIC_CONTEXT\",\n\tPARENT_ENTRY_NOT_FOUND: \"PARENT_ENTRY_NOT_FOUND\",\n\tLEAF_ID_NOT_FOUND: \"LEAF_ID_NOT_FOUND\",\n\tENTRY_ALREADY_EXISTS: \"ENTRY_ALREADY_EXISTS\",\n\tINVALID_REQUEST: \"INVALID_REQUEST\",\n\tREQUIRED_FIELD_MISSING: \"REQUIRED_FIELD_MISSING\",\n\tRUN_NOT_FOUND: \"RUN_NOT_FOUND\",\n\tRUN_IN_PROGRESS: \"RUN_IN_PROGRESS\",\n\tSESSION_STATE_CONFLICT: \"SESSION_STATE_CONFLICT\",\n\tINTERNAL_ERROR: \"INTERNAL_ERROR\",\n} as const;\n\nexport type PiServerErrorCode = (typeof PiServerErrorCode)[keyof typeof PiServerErrorCode];\n\nexport interface PiServerErrorResponse {\n\terror: string;\n\tcode?: PiServerErrorCode;\n\tdetails?: Record<string, unknown>;\n}\n\nexport class PiServerError extends Error {\n\tpublic readonly code: PiServerErrorCode;\n\tpublic readonly details?: Record<string, unknown>;\n\n\tconstructor(message: string, code: PiServerErrorCode, details?: Record<string, unknown>) {\n\t\tsuper(message);\n\t\tthis.name = \"PiServerError\";\n\t\tthis.code = code;\n\t\tthis.details = details;\n\t}\n}\n\nexport function isRecoverableTreeDivergenceCode(code: PiServerErrorCode | undefined): boolean {\n\treturn (\n\t\tcode === PiServerErrorCode.PARENT_ENTRY_NOT_FOUND ||\n\t\tcode === PiServerErrorCode.LEAF_ID_NOT_FOUND ||\n\t\tcode === PiServerErrorCode.ENTRY_ALREADY_EXISTS\n\t);\n}\n\nexport function isRecoverableMissingServerStateCode(code: PiServerErrorCode | undefined): boolean {\n\treturn (\n\t\tcode === PiServerErrorCode.SESSION_NOT_FOUND ||\n\t\tcode === PiServerErrorCode.SESSION_NO_STATIC_CONTEXT ||\n\t\tcode === PiServerErrorCode.PARENT_ENTRY_NOT_FOUND ||\n\t\tcode === PiServerErrorCode.LEAF_ID_NOT_FOUND ||\n\t\tcode === PiServerErrorCode.ENTRY_ALREADY_EXISTS\n\t);\n}\n"]}
|
|
@@ -1 +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"
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-encoding.js","sourceRoot":"","sources":["../src/event-encoding.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,KAAiC
|
|
1
|
+
{"version":3,"file":"event-encoding.js","sourceRoot":"","sources":["../src/event-encoding.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,KAAiC;IACjE,OAAO,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC/C,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;AACpP,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC3C,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;AACvD,CAAC","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.map
CHANGED
|
@@ -1 +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,EACN,mCAAmC,EACnC,+BAA+B,EAC/B,aAAa,EACb,iBAAiB,EACjB,KAAK,qBAAqB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EACN,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"
|
|
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,EACN,mCAAmC,EACnC,+BAA+B,EAC/B,aAAa,EACb,iBAAiB,EACjB,KAAK,qBAAqB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EACN,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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi-server-protocol.d.ts","sourceRoot":"","sources":["../src/pi-server-protocol.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;CACf;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,SAAS,GAAG,MAAM,CAW5F;AAeD,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAIjF;AAED,eAAO,MAAM,yBAAyB,QAA0D,CAAC;AAEjG,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAErE;AAED,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAMnF;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAE9E"
|
|
1
|
+
{"version":3,"file":"pi-server-protocol.d.ts","sourceRoot":"","sources":["../src/pi-server-protocol.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;CACf;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,SAAS,GAAG,MAAM,CAW5F;AAeD,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAIjF;AAED,eAAO,MAAM,yBAAyB,QAA0D,CAAC;AAEjG,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAErE;AAED,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAMnF;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAE9E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi-server-protocol.js","sourceRoot":"","sources":["../src/pi-server-protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AASzC,MAAM,UAAU,yBAAyB,CAAC,OAA0C
|
|
1
|
+
{"version":3,"file":"pi-server-protocol.js","sourceRoot":"","sources":["../src/pi-server-protocol.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { SessionTreeEntry } from \"@earendil-works/pi-agent-core\";\nimport type { Message, Tool } from \"@earendil-works/pi-ai\";\n\nexport interface PiServerStaticContext {\n\tsystemPrompt?: string;\n\ttools?: Tool[];\n}\n\nexport function hashPiServerStaticContext(context: PiServerStaticContext | undefined): string {\n\tif (!context) return \"\";\n\tconst canonical = {\n\t\tsystemPrompt: context.systemPrompt,\n\t\ttools: context.tools?.map((tool) => ({\n\t\t\tname: tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tparameters: tool.parameters,\n\t\t})),\n\t};\n\treturn createHash(\"sha256\").update(JSON.stringify(canonical)).digest(\"hex\");\n}\n\nfunction canonicalizeJson(value: unknown): unknown {\n\tif (Array.isArray(value)) return value.map(canonicalizeJson);\n\tif (value && typeof value === \"object\") {\n\t\tconst record = value as Record<string, unknown>;\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(record)\n\t\t\t\t.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))\n\t\t\t\t.map(([key, item]) => [key, canonicalizeJson(item)]),\n\t\t);\n\t}\n\treturn value;\n}\n\nexport function hashPiServerProviderMessages(messages: readonly Message[]): string {\n\treturn createHash(\"sha256\")\n\t\t.update(JSON.stringify(canonicalizeJson(messages)))\n\t\t.digest(\"hex\");\n}\n\nexport const PI_SERVER_EMPTY_TREE_HASH = createHash(\"sha256\").update(\"pi-tree-v1\").digest(\"hex\");\n\nexport function hashPiServerTreeEntry(entry: SessionTreeEntry): string {\n\treturn createHash(\"sha256\").update(JSON.stringify(entry)).digest(\"hex\");\n}\n\nexport function appendPiServerTreeHash(previousHash: string, entryHash: string): string {\n\treturn createHash(\"sha256\").update(`${previousHash}:${entryHash}`).digest(\"hex\");\n}\n\nexport function buildPiServerTreePrefixHashes(entries: SessionTreeEntry[]): string[] {\n\tconst hashes = [PI_SERVER_EMPTY_TREE_HASH];\n\tfor (const entry of entries) {\n\t\thashes.push(appendPiServerTreeHash(hashes[hashes.length - 1], hashPiServerTreeEntry(entry)));\n\t}\n\treturn hashes;\n}\n\nexport function hashPiServerSessionEntries(entries: SessionTreeEntry[]): string {\n\treturn buildPiServerTreePrefixHashes(entries)[entries.length];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receive-upload.d.ts","sourceRoot":"","sources":["../src/receive-upload.ts"],"names":[],"mappings":"AASA,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"receive-upload.d.ts","sourceRoot":"","sources":["../src/receive-upload.ts"],"names":[],"mappings":"AASA,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,MAAM,EAAE,MAAM,CAAC;gBAEH,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI3C;AA8DD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAqB/F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receive-upload.js","sourceRoot":"","sources":["../src/receive-upload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAChG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQ1C,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC5C,MAAM,CAAS;IAEf,YAAY,MAAc,EAAE,OAAe
|
|
1
|
+
{"version":3,"file":"receive-upload.js","sourceRoot":"","sources":["../src/receive-upload.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\ninterface UploadEntry {\n\tpath: string;\n\ttype: \"file\" | \"directory\";\n\tdata?: Buffer;\n}\n\nexport class ReceiveUploadError extends Error {\n\tstatus: number;\n\n\tconstructor(status: number, message: string) {\n\t\tsuper(message);\n\t\tthis.status = status;\n\t}\n}\n\nfunction invalid(message: string): never {\n\tthrow new ReceiveUploadError(400, message);\n}\n\nfunction validatePath(path: unknown): string {\n\tif (typeof path !== \"string\") invalid(\"entry path must be a string\");\n\tif (path.includes(\"\\\\\") || path.includes(\"\\0\")) invalid(`invalid entry path: ${path}`);\n\tif (path !== \"\" && path.split(\"/\").some((part) => part === \"\" || part === \".\" || part === \"..\")) {\n\t\tinvalid(`invalid entry path: ${path}`);\n\t}\n\treturn path;\n}\n\nfunction validateBody(body: unknown): { name: string; entries: UploadEntry[] } {\n\tif (typeof body !== \"object\" || body === null) invalid(\"upload body must be an object\");\n\tconst upload = body as { name?: unknown; entries?: unknown };\n\tif (\n\t\ttypeof upload.name !== \"string\" ||\n\t\tupload.name === \"\" ||\n\t\tupload.name === \".\" ||\n\t\tupload.name === \"..\" ||\n\t\tupload.name.includes(\"/\") ||\n\t\tupload.name.includes(\"\\\\\") ||\n\t\tupload.name.includes(\"\\0\")\n\t) {\n\t\tinvalid(\"invalid upload name\");\n\t}\n\tif (!Array.isArray(upload.entries) || upload.entries.length === 0) invalid(\"entries must be a non-empty array\");\n\n\tconst entries: UploadEntry[] = [];\n\tconst entryTypes = new Map<string, \"file\" | \"directory\">();\n\tfor (const value of upload.entries) {\n\t\tif (typeof value !== \"object\" || value === null) invalid(\"each entry must be an object\");\n\t\tconst entry = value as { path?: unknown; type?: unknown; data?: unknown };\n\t\tconst path = validatePath(entry.path);\n\t\tif (entry.type !== \"file\" && entry.type !== \"directory\") invalid(`invalid entry type: ${String(entry.type)}`);\n\t\tif (entryTypes.has(path)) invalid(`duplicate entry path: ${path}`);\n\t\tentryTypes.set(path, entry.type);\n\t\tif (entry.type === \"directory\") {\n\t\t\tif (entry.data !== undefined) invalid(`directory entry must not contain data: ${path}`);\n\t\t\tentries.push({ path, type: \"directory\" });\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof entry.data !== \"string\") invalid(`file entry data must be base64: ${path}`);\n\t\tconst data = Buffer.from(entry.data, \"base64\");\n\t\tif (data.toString(\"base64\") !== entry.data) invalid(`file entry data must be valid base64: ${path}`);\n\t\tentries.push({ path, type: \"file\", data });\n\t}\n\n\tconst rootType = entryTypes.get(\"\");\n\tif (!rootType) invalid(\"entries must include the root path\");\n\tif (rootType === \"file\" && entries.length !== 1) invalid(\"a file upload cannot contain child entries\");\n\tfor (const entry of entries) {\n\t\tif (entry.path === \"\") continue;\n\t\tconst parent = entry.path.includes(\"/\") ? entry.path.slice(0, entry.path.lastIndexOf(\"/\")) : \"\";\n\t\tif (entryTypes.get(parent) !== \"directory\") invalid(`missing directory entry: ${parent}`);\n\t}\n\treturn { name: upload.name, entries };\n}\n\nexport function receiveUpload(uploadDir: string, body: unknown): { path: string; files: number } {\n\tconst upload = validateBody(body);\n\tmkdirSync(uploadDir, { recursive: true });\n\tconst destination = join(uploadDir, upload.name);\n\tif (existsSync(destination)) throw new ReceiveUploadError(409, `destination already exists: ${destination}`);\n\tconst temporaryDir = mkdtempSync(join(uploadDir, \".upload-\"));\n\tconst temporaryRoot = join(temporaryDir, upload.name);\n\ttry {\n\t\tfor (const entry of upload.entries.filter((entry) => entry.type === \"directory\")) {\n\t\t\tmkdirSync(join(temporaryRoot, entry.path), { recursive: true });\n\t\t}\n\t\tfor (const entry of upload.entries.filter((entry) => entry.type === \"file\")) {\n\t\t\tconst path = join(temporaryRoot, entry.path);\n\t\t\tmkdirSync(dirname(path), { recursive: true });\n\t\t\twriteFileSync(path, entry.data!);\n\t\t}\n\t\trenameSync(temporaryRoot, destination);\n\t} finally {\n\t\trmSync(temporaryDir, { recursive: true, force: true });\n\t}\n\treturn { path: destination, files: upload.entries.filter((entry) => entry.type === \"file\").length };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-chunks.d.ts","sourceRoot":"","sources":["../src/request-chunks.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,uBAAuB,CAAC;AAenD,MAAM,WAAW,gBAAgB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACd;AAwBD,UAAU,QAAQ;IACjB,QAAQ,EAAE,IAAI,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACpB;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;AAMD,eAAO,MAAM,4BAA4B,QAAgB,CAAC;AAC1D,eAAO,MAAM,+BAA+B,QAAqB,CAAC;AAClE,eAAO,MAAM,8BAA8B,QAAY,CAAC;AAExD,UAAU,0BAA0B;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAkED,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,gBAAgB,EACtB,OAAO,GAAE,0BAA+B,GACtC,kBAAkB,GAAG,mBAAmB,CAuF1C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C"
|
|
1
|
+
{"version":3,"file":"request-chunks.d.ts","sourceRoot":"","sources":["../src/request-chunks.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,uBAAuB,CAAC;AAenD,MAAM,WAAW,gBAAgB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACd;AAwBD,UAAU,QAAQ;IACjB,QAAQ,EAAE,IAAI,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACpB;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;AAMD,eAAO,MAAM,4BAA4B,QAAgB,CAAC;AAC1D,eAAO,MAAM,+BAA+B,QAAqB,CAAC;AAClE,eAAO,MAAM,8BAA8B,QAAY,CAAC;AAExD,UAAU,0BAA0B;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAkED,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,gBAAgB,EACtB,OAAO,GAAE,0BAA+B,GACtC,kBAAkB,GAAG,mBAAmB,CAuF1C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C"}
|
|
@@ -1 +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\";\nimport { createHash } from \"node:crypto\";\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/compact\",\n\t\"/api/stream\",\n\t\"/api/receive\",\n]);\n\nexport interface RequestChunkBody {\n\trequestId: string;\n\ttarget: string;\n\tchunkIndex: number;\n\ttotalChunks: number;\n\tsha256: string;\n\tchunk: string;\n}\n\ninterface RequestChunk {\n\tchunk: string;\n\tsha256: string;\n}\n\ninterface PendingRequest {\n\ttarget: string;\n\ttotalChunks: number;\n\tchunks: Map<number, RequestChunk>;\n\treceivedBytes: number;\n\tupdatedAtMs: number;\n}\n\ninterface CompletedRequest {\n\ttarget: string;\n\ttotalChunks: number;\n\tbodyJson: string;\n\tcompletedChunkIndex: number;\n\tcompletedAtMs: number;\n\tchunks: Map<number, RequestChunk>;\n}\n\ninterface ChunkAck {\n\treceived: true;\n\trequestId: string;\n\tchunkIndex: number;\n\ttotalChunks: 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>();\nconst completedRequests = new Map<string, CompletedRequest>();\nlet pendingRequestBytes = 0;\n\nexport const REQUEST_CHUNK_PENDING_TTL_MS = 5 * 60 * 1000;\nexport const REQUEST_CHUNK_MAX_PENDING_BYTES = 1024 * 1024 * 1024;\nexport const REQUEST_CHUNK_COMPLETED_TTL_MS = 60 * 1000;\n\ninterface ReceiveRequestChunkOptions {\n\tnowMs?: number;\n\tpendingTtlMs?: number;\n\tmaxPendingBytes?: number;\n\tcompletedTtlMs?: number;\n}\n\nfunction sha256(value: string): string {\n\treturn createHash(\"sha256\").update(value).digest(\"hex\");\n}\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.totalChunks) || body.totalChunks <= 0) {\n\t\tthrow new Error(\"totalChunks must be a positive integer\");\n\t}\n\tif (!Number.isInteger(body.chunkIndex) || body.chunkIndex < 0 || body.chunkIndex >= body.totalChunks) {\n\t\tthrow new Error(\"chunkIndex 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\tif (typeof body.sha256 !== \"string\" || !/^[a-f0-9]{64}$/i.test(body.sha256)) {\n\t\tthrow new Error(\"sha256 must be a 64-character hex string\");\n\t}\n\tif (sha256(body.chunk) !== body.sha256) {\n\t\tthrow new Error(`Chunk checksum mismatch: ${body.chunkIndex}`);\n\t}\n}\n\nfunction makeAck(body: RequestChunkBody): PendingChunkResult {\n\treturn {\n\t\tcomplete: false,\n\t\tack: {\n\t\t\treceived: true,\n\t\t\trequestId: body.requestId,\n\t\t\tchunkIndex: body.chunkIndex,\n\t\t\ttotalChunks: body.totalChunks,\n\t\t},\n\t};\n}\n\nfunction deletePendingRequest(requestId: string, pending: PendingRequest): void {\n\tpendingRequests.delete(requestId);\n\tpendingRequestBytes -= pending.receivedBytes;\n}\n\nfunction cleanupExpiredRequests(nowMs: number, pendingTtlMs: number, completedTtlMs: number): void {\n\tfor (const [requestId, pending] of pendingRequests) {\n\t\tif (nowMs - pending.updatedAtMs > pendingTtlMs) {\n\t\t\tdeletePendingRequest(requestId, pending);\n\t\t}\n\t}\n\tfor (const [requestId, completed] of completedRequests) {\n\t\tif (nowMs - completed.completedAtMs > completedTtlMs) {\n\t\t\tcompletedRequests.delete(requestId);\n\t\t}\n\t}\n}\n\nfunction cleanupForPendingBytes(extraBytes: number, maxPendingBytes: number, protectedRequestId: string): void {\n\tfor (const [requestId, pending] of pendingRequests) {\n\t\tif (pendingRequestBytes + extraBytes <= maxPendingBytes) return;\n\t\tif (requestId !== protectedRequestId) {\n\t\t\tdeletePendingRequest(requestId, pending);\n\t\t}\n\t}\n\tif (pendingRequestBytes + extraBytes > maxPendingBytes) {\n\t\tthrow new Error(\"Request chunk pending bytes limit exceeded\");\n\t}\n}\n\nexport function receiveRequestChunk(\n\tbody: RequestChunkBody,\n\toptions: ReceiveRequestChunkOptions = {},\n): PendingChunkResult | CompleteChunkResult {\n\tassertValidChunk(body);\n\n\tconst nowMs = options.nowMs ?? Date.now();\n\tcleanupExpiredRequests(\n\t\tnowMs,\n\t\toptions.pendingTtlMs ?? REQUEST_CHUNK_PENDING_TTL_MS,\n\t\toptions.completedTtlMs ?? REQUEST_CHUNK_COMPLETED_TTL_MS,\n\t);\n\n\tconst completed = completedRequests.get(body.requestId);\n\tif (completed) {\n\t\tif (completed.target !== body.target || completed.totalChunks !== body.totalChunks) {\n\t\t\tthrow new Error(\"Chunk metadata does not match the completed request\");\n\t\t}\n\t\tconst existing = completed.chunks.get(body.chunkIndex);\n\t\tif (!existing || existing.chunk !== body.chunk || existing.sha256 !== body.sha256) {\n\t\t\tthrow new Error(`Duplicate chunk index does not match: ${body.chunkIndex}`);\n\t\t}\n\t\tif (body.chunkIndex === completed.completedChunkIndex) {\n\t\t\treturn {\n\t\t\t\tcomplete: true,\n\t\t\t\ttarget: completed.target,\n\t\t\t\tbodyJson: completed.bodyJson,\n\t\t\t};\n\t\t}\n\t\treturn makeAck(body);\n\t}\n\n\tlet pending = pendingRequests.get(body.requestId);\n\tif (pending) {\n\t\tif (pending.target !== body.target || pending.totalChunks !== body.totalChunks) {\n\t\t\tthrow new Error(\"Chunk metadata does not match the pending request\");\n\t\t}\n\t\tconst existing = pending.chunks.get(body.chunkIndex);\n\t\tif (existing) {\n\t\t\tif (existing.chunk !== body.chunk || existing.sha256 !== body.sha256) {\n\t\t\t\tthrow new Error(`Duplicate chunk index does not match: ${body.chunkIndex}`);\n\t\t\t}\n\t\t\tpending.updatedAtMs = nowMs;\n\t\t\treturn makeAck(body);\n\t\t}\n\t}\n\n\tconst chunkBytes = Buffer.byteLength(body.chunk, \"utf-8\");\n\tcleanupForPendingBytes(chunkBytes, options.maxPendingBytes ?? REQUEST_CHUNK_MAX_PENDING_BYTES, body.requestId);\n\tif (!pending) {\n\t\tpending = {\n\t\t\ttarget: body.target,\n\t\t\ttotalChunks: body.totalChunks,\n\t\t\tchunks: new Map(),\n\t\t\treceivedBytes: 0,\n\t\t\tupdatedAtMs: nowMs,\n\t\t};\n\t\tpendingRequests.set(body.requestId, pending);\n\t}\n\tpending.chunks.set(body.chunkIndex, { chunk: body.chunk, sha256: body.sha256 });\n\tpending.receivedBytes += chunkBytes;\n\tpending.updatedAtMs = nowMs;\n\tpendingRequestBytes += chunkBytes;\n\n\tif (pending.chunks.size !== pending.totalChunks) {\n\t\treturn makeAck(body);\n\t}\n\n\tconst encodedChunks: string[] = [];\n\tfor (let index = 0; index < pending.totalChunks; 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.chunk);\n\t}\n\n\tdeletePendingRequest(body.requestId, pending);\n\tconst bodyJson = Buffer.from(encodedChunks.join(\"\"), \"base64\").toString(\"utf-8\");\n\tcompletedRequests.set(body.requestId, {\n\t\ttarget: pending.target,\n\t\ttotalChunks: pending.totalChunks,\n\t\tbodyJson,\n\t\tcompletedChunkIndex: body.chunkIndex,\n\t\tcompletedAtMs: nowMs,\n\t\tchunks: pending.chunks,\n\t});\n\treturn {\n\t\tcomplete: true,\n\t\ttarget: pending.target,\n\t\tbodyJson,\n\t};\n}\n\nexport function clearAllRequestChunks(): void {\n\tpendingRequests.clear();\n\tcompletedRequests.clear();\n\tpendingRequestBytes = 0;\n}\n"]}
|
|
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\";\nimport { createHash } from \"node:crypto\";\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/compact\",\n\t\"/api/stream\",\n\t\"/api/receive\",\n]);\n\nexport interface RequestChunkBody {\n\trequestId: string;\n\ttarget: string;\n\tchunkIndex: number;\n\ttotalChunks: number;\n\tsha256: string;\n\tchunk: string;\n}\n\ninterface RequestChunk {\n\tchunk: string;\n\tsha256: string;\n}\n\ninterface PendingRequest {\n\ttarget: string;\n\ttotalChunks: number;\n\tchunks: Map<number, RequestChunk>;\n\treceivedBytes: number;\n\tupdatedAtMs: number;\n}\n\ninterface CompletedRequest {\n\ttarget: string;\n\ttotalChunks: number;\n\tbodyJson: string;\n\tcompletedChunkIndex: number;\n\tcompletedAtMs: number;\n\tchunks: Map<number, RequestChunk>;\n}\n\ninterface ChunkAck {\n\treceived: true;\n\trequestId: string;\n\tchunkIndex: number;\n\ttotalChunks: 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>();\nconst completedRequests = new Map<string, CompletedRequest>();\nlet pendingRequestBytes = 0;\n\nexport const REQUEST_CHUNK_PENDING_TTL_MS = 5 * 60 * 1000;\nexport const REQUEST_CHUNK_MAX_PENDING_BYTES = 1024 * 1024 * 1024;\nexport const REQUEST_CHUNK_COMPLETED_TTL_MS = 60 * 1000;\n\ninterface ReceiveRequestChunkOptions {\n\tnowMs?: number;\n\tpendingTtlMs?: number;\n\tmaxPendingBytes?: number;\n\tcompletedTtlMs?: number;\n}\n\nfunction sha256(value: string): string {\n\treturn createHash(\"sha256\").update(value).digest(\"hex\");\n}\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.totalChunks) || body.totalChunks <= 0) {\n\t\tthrow new Error(\"totalChunks must be a positive integer\");\n\t}\n\tif (!Number.isInteger(body.chunkIndex) || body.chunkIndex < 0 || body.chunkIndex >= body.totalChunks) {\n\t\tthrow new Error(\"chunkIndex 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\tif (typeof body.sha256 !== \"string\" || !/^[a-f0-9]{64}$/i.test(body.sha256)) {\n\t\tthrow new Error(\"sha256 must be a 64-character hex string\");\n\t}\n\tif (sha256(body.chunk) !== body.sha256) {\n\t\tthrow new Error(`Chunk checksum mismatch: ${body.chunkIndex}`);\n\t}\n}\n\nfunction makeAck(body: RequestChunkBody): PendingChunkResult {\n\treturn {\n\t\tcomplete: false,\n\t\tack: {\n\t\t\treceived: true,\n\t\t\trequestId: body.requestId,\n\t\t\tchunkIndex: body.chunkIndex,\n\t\t\ttotalChunks: body.totalChunks,\n\t\t},\n\t};\n}\n\nfunction deletePendingRequest(requestId: string, pending: PendingRequest): void {\n\tpendingRequests.delete(requestId);\n\tpendingRequestBytes -= pending.receivedBytes;\n}\n\nfunction cleanupExpiredRequests(nowMs: number, pendingTtlMs: number, completedTtlMs: number): void {\n\tfor (const [requestId, pending] of pendingRequests) {\n\t\tif (nowMs - pending.updatedAtMs > pendingTtlMs) {\n\t\t\tdeletePendingRequest(requestId, pending);\n\t\t}\n\t}\n\tfor (const [requestId, completed] of completedRequests) {\n\t\tif (nowMs - completed.completedAtMs > completedTtlMs) {\n\t\t\tcompletedRequests.delete(requestId);\n\t\t}\n\t}\n}\n\nfunction cleanupForPendingBytes(extraBytes: number, maxPendingBytes: number, protectedRequestId: string): void {\n\tfor (const [requestId, pending] of pendingRequests) {\n\t\tif (pendingRequestBytes + extraBytes <= maxPendingBytes) return;\n\t\tif (requestId !== protectedRequestId) {\n\t\t\tdeletePendingRequest(requestId, pending);\n\t\t}\n\t}\n\tif (pendingRequestBytes + extraBytes > maxPendingBytes) {\n\t\tthrow new Error(\"Request chunk pending bytes limit exceeded\");\n\t}\n}\n\nexport function receiveRequestChunk(\n\tbody: RequestChunkBody,\n\toptions: ReceiveRequestChunkOptions = {},\n): PendingChunkResult | CompleteChunkResult {\n\tassertValidChunk(body);\n\n\tconst nowMs = options.nowMs ?? Date.now();\n\tcleanupExpiredRequests(\n\t\tnowMs,\n\t\toptions.pendingTtlMs ?? REQUEST_CHUNK_PENDING_TTL_MS,\n\t\toptions.completedTtlMs ?? REQUEST_CHUNK_COMPLETED_TTL_MS,\n\t);\n\n\tconst completed = completedRequests.get(body.requestId);\n\tif (completed) {\n\t\tif (completed.target !== body.target || completed.totalChunks !== body.totalChunks) {\n\t\t\tthrow new Error(\"Chunk metadata does not match the completed request\");\n\t\t}\n\t\tconst existing = completed.chunks.get(body.chunkIndex);\n\t\tif (!existing || existing.chunk !== body.chunk || existing.sha256 !== body.sha256) {\n\t\t\tthrow new Error(`Duplicate chunk index does not match: ${body.chunkIndex}`);\n\t\t}\n\t\tif (body.chunkIndex === completed.completedChunkIndex) {\n\t\t\treturn {\n\t\t\t\tcomplete: true,\n\t\t\t\ttarget: completed.target,\n\t\t\t\tbodyJson: completed.bodyJson,\n\t\t\t};\n\t\t}\n\t\treturn makeAck(body);\n\t}\n\n\tlet pending = pendingRequests.get(body.requestId);\n\tif (pending) {\n\t\tif (pending.target !== body.target || pending.totalChunks !== body.totalChunks) {\n\t\t\tthrow new Error(\"Chunk metadata does not match the pending request\");\n\t\t}\n\t\tconst existing = pending.chunks.get(body.chunkIndex);\n\t\tif (existing) {\n\t\t\tif (existing.chunk !== body.chunk || existing.sha256 !== body.sha256) {\n\t\t\t\tthrow new Error(`Duplicate chunk index does not match: ${body.chunkIndex}`);\n\t\t\t}\n\t\t\tpending.updatedAtMs = nowMs;\n\t\t\treturn makeAck(body);\n\t\t}\n\t}\n\n\tconst chunkBytes = Buffer.byteLength(body.chunk, \"utf-8\");\n\tcleanupForPendingBytes(chunkBytes, options.maxPendingBytes ?? REQUEST_CHUNK_MAX_PENDING_BYTES, body.requestId);\n\tif (!pending) {\n\t\tpending = {\n\t\t\ttarget: body.target,\n\t\t\ttotalChunks: body.totalChunks,\n\t\t\tchunks: new Map(),\n\t\t\treceivedBytes: 0,\n\t\t\tupdatedAtMs: nowMs,\n\t\t};\n\t\tpendingRequests.set(body.requestId, pending);\n\t}\n\tpending.chunks.set(body.chunkIndex, { chunk: body.chunk, sha256: body.sha256 });\n\tpending.receivedBytes += chunkBytes;\n\tpending.updatedAtMs = nowMs;\n\tpendingRequestBytes += chunkBytes;\n\n\tif (pending.chunks.size !== pending.totalChunks) {\n\t\treturn makeAck(body);\n\t}\n\n\tconst encodedChunks: string[] = [];\n\tfor (let index = 0; index < pending.totalChunks; 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.chunk);\n\t}\n\n\tdeletePendingRequest(body.requestId, pending);\n\tconst bodyJson = Buffer.from(encodedChunks.join(\"\"), \"base64\").toString(\"utf-8\");\n\tcompletedRequests.set(body.requestId, {\n\t\ttarget: pending.target,\n\t\ttotalChunks: pending.totalChunks,\n\t\tbodyJson,\n\t\tcompletedChunkIndex: body.chunkIndex,\n\t\tcompletedAtMs: nowMs,\n\t\tchunks: pending.chunks,\n\t});\n\treturn {\n\t\tcomplete: true,\n\t\ttarget: pending.target,\n\t\tbodyJson,\n\t};\n}\n\nexport function clearAllRequestChunks(): void {\n\tpendingRequests.clear();\n\tcompletedRequests.clear();\n\tpendingRequestBytes = 0;\n}\n"]}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +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;AAc/G,OAAO,EAGN,KAAK,OAAO,EAGZ,KAAK,OAAO,EACZ,KAAK,KAAK,EAEV,KAAK,mBAAmB,EACxB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQhD,OAAO,EAWN,KAAK,YAAY,EACjB,KAAK,oBAAoB,EAGzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAc5D,UAAU,iBAAiB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;CAC3B;AA67BD,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,gBAAgB,GAAG,mBAAmB,CAAC,GACnE,OAAO,CAOT;AAoMD,wBAAgB,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,UAAU,CAoIjF;AAqED,wBAAgB,WAAW,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,UAAU,CAU9E","sourcesContent":["import { createServer, type Server as HttpServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createRequire } from \"node:module\";\nimport {\n\ttype CompactionCacheRequestOptions,\n\ttype CompactionPreparationOptions,\n\ttype CompactionSettings,\n\tcompactLegacy,\n\tconvertToLlm,\n\tDEFAULT_COMPACTION_SETTINGS,\n\ttype LegacyCompactResult,\n\ttype ProxyAssistantMessageEvent,\n\tprepareLegacyCompaction,\n\ttype SessionTreeEntry,\n} from \"@earendil-works/pi-agent-core\";\nimport {\n\ttype AssistantMessage,\n\ttype AssistantMessageEvent,\n\ttype Context,\n\tcreateModels,\n\tcreateProvider,\n\ttype Message,\n\ttype Model,\n\ttype ProviderStreams,\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 { PiServerError, PiServerErrorCode, type PiServerErrorResponse } from \"./error-codes.ts\";\nimport { encodeErrorEvent, encodeProxyEvent } from \"./event-encoding.ts\";\nimport { hashPiServerProviderMessages } from \"./pi-server-protocol.ts\";\nimport { ReceiveUploadError, receiveUpload } from \"./receive-upload.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\tgetOrCreateSession,\n\tgetSession,\n\tgetSessionBranch,\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 PackageMetadata {\n\tversion: string;\n}\n\nconst packageMetadata = createRequire(import.meta.url)(\"../package.json\") as PackageMetadata;\nconst PI_SERVER_VERSION = packageMetadata.version;\n\ninterface SessionInitBody {\n\tsessionId: string;\n\tstaticContext?: SessionStaticContext;\n}\n\ninterface StreamRequestBody {\n\tsessionId: string;\n\trunId?: string;\n\tmodel: Model<any>;\n\toptions?: SimpleStreamOptions;\n\tstaticContext?: SessionStaticContext;\n\tephemeralMessages?: Message[];\n\tcontextOverlay?: Message[];\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\trunId?: string;\n\tmodel: Model<any>;\n\toptions?: SimpleStreamOptions;\n\tsettings?: CompactionSettings;\n\tpreparation?: CompactionPreparationOptions;\n\tcustomInstructions?: string;\n\tcacheContextHash?: string;\n\tcacheStaticContextHash?: string;\n\tcacheFallbackReason?: string;\n\tbaseTreeHash?: string;\n\tfullResponse?: boolean;\n\tstreamResponse?: boolean;\n}\n\nfunction createRequestModels(model: Model<any>, options: SimpleStreamOptions) {\n\tconst models = createModels();\n\tconst requestStream: ProviderStreams[\"streamSimple\"] = (requestModel, context, streamOptions) =>\n\t\tstreamSimple(requestModel, context, {\n\t\t\t...streamOptions,\n\t\t\t...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n\t\t});\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: requestStream,\n\t\t\t\tstreamSimple: requestStream,\n\t\t\t},\n\t\t}),\n\t);\n\treturn models;\n}\n\ninterface StreamRunRecord {\n\tsessionId: string;\n\trunId: string;\n\tkind: \"stream\" | \"compact\";\n\tstatus: \"running\" | \"completed\" | \"failed\" | \"aborted\";\n\tmessage?: AssistantMessage;\n\tcompactResult?: SessionCompactSuccessBody;\n\terrorMessage?: string;\n\tcreatedAt: number;\n\tupdatedAt: number;\n\tcancelRequested: boolean;\n\tcontroller: AbortController;\n\tsettled: Promise<void>;\n\tresolveSettled: (() => void) | undefined;\n}\n\nconst STREAM_HEARTBEAT = \": keep-alive\\n\\n\";\nconst JSON_HEARTBEAT = \" \\n\";\nconst STREAM_HEARTBEAT_INTERVAL_MS = 25_000;\nconst STREAM_RUN_TTL_MS = 60 * 60 * 1000; // 1 hour\nconst streamRuns = new Map<string, StreamRunRecord>();\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 sendError(\n\tres: ServerResponse,\n\tstatus: number,\n\tmessage: string,\n\tcode?: PiServerErrorCode,\n\tdetails?: Record<string, unknown>,\n): void {\n\tconst body: PiServerErrorResponse = { error: message };\n\tif (code) body.code = code;\n\tif (details) body.details = details;\n\tsendJson(res, status, body);\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\nfunction sessionResponseBody(session: SessionState) {\n\treturn {\n\t\tsessionId: session.sessionId,\n\t\tstaticContextHash: session.staticContextHash,\n\t\ttreeHash: session.treeHash,\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\nfunction runKey(sessionId: string, runId: string): string {\n\treturn `${sessionId}\\0${runId}`;\n}\n\nfunction getStreamRun(sessionId: string, runId: string): StreamRunRecord | undefined {\n\treturn streamRuns.get(runKey(sessionId, runId));\n}\n\nfunction createRunRecord(sessionId: string, runId: string, kind: StreamRunRecord[\"kind\"]): StreamRunRecord {\n\tlet resolveSettled: (() => void) | undefined;\n\tconst settled = new Promise<void>((resolve) => {\n\t\tresolveSettled = resolve;\n\t});\n\treturn {\n\t\tsessionId,\n\t\trunId,\n\t\tkind,\n\t\tstatus: \"running\",\n\t\tcreatedAt: Date.now(),\n\t\tupdatedAt: Date.now(),\n\t\tcancelRequested: false,\n\t\tcontroller: new AbortController(),\n\t\tsettled,\n\t\tresolveSettled,\n\t};\n}\n\nfunction settleRun(run: StreamRunRecord | undefined): void {\n\tif (!run) return;\n\tconst resolve = run.resolveSettled;\n\trun.resolveSettled = undefined;\n\tresolve?.();\n}\n\nfunction startStreamRun(sessionId: string, runId: string, kind: StreamRunRecord[\"kind\"] = \"stream\"): StreamRunRecord {\n\tconst existing = getStreamRun(sessionId, runId);\n\tif (existing?.status === \"completed\" || existing?.status === \"aborted\") return existing;\n\tconst run = existing ?? createRunRecord(sessionId, runId, kind);\n\tif (existing) {\n\t\trun.message = undefined;\n\t\trun.errorMessage = undefined;\n\t\trun.cancelRequested = false;\n\t\trun.controller = new AbortController();\n\t\tlet resolveSettled: (() => void) | undefined;\n\t\trun.settled = new Promise<void>((resolve) => {\n\t\t\tresolveSettled = resolve;\n\t\t});\n\t\trun.resolveSettled = resolveSettled;\n\t}\n\trun.kind = kind;\n\trun.status = \"running\";\n\trun.updatedAt = Date.now();\n\tstreamRuns.set(runKey(sessionId, runId), run);\n\treturn run;\n}\n\nfunction streamRunResponseBody(run: StreamRunRecord) {\n\treturn {\n\t\tsessionId: run.sessionId,\n\t\trunId: run.runId,\n\t\tstatus: run.status,\n\t\tcreatedAt: run.createdAt,\n\t\tupdatedAt: run.updatedAt,\n\t\t...(run.message ? { message: run.message } : {}),\n\t\t...(run.compactResult ? { compactResult: run.compactResult } : {}),\n\t\t...(run.errorMessage ? { errorMessage: run.errorMessage } : {}),\n\t};\n}\n\nfunction replayStreamRun(run: StreamRunRecord): ProxyAssistantMessageEvent[] {\n\tif (!run.message) return [];\n\tif (\n\t\trun.message.stopReason !== \"stop\" &&\n\t\trun.message.stopReason !== \"length\" &&\n\t\trun.message.stopReason !== \"toolUse\" &&\n\t\trun.message.stopReason !== \"deferred\"\n\t) {\n\t\treturn [];\n\t}\n\tconst events: ProxyAssistantMessageEvent[] = [{ type: \"start\" }];\n\tfor (const [contentIndex, content] of run.message.content.entries()) {\n\t\tswitch (content.type) {\n\t\t\tcase \"text\":\n\t\t\t\tevents.push(\n\t\t\t\t\t{ type: \"text_start\", contentIndex },\n\t\t\t\t\t{ type: \"text_delta\", contentIndex, delta: content.text },\n\t\t\t\t\t{ type: \"text_end\", contentIndex, contentSignature: content.textSignature },\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"thinking\":\n\t\t\t\tevents.push(\n\t\t\t\t\t{ type: \"thinking_start\", contentIndex },\n\t\t\t\t\t{ type: \"thinking_delta\", contentIndex, delta: content.thinking },\n\t\t\t\t\t{ type: \"thinking_end\", contentIndex, contentSignature: content.thinkingSignature },\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"toolCall\":\n\t\t\t\tevents.push(\n\t\t\t\t\t{ type: \"toolcall_start\", contentIndex, id: content.id, toolName: content.name },\n\t\t\t\t\t{ type: \"toolcall_delta\", contentIndex, delta: JSON.stringify(content.arguments) },\n\t\t\t\t\t{ type: \"toolcall_end\", contentIndex, toolCall: content },\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tevents.push({\n\t\ttype: \"done\",\n\t\treason: run.message.stopReason,\n\t\tusage: run.message.usage,\n\t\tdeferred: run.message.deferred,\n\t\tmessage: run.message,\n\t});\n\treturn events;\n}\n\nfunction completeStreamRun(run: StreamRunRecord | undefined, message: AssistantMessage): void {\n\tif (!run) return;\n\tif (run.cancelRequested) {\n\t\trun.status = \"aborted\";\n\t\trun.message = undefined;\n\t\trun.errorMessage = \"Stream run aborted\";\n\t\trun.updatedAt = Date.now();\n\t\treturn;\n\t}\n\trun.status = \"completed\";\n\trun.message = message;\n\trun.errorMessage = undefined;\n\trun.updatedAt = Date.now();\n}\n\nfunction failStreamRun(run: StreamRunRecord | undefined, errorMessage: string): void {\n\tif (!run) return;\n\tif (run.cancelRequested) {\n\t\trun.status = \"aborted\";\n\t\trun.errorMessage = \"Stream run aborted\";\n\t\trun.updatedAt = Date.now();\n\t\treturn;\n\t}\n\trun.status = \"failed\";\n\trun.errorMessage = errorMessage;\n\trun.updatedAt = Date.now();\n}\n\nfunction touchStreamRun(run: StreamRunRecord | undefined): void {\n\tif (run) run.updatedAt = Date.now();\n}\n\nfunction writeStreamEvent(res: ServerResponse, event: ProxyAssistantMessageEvent): void {\n\tif (!res.writableEnded && !res.destroyed) {\n\t\tres.write(encodeProxyEvent(event));\n\t}\n}\n\nfunction writeStreamError(res: ServerResponse, message: string): void {\n\tif (!res.writableEnded && !res.destroyed) {\n\t\tres.write(encodeErrorEvent(message));\n\t}\n}\n\nfunction endStreamResponse(res: ServerResponse): void {\n\tif (!res.writableEnded && !res.destroyed) {\n\t\tres.end();\n\t}\n}\n\nfunction cleanupExpiredStreamRuns(nowMs: number): void {\n\tfor (const [key, run] of streamRuns) {\n\t\tif (nowMs - run.updatedAt > STREAM_RUN_TTL_MS) {\n\t\t\tstreamRuns.delete(key);\n\t\t}\n\t}\n}\n\nfunction deleteStreamRunsForSession(sessionId: string): void {\n\tfor (const [key, run] of streamRuns) {\n\t\tif (run.sessionId === sessionId) streamRuns.delete(key);\n\t}\n}\n\nconst streamRunCleanupTimer = setInterval(() => cleanupExpiredStreamRuns(Date.now()), STREAM_RUN_TTL_MS / 4);\nstreamRunCleanupTimer.unref();\n\nfunction sessionHistoryFullResponseBody(session: SessionState, baseMessageCount: number) {\n\treturn {\n\t\tsessionId: session.sessionId,\n\t\tstaticContext: session.staticContext,\n\t\tstaticContextHash: session.staticContextHash,\n\t\ttreeHash: session.treeHash,\n\t\tmessageCount: session.messages.length,\n\t\tentryCount: session.entries.length,\n\t\tleafId: session.leafId,\n\t\trevision: session.revision,\n\t\tentries: session.entries,\n\t\tbaseMessageCount,\n\t\tmessages: session.messages.slice(baseMessageCount),\n\t};\n}\n\nfunction sessionTreePatchResponseBody(\n\tsession: SessionState,\n\tbaseMessageCount: number,\n\tentriesFrom: number,\n\tbaseRevision: number | undefined,\n) {\n\treturn {\n\t\tsessionId: session.sessionId,\n\t\tstaticContext: session.staticContext,\n\t\tstaticContextHash: session.staticContextHash,\n\t\ttreeHash: session.treeHash,\n\t\tmessageCount: session.messages.length,\n\t\tentryCount: session.entries.length,\n\t\tleafId: session.leafId,\n\t\trevision: session.revision,\n\t\tbaseMessageCount,\n\t\tmessages: session.messages.slice(baseMessageCount),\n\t\ttreePatch: {\n\t\t\tentriesFrom,\n\t\t\tbaseRevision,\n\t\t\tentries: session.entries.slice(entriesFrom),\n\t\t\tleafId: session.leafId,\n\t\t\trevision: session.revision,\n\t\t},\n\t};\n}\n\ntype PreparedCompaction = Parameters<typeof compactLegacy>[0];\n\ninterface PreparedSessionCompact {\n\tsession: SessionState;\n\tsessionId: string;\n\trevision: number;\n\ttreeHash: string;\n\tleafId: string | null;\n\tentryCount: number;\n\tpreparation: PreparedCompaction;\n\toptions: SimpleStreamOptions;\n\tcacheContext?: Context;\n\tcacheFallbackReason: string;\n}\n\ninterface SessionCompactSuccessBody {\n\tsuccess: true;\n\tcompaction: Omit<LegacyCompactResult, \"retainedTail\">;\n\tcompactionEntry: SessionTreeEntry;\n\tsessionId: string;\n\tstaticContextHash: string;\n\ttreeHash: string;\n\tmessageCount: number;\n\tentryCount: number;\n\tleafId: string | null;\n\trevision: number;\n\tstaticContext?: SessionStaticContext;\n\ttreePatch?: {\n\t\tbaseTreeHash: string;\n\t\tentriesFrom: number;\n\t\tentries: SessionTreeEntry[];\n\t\tleafId: string | null;\n\t\trevision: number;\n\t};\n\tentries?: SessionTreeEntry[];\n\tmessages?: Message[];\n}\n\ninterface SessionCompactHttpResponse {\n\tstatus: number;\n\tbody: PiServerErrorResponse | SessionCompactSuccessBody;\n}\n\nfunction handleSessionInit(config: ServerConfig, body: SessionInitBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendError(res, 400, \"sessionId is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\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\tsendError(res, 400, \"sessionId is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\tif (!body.staticContext) {\n\t\tsendError(res, 400, \"staticContext is required for update\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\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\tsendError(res, 400, \"sessionId is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\tif (!Array.isArray(body.messages)) {\n\t\tsendError(res, 400, \"messages is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\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\tsendError(res, 400, \"sessionId is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\tif (!Array.isArray(body.messages)) {\n\t\tsendError(res, 400, \"messages is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\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\tsendError(res, 400, \"sessionId is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\tif (!Array.isArray(body.entries)) {\n\t\tsendError(res, 400, \"entries is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\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\tsendError(res, 400, \"sessionId is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\tif (!Array.isArray(body.entries)) {\n\t\tsendError(res, 400, \"entries is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\ttry {\n\t\tif (body.staticContext) {\n\t\t\tsetStaticContext(body.sessionId, body.staticContext);\n\t\t}\n\t\tconst session = appendSessionEntries(body.sessionId, body.entries, body.leafId ?? null);\n\t\tpersistSession(config, session);\n\t\tsendJson(res, 200, sessionResponseBody(session));\n\t} catch (error) {\n\t\tif (error instanceof PiServerError) {\n\t\t\tsendError(res, 400, error.message, error.code, error.details);\n\t\t} else {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tsendError(res, 500, message, PiServerErrorCode.INTERNAL_ERROR);\n\t\t}\n\t}\n}\n\nfunction handleSessionTreeSwitch(config: ServerConfig, body: SessionTreeSwitchBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendError(res, 400, \"sessionId is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\ttry {\n\t\tconst session = switchSessionLeaf(body.sessionId, body.leafId ?? null);\n\t\tpersistSession(config, session);\n\t\tsendJson(res, 200, sessionResponseBody(session));\n\t} catch (error) {\n\t\tif (error instanceof PiServerError) {\n\t\t\tsendError(res, 400, error.message, error.code, error.details);\n\t\t} else {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tsendError(res, 500, message, PiServerErrorCode.INTERNAL_ERROR);\n\t\t}\n\t}\n}\n\nfunction prepareSessionCompact(body: SessionCompactBody): PreparedSessionCompact | SessionCompactHttpResponse {\n\tif (!body.sessionId) {\n\t\treturn { status: 400, body: { error: \"sessionId is required\" } };\n\t}\n\tif (!body.model) {\n\t\treturn { status: 400, body: { error: \"model is required\" } };\n\t}\n\tconst validDigest = (value: string | undefined): boolean => value === undefined || /^[a-f0-9]{64}$/.test(value);\n\tif (!validDigest(body.cacheContextHash) || !validDigest(body.cacheStaticContextHash)) {\n\t\treturn { status: 400, body: { error: \"cache context digests must be 64-character SHA-256 hex strings\" } };\n\t}\n\tif ((body.cacheContextHash === undefined) !== (body.cacheStaticContextHash === undefined)) {\n\t\treturn { status: 400, body: { error: \"cacheContextHash and cacheStaticContextHash must be supplied together\" } };\n\t}\n\n\tconst session = getSession(body.sessionId);\n\tif (!session) {\n\t\treturn { status: 404, body: { error: \"session not found\", code: PiServerErrorCode.SESSION_NOT_FOUND } };\n\t}\n\n\tconst entries = getSessionBranch(session);\n\tconst leaf = entries.at(-1);\n\tif (leaf?.type === \"compaction\") {\n\t\tif (!leaf.firstKeptEntryId) {\n\t\t\treturn { status: 400, body: { error: \"Stored compaction is missing firstKeptEntryId\" } };\n\t\t}\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: {\n\t\t\t\tsuccess: true,\n\t\t\t\tcompaction: {\n\t\t\t\t\tsummary: leaf.summary,\n\t\t\t\t\tfirstKeptEntryId: leaf.firstKeptEntryId,\n\t\t\t\t\ttokensBefore: leaf.tokensBefore,\n\t\t\t\t\tdetails: leaf.details,\n\t\t\t\t},\n\t\t\t\tcompactionEntry: leaf,\n\t\t\t\t...sessionResponseBody(session),\n\t\t\t\tentries: session.entries,\n\t\t\t\tmessages: session.messages,\n\t\t\t},\n\t\t};\n\t}\n\tconst preparationResult = prepareLegacyCompaction(\n\t\tentries,\n\t\tbody.settings ?? DEFAULT_COMPACTION_SETTINGS,\n\t\tbody.preparation,\n\t);\n\tif (!preparationResult.ok) {\n\t\treturn { status: 400, body: { error: preparationResult.error.message } };\n\t}\n\tif (!preparationResult.value) {\n\t\treturn { status: 400, body: { error: \"Nothing to compact\" } };\n\t}\n\n\tconst options = body.options ?? {};\n\tlet cacheContext: Context | undefined;\n\tlet cacheFallbackReason = body.cacheFallbackReason ?? \"cache_context_digest_missing\";\n\tif (body.cacheContextHash && body.cacheStaticContextHash) {\n\t\tif (options.sessionId !== session.sessionId) {\n\t\t\tcacheFallbackReason = \"session_id_mismatch\";\n\t\t} else if (options.cacheRetention === \"none\") {\n\t\t\tcacheFallbackReason = \"prompt_cache_disabled\";\n\t\t} else if (body.cacheStaticContextHash !== session.staticContextHash) {\n\t\t\tcacheFallbackReason = \"static_context_hash_mismatch\";\n\t\t} else {\n\t\t\tconst projectedMessages = convertToLlm(session.messages);\n\t\t\tconst priorAssistant = [...session.messages].reverse().find((message) => message.role === \"assistant\");\n\t\t\tif (hashPiServerProviderMessages(projectedMessages) !== body.cacheContextHash) {\n\t\t\t\tcacheFallbackReason = \"request_context_digest_mismatch\";\n\t\t\t} else if (\n\t\t\t\t!priorAssistant ||\n\t\t\t\tpriorAssistant.provider !== body.model.provider ||\n\t\t\t\tpriorAssistant.model !== body.model.id ||\n\t\t\t\tpriorAssistant.api !== body.model.api\n\t\t\t) {\n\t\t\t\tcacheFallbackReason = \"summary_model_mismatch\";\n\t\t\t} else {\n\t\t\t\tcacheContext = buildStreamContext(session, { contextOverlay: projectedMessages });\n\t\t\t\tcacheFallbackReason = \"exact_prefix_unavailable\";\n\t\t\t}\n\t\t}\n\t}\n\treturn {\n\t\tsession,\n\t\tsessionId: session.sessionId,\n\t\trevision: session.revision,\n\t\ttreeHash: session.treeHash,\n\t\tleafId: session.leafId,\n\t\tentryCount: session.entries.length,\n\t\tpreparation: preparationResult.value,\n\t\toptions,\n\t\tcacheContext,\n\t\tcacheFallbackReason,\n\t};\n}\n\nfunction sessionCompactConflict(\n\tbody: SessionCompactBody,\n\tprepared: PreparedSessionCompact,\n\tcurrentSession: SessionState | undefined,\n): SessionCompactHttpResponse {\n\treturn {\n\t\tstatus: 409,\n\t\tbody: {\n\t\t\terror: \"Session changed while compaction was running\",\n\t\t\tcode: PiServerErrorCode.SESSION_STATE_CONFLICT,\n\t\t\tdetails: {\n\t\t\t\tsessionId: body.sessionId,\n\t\t\t\texpectedRevision: prepared.revision,\n\t\t\t\texpectedTreeHash: prepared.treeHash,\n\t\t\t\texpectedLeafId: prepared.leafId,\n\t\t\t\tactualRevision: currentSession?.revision ?? null,\n\t\t\t\tactualTreeHash: currentSession?.treeHash ?? null,\n\t\t\t\tactualLeafId: currentSession?.leafId ?? null,\n\t\t\t},\n\t\t},\n\t};\n}\n\nasync function completeSessionCompact(\n\tconfig: ServerConfig,\n\tbody: SessionCompactBody,\n\tprepared: PreparedSessionCompact,\n\trun?: StreamRunRecord,\n): Promise<SessionCompactHttpResponse> {\n\tconst result = await compactLegacy(\n\t\tprepared.preparation,\n\t\tcreateRequestModels(body.model, prepared.options),\n\t\tbody.model,\n\t\tbody.customInstructions,\n\t\trun?.controller.signal,\n\t\tprepared.options.reasoning,\n\t\tundefined,\n\t\tundefined,\n\t\tprepared.cacheContext,\n\t\tprepared.options as CompactionCacheRequestOptions,\n\t\tprepared.cacheFallbackReason,\n\t);\n\tif (!result.ok) {\n\t\treturn { status: 500, body: { error: result.error.message, code: PiServerErrorCode.INTERNAL_ERROR } };\n\t}\n\tif (run?.cancelRequested || run?.controller.signal.aborted) {\n\t\treturn { status: 409, body: { error: \"Compaction aborted\", code: PiServerErrorCode.INVALID_REQUEST } };\n\t}\n\n\tconst currentSession = getSession(prepared.sessionId);\n\tif (\n\t\t!currentSession ||\n\t\tcurrentSession !== prepared.session ||\n\t\tcurrentSession.revision !== prepared.revision ||\n\t\tcurrentSession.treeHash !== prepared.treeHash ||\n\t\tcurrentSession.leafId !== prepared.leafId\n\t) {\n\t\treturn sessionCompactConflict(body, prepared, currentSession);\n\t}\n\n\tconst baseTreeHash = prepared.treeHash;\n\tconst baseEntryCount = prepared.entryCount;\n\tconst compaction = result.value;\n\tconst { session: updatedSession, entry: compactionEntry } = appendCompactionEntry(body.sessionId, compaction);\n\tpersistSession(config, updatedSession);\n\tif (!body.fullResponse && body.baseTreeHash === baseTreeHash) {\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: {\n\t\t\t\tsuccess: true,\n\t\t\t\tcompaction: compaction satisfies LegacyCompactResult,\n\t\t\t\tcompactionEntry,\n\t\t\t\t...sessionResponseBody(updatedSession),\n\t\t\t\tstaticContext: updatedSession.staticContext,\n\t\t\t\ttreePatch: {\n\t\t\t\t\tbaseTreeHash,\n\t\t\t\t\tentriesFrom: baseEntryCount,\n\t\t\t\t\tentries: [compactionEntry],\n\t\t\t\t\tleafId: updatedSession.leafId,\n\t\t\t\t\trevision: updatedSession.revision,\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\treturn {\n\t\tstatus: 200,\n\t\tbody: {\n\t\t\tsuccess: true,\n\t\t\tcompaction: compaction satisfies LegacyCompactResult,\n\t\t\tcompactionEntry,\n\t\t\t...sessionResponseBody(updatedSession),\n\t\t\tstaticContext: updatedSession.staticContext,\n\t\t\tentries: updatedSession.entries,\n\t\t\tmessages: updatedSession.messages,\n\t\t},\n\t};\n}\n\nfunction writeServerSentEvent(res: ServerResponse, event: string, body: unknown): void {\n\tres.write(`event: ${event}\\n`);\n\tres.write(`data: ${JSON.stringify(body)}\\n\\n`);\n}\n\nasync function handleSessionCompactStream(\n\tconfig: ServerConfig,\n\tbody: SessionCompactBody,\n\tprepared: PreparedSessionCompact,\n\tres: ServerResponse,\n\trun?: StreamRunRecord,\n): Promise<void> {\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\ttouchStreamRun(run);\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\ttry {\n\t\tconst result = await completeSessionCompact(config, body, prepared, run);\n\t\tfinishCompactRun(run, result);\n\t\twriteServerSentEvent(res, result.status >= 400 ? \"error\" : \"result\", result.body);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (run?.status === \"running\") {\n\t\t\trun.status = run.cancelRequested ? \"aborted\" : \"failed\";\n\t\t\trun.errorMessage = message;\n\t\t\trun.updatedAt = Date.now();\n\t\t}\n\t\twriteServerSentEvent(res, \"error\", { error: message });\n\t} finally {\n\t\tclearInterval(heartbeat);\n\t\tsettleRun(run);\n\t\tres.end();\n\t}\n}\n\nasync function handleSessionCompactJsonStream(\n\tconfig: ServerConfig,\n\tbody: SessionCompactBody,\n\tprepared: PreparedSessionCompact,\n\tres: ServerResponse,\n\trun?: StreamRunRecord,\n): Promise<void> {\n\tres.writeHead(200, {\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"Cache-Control\": \"no-cache\",\n\t\tConnection: \"keep-alive\",\n\t});\n\tres.flushHeaders();\n\tres.write(JSON_HEARTBEAT);\n\n\tconst heartbeat = setInterval(() => {\n\t\ttouchStreamRun(run);\n\t\tif (!res.writableEnded) {\n\t\t\tres.write(JSON_HEARTBEAT);\n\t\t}\n\t}, STREAM_HEARTBEAT_INTERVAL_MS);\n\theartbeat.unref();\n\n\ttry {\n\t\tconst result = await completeSessionCompact(config, body, prepared, run);\n\t\tfinishCompactRun(run, result);\n\t\tres.write(JSON.stringify(result.body));\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (run?.status === \"running\") {\n\t\t\trun.status = run.cancelRequested ? \"aborted\" : \"failed\";\n\t\t\trun.errorMessage = message;\n\t\t\trun.updatedAt = Date.now();\n\t\t}\n\t\tres.write(JSON.stringify({ error: message }));\n\t} finally {\n\t\tclearInterval(heartbeat);\n\t\tsettleRun(run);\n\t\tres.end();\n\t}\n}\n\nfunction finishCompactRun(run: StreamRunRecord | undefined, result: SessionCompactHttpResponse): void {\n\tif (!run) return;\n\tif (run.cancelRequested || run.controller.signal.aborted) {\n\t\trun.status = \"aborted\";\n\t\trun.errorMessage = \"Compaction aborted\";\n\t} else {\n\t\trun.status = result.status >= 400 ? \"failed\" : \"completed\";\n\t\tif (result.status >= 400 && \"error\" in result.body) {\n\t\t\trun.errorMessage = result.body.error;\n\t\t} else if (\"success\" in result.body) {\n\t\t\trun.compactResult = { ...result.body, entries: result.body.entries?.slice() };\n\t\t}\n\t}\n\trun.updatedAt = Date.now();\n}\n\nasync function handleSessionCompact(\n\tconfig: ServerConfig,\n\tbody: SessionCompactBody,\n\tres: ServerResponse,\n): Promise<void> {\n\tconst existingRun = body.runId ? getStreamRun(body.sessionId, body.runId) : undefined;\n\tif (existingRun?.status === \"aborted\") {\n\t\tsendJson(res, 409, { error: \"Compaction run was aborted\", code: PiServerErrorCode.INVALID_REQUEST });\n\t\treturn;\n\t}\n\tif (existingRun?.status === \"completed\" && existingRun.compactResult) {\n\t\tsendJson(res, 200, existingRun.compactResult);\n\t\treturn;\n\t}\n\tconst prepared = prepareSessionCompact(body);\n\tif (\"status\" in prepared) {\n\t\tsendJson(res, prepared.status, prepared.body);\n\t\treturn;\n\t}\n\n\tlet run: StreamRunRecord | undefined;\n\tif (body.runId) {\n\t\tconst existingRun = getStreamRun(body.sessionId, body.runId);\n\t\tif (existingRun?.status === \"aborted\") {\n\t\t\tsendJson(res, 409, { error: \"Compaction run was aborted\", code: PiServerErrorCode.INVALID_REQUEST });\n\t\t\treturn;\n\t\t}\n\t\tif (existingRun?.status === \"running\") {\n\t\t\tsendError(res, 409, \"A run with this runId is already in progress\", PiServerErrorCode.RUN_IN_PROGRESS);\n\t\t\treturn;\n\t\t}\n\t\trun = startStreamRun(body.sessionId, body.runId, \"compact\");\n\t}\n\n\tif (body.streamResponse) {\n\t\tawait handleSessionCompactStream(config, body, prepared, res, run);\n\t\treturn;\n\t}\n\n\tawait handleSessionCompactJsonStream(config, body, prepared, res, run);\n}\n\nfunction handleSessionHistory(\n\tsessionId: string,\n\tfrom: number | undefined,\n\tentriesFrom: number | undefined,\n\trevision: number | undefined,\n\tbaseTreeHash: string | undefined,\n\tres: ServerResponse,\n): void {\n\tconst session = getSession(sessionId);\n\tif (!session) {\n\t\tsendError(res, 404, \"session not found\", PiServerErrorCode.SESSION_NOT_FOUND);\n\t\treturn;\n\t}\n\tconst baseMessageCount = from ?? 0;\n\tif (\n\t\tentriesFrom !== undefined &&\n\t\tentriesFrom <= session.entries.length &&\n\t\t(revision === undefined || revision <= session.revision) &&\n\t\t(baseTreeHash === undefined || baseTreeHash === session.prefixHashes[entriesFrom])\n\t) {\n\t\tsendJson(res, 200, sessionTreePatchResponseBody(session, baseMessageCount, entriesFrom, revision));\n\t\treturn;\n\t}\n\tsendJson(res, 200, sessionHistoryFullResponseBody(session, baseMessageCount));\n}\n\nfunction handleSessionRun(sessionId: string, runId: string, res: ServerResponse): void {\n\tconst run = getStreamRun(sessionId, runId);\n\tif (!run) {\n\t\tsendError(res, 404, \"run not found\", PiServerErrorCode.RUN_NOT_FOUND);\n\t\treturn;\n\t}\n\tsendJson(res, 200, streamRunResponseBody(run));\n}\n\nasync function handleSessionRunAbort(sessionId: string, runId: string, res: ServerResponse): Promise<void> {\n\tif (!sessionId || !runId) {\n\t\tsendError(res, 400, \"sessionId and runId are required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\n\tlet run = getStreamRun(sessionId, runId);\n\tif (!run) {\n\t\trun = createRunRecord(sessionId, runId, \"stream\");\n\t\trun.status = \"aborted\";\n\t\trun.cancelRequested = true;\n\t\trun.errorMessage = \"Stream run aborted before it started\";\n\t\trun.updatedAt = Date.now();\n\t\tsettleRun(run);\n\t\tstreamRuns.set(runKey(sessionId, runId), run);\n\t\tsendJson(res, 200, streamRunResponseBody(run));\n\t\treturn;\n\t}\n\n\tif (run.status !== \"running\") {\n\t\tawait run.settled;\n\t\tsendJson(res, 200, streamRunResponseBody(run));\n\t\treturn;\n\t}\n\n\trun.cancelRequested = true;\n\trun.updatedAt = Date.now();\n\trun.controller.abort();\n\tawait run.settled;\n\tsendJson(res, 200, streamRunResponseBody(run));\n}\n\nexport function buildStreamContext(\n\tsession: SessionState,\n\tbody: Pick<StreamRequestBody, \"contextOverlay\" | \"ephemeralMessages\">,\n): Context {\n\tconst messages = body.contextOverlay ?? [...session.messages, ...(body.ephemeralMessages ?? [])];\n\treturn {\n\t\tsystemPrompt: session.staticContext?.systemPrompt,\n\t\tmessages,\n\t\ttools: session.staticContext?.tools,\n\t};\n}\n\nfunction handleStream(config: ServerConfig, body: StreamRequestBody, res: ServerResponse): void {\n\tif (!body.sessionId) {\n\t\tsendError(res, 400, \"sessionId is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\tif (!body.model) {\n\t\tsendError(res, 400, \"model is required\", PiServerErrorCode.REQUIRED_FIELD_MISSING);\n\t\treturn;\n\t}\n\n\tcleanupExpiredStreamRuns(Date.now());\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\tsendError(\n\t\t\tres,\n\t\t\t400,\n\t\t\t\"Session has no static context. Initialize with /api/session/init first.\",\n\t\t\tPiServerErrorCode.SESSION_NO_STATIC_CONTEXT,\n\t\t);\n\t\treturn;\n\t}\n\n\tif (body.ephemeralMessages !== undefined && !Array.isArray(body.ephemeralMessages)) {\n\t\tsendError(res, 400, \"ephemeralMessages must be an array\", PiServerErrorCode.INVALID_REQUEST);\n\t\treturn;\n\t}\n\tif (body.contextOverlay !== undefined && !Array.isArray(body.contextOverlay)) {\n\t\tsendError(res, 400, \"contextOverlay must be an array\", PiServerErrorCode.INVALID_REQUEST);\n\t\treturn;\n\t}\n\n\tconst context = buildStreamContext(session, body);\n\tconst existingRun = body.runId ? getStreamRun(body.sessionId, body.runId) : undefined;\n\n\tconst resolvedModel = body.model;\n\n\tif (existingRun?.status === \"completed\") {\n\t\tres.writeHead(200, {\n\t\t\t\"Content-Type\": \"text/event-stream\",\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\tConnection: \"keep-alive\",\n\t\t});\n\t\tres.flushHeaders();\n\t\tres.write(STREAM_HEARTBEAT);\n\t\tfor (const event of replayStreamRun(existingRun)) {\n\t\t\twriteStreamEvent(res, event);\n\t\t}\n\t\tendStreamResponse(res);\n\t\treturn;\n\t}\n\tif (existingRun?.status === \"aborted\") {\n\t\tsendError(res, 409, \"The stream run was aborted\", PiServerErrorCode.INVALID_REQUEST);\n\t\treturn;\n\t}\n\tif (existingRun?.status === \"running\") {\n\t\tsendError(res, 409, \"A stream with this runId is already in progress\", PiServerErrorCode.RUN_IN_PROGRESS);\n\t\treturn;\n\t}\n\n\tconst run = body.runId ? startStreamRun(body.sessionId, body.runId) : undefined;\n\tconst streamOptions: SimpleStreamOptions = {\n\t\t...(body.options ?? {}),\n\t\t...(run ? { signal: run.controller.signal } : {}),\n\t};\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\ttouchStreamRun(run);\n\t\tif (!res.writableEnded && !res.destroyed) {\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\tconst message = err instanceof Error ? err.message : String(err);\n\t\tfailStreamRun(run, message);\n\t\tsettleRun(run);\n\t\twriteStreamError(res, message);\n\t\tendStreamResponse(res);\n\t\treturn;\n\t}\n\n\tvoid (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\ttouchStreamRun(run);\n\t\t\t\t\twriteStreamEvent(res, proxyEvent);\n\t\t\t\t}\n\t\t\t\tif (event.type === \"done\") {\n\t\t\t\t\tcompleteStreamRun(run, event.message);\n\t\t\t\t} else if (event.type === \"error\") {\n\t\t\t\t\tfailStreamRun(run, event.error.errorMessage ?? event.reason);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tfailStreamRun(run, message);\n\t\t\twriteStreamError(res, message);\n\t\t} finally {\n\t\t\tclearInterval(heartbeat);\n\t\t\tif (run?.status === \"running\") {\n\t\t\t\tfailStreamRun(run, run.cancelRequested ? \"Stream run aborted\" : \"Stream ended before a terminal event\");\n\t\t\t}\n\t\t\tsettleRun(run);\n\t\t\tendStreamResponse(res);\n\t\t}\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/receive\") {\n\t\ttry {\n\t\t\tsendJson(res, 200, receiveUpload(config.uploadDir, body));\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof ReceiveUploadError)) throw error;\n\t\t\tsendJson(res, error.status, { error: error.message });\n\t\t}\n\t\treturn true;\n\t}\n\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/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://localhost\");\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\tconst body =\n\t\t\t\treq.method === \"GET\" && url.pathname === \"/\"\n\t\t\t\t\t? { error: \"Unauthorized\", version: PI_SERVER_VERSION }\n\t\t\t\t\t: { error: \"Unauthorized\" };\n\t\t\tsendJson(res, 401, body);\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\tconst runMatch = /^\\/api\\/session\\/([^/]+)\\/runs\\/([^/]+)$/.exec(url.pathname);\n\t\tif (req.method === \"GET\" && runMatch) {\n\t\t\thandleSessionRun(decodeURIComponent(runMatch[1]), decodeURIComponent(runMatch[2]), res);\n\t\t\treturn;\n\t\t}\n\n\t\tconst runAbortMatch = /^\\/api\\/session\\/([^/]+)\\/runs\\/([^/]+)\\/abort$/.exec(url.pathname);\n\t\tif (req.method === \"POST\" && runAbortMatch) {\n\t\t\tawait readBody(req);\n\t\t\tawait handleSessionRunAbort(decodeURIComponent(runAbortMatch[1]), decodeURIComponent(runAbortMatch[2]), res);\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\tsendError(res, 400, \"from must be a non-negative integer\", PiServerErrorCode.INVALID_REQUEST);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst entriesFromParam = url.searchParams.get(\"entriesFrom\");\n\t\t\tconst entriesFrom = entriesFromParam === null ? undefined : Number(entriesFromParam);\n\t\t\tif (entriesFrom !== undefined && (!Number.isInteger(entriesFrom) || entriesFrom < 0)) {\n\t\t\t\tsendError(res, 400, \"entriesFrom must be a non-negative integer\", PiServerErrorCode.INVALID_REQUEST);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst revisionParam = url.searchParams.get(\"revision\");\n\t\t\tconst revision = revisionParam === null ? undefined : Number(revisionParam);\n\t\t\tif (revision !== undefined && (!Number.isInteger(revision) || revision < 0)) {\n\t\t\t\tsendError(res, 400, \"revision must be a non-negative integer\", PiServerErrorCode.INVALID_REQUEST);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thandleSessionHistory(\n\t\t\t\tdecodeURIComponent(encodedSessionId),\n\t\t\t\tfrom,\n\t\t\t\tentriesFrom,\n\t\t\t\trevision,\n\t\t\t\turl.searchParams.get(\"baseTreeHash\") ?? undefined,\n\t\t\t\tres,\n\t\t\t);\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\tconst handled = await handlePostRequest(\n\t\t\t\t\tconfig,\n\t\t\t\t\tchunkResult.target,\n\t\t\t\t\tJSON.parse(chunkResult.bodyJson) as unknown,\n\t\t\t\t\tres,\n\t\t\t\t);\n\t\t\t\tif (!handled && !res.headersSent) {\n\t\t\t\t\tsendError(res, 404, \"Not found\", PiServerErrorCode.INVALID_REQUEST);\n\t\t\t\t}\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\tsendError(res, 400, err instanceof Error ? err.message : String(err), PiServerErrorCode.INVALID_REQUEST);\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\tif (!res.headersSent) {\n\t\t\t\t\tsendError(res, 404, \"Not found\", PiServerErrorCode.INVALID_REQUEST);\n\t\t\t\t\treturn;\n\t\t\t\t}\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\tsendError(res, 500, err instanceof Error ? err.message : String(err), PiServerErrorCode.INTERNAL_ERROR);\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\tdeleteStreamRunsForSession(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\tsendError(res, 404, \"Not found\", PiServerErrorCode.INVALID_REQUEST);\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, toolCall: event.toolCall };\n\t\tcase \"done\":\n\t\t\treturn {\n\t\t\t\ttype: \"done\",\n\t\t\t\treason: event.reason,\n\t\t\t\tusage: event.message.usage,\n\t\t\t\tdeferred: event.message.deferred,\n\t\t\t\tmessage: event.message,\n\t\t\t};\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\tconst address = server.address();\n\t\tconst port = typeof address === \"object\" && address !== null ? address.port : config.port;\n\t\tconst host = config.host.includes(\":\") && !config.host.startsWith(\"[\") ? `[${config.host}]` : config.host;\n\t\tconsole.log(`pi-server listening on ${host}:${port}`);\n\t});\n\treturn server;\n}\n"]}
|
|
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;AAc/G,OAAO,EAGN,KAAK,OAAO,EAGZ,KAAK,OAAO,EACZ,KAAK,KAAK,EAEV,KAAK,mBAAmB,EACxB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQhD,OAAO,EAWN,KAAK,YAAY,EACjB,KAAK,oBAAoB,EAGzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAc5D,UAAU,iBAAiB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;CAC3B;AA67BD,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,gBAAgB,GAAG,mBAAmB,CAAC,GACnE,OAAO,CAOT;AAoMD,wBAAgB,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,UAAU,CAoIjF;AAqED,wBAAgB,WAAW,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,UAAU,CAU9E"}
|