@computesdk/loops 0.1.10 → 0.1.11
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/API.md +34 -0
- package/README.md +41 -2
- package/bin/pi-rpc-startup.mjs +8 -0
- package/client.ts +27 -0
- package/dist/assets/{abnfDiagram-VCTEODGH-DCBDcsG9.js → abnfDiagram-VCTEODGH-DCYXdRzD.js} +1 -1
- package/dist/assets/{arc-XHL_gOXE.js → arc-C0Zv4bRf.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-D2AZUwQa.js → architectureDiagram-5GKGNRK7-udHqlZFO.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-B4gIbwtQ.js → blockDiagram-I7D4REHJ-DkwphONO.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-BVQsOVAp.js → c4Diagram-7LVT6UL2-C0Qkqogh.js} +1 -1
- package/dist/assets/channel-vsnAMebC.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-JT4Vadns.js → chunk-2Q5K7J3B-BQDQoQmL.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-CDeArlJ5.js → chunk-5VM5RSS4-CvqeMalj.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-CCc4sEwP.js → chunk-F27PBJKO-D_B9YCM7.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-CXd4ZLrB.js → chunk-IMKFNOWR-BYIRSti2.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DtGxXURA.js → chunk-JWPE2WC7-BPJdwBVM.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-TYAe-WXZ.js → chunk-POPQ4Y6H-lztII1aN.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-DmyN3wGH.js → chunk-SVP7TREG-DoYdkbun.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-CoQRzD2R.js → chunk-TICWLB2K-CjnLsube.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Cdv4d7MB.js → chunk-XXDRQBXY-Jsq5tSht.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-Dql6Ch3d.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-Dql6Ch3d.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-B_tFtaAU.js → cose-bilkent-JH36ORCC-BIP9YsAI.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-Cwqvcd49.js → cynefin-OW5HDTMX-wj1eU8-l.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-DABTV6Vb.js → cynefinDiagram-5FMLGOSQ-DW6veCDh.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-TYrmYsuz.js → dagre-GXQ25YYZ-ChQ1GCij.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-DHSEu2H3.js → diagram-S7CK7UJ4-B0pgpZ9e.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-C019YqL0.js → diagram-UQ7AKVKN-BoNV14ty.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-D4MEzffI.js → diagram-VSXAHHWV-CpDQf6Z1.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-kFJ0JvUv.js → diagram-VX7I27RA-BzTGDavi.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-BcO31l9N.js → diagram-Z3DM3KII-B5bOyrqx.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-DZj4QQh4.js → ebnfDiagram-PWID7BFC-z4WVJ94j.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-CmJHu3yl.js → erDiagram-RLTQ6QDP-iCda9mWB.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-Cs2i9PHj.js → flowDiagram-HODETNUW-DGAIDu__.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-DeROwj00.js → ganttDiagram-EL5Y4UJY-C_TD7p_8.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX--qyeTDhG.js → gitGraphDiagram-WWUBYQGX-BhyVS4hu.js} +1 -1
- package/dist/assets/{highlighted-body-JLMTFACH-ChXoKzQk.js → highlighted-body-JLMTFACH-DmjzCqfz.js} +1 -1
- package/dist/assets/index-Cfkb4x7R.js +902 -0
- package/dist/assets/index-D-CIRYJ5.css +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-CL4TN3Oz.js → infoDiagram-27XIBGKW-VKT9hRl2.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-BOpnwC1K.js → ishikawaDiagram-5VMMS53U-CTbJYQrg.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-B9WrcTg4.js → journeyDiagram-3NMN7TZE-CuRQRZVc.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-k-zPWS3C.js → kanban-definition-UXKFOSKX-ConV4y36.js} +1 -1
- package/dist/assets/{layout-3kvHth78.js → layout-dfLpQ9-g.js} +1 -1
- package/dist/assets/{linear-BWj_7-Qu.js → linear-jzcXY-L-.js} +1 -1
- package/dist/assets/{mindmap-definition-YA3MSWOX-CHQiqXfo.js → mindmap-definition-YA3MSWOX-BZ44_mmM.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-CbU0Toll.js → pegDiagram-XKGWAZYB-qwQPRnqR.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-DMet_oui.js → pieDiagram-E7YTZNPT-C41zts6x.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-BebzO8sd.js → quadrantDiagram-AXDQQJYC-Bz7te34m.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-Bh8-vPzn.js → railroadDiagram-O6MQD6OU-DQYw8Oka.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-BPUXWj9N.js → requirementDiagram-BXWQKSXE-C-WJyiUK.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-py5UjYxE.js → sankeyDiagram-P5KCCOFB-BikqKGtk.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-7dG5yYB3.js → sequenceDiagram-WJ2MYXX4-009Ku6v1.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-84MVWGh6.js → sizeCapture-INFHLROL-BtG1hGpf.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-BI7nUn04.js → stateDiagram-D77RDMKH-BX2Uia62.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-BpIwsc6N.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-kmjOxJ3M.js → swimlanes-42K2YHIH-oE_h5Q_J.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-CAtySeEC.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-x-0bw4L3.js → timeline-definition-24CTP7MA-tmMXsAa-.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-CPpgLcl3.js → vennDiagram-4TSXK5OY-D3FmrVNo.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-B32JnOAm.js → wardleyDiagram-VM6X3IG4-JGmd8Ley.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-HnFQQ24s.js → xychartDiagram-S5SC5T6Z-CVriivaU.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/server.mjs +691 -129
- package/package.json +4 -4
- package/src/shared/types.ts +40 -0
- package/dist/assets/channel-BflrI4kn.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-hGKBk6GI.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-hGKBk6GI.js +0 -1
- package/dist/assets/index-C1H-VPU1.css +0 -1
- package/dist/assets/index-D-f-6Xq7.js +0 -902
- package/dist/assets/stateDiagram-v2-MP3YSRHH-C1raQMUd.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-Du4mhJod.js +0 -8
package/dist/server.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// server/index.ts
|
|
2
2
|
import fs9 from "node:fs";
|
|
3
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import { randomBytes, randomUUID as randomUUID2 } from "node:crypto";
|
|
4
4
|
import http from "node:http";
|
|
5
|
-
import
|
|
5
|
+
import path10 from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { execFile as execFile2, execFileSync as execFileSync2, spawn as spawn6 } from "node:child_process";
|
|
8
8
|
|
|
@@ -107,26 +107,26 @@ var throwNestingLimitExceeded = () => {
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/utils/url.js
|
|
110
|
-
var splitPath = (
|
|
111
|
-
const paths =
|
|
110
|
+
var splitPath = (path11) => {
|
|
111
|
+
const paths = path11.split("/");
|
|
112
112
|
if (paths[0] === "") paths.shift();
|
|
113
113
|
return paths;
|
|
114
114
|
};
|
|
115
115
|
var splitRoutingPath = (routePath) => {
|
|
116
|
-
const { groups, path:
|
|
117
|
-
const paths = splitPath(
|
|
116
|
+
const { groups, path: path11 } = extractGroupsFromPath(routePath);
|
|
117
|
+
const paths = splitPath(path11);
|
|
118
118
|
return replaceGroupMarks(paths, groups);
|
|
119
119
|
};
|
|
120
|
-
var extractGroupsFromPath = (
|
|
120
|
+
var extractGroupsFromPath = (path11) => {
|
|
121
121
|
const groups = [];
|
|
122
|
-
|
|
122
|
+
path11 = path11.replace(/\{[^}]+\}/g, (match2, index) => {
|
|
123
123
|
const mark = `@${index}`;
|
|
124
124
|
groups.push([mark, match2]);
|
|
125
125
|
return mark;
|
|
126
126
|
});
|
|
127
127
|
return {
|
|
128
128
|
groups,
|
|
129
|
-
path:
|
|
129
|
+
path: path11
|
|
130
130
|
};
|
|
131
131
|
};
|
|
132
132
|
var replaceGroupMarks = (paths, groups) => {
|
|
@@ -189,8 +189,8 @@ var getPath = (request) => {
|
|
|
189
189
|
const queryIndex = url.indexOf("?", i);
|
|
190
190
|
const hashIndex = url.indexOf("#", i);
|
|
191
191
|
const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
|
|
192
|
-
const
|
|
193
|
-
return tryDecodeURI(
|
|
192
|
+
const path11 = url.slice(start, end);
|
|
193
|
+
return tryDecodeURI(path11.includes("%25") ? path11.replace(/%25/g, "%2525") : path11);
|
|
194
194
|
} else if (charCode === 63 || charCode === 35) break;
|
|
195
195
|
}
|
|
196
196
|
return url.slice(start, i);
|
|
@@ -203,9 +203,9 @@ var mergePath = (base, sub, ...rest) => {
|
|
|
203
203
|
if (rest.length) sub = mergePath(sub, ...rest);
|
|
204
204
|
return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
|
|
205
205
|
};
|
|
206
|
-
var checkOptionalParameter = (
|
|
207
|
-
if (
|
|
208
|
-
const segments =
|
|
206
|
+
var checkOptionalParameter = (path11) => {
|
|
207
|
+
if (path11.charCodeAt(path11.length - 1) !== 63 || !path11.includes(":")) return null;
|
|
208
|
+
const segments = path11.split("/");
|
|
209
209
|
const results = [];
|
|
210
210
|
let basePath = "";
|
|
211
211
|
segments.forEach((segment) => {
|
|
@@ -316,9 +316,9 @@ var HonoRequest = class {
|
|
|
316
316
|
*/
|
|
317
317
|
path;
|
|
318
318
|
bodyCache = {};
|
|
319
|
-
constructor(request,
|
|
319
|
+
constructor(request, path11 = "/", matchResult = [[]]) {
|
|
320
320
|
this.raw = request;
|
|
321
|
-
this.path =
|
|
321
|
+
this.path = path11;
|
|
322
322
|
this.#matchResult = matchResult;
|
|
323
323
|
}
|
|
324
324
|
param(key) {
|
|
@@ -1048,8 +1048,8 @@ var Hono = class Hono2 {
|
|
|
1048
1048
|
return this;
|
|
1049
1049
|
};
|
|
1050
1050
|
});
|
|
1051
|
-
this.on = (method,
|
|
1052
|
-
for (const p of [
|
|
1051
|
+
this.on = (method, path11, ...handlers) => {
|
|
1052
|
+
for (const p of [path11].flat()) {
|
|
1053
1053
|
this.#path = p;
|
|
1054
1054
|
for (const m of [method].flat()) {
|
|
1055
1055
|
const methodName = m.toUpperCase();
|
|
@@ -1103,8 +1103,8 @@ var Hono = class Hono2 {
|
|
|
1103
1103
|
* app.route("/api", app2) // GET /api/user
|
|
1104
1104
|
* ```
|
|
1105
1105
|
*/
|
|
1106
|
-
route(
|
|
1107
|
-
const subApp = this.basePath(
|
|
1106
|
+
route(path11, app2) {
|
|
1107
|
+
const subApp = this.basePath(path11);
|
|
1108
1108
|
app2.routes.map((r) => {
|
|
1109
1109
|
let handler;
|
|
1110
1110
|
if (app2.errorHandler === errorHandler) handler = r.handler;
|
|
@@ -1129,9 +1129,9 @@ var Hono = class Hono2 {
|
|
|
1129
1129
|
* const api = new Hono().basePath('/api')
|
|
1130
1130
|
* ```
|
|
1131
1131
|
*/
|
|
1132
|
-
basePath(
|
|
1132
|
+
basePath(path11) {
|
|
1133
1133
|
const subApp = this.#clone();
|
|
1134
|
-
subApp._basePath = mergePath(this._basePath,
|
|
1134
|
+
subApp._basePath = mergePath(this._basePath, path11);
|
|
1135
1135
|
return subApp;
|
|
1136
1136
|
}
|
|
1137
1137
|
/**
|
|
@@ -1207,7 +1207,7 @@ var Hono = class Hono2 {
|
|
|
1207
1207
|
* })
|
|
1208
1208
|
* ```
|
|
1209
1209
|
*/
|
|
1210
|
-
mount(
|
|
1210
|
+
mount(path11, applicationHandler, options) {
|
|
1211
1211
|
let replaceRequest;
|
|
1212
1212
|
let optionHandler;
|
|
1213
1213
|
if (options) {
|
|
@@ -1230,7 +1230,7 @@ var Hono = class Hono2 {
|
|
|
1230
1230
|
return [c.env, executionContext];
|
|
1231
1231
|
};
|
|
1232
1232
|
replaceRequest ||= (() => {
|
|
1233
|
-
const mergedPath = mergePath(this._basePath,
|
|
1233
|
+
const mergedPath = mergePath(this._basePath, path11);
|
|
1234
1234
|
const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
|
|
1235
1235
|
return (request) => {
|
|
1236
1236
|
const url = new URL(request.url);
|
|
@@ -1243,18 +1243,18 @@ var Hono = class Hono2 {
|
|
|
1243
1243
|
if (res) return res;
|
|
1244
1244
|
await next();
|
|
1245
1245
|
};
|
|
1246
|
-
this.#addRoute("ALL", mergePath(
|
|
1246
|
+
this.#addRoute("ALL", mergePath(path11, "*"), handler);
|
|
1247
1247
|
return this;
|
|
1248
1248
|
}
|
|
1249
|
-
#addRoute(method,
|
|
1250
|
-
|
|
1249
|
+
#addRoute(method, path11, handler, baseRoutePath) {
|
|
1250
|
+
path11 = mergePath(this._basePath, path11);
|
|
1251
1251
|
const r = {
|
|
1252
1252
|
basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
1253
|
-
path:
|
|
1253
|
+
path: path11,
|
|
1254
1254
|
method,
|
|
1255
1255
|
handler
|
|
1256
1256
|
};
|
|
1257
|
-
this.router.add(method,
|
|
1257
|
+
this.router.add(method, path11, [handler, r]);
|
|
1258
1258
|
this.routes.push(r);
|
|
1259
1259
|
}
|
|
1260
1260
|
#handleError(err, c) {
|
|
@@ -1263,10 +1263,10 @@ var Hono = class Hono2 {
|
|
|
1263
1263
|
}
|
|
1264
1264
|
#dispatch(request, executionCtx, env, method) {
|
|
1265
1265
|
if (method === "HEAD") return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
|
|
1266
|
-
const
|
|
1267
|
-
const matchResult = this.router.match(method,
|
|
1266
|
+
const path11 = this.getPath(request, { env });
|
|
1267
|
+
const matchResult = this.router.match(method, path11);
|
|
1268
1268
|
const c = new Context(request, {
|
|
1269
|
-
path:
|
|
1269
|
+
path: path11,
|
|
1270
1270
|
matchResult,
|
|
1271
1271
|
env,
|
|
1272
1272
|
executionCtx,
|
|
@@ -1320,10 +1320,10 @@ var Hono = class Hono2 {
|
|
|
1320
1320
|
* ```
|
|
1321
1321
|
* @see https://hono.dev/docs/api/hono#request
|
|
1322
1322
|
*/
|
|
1323
|
-
request = (
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
return this.fetch(new Request(/^https?:\/\//.test(
|
|
1323
|
+
request = (input2, requestInit, Env, executionCtx) => {
|
|
1324
|
+
if (input2 instanceof Request) return this.fetch(requestInit ? new Request(input2, requestInit) : input2, Env, executionCtx);
|
|
1325
|
+
input2 = input2.toString();
|
|
1326
|
+
return this.fetch(new Request(/^https?:\/\//.test(input2) ? input2 : `http://localhost${mergePath("/", input2)}`, requestInit), Env, executionCtx);
|
|
1327
1327
|
};
|
|
1328
1328
|
/**
|
|
1329
1329
|
* `.fire()` automatically adds a global fetch event listener.
|
|
@@ -1354,19 +1354,19 @@ var createNullObject = () => /* @__PURE__ */ Object.create(null);
|
|
|
1354
1354
|
|
|
1355
1355
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
1356
1356
|
var emptyParam = [];
|
|
1357
|
-
function match(method,
|
|
1357
|
+
function match(method, path11) {
|
|
1358
1358
|
const matchers = this.buildAllMatchers();
|
|
1359
|
-
const match2 = ((method2,
|
|
1359
|
+
const match2 = ((method2, path12) => {
|
|
1360
1360
|
const matcher = matchers[method2] || matchers["ALL"];
|
|
1361
|
-
const staticMatch = matcher[2][
|
|
1361
|
+
const staticMatch = matcher[2][path12];
|
|
1362
1362
|
if (staticMatch) return staticMatch;
|
|
1363
|
-
const match3 =
|
|
1363
|
+
const match3 = path12.match(matcher[0]);
|
|
1364
1364
|
if (!match3) return [[], emptyParam];
|
|
1365
1365
|
const index = match3.indexOf("", 1);
|
|
1366
1366
|
return [matcher[1][index], match3];
|
|
1367
1367
|
});
|
|
1368
1368
|
this.match = match2;
|
|
1369
|
-
return match2(method,
|
|
1369
|
+
return match2(method, path11);
|
|
1370
1370
|
}
|
|
1371
1371
|
|
|
1372
1372
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
@@ -1456,14 +1456,14 @@ var Trie = class {
|
|
|
1456
1456
|
#root = new Node();
|
|
1457
1457
|
#index = 0;
|
|
1458
1458
|
paths = createNullObject();
|
|
1459
|
-
insert(
|
|
1459
|
+
insert(path11, isStatic) {
|
|
1460
1460
|
if (isStatic) {
|
|
1461
|
-
this.#root.insert(
|
|
1461
|
+
this.#root.insert(path11.split(""), 0, [], this.#context, true);
|
|
1462
1462
|
return;
|
|
1463
1463
|
}
|
|
1464
1464
|
const paramAssoc = [];
|
|
1465
1465
|
const groups = [];
|
|
1466
|
-
let markedPath =
|
|
1466
|
+
let markedPath = path11;
|
|
1467
1467
|
for (let i = 0; ; ) {
|
|
1468
1468
|
let replaced = false;
|
|
1469
1469
|
markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
|
|
@@ -1484,7 +1484,7 @@ var Trie = class {
|
|
|
1484
1484
|
}
|
|
1485
1485
|
}
|
|
1486
1486
|
this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
|
|
1487
|
-
this.paths[
|
|
1487
|
+
this.paths[path11] = [this.#index++, paramAssoc];
|
|
1488
1488
|
}
|
|
1489
1489
|
buildRegExp() {
|
|
1490
1490
|
let regexp = this.#root.buildRegExpStr();
|
|
@@ -1517,11 +1517,11 @@ var Trie = class {
|
|
|
1517
1517
|
|
|
1518
1518
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
1519
1519
|
var wildcardRegExpCache = createNullObject();
|
|
1520
|
-
function buildWildcardRegExp(
|
|
1521
|
-
return wildcardRegExpCache[
|
|
1520
|
+
function buildWildcardRegExp(path11) {
|
|
1521
|
+
return wildcardRegExpCache[path11] ??= new RegExp(`^${path11.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ".*" : `/:${LABEL_REG_EXP_STR}`)}$`);
|
|
1522
1522
|
}
|
|
1523
|
-
function findMiddleware(middleware,
|
|
1524
|
-
for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) if (buildWildcardRegExp(k).test(
|
|
1523
|
+
function findMiddleware(middleware, path11) {
|
|
1524
|
+
for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) if (buildWildcardRegExp(k).test(path11)) return [...middleware[k]];
|
|
1525
1525
|
}
|
|
1526
1526
|
var RegExpRouter = class {
|
|
1527
1527
|
name = "RegExpRouter";
|
|
@@ -1533,14 +1533,14 @@ var RegExpRouter = class {
|
|
|
1533
1533
|
this.#routes = { ["ALL"]: createNullObject() };
|
|
1534
1534
|
this.#tries = { ["ALL"]: new Trie() };
|
|
1535
1535
|
}
|
|
1536
|
-
#insertPath(method,
|
|
1536
|
+
#insertPath(method, path11) {
|
|
1537
1537
|
try {
|
|
1538
|
-
this.#tries[method].insert(
|
|
1538
|
+
this.#tries[method].insert(path11, !/\*|\/:/.test(path11));
|
|
1539
1539
|
} catch (e) {
|
|
1540
|
-
throw e === PATH_ERROR ? new UnsupportedPathError(
|
|
1540
|
+
throw e === PATH_ERROR ? new UnsupportedPathError(path11) : e;
|
|
1541
1541
|
}
|
|
1542
1542
|
}
|
|
1543
|
-
add(method,
|
|
1543
|
+
add(method, path11, handler) {
|
|
1544
1544
|
const middleware = this.#middleware;
|
|
1545
1545
|
const routes = this.#routes;
|
|
1546
1546
|
if (!middleware) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
@@ -1554,24 +1554,24 @@ var RegExpRouter = class {
|
|
|
1554
1554
|
}
|
|
1555
1555
|
}
|
|
1556
1556
|
}
|
|
1557
|
-
if (
|
|
1557
|
+
if (path11 === "/*") path11 = "*";
|
|
1558
1558
|
const methods = method === "ALL" ? Object.keys(middleware) : [method];
|
|
1559
|
-
if (/\*$/.test(
|
|
1560
|
-
const re = buildWildcardRegExp(
|
|
1561
|
-
for (const m of methods) if (!middleware[m][
|
|
1562
|
-
this.#insertPath(m,
|
|
1563
|
-
middleware[m][
|
|
1559
|
+
if (/\*$/.test(path11)) {
|
|
1560
|
+
const re = buildWildcardRegExp(path11);
|
|
1561
|
+
for (const m of methods) if (!middleware[m][path11]) {
|
|
1562
|
+
this.#insertPath(m, path11);
|
|
1563
|
+
middleware[m][path11] = findMiddleware(middleware[m], path11) || findMiddleware(middleware["ALL"], path11) || [];
|
|
1564
1564
|
}
|
|
1565
|
-
for (const handlerMap of [middleware, routes]) for (const m of methods) for (const p in handlerMap[m]) re.test(p) && handlerMap[m][p].push([handler,
|
|
1565
|
+
for (const handlerMap of [middleware, routes]) for (const m of methods) for (const p in handlerMap[m]) re.test(p) && handlerMap[m][p].push([handler, path11]);
|
|
1566
1566
|
return;
|
|
1567
1567
|
}
|
|
1568
|
-
const paths = checkOptionalParameter(
|
|
1569
|
-
for (const
|
|
1570
|
-
if (!routes[m][
|
|
1571
|
-
this.#insertPath(m,
|
|
1572
|
-
routes[m][
|
|
1568
|
+
const paths = checkOptionalParameter(path11) || [path11];
|
|
1569
|
+
for (const path12 of paths) for (const m of methods) {
|
|
1570
|
+
if (!routes[m][path12]) {
|
|
1571
|
+
this.#insertPath(m, path12);
|
|
1572
|
+
routes[m][path12] = findMiddleware(middleware[m], path12) || findMiddleware(middleware["ALL"], path12) || [];
|
|
1573
1573
|
}
|
|
1574
|
-
routes[m][
|
|
1574
|
+
routes[m][path12].push([handler, path12]);
|
|
1575
1575
|
}
|
|
1576
1576
|
}
|
|
1577
1577
|
match = match;
|
|
@@ -1589,11 +1589,11 @@ var RegExpRouter = class {
|
|
|
1589
1589
|
const staticMap = createNullObject();
|
|
1590
1590
|
const handlerData = [];
|
|
1591
1591
|
const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
|
|
1592
|
-
for (const r of [middleware, routes]) for (const
|
|
1593
|
-
const handlers = r[
|
|
1594
|
-
const pathData = trie.paths[
|
|
1592
|
+
for (const r of [middleware, routes]) for (const path11 in r) {
|
|
1593
|
+
const handlers = r[path11];
|
|
1594
|
+
const pathData = trie.paths[path11];
|
|
1595
1595
|
if (!pathData) {
|
|
1596
|
-
staticMap[
|
|
1596
|
+
staticMap[path11] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
|
|
1597
1597
|
continue;
|
|
1598
1598
|
}
|
|
1599
1599
|
handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [h, trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
|
|
@@ -1617,15 +1617,15 @@ var SmartRouter = class {
|
|
|
1617
1617
|
constructor(init) {
|
|
1618
1618
|
this.#routers = init.routers;
|
|
1619
1619
|
}
|
|
1620
|
-
add(method,
|
|
1620
|
+
add(method, path11, handler) {
|
|
1621
1621
|
if (!this.#routes) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
1622
1622
|
this.#routes.push([
|
|
1623
1623
|
method,
|
|
1624
|
-
|
|
1624
|
+
path11,
|
|
1625
1625
|
handler
|
|
1626
1626
|
]);
|
|
1627
1627
|
}
|
|
1628
|
-
match(method,
|
|
1628
|
+
match(method, path11) {
|
|
1629
1629
|
if (!this.#routes) throw new Error("Fatal error");
|
|
1630
1630
|
const routers = this.#routers;
|
|
1631
1631
|
const routes = this.#routes;
|
|
@@ -1636,7 +1636,7 @@ var SmartRouter = class {
|
|
|
1636
1636
|
const router = routers[i];
|
|
1637
1637
|
try {
|
|
1638
1638
|
for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) router.add(...routes[i2]);
|
|
1639
|
-
res = router.match(method,
|
|
1639
|
+
res = router.match(method, path11);
|
|
1640
1640
|
} catch (e) {
|
|
1641
1641
|
if (e instanceof UnsupportedPathError) continue;
|
|
1642
1642
|
throw e;
|
|
@@ -1665,9 +1665,9 @@ var Node3 = class Node4 {
|
|
|
1665
1665
|
#patterns = [];
|
|
1666
1666
|
#pattern;
|
|
1667
1667
|
#params = emptyParams;
|
|
1668
|
-
insert(method,
|
|
1668
|
+
insert(method, path11, handler) {
|
|
1669
1669
|
let curNode = this;
|
|
1670
|
-
const parts = splitRoutingPath(
|
|
1670
|
+
const parts = splitRoutingPath(path11);
|
|
1671
1671
|
const possibleKeys = /* @__PURE__ */ new Set();
|
|
1672
1672
|
let i = 0;
|
|
1673
1673
|
for (const p of parts) {
|
|
@@ -1703,11 +1703,11 @@ var Node3 = class Node4 {
|
|
|
1703
1703
|
}
|
|
1704
1704
|
}
|
|
1705
1705
|
}
|
|
1706
|
-
search(method,
|
|
1706
|
+
search(method, path11) {
|
|
1707
1707
|
const handlerSets = [];
|
|
1708
1708
|
this.#params = emptyParams;
|
|
1709
1709
|
let curNodes = [this];
|
|
1710
|
-
const parts = splitPath(
|
|
1710
|
+
const parts = splitPath(path11);
|
|
1711
1711
|
const curNodesQueue = [];
|
|
1712
1712
|
const len = parts.length;
|
|
1713
1713
|
let partOffsets = null;
|
|
@@ -1743,13 +1743,13 @@ var Node3 = class Node4 {
|
|
|
1743
1743
|
if (matcher !== true) {
|
|
1744
1744
|
if (!partOffsets) {
|
|
1745
1745
|
partOffsets = [];
|
|
1746
|
-
let offset =
|
|
1746
|
+
let offset = path11[0] === "/" ? 1 : 0;
|
|
1747
1747
|
for (let p = 0; p < len; p++) {
|
|
1748
1748
|
partOffsets[p] = offset;
|
|
1749
1749
|
offset += parts[p].length + 1;
|
|
1750
1750
|
}
|
|
1751
1751
|
}
|
|
1752
|
-
const restPathString =
|
|
1752
|
+
const restPathString = path11.slice(partOffsets[i]);
|
|
1753
1753
|
const m = matcher.exec(restPathString);
|
|
1754
1754
|
if (m) {
|
|
1755
1755
|
if (!Object.hasOwn(params, name)) params[name] = m[0];
|
|
@@ -1790,11 +1790,11 @@ var Node3 = class Node4 {
|
|
|
1790
1790
|
var TrieRouter = class {
|
|
1791
1791
|
name = "TrieRouter";
|
|
1792
1792
|
#node = new Node3();
|
|
1793
|
-
add(method,
|
|
1794
|
-
for (const result of checkOptionalParameter(
|
|
1793
|
+
add(method, path11, handler) {
|
|
1794
|
+
for (const result of checkOptionalParameter(path11) || [path11]) this.#node.insert(method, result, handler);
|
|
1795
1795
|
}
|
|
1796
|
-
match(method,
|
|
1797
|
-
return this.#node.search(method,
|
|
1796
|
+
match(method, path11) {
|
|
1797
|
+
return this.#node.search(method, path11);
|
|
1798
1798
|
}
|
|
1799
1799
|
};
|
|
1800
1800
|
|
|
@@ -1844,16 +1844,16 @@ var StreamingApi = class {
|
|
|
1844
1844
|
}
|
|
1845
1845
|
});
|
|
1846
1846
|
}
|
|
1847
|
-
async write(
|
|
1847
|
+
async write(input2) {
|
|
1848
1848
|
try {
|
|
1849
|
-
if (typeof
|
|
1850
|
-
await this.writer.write(
|
|
1849
|
+
if (typeof input2 === "string") input2 = this.encoder.encode(input2);
|
|
1850
|
+
await this.writer.write(input2);
|
|
1851
1851
|
} catch {
|
|
1852
1852
|
}
|
|
1853
1853
|
return this;
|
|
1854
1854
|
}
|
|
1855
|
-
async writeln(
|
|
1856
|
-
await this.write(
|
|
1855
|
+
async writeln(input2) {
|
|
1856
|
+
await this.write(input2 + "\n");
|
|
1857
1857
|
return this;
|
|
1858
1858
|
}
|
|
1859
1859
|
sleep(ms) {
|
|
@@ -1979,15 +1979,15 @@ var toRequestError = (e) => {
|
|
|
1979
1979
|
};
|
|
1980
1980
|
var GlobalRequest = global.Request;
|
|
1981
1981
|
var Request2 = class extends GlobalRequest {
|
|
1982
|
-
constructor(
|
|
1983
|
-
if (typeof
|
|
1984
|
-
|
|
1982
|
+
constructor(input2, options) {
|
|
1983
|
+
if (typeof input2 === "object" && getRequestCache in input2) {
|
|
1984
|
+
input2 = input2[getRequestCache]();
|
|
1985
1985
|
}
|
|
1986
1986
|
if (typeof options?.body?.getReader !== "undefined") {
|
|
1987
1987
|
;
|
|
1988
1988
|
options.duplex ??= "half";
|
|
1989
1989
|
}
|
|
1990
|
-
super(
|
|
1990
|
+
super(input2, options);
|
|
1991
1991
|
}
|
|
1992
1992
|
};
|
|
1993
1993
|
var newHeadersFromIncoming = (incoming) => {
|
|
@@ -3166,6 +3166,26 @@ function buildRuns({ cwd, entries, live }) {
|
|
|
3166
3166
|
}
|
|
3167
3167
|
|
|
3168
3168
|
// server/chats.ts
|
|
3169
|
+
var SessionActionError = class extends Error {
|
|
3170
|
+
status;
|
|
3171
|
+
uncertain;
|
|
3172
|
+
constructor(message, status = 409, uncertain = false) {
|
|
3173
|
+
super(message);
|
|
3174
|
+
this.status = status;
|
|
3175
|
+
this.uncertain = uncertain;
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
var CLIENT_COMMANDS = /* @__PURE__ */ new Set(["login", "model", "compact", "new"]);
|
|
3179
|
+
async function boundedAction(operation, ms, name) {
|
|
3180
|
+
let timer;
|
|
3181
|
+
try {
|
|
3182
|
+
return await Promise.race([operation, new Promise((_, reject) => {
|
|
3183
|
+
timer = setTimeout(() => reject(new SessionActionError(`${name} is still awaiting Pi. Delivery/completion is uncertain; do not retry. Read the chat's latest state.`, 504, true)), ms);
|
|
3184
|
+
})]);
|
|
3185
|
+
} finally {
|
|
3186
|
+
clearTimeout(timer);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3169
3189
|
var toolLine = (name, args) => {
|
|
3170
3190
|
const a = args ?? {};
|
|
3171
3191
|
if (name === "bash") return `bash \xB7 ${String(a.command ?? "").split("\n")[0].slice(0, 90)}`;
|
|
@@ -3238,10 +3258,15 @@ var Chat = class {
|
|
|
3238
3258
|
file = null;
|
|
3239
3259
|
sessionName = null;
|
|
3240
3260
|
autoNamed = false;
|
|
3261
|
+
sessionReady = false;
|
|
3262
|
+
action = null;
|
|
3263
|
+
compaction = { status: "idle" };
|
|
3264
|
+
currentModel;
|
|
3265
|
+
modelRevision = 0;
|
|
3266
|
+
modelsRequireRestart = false;
|
|
3241
3267
|
constructor(id, options) {
|
|
3242
3268
|
this.id = id;
|
|
3243
3269
|
this.options = options;
|
|
3244
|
-
this.model = options.model ?? null;
|
|
3245
3270
|
this.pi = new PiRpc({
|
|
3246
3271
|
command: options.piCommand,
|
|
3247
3272
|
args: ["--session-dir", options.sessionDir, ...options.sessionFile ? ["--session", options.sessionFile] : ["--session-id", id], ...options.args ?? [], ...options.model ? ["--model", options.model] : []],
|
|
@@ -3281,6 +3306,12 @@ var Chat = class {
|
|
|
3281
3306
|
get busy() {
|
|
3282
3307
|
return !!this.live?.busy;
|
|
3283
3308
|
}
|
|
3309
|
+
get setupBlocked() {
|
|
3310
|
+
return !this.sessionReady && this.dialogs.length > 0;
|
|
3311
|
+
}
|
|
3312
|
+
get sessionActions() {
|
|
3313
|
+
return { setupBlocked: this.setupBlocked, busy: this.busy || this.runs.some((r) => r.alive) || this.liveLoops.size > 0 || !!this.action, compaction: { ...this.compaction } };
|
|
3314
|
+
}
|
|
3284
3315
|
touch() {
|
|
3285
3316
|
try {
|
|
3286
3317
|
this.options.onUpdate?.(this);
|
|
@@ -3325,22 +3356,25 @@ var Chat = class {
|
|
|
3325
3356
|
}
|
|
3326
3357
|
/** Re-read the session's entries (cursor-based) and messages, then rebuild the runs. */
|
|
3327
3358
|
async refresh(full = false) {
|
|
3359
|
+
if (this.stopping) return;
|
|
3328
3360
|
if (this.refreshing) return this.refreshing;
|
|
3329
3361
|
this.refreshing = (async () => {
|
|
3330
3362
|
try {
|
|
3331
3363
|
if (full) this.cursor = void 0;
|
|
3332
|
-
const entries = await this.pi.send({ type: "get_entries", ...this.cursor ? { since: this.cursor } : {} });
|
|
3364
|
+
const entries = await this.pi.send({ type: "get_entries", ...this.cursor ? { since: this.cursor } : {} }, 1e4);
|
|
3333
3365
|
const got = entries.entries ?? [];
|
|
3334
3366
|
if (this.cursor) this.entries = got.length ? [...this.entries, ...got] : this.entries;
|
|
3335
3367
|
else this.entries = got;
|
|
3336
3368
|
this.cursor = got.at(-1)?.id ?? this.cursor;
|
|
3337
|
-
const messages = await this.pi.send({ type: "get_messages" });
|
|
3369
|
+
const messages = await this.pi.send({ type: "get_messages" }, 1e4);
|
|
3338
3370
|
if (process.env.LOOPS_UI_DEBUG) console.error(`[${this.id}] refresh: ${got.length} new entries, ${(messages.messages ?? []).map((m) => `${m.role}[${Array.isArray(m.content) ? m.content.map((c) => c.type).join("+") : typeof m.content}]`).join(" ")}`);
|
|
3339
3371
|
this.items = itemsFromMessages(messages.messages ?? []);
|
|
3340
|
-
const
|
|
3372
|
+
const modelRevision = this.modelRevision;
|
|
3373
|
+
const state = await this.pi.send({ type: "get_state" }, 1e4).catch(() => null);
|
|
3341
3374
|
const st = state;
|
|
3342
3375
|
if (st?.sessionName) this.sessionName = String(st.sessionName);
|
|
3343
|
-
if (st
|
|
3376
|
+
if (st) this.readModel(st.model, modelRevision);
|
|
3377
|
+
this.sessionReady = true;
|
|
3344
3378
|
this.runs = buildRuns({ cwd: this.options.cwd, entries: this.entries, live: this.liveLoops });
|
|
3345
3379
|
const auto = autoNameFor(this.runs, this.sessionName, this.autoNamed);
|
|
3346
3380
|
if (auto) {
|
|
@@ -3350,8 +3384,10 @@ var Chat = class {
|
|
|
3350
3384
|
}
|
|
3351
3385
|
this.refreshError = "";
|
|
3352
3386
|
} catch (e) {
|
|
3353
|
-
|
|
3354
|
-
|
|
3387
|
+
if (!this.stopping) {
|
|
3388
|
+
console.error(`[${this.id}] refresh failed:`, e.message ?? e);
|
|
3389
|
+
this.refreshError = String(e.message ?? e);
|
|
3390
|
+
}
|
|
3355
3391
|
} finally {
|
|
3356
3392
|
this.refreshing = null;
|
|
3357
3393
|
this.broadcast(true);
|
|
@@ -3426,6 +3462,22 @@ var Chat = class {
|
|
|
3426
3462
|
this.touch();
|
|
3427
3463
|
}
|
|
3428
3464
|
async prompt(text, behavior) {
|
|
3465
|
+
const candidate = text.trimStart();
|
|
3466
|
+
if (candidate.startsWith("/")) {
|
|
3467
|
+
const slash = /^\/([^\s]+)([\s\S]*)$/.exec(candidate);
|
|
3468
|
+
if (!slash) throw new SessionActionError("A discovered slash command name is required.", 400);
|
|
3469
|
+
await this.validateCommand(slash[1]);
|
|
3470
|
+
text = `/${slash[1]}${slash[2].replace(/^\s/, " ")}`;
|
|
3471
|
+
}
|
|
3472
|
+
if (this.action && !/^\s*\/loop\s+stop(?:\s|$)/.test(text)) throw new SessionActionError("A session action is in progress. Wait for Pi to finish.");
|
|
3473
|
+
return this.dispatchPrompt(text, behavior);
|
|
3474
|
+
}
|
|
3475
|
+
/** A server-owned safety control, not an arbitrary slash/RPC bypass. Run ownership is checked by the route. */
|
|
3476
|
+
async stopLoop(run3) {
|
|
3477
|
+
if (!/^[\w.-]+$/.test(run3)) throw new SessionActionError("Exact run ID required.", 400);
|
|
3478
|
+
return this.dispatchPrompt(`/loop stop ${run3}`);
|
|
3479
|
+
}
|
|
3480
|
+
async dispatchPrompt(text, behavior) {
|
|
3429
3481
|
const previous = this.live;
|
|
3430
3482
|
if (!text.startsWith("/") && !this.live) this.live = { busy: true, chat: this.id, startedAt: Date.now(), user: text, text: "", tools: [] };
|
|
3431
3483
|
this.broadcast(true);
|
|
@@ -3508,19 +3560,148 @@ var Chat = class {
|
|
|
3508
3560
|
stateFor(run3) {
|
|
3509
3561
|
return stateFromEntries(this.entries, run3);
|
|
3510
3562
|
}
|
|
3511
|
-
|
|
3563
|
+
readModel(model, revision = this.modelRevision) {
|
|
3564
|
+
if (revision !== this.modelRevision) return;
|
|
3565
|
+
if (typeof model?.id === "string" && typeof model?.provider === "string") {
|
|
3566
|
+
this.currentModel = { provider: model.provider, id: model.id };
|
|
3567
|
+
this.model = `${model.provider}/${model.id}`;
|
|
3568
|
+
} else if (model == null) {
|
|
3569
|
+
this.currentModel = void 0;
|
|
3570
|
+
this.model = null;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
/** Only Pi's dynamic prompt-invocable resources, never terminal-only builtins. */
|
|
3574
|
+
async commands() {
|
|
3575
|
+
if (this.setupBlocked) return { commands: [], supported: false, setupBlocked: true, error: "Complete Pi's startup decision before loading commands." };
|
|
3576
|
+
try {
|
|
3577
|
+
const result = await this.pi.send({ type: "get_commands" }, 1e4);
|
|
3578
|
+
if (!Array.isArray(result?.commands)) return { commands: [], supported: false, setupBlocked: false, error: "This Pi executable does not support command discovery." };
|
|
3579
|
+
return {
|
|
3580
|
+
commands: result.commands.filter((c) => c && typeof c.name === "string" && !!c.name && !/[\s/]/.test(c.name) && ["extension", "prompt", "skill"].includes(c.source)).map((c) => ({ name: c.name, source: c.source, ...typeof c.description === "string" ? { description: c.description } : {} })),
|
|
3581
|
+
supported: true,
|
|
3582
|
+
setupBlocked: false
|
|
3583
|
+
};
|
|
3584
|
+
} catch (error) {
|
|
3585
|
+
return { commands: [], supported: false, setupBlocked: false, error: String(error.message ?? error) };
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
async validateCommand(name) {
|
|
3589
|
+
const catalog = await this.commands();
|
|
3590
|
+
if (catalog.commands.some((command) => command.name === name)) return;
|
|
3591
|
+
if (CLIENT_COMMANDS.has(name)) throw new SessionActionError(`/${name} is a browser action. Use ${name === "new" ? "New chat" : name === "login" ? "provider settings" : `the ${name} control`} instead.`, 400);
|
|
3592
|
+
if (!catalog.supported) throw new SessionActionError(catalog.error ?? "Pi command discovery is unavailable; slash commands cannot be safely sent.", 409);
|
|
3593
|
+
throw new SessionActionError(`Unknown or unsupported slash command: /${name}. It was not sent to the model.`, 400);
|
|
3594
|
+
}
|
|
3595
|
+
/** Public RPC returns Pi's available snapshot; never manufacture provider/model spellings. */
|
|
3596
|
+
async models() {
|
|
3597
|
+
if (this.setupBlocked) return { models: [], current: this.currentModel, setupBlocked: true, error: "Complete Pi's startup decision before loading models." };
|
|
3598
|
+
const modelRevision = this.modelRevision;
|
|
3599
|
+
const [available2, state] = await Promise.all([
|
|
3600
|
+
this.pi.send({ type: "get_available_models" }, 1e4),
|
|
3601
|
+
this.pi.send({ type: "get_state" }, 1e4)
|
|
3602
|
+
]);
|
|
3603
|
+
this.readModel(state?.model, modelRevision);
|
|
3604
|
+
return {
|
|
3605
|
+
// Custom SDK models may carry API headers or credential-bearing base URLs. Only this display DTO crosses HTTP.
|
|
3606
|
+
models: (available2?.models ?? []).filter((m) => m && typeof m.id === "string" && typeof m.provider === "string").map((m) => ({ id: m.id, provider: m.provider, ...typeof m.name === "string" ? { name: m.name } : {}, ...Array.isArray(m.input) ? { input: m.input.filter((v) => typeof v === "string") } : {} })),
|
|
3607
|
+
current: this.currentModel,
|
|
3608
|
+
setupBlocked: false,
|
|
3609
|
+
...this.modelsRequireRestart ? { requiresRestart: true, warning: "This Pi executable cannot refresh model credentials over RPC. Restart the agent explicitly after work finishes to see newly configured models." } : {}
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
/** The bundled compatibility runner refreshes through Pi's public ModelRuntime API, without network calls. */
|
|
3613
|
+
async refreshModels() {
|
|
3614
|
+
if (this.setupBlocked || this.sessionActions.busy) return this.models();
|
|
3615
|
+
this.action = "metadata";
|
|
3616
|
+
this.broadcast(true);
|
|
3617
|
+
const operation = (async () => {
|
|
3618
|
+
try {
|
|
3619
|
+
await this.pi.send({ type: "refresh_available_models" });
|
|
3620
|
+
this.modelsRequireRestart = false;
|
|
3621
|
+
} catch (error) {
|
|
3622
|
+
if (!/unknown command|unsupported|not supported/i.test(String(error.message ?? error))) throw error;
|
|
3623
|
+
this.modelsRequireRestart = true;
|
|
3624
|
+
} finally {
|
|
3625
|
+
this.action = null;
|
|
3626
|
+
this.broadcast(true);
|
|
3627
|
+
}
|
|
3628
|
+
})();
|
|
3629
|
+
await boundedAction(operation, 1e4, "Model metadata refresh");
|
|
3630
|
+
return this.models();
|
|
3631
|
+
}
|
|
3632
|
+
/** Compatibility for the existing config editor's composite model IDs. */
|
|
3512
3633
|
async availableModels() {
|
|
3513
3634
|
try {
|
|
3514
|
-
const list = await this.
|
|
3635
|
+
const list = await this.models();
|
|
3515
3636
|
return (list.models ?? []).map((m) => ({ id: `${m.provider}/${m.id}`, provider: m.provider, model: m.id, images: Array.isArray(m.input) ? m.input.includes("image") : null }));
|
|
3516
3637
|
} catch {
|
|
3517
3638
|
return [];
|
|
3518
3639
|
}
|
|
3519
3640
|
}
|
|
3520
|
-
async
|
|
3521
|
-
|
|
3522
|
-
this.
|
|
3641
|
+
async beginAction(action) {
|
|
3642
|
+
if (this.setupBlocked) throw new SessionActionError("Complete Pi's startup decision first.");
|
|
3643
|
+
if (this.action) throw new SessionActionError("A session action is already in progress. Do not retry until Pi finishes.");
|
|
3644
|
+
this.action = action;
|
|
3523
3645
|
this.broadcast(true);
|
|
3646
|
+
try {
|
|
3647
|
+
await this.refresh();
|
|
3648
|
+
const state = await this.pi.send({ type: "get_state" }, 1e4);
|
|
3649
|
+
if (this.busy || this.runs.some((r) => r.alive) || this.liveLoops.size || state.isStreaming || state.isCompacting || state.pendingMessageCount) {
|
|
3650
|
+
throw new SessionActionError("Stop or finish the parent agent and background loops before changing the model or compacting.");
|
|
3651
|
+
}
|
|
3652
|
+
} catch (error) {
|
|
3653
|
+
this.action = null;
|
|
3654
|
+
this.broadcast(true);
|
|
3655
|
+
throw error;
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
async setModel(provider, modelId) {
|
|
3659
|
+
if (!provider || !modelId) throw new SessionActionError("Exact provider and modelId are required.", 400);
|
|
3660
|
+
await this.beginAction("model");
|
|
3661
|
+
const operation = (async () => {
|
|
3662
|
+
try {
|
|
3663
|
+
const models = await this.pi.send({ type: "get_available_models" }, 1e4);
|
|
3664
|
+
if (!models.models?.some((m) => m.provider === provider && m.id === modelId)) throw new SessionActionError("The exact model is not in Pi's available catalog.", 400);
|
|
3665
|
+
const result = await this.pi.send({ type: "set_model", provider, modelId });
|
|
3666
|
+
this.modelRevision++;
|
|
3667
|
+
this.readModel(result?.provider && result?.id ? result : { provider, id: modelId });
|
|
3668
|
+
return this.currentModel;
|
|
3669
|
+
} finally {
|
|
3670
|
+
this.action = null;
|
|
3671
|
+
this.broadcast(true);
|
|
3672
|
+
}
|
|
3673
|
+
})();
|
|
3674
|
+
return boundedAction(operation, 2e4, "Model change");
|
|
3675
|
+
}
|
|
3676
|
+
async compact(customInstructions) {
|
|
3677
|
+
if (customInstructions !== void 0 && typeof customInstructions !== "string") throw new SessionActionError("customInstructions must be text.", 400);
|
|
3678
|
+
await this.beginAction("compact");
|
|
3679
|
+
this.compaction = { status: "running", startedAt: Date.now() };
|
|
3680
|
+
this.broadcast(true);
|
|
3681
|
+
const operation = (async () => {
|
|
3682
|
+
try {
|
|
3683
|
+
const result = await this.pi.send({ type: "compact", ...customInstructions !== void 0 ? { customInstructions } : {} });
|
|
3684
|
+
this.compaction = { ...this.compaction, status: "succeeded", finishedAt: Date.now(), result };
|
|
3685
|
+
if (this.refreshing) await this.refreshing;
|
|
3686
|
+
await this.refresh(true);
|
|
3687
|
+
return result;
|
|
3688
|
+
} catch (error) {
|
|
3689
|
+
this.compaction = { ...this.compaction, status: "error", finishedAt: Date.now(), error: String(error.message ?? error) };
|
|
3690
|
+
throw error;
|
|
3691
|
+
} finally {
|
|
3692
|
+
this.action = null;
|
|
3693
|
+
this.broadcast(true);
|
|
3694
|
+
}
|
|
3695
|
+
})();
|
|
3696
|
+
try {
|
|
3697
|
+
return await boundedAction(operation, 12e4, "Compaction");
|
|
3698
|
+
} catch (error) {
|
|
3699
|
+
if (error instanceof SessionActionError && error.uncertain && this.compaction.status === "running") {
|
|
3700
|
+
this.compaction = { ...this.compaction, status: "uncertain", error: error.message };
|
|
3701
|
+
this.broadcast(true);
|
|
3702
|
+
}
|
|
3703
|
+
throw error;
|
|
3704
|
+
}
|
|
3524
3705
|
}
|
|
3525
3706
|
/** An attempt's transcript, straight from the session file the attempt forked into. */
|
|
3526
3707
|
sessionItems(file) {
|
|
@@ -3557,6 +3738,7 @@ var Chat = class {
|
|
|
3557
3738
|
live: this.live,
|
|
3558
3739
|
runs,
|
|
3559
3740
|
model: this.model,
|
|
3741
|
+
sessionActions: this.sessionActions,
|
|
3560
3742
|
error: this.pi.running ? this.refreshError || void 0 : this.exitError || this.refreshError || void 0,
|
|
3561
3743
|
questions: runs.flatMap((r) => r.questions),
|
|
3562
3744
|
dialogs: this.dialogs,
|
|
@@ -3933,9 +4115,9 @@ async function connect(wsUrl) {
|
|
|
3933
4115
|
sock.addEventListener("open", () => res());
|
|
3934
4116
|
sock.addEventListener("error", () => rej(new Error("could not connect to the browser's DevTools endpoint")));
|
|
3935
4117
|
});
|
|
3936
|
-
const send = (method, params = {}, sessionId) => new Promise((ok,
|
|
4118
|
+
const send = (method, params = {}, sessionId) => new Promise((ok, fail2) => {
|
|
3937
4119
|
const n = ++id;
|
|
3938
|
-
pending.set(n, { ok, fail });
|
|
4120
|
+
pending.set(n, { ok, fail: fail2 });
|
|
3939
4121
|
sock.send(JSON.stringify({ id: n, method, params, ...sessionId ? { sessionId } : {} }));
|
|
3940
4122
|
});
|
|
3941
4123
|
return {
|
|
@@ -4733,9 +4915,377 @@ function originalBaseline(state) {
|
|
|
4733
4915
|
return state.rounds?.find((round) => round.n === 1)?.from || state.baseCommit;
|
|
4734
4916
|
}
|
|
4735
4917
|
|
|
4918
|
+
// server/session-actions.ts
|
|
4919
|
+
function createSessionActions(options) {
|
|
4920
|
+
const routes = new Hono3();
|
|
4921
|
+
const chat = async (id) => {
|
|
4922
|
+
if (typeof id !== "string" || !id.trim()) throw new SessionActionError("chat required", 400);
|
|
4923
|
+
return options.requireChat(id);
|
|
4924
|
+
};
|
|
4925
|
+
routes.onError((error, c) => {
|
|
4926
|
+
if (error instanceof HTTPException) return c.json({ error: error.message }, error.status);
|
|
4927
|
+
if (error instanceof SessionActionError) return c.json({ error: error.message, ...error.uncertain ? { uncertain: true } : {} }, error.status);
|
|
4928
|
+
return c.json({ error: error.message }, 502);
|
|
4929
|
+
});
|
|
4930
|
+
routes.get("/commands", async (c) => c.json(await (await chat(c.req.query("chat"))).commands()));
|
|
4931
|
+
routes.get("/models", async (c) => {
|
|
4932
|
+
const target = await chat(c.req.query("chat"));
|
|
4933
|
+
return c.json(await (target.sessionActions.busy ? target.models() : target.refreshModels()));
|
|
4934
|
+
});
|
|
4935
|
+
routes.post("/models", async (c) => {
|
|
4936
|
+
const body = await c.req.json().catch(() => null);
|
|
4937
|
+
if (typeof body?.provider !== "string" || !body.provider || typeof body?.modelId !== "string" || !body.modelId) throw new SessionActionError("Exact provider and modelId are required.", 400);
|
|
4938
|
+
const current = await (await chat(body.chat)).setModel(body.provider, body.modelId);
|
|
4939
|
+
return c.json({ ok: true, current });
|
|
4940
|
+
});
|
|
4941
|
+
routes.post("/compact", async (c) => {
|
|
4942
|
+
const body = await c.req.json().catch(() => null);
|
|
4943
|
+
if (body?.customInstructions !== void 0 && typeof body.customInstructions !== "string") throw new SessionActionError("customInstructions must be text.", 400);
|
|
4944
|
+
const result = await (await chat(body?.chat)).compact(body?.customInstructions);
|
|
4945
|
+
return c.json({ ok: true, result });
|
|
4946
|
+
});
|
|
4947
|
+
routes.post("/commands/run", async (c) => {
|
|
4948
|
+
const body = await c.req.json().catch(() => null);
|
|
4949
|
+
if (typeof body?.name !== "string" || !body.name || /[\s/]/.test(body.name)) throw new SessionActionError("An exact discovered command name (without /) is required.", 400);
|
|
4950
|
+
if (body.args !== void 0 && typeof body.args !== "string") throw new SessionActionError("Command args must be text.", 400);
|
|
4951
|
+
const target = await chat(body.chat);
|
|
4952
|
+
const disposition = await target.prompt(`/${body.name}${body.args ? ` ${body.args}` : ""}`);
|
|
4953
|
+
return c.json({ disposition });
|
|
4954
|
+
});
|
|
4955
|
+
return routes;
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4958
|
+
// server/provider-auth.ts
|
|
4959
|
+
import { randomUUID } from "node:crypto";
|
|
4960
|
+
import path9 from "node:path";
|
|
4961
|
+
|
|
4962
|
+
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/middleware/body-limit/index.js
|
|
4963
|
+
var ERROR_MESSAGE = "Payload Too Large";
|
|
4964
|
+
var bodyLimit = (options) => {
|
|
4965
|
+
const onError = options.onError || (() => {
|
|
4966
|
+
const res = new Response(ERROR_MESSAGE, { status: 413 });
|
|
4967
|
+
throw new HTTPException(413, { res });
|
|
4968
|
+
});
|
|
4969
|
+
const maxSize = options.maxSize;
|
|
4970
|
+
return async function bodyLimit2(c, next) {
|
|
4971
|
+
if (!c.req.raw.body) return next();
|
|
4972
|
+
const hasTransferEncoding = c.req.raw.headers.has("transfer-encoding");
|
|
4973
|
+
if (c.req.raw.headers.has("content-length") && !hasTransferEncoding) return parseInt(c.req.raw.headers.get("content-length") || "0", 10) > maxSize ? onError(c) : next();
|
|
4974
|
+
let size = 0;
|
|
4975
|
+
const chunks = [];
|
|
4976
|
+
const rawReader = c.req.raw.body.getReader();
|
|
4977
|
+
for (; ; ) {
|
|
4978
|
+
const { done, value } = await rawReader.read();
|
|
4979
|
+
if (done) break;
|
|
4980
|
+
size += value.length;
|
|
4981
|
+
if (size > maxSize) return onError(c);
|
|
4982
|
+
chunks.push(value);
|
|
4983
|
+
}
|
|
4984
|
+
const requestInit = {
|
|
4985
|
+
body: new ReadableStream({ start(controller) {
|
|
4986
|
+
for (const chunk of chunks) controller.enqueue(chunk);
|
|
4987
|
+
controller.close();
|
|
4988
|
+
} }),
|
|
4989
|
+
duplex: "half"
|
|
4990
|
+
};
|
|
4991
|
+
c.req.raw = new Request(c.req.raw, requestInit);
|
|
4992
|
+
return next();
|
|
4993
|
+
};
|
|
4994
|
+
};
|
|
4995
|
+
|
|
4996
|
+
// server/provider-auth.ts
|
|
4997
|
+
var AuthRequestError = class extends Error {
|
|
4998
|
+
status;
|
|
4999
|
+
constructor(status, message) {
|
|
5000
|
+
super(message);
|
|
5001
|
+
this.status = status;
|
|
5002
|
+
}
|
|
5003
|
+
};
|
|
5004
|
+
var fail = (status, message) => {
|
|
5005
|
+
throw new AuthRequestError(status, message);
|
|
5006
|
+
};
|
|
5007
|
+
var MAX_FLOW_MS = 15 * 6e4;
|
|
5008
|
+
var TERMINAL_MS = 6e4;
|
|
5009
|
+
var MAX_INPUT = 16384;
|
|
5010
|
+
var copy = (value) => structuredClone(value);
|
|
5011
|
+
function httpsUrl(value) {
|
|
5012
|
+
try {
|
|
5013
|
+
const url = new URL(value);
|
|
5014
|
+
if (url.protocol === "https:" && !url.username && !url.password && value.length <= MAX_INPUT) return url.href;
|
|
5015
|
+
} catch {
|
|
5016
|
+
}
|
|
5017
|
+
return void 0;
|
|
5018
|
+
}
|
|
5019
|
+
function input(value, label, max = MAX_INPUT) {
|
|
5020
|
+
if (typeof value !== "string" || !value.trim() || value.length > max || value.includes("\0")) fail(400, `Invalid ${label}`);
|
|
5021
|
+
return value;
|
|
5022
|
+
}
|
|
5023
|
+
function keyInput(value) {
|
|
5024
|
+
const key = input(value, "API key", 8192).trim();
|
|
5025
|
+
if (key.startsWith("!") || /[\r\n]/.test(key)) fail(400, "A literal, single-line API key is required");
|
|
5026
|
+
return key;
|
|
5027
|
+
}
|
|
5028
|
+
var ProviderAuth = class {
|
|
5029
|
+
runtime;
|
|
5030
|
+
options;
|
|
5031
|
+
flow;
|
|
5032
|
+
active;
|
|
5033
|
+
waiting;
|
|
5034
|
+
terminalAt = 0;
|
|
5035
|
+
closed = false;
|
|
5036
|
+
constructor(runtime, options = {}) {
|
|
5037
|
+
this.runtime = runtime;
|
|
5038
|
+
this.options = options;
|
|
5039
|
+
}
|
|
5040
|
+
async snapshot() {
|
|
5041
|
+
const stored = new Set((await this.runtime.listCredentials()).map((c) => c.providerId));
|
|
5042
|
+
const providers = this.runtime.getProviders().map((p) => ({
|
|
5043
|
+
id: p.id,
|
|
5044
|
+
name: p.name,
|
|
5045
|
+
oauth: Boolean(p.auth.oauth),
|
|
5046
|
+
apiKey: Boolean(p.auth.apiKey?.login),
|
|
5047
|
+
connected: stored.has(p.id)
|
|
5048
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
5049
|
+
if (!this.active && this.flow && Date.now() - this.terminalAt > TERMINAL_MS) this.flow = void 0;
|
|
5050
|
+
return { providers, ...this.flow ? { flow: copy(this.flow) } : {} };
|
|
5051
|
+
}
|
|
5052
|
+
start(provider, key) {
|
|
5053
|
+
if (this.closed) fail(503, "Provider login is unavailable");
|
|
5054
|
+
if (this.active) fail(409, "A provider connection is already in progress");
|
|
5055
|
+
const id = input(provider, "provider", 128);
|
|
5056
|
+
const selected = this.runtime.getProviders().find((p) => p.id === id);
|
|
5057
|
+
if (!selected) fail(400, "Unknown provider");
|
|
5058
|
+
const type = key === void 0 ? "oauth" : "api_key";
|
|
5059
|
+
if (type === "oauth" ? !selected.auth.oauth : !selected.auth.apiKey?.login) fail(400, "Login method is not supported");
|
|
5060
|
+
let suppliedKey = type === "api_key" ? keyInput(key) : void 0;
|
|
5061
|
+
const controller = new AbortController();
|
|
5062
|
+
const flow = { id: randomUUID(), provider: id, status: "pending" };
|
|
5063
|
+
this.flow = flow;
|
|
5064
|
+
const interaction = {
|
|
5065
|
+
signal: controller.signal,
|
|
5066
|
+
notify: (event) => {
|
|
5067
|
+
if (this.flow !== flow || flow.status !== "pending" || controller.signal.aborted) return;
|
|
5068
|
+
this.notify(flow, event);
|
|
5069
|
+
},
|
|
5070
|
+
prompt: (prompt) => {
|
|
5071
|
+
controller.signal.throwIfAborted();
|
|
5072
|
+
prompt.signal?.throwIfAborted();
|
|
5073
|
+
if (suppliedKey !== void 0 && prompt.type === "secret") {
|
|
5074
|
+
const value = suppliedKey;
|
|
5075
|
+
suppliedKey = void 0;
|
|
5076
|
+
return Promise.resolve(value);
|
|
5077
|
+
}
|
|
5078
|
+
return this.prompt(flow, prompt, controller.signal, type === "api_key");
|
|
5079
|
+
}
|
|
5080
|
+
};
|
|
5081
|
+
const timeout = Math.min(MAX_FLOW_MS, Math.max(1, this.options.timeoutMs ?? MAX_FLOW_MS));
|
|
5082
|
+
const timer = setTimeout(() => this.stop(flow.id, "expired"), timeout);
|
|
5083
|
+
timer.unref();
|
|
5084
|
+
const active = { controller, timer, done: Promise.resolve(), deadline: Date.now() + timeout };
|
|
5085
|
+
this.active = active;
|
|
5086
|
+
active.done = Promise.resolve().then(async () => {
|
|
5087
|
+
try {
|
|
5088
|
+
await this.runtime.login(id, type, interaction, this.options.loginOptions);
|
|
5089
|
+
if (flow.status === "pending") this.finish(flow, "succeeded");
|
|
5090
|
+
this.changed(id);
|
|
5091
|
+
} catch (error) {
|
|
5092
|
+
if (error instanceof Error && error.name === "CredentialSynchronizationError") {
|
|
5093
|
+
if (flow.status === "pending") this.finish(flow, "succeeded");
|
|
5094
|
+
this.changed(id);
|
|
5095
|
+
} else if (flow.status === "pending") {
|
|
5096
|
+
this.finish(flow, "failed", "Connection failed. Check the provider sign-in requirements and try again.");
|
|
5097
|
+
}
|
|
5098
|
+
} finally {
|
|
5099
|
+
suppliedKey = void 0;
|
|
5100
|
+
clearTimeout(active.timer);
|
|
5101
|
+
this.waiting?.reject(new Error("Login finished"));
|
|
5102
|
+
if (this.active === active) this.active = void 0;
|
|
5103
|
+
}
|
|
5104
|
+
});
|
|
5105
|
+
return copy(flow);
|
|
5106
|
+
}
|
|
5107
|
+
answer(id, promptId, value) {
|
|
5108
|
+
const flow = this.current(id);
|
|
5109
|
+
if (!this.waiting || this.waiting.id !== promptId || flow.prompt?.id !== promptId) fail(409, "This login prompt is no longer active");
|
|
5110
|
+
if (typeof value !== "string" || value.length > MAX_INPUT || value.includes("\0")) fail(400, "Invalid login answer");
|
|
5111
|
+
if (flow.prompt?.type === "select" && !flow.prompt.options?.some((o) => o.id === value)) fail(400, "Invalid login option");
|
|
5112
|
+
this.waiting.resolve(value);
|
|
5113
|
+
return copy(flow);
|
|
5114
|
+
}
|
|
5115
|
+
cancel(id) {
|
|
5116
|
+
const flow = this.current(id);
|
|
5117
|
+
this.stop(flow.id, "cancelled");
|
|
5118
|
+
return copy(flow);
|
|
5119
|
+
}
|
|
5120
|
+
/** Abort pending SDK interactions during server shutdown (including loopback listeners). */
|
|
5121
|
+
async close() {
|
|
5122
|
+
this.closed = true;
|
|
5123
|
+
const active = this.active;
|
|
5124
|
+
if (active && this.flow?.status === "pending") this.stop(this.flow.id, "cancelled");
|
|
5125
|
+
await active?.done;
|
|
5126
|
+
}
|
|
5127
|
+
current(id) {
|
|
5128
|
+
if (typeof id !== "string" || id !== this.flow?.id) return fail(404, "Unknown or stale login flow");
|
|
5129
|
+
if (!this.active || this.flow.status !== "pending") return fail(409, "This login flow is no longer active");
|
|
5130
|
+
return this.flow;
|
|
5131
|
+
}
|
|
5132
|
+
changed(provider) {
|
|
5133
|
+
void Promise.resolve().then(() => this.options.onCredentialsChanged?.(provider)).catch(() => {
|
|
5134
|
+
});
|
|
5135
|
+
}
|
|
5136
|
+
stop(id, status) {
|
|
5137
|
+
if (this.flow?.id !== id || this.flow.status !== "pending") return;
|
|
5138
|
+
this.finish(this.flow, status, status === "expired" ? "Sign-in expired. Start a new connection." : void 0);
|
|
5139
|
+
this.active?.controller.abort(new Error("Login cancelled"));
|
|
5140
|
+
this.waiting?.reject(new Error("Login cancelled"));
|
|
5141
|
+
}
|
|
5142
|
+
finish(flow, status, error) {
|
|
5143
|
+
for (const key of ["authorizationUrl", "instructions", "prompt", "deviceCode"]) delete flow[key];
|
|
5144
|
+
flow.status = status;
|
|
5145
|
+
if (error) flow.error = error;
|
|
5146
|
+
this.terminalAt = Date.now();
|
|
5147
|
+
}
|
|
5148
|
+
notify(flow, event) {
|
|
5149
|
+
if (event.type === "auth_url") {
|
|
5150
|
+
const url = httpsUrl(event.url);
|
|
5151
|
+
if (!url) throw new Error("Unsafe authorization URL");
|
|
5152
|
+
flow.authorizationUrl = url;
|
|
5153
|
+
flow.instructions = event.instructions?.slice(0, 4096);
|
|
5154
|
+
} else if (event.type === "device_code") {
|
|
5155
|
+
const url = httpsUrl(event.verificationUri);
|
|
5156
|
+
if (!url) throw new Error("Unsafe verification URL");
|
|
5157
|
+
const expires = event.expiresInSeconds;
|
|
5158
|
+
const lifetime = typeof expires === "number" && Number.isFinite(expires) && expires > 0 ? expires : void 0;
|
|
5159
|
+
flow.authorizationUrl = url;
|
|
5160
|
+
flow.deviceCode = {
|
|
5161
|
+
userCode: event.userCode.slice(0, 256),
|
|
5162
|
+
verificationUri: url,
|
|
5163
|
+
...lifetime === void 0 ? {} : { expiresInSeconds: lifetime }
|
|
5164
|
+
};
|
|
5165
|
+
if (lifetime !== void 0 && this.active) {
|
|
5166
|
+
const remaining = Math.min(this.active.deadline - Date.now(), lifetime * 1e3);
|
|
5167
|
+
clearTimeout(this.active.timer);
|
|
5168
|
+
this.active.deadline = Date.now() + remaining;
|
|
5169
|
+
this.active.timer = setTimeout(() => this.stop(flow.id, "expired"), Math.max(1, remaining));
|
|
5170
|
+
this.active.timer.unref();
|
|
5171
|
+
}
|
|
5172
|
+
} else if (event.type === "info") {
|
|
5173
|
+
flow.instructions = event.message.slice(0, 4096);
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
5176
|
+
prompt(flow, prompt, signal, apiKey) {
|
|
5177
|
+
if (this.waiting) return Promise.reject(new Error("Concurrent login prompts are not supported"));
|
|
5178
|
+
const id = randomUUID();
|
|
5179
|
+
flow.prompt = {
|
|
5180
|
+
id,
|
|
5181
|
+
type: prompt.type,
|
|
5182
|
+
message: prompt.message.slice(0, 4096),
|
|
5183
|
+
// Pi text prompts include "blank for github.com" and "press Enter to
|
|
5184
|
+
// continue". The public prompt API has no required flag; let the provider
|
|
5185
|
+
// validate text input instead of inventing a browser-only restriction.
|
|
5186
|
+
...prompt.type === "text" ? { allowEmpty: true } : {},
|
|
5187
|
+
..."placeholder" in prompt ? { placeholder: prompt.placeholder?.slice(0, 1024) } : {},
|
|
5188
|
+
...prompt.type === "select" ? { options: prompt.options.map((o) => ({ ...o })) } : {}
|
|
5189
|
+
};
|
|
5190
|
+
return new Promise((resolve, reject) => {
|
|
5191
|
+
const signals = [signal, ...prompt.signal ? [prompt.signal] : []];
|
|
5192
|
+
const cleanup = () => {
|
|
5193
|
+
signals.forEach((s) => s.removeEventListener("abort", abort));
|
|
5194
|
+
if (this.waiting?.id === id) this.waiting = void 0;
|
|
5195
|
+
if (flow.prompt?.id === id) delete flow.prompt;
|
|
5196
|
+
};
|
|
5197
|
+
const abort = () => {
|
|
5198
|
+
cleanup();
|
|
5199
|
+
reject(new Error("Login prompt cancelled"));
|
|
5200
|
+
};
|
|
5201
|
+
this.waiting = {
|
|
5202
|
+
id,
|
|
5203
|
+
resolve: (value) => {
|
|
5204
|
+
if (apiKey && value.trim().startsWith("!")) {
|
|
5205
|
+
cleanup();
|
|
5206
|
+
reject(new Error("Literal input required"));
|
|
5207
|
+
return;
|
|
5208
|
+
}
|
|
5209
|
+
cleanup();
|
|
5210
|
+
resolve(value);
|
|
5211
|
+
},
|
|
5212
|
+
reject: (error) => {
|
|
5213
|
+
cleanup();
|
|
5214
|
+
reject(error);
|
|
5215
|
+
}
|
|
5216
|
+
};
|
|
5217
|
+
signals.forEach((s) => s.addEventListener("abort", abort, { once: true }));
|
|
5218
|
+
if (signals.some((s) => s.aborted)) abort();
|
|
5219
|
+
});
|
|
5220
|
+
}
|
|
5221
|
+
};
|
|
5222
|
+
async function createProviderAuth(options = {}) {
|
|
5223
|
+
if (options.runtime) return new ProviderAuth(options.runtime, options);
|
|
5224
|
+
const { ModelRuntime, SettingsManager, getAgentDir } = await import("@earendil-works/pi-coding-agent");
|
|
5225
|
+
const runtime = await ModelRuntime.create({
|
|
5226
|
+
authPath: options.authPath,
|
|
5227
|
+
allowModelNetwork: false,
|
|
5228
|
+
refreshOnCreate: false
|
|
5229
|
+
});
|
|
5230
|
+
let settings;
|
|
5231
|
+
const agentDir3 = options.authPath ? path9.dirname(options.authPath) : getAgentDir();
|
|
5232
|
+
return new ProviderAuth(runtime, {
|
|
5233
|
+
...options,
|
|
5234
|
+
loginOptions: options.loginOptions ?? {
|
|
5235
|
+
getDeviceId: () => {
|
|
5236
|
+
settings ??= SettingsManager.create(agentDir3, agentDir3, { projectTrusted: false });
|
|
5237
|
+
return settings.getOrCreateDeviceId();
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
});
|
|
5241
|
+
}
|
|
5242
|
+
function mountProviderAuthRoutes(api2, auth) {
|
|
5243
|
+
api2.use("/providers", async (c, next) => {
|
|
5244
|
+
c.header("Cache-Control", "no-store");
|
|
5245
|
+
c.header("Referrer-Policy", "no-referrer");
|
|
5246
|
+
await next();
|
|
5247
|
+
});
|
|
5248
|
+
api2.use("/providers/*", async (c, next) => {
|
|
5249
|
+
c.header("Cache-Control", "no-store");
|
|
5250
|
+
c.header("Referrer-Policy", "no-referrer");
|
|
5251
|
+
await next();
|
|
5252
|
+
});
|
|
5253
|
+
api2.use("/providers/*", bodyLimit({ maxSize: 32768, onError: (c) => c.json({ error: "Login request is too large" }, 413) }));
|
|
5254
|
+
const safe = async (fn, c) => {
|
|
5255
|
+
try {
|
|
5256
|
+
return c.json(await fn());
|
|
5257
|
+
} catch (error) {
|
|
5258
|
+
if (error instanceof AuthRequestError) return c.json({ error: error.message }, error.status);
|
|
5259
|
+
return c.json({ error: "Provider connection is unavailable" }, 503);
|
|
5260
|
+
}
|
|
5261
|
+
};
|
|
5262
|
+
const body = async (c, fields) => {
|
|
5263
|
+
const value = await c.req.json().catch(() => null);
|
|
5264
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((k) => !fields.includes(k))) fail(400, "Invalid login request");
|
|
5265
|
+
return value;
|
|
5266
|
+
};
|
|
5267
|
+
api2.get("/providers", (c) => safe(() => auth.snapshot(), c));
|
|
5268
|
+
api2.post("/providers/login", (c) => safe(async () => {
|
|
5269
|
+
const b = await body(c, ["provider"]);
|
|
5270
|
+
return { flow: auth.start(b.provider) };
|
|
5271
|
+
}, c));
|
|
5272
|
+
api2.post("/providers/key", (c) => safe(async () => {
|
|
5273
|
+
const b = await body(c, ["provider", "key"]);
|
|
5274
|
+
return { flow: auth.start(b.provider, keyInput(b.key)) };
|
|
5275
|
+
}, c));
|
|
5276
|
+
api2.post("/providers/answer", (c) => safe(async () => {
|
|
5277
|
+
const b = await body(c, ["id", "promptId", "value"]);
|
|
5278
|
+
return { flow: auth.answer(b.id, b.promptId, b.value) };
|
|
5279
|
+
}, c));
|
|
5280
|
+
api2.post("/providers/cancel", (c) => safe(async () => {
|
|
5281
|
+
const b = await body(c, ["id"]);
|
|
5282
|
+
return { flow: auth.cancel(b.id) };
|
|
5283
|
+
}, c));
|
|
5284
|
+
}
|
|
5285
|
+
|
|
4736
5286
|
// server/index.ts
|
|
4737
|
-
var HERE =
|
|
4738
|
-
var APP_ROOT =
|
|
5287
|
+
var HERE = path10.dirname(fileURLToPath(import.meta.url));
|
|
5288
|
+
var APP_ROOT = path10.resolve(HERE, "..");
|
|
4739
5289
|
var arg = (name, fallback) => {
|
|
4740
5290
|
const i = process.argv.indexOf(`--${name}`);
|
|
4741
5291
|
return i >= 0 ? process.argv[i + 1] : process.env[`LOOPS_UI_${name.toUpperCase().replace(/-/g, "_")}`] ?? fallback;
|
|
@@ -4751,13 +5301,13 @@ var PUBLIC_ORIGINS = [...new Set((arg("public-origin", "") ?? "").split(",").map
|
|
|
4751
5301
|
return url.origin;
|
|
4752
5302
|
}))];
|
|
4753
5303
|
var SEED = arg("cwd");
|
|
4754
|
-
var CWD = SEED ?
|
|
5304
|
+
var CWD = SEED ? path10.resolve(SEED) : null;
|
|
4755
5305
|
var DEV = process.argv.includes("--dev") || process.env.NODE_ENV === "development";
|
|
4756
5306
|
var VITE = arg("vite", "http://localhost:5173");
|
|
4757
|
-
var PI = arg("pi",
|
|
4758
|
-
var DATA =
|
|
5307
|
+
var PI = arg("pi", path10.join(APP_ROOT, "bin", "pi-rpc.mjs"));
|
|
5308
|
+
var DATA = path10.resolve(arg("data", process.env.LOOPS_UI_DATA ?? path10.join(process.env.HOME ?? process.cwd(), ".pi", "loops-ui")));
|
|
4759
5309
|
var MODEL = arg("model", void 0) || null;
|
|
4760
|
-
var EXTENSIONS = (arg("extensions",
|
|
5310
|
+
var EXTENSIONS = (arg("extensions", path10.resolve(APP_ROOT, "../pi-loops/extensions/loop.ts")) ?? "").split(",").filter(Boolean);
|
|
4761
5311
|
function prerequisites() {
|
|
4762
5312
|
const out = [];
|
|
4763
5313
|
const onPath = PI.includes("/") || /^[A-Za-z]:\\/.test(PI) ? fs9.existsSync(PI) : (() => {
|
|
@@ -4778,7 +5328,7 @@ var PROBLEMS = prerequisites();
|
|
|
4778
5328
|
fs9.mkdirSync(DATA, { recursive: true });
|
|
4779
5329
|
var TOKEN = (() => {
|
|
4780
5330
|
const explicit = arg("token", void 0);
|
|
4781
|
-
const file =
|
|
5331
|
+
const file = path10.join(DATA, "token");
|
|
4782
5332
|
const stored = (() => {
|
|
4783
5333
|
try {
|
|
4784
5334
|
return fs9.readFileSync(file, "utf8").trim() || null;
|
|
@@ -4834,7 +5384,7 @@ var manager = new ChatManager({
|
|
|
4834
5384
|
});
|
|
4835
5385
|
var wsOf = (chat) => chat ? requireChatWorkspace(chat) : requireWorkspace();
|
|
4836
5386
|
var wsOfRun = (run3) => {
|
|
4837
|
-
const byIndex = workspaces.list().find((w) => fs9.existsSync(
|
|
5387
|
+
const byIndex = workspaces.list().find((w) => fs9.existsSync(path10.join(w.path, STATE_DIR, `${run3}.json`)));
|
|
4838
5388
|
if (byIndex) return byIndex;
|
|
4839
5389
|
const chat = manager.findRun(run3);
|
|
4840
5390
|
if (chat) return requireChatWorkspace(chat.id);
|
|
@@ -4868,7 +5418,7 @@ var watchOf = (wsId) => {
|
|
|
4868
5418
|
if (!w) {
|
|
4869
5419
|
const read = () => {
|
|
4870
5420
|
try {
|
|
4871
|
-
return JSON.parse(fs9.readFileSync(
|
|
5421
|
+
return JSON.parse(fs9.readFileSync(path10.join(ws.path, ".pi", "loops.json"), "utf8"));
|
|
4872
5422
|
} catch {
|
|
4873
5423
|
return {};
|
|
4874
5424
|
}
|
|
@@ -4876,14 +5426,14 @@ var watchOf = (wsId) => {
|
|
|
4876
5426
|
w = { cwd: ws.path, accepted: read() };
|
|
4877
5427
|
watches.set(ws.id, w);
|
|
4878
5428
|
try {
|
|
4879
|
-
fs9.watch(
|
|
5429
|
+
fs9.watch(path10.join(ws.path, ".pi"), (_e, name) => {
|
|
4880
5430
|
if (name && name !== "loops.json") return;
|
|
4881
5431
|
clearTimeout(watchTimers.get(ws.id));
|
|
4882
5432
|
watchTimers.set(ws.id, setTimeout(() => {
|
|
4883
5433
|
try {
|
|
4884
5434
|
checkConfig(ws.id);
|
|
4885
5435
|
} catch (e) {
|
|
4886
|
-
configErrors.set(ws.id, { ws: ws.id, file:
|
|
5436
|
+
configErrors.set(ws.id, { ws: ws.id, file: path10.join(ws.path, ".pi/loops.json"), error: `Could not validate settings: ${e.message}. Fix the file or save settings again.` });
|
|
4887
5437
|
notifyProposals();
|
|
4888
5438
|
}
|
|
4889
5439
|
}, 120));
|
|
@@ -4911,7 +5461,7 @@ function notifyProposals() {
|
|
|
4911
5461
|
}
|
|
4912
5462
|
function checkConfig(wsId) {
|
|
4913
5463
|
const w = watchOf(wsId);
|
|
4914
|
-
const CONFIG =
|
|
5464
|
+
const CONFIG = path10.join(w.cwd, ".pi", "loops.json");
|
|
4915
5465
|
const raw2 = (() => {
|
|
4916
5466
|
try {
|
|
4917
5467
|
return fs9.readFileSync(CONFIG, "utf8");
|
|
@@ -4954,8 +5504,8 @@ function checkConfig(wsId) {
|
|
|
4954
5504
|
notifyProposals();
|
|
4955
5505
|
}
|
|
4956
5506
|
var inside = (root, p) => {
|
|
4957
|
-
const abs =
|
|
4958
|
-
return abs === root || abs.startsWith(root +
|
|
5507
|
+
const abs = path10.resolve(root, p);
|
|
5508
|
+
return abs === root || abs.startsWith(root + path10.sep) ? abs : null;
|
|
4959
5509
|
};
|
|
4960
5510
|
var stateOf = (run3) => manager.findRun(run3)?.stateFor(run3) ?? readState(wsOfRun(run3).path, run3);
|
|
4961
5511
|
var app = new Hono3();
|
|
@@ -5000,10 +5550,21 @@ app.use("/api/v1/*", async (c, next) => {
|
|
|
5000
5550
|
var api = new Hono3();
|
|
5001
5551
|
api.onError((error, c) => {
|
|
5002
5552
|
if (error instanceof HTTPException) return c.json({ error: error.message }, error.status);
|
|
5553
|
+
if (error instanceof SessionActionError) return c.json({ error: error.message, ...error.uncertain ? { uncertain: true } : {} }, error.status);
|
|
5003
5554
|
console.error(error);
|
|
5004
5555
|
return c.json({ error: error.message }, 500);
|
|
5005
5556
|
});
|
|
5006
5557
|
api.get("/health", (c) => c.json({ ok: PROBLEMS.length === 0, problems: PROBLEMS, cwd: CWD, data: DATA, pi: PI, extensions: EXTENSIONS, workspaces: workspaces.list() }));
|
|
5558
|
+
var providerAuth = await createProviderAuth({
|
|
5559
|
+
onCredentialsChanged: async () => {
|
|
5560
|
+
await Promise.allSettled(manager.openChats().filter((chat) => !chat.sessionActions.busy).map((chat) => chat.refreshModels()));
|
|
5561
|
+
}
|
|
5562
|
+
});
|
|
5563
|
+
mountProviderAuthRoutes(api, providerAuth);
|
|
5564
|
+
api.route("/", createSessionActions({ requireChat: async (id) => {
|
|
5565
|
+
requireChatWorkspace(id);
|
|
5566
|
+
return manager.open(id);
|
|
5567
|
+
} }));
|
|
5007
5568
|
api.get("/credentials", (c) => c.json({ dir: agentDir2(), providers: credentialStatus() }));
|
|
5008
5569
|
api.post("/credentials", async (c) => {
|
|
5009
5570
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -5073,7 +5634,7 @@ api.post("/chat", async (c) => {
|
|
|
5073
5634
|
if (typeof body.ws !== "string" || !body.ws) return c.json({ error: "workspace required for a new chat" }, 400);
|
|
5074
5635
|
ws = requireWorkspace(body.ws);
|
|
5075
5636
|
if (!ws.open) return c.json({ error: "open the workspace before creating a chat" }, 409);
|
|
5076
|
-
id = `chat-${
|
|
5637
|
+
id = `chat-${randomUUID2()}`;
|
|
5077
5638
|
workspaces.bindChat(id, ws.id);
|
|
5078
5639
|
}
|
|
5079
5640
|
watchOf(ws.id);
|
|
@@ -5201,7 +5762,7 @@ api.post("/steer", async (c) => {
|
|
|
5201
5762
|
// Pi executes extension commands before its streaming queue. Do not send this as a queued follow-up:
|
|
5202
5763
|
// a background round must remain independently stoppable while the parent is in a long-running tool.
|
|
5203
5764
|
case "stop":
|
|
5204
|
-
return c.json({ ok: true, requested: true, sent: await
|
|
5765
|
+
return c.json({ ok: true, requested: true, sent: await chat.stopLoop(run3) });
|
|
5205
5766
|
case "clean":
|
|
5206
5767
|
return c.json({ ok: true, sent: await slash(`/loop clean ${run3}`) });
|
|
5207
5768
|
case "rule":
|
|
@@ -5255,13 +5816,13 @@ api.get("/shot", (c) => {
|
|
|
5255
5816
|
}
|
|
5256
5817
|
const dir = attempt === "CURRENT" ? state?.best?.dir ?? null : state?.rounds?.flatMap((r) => r.attempts).find((a) => a.label === attempt || a.label?.toLowerCase() === attempt.toLowerCase())?.dir;
|
|
5257
5818
|
if (!dir) return c.notFound();
|
|
5258
|
-
const abs = inside(repo,
|
|
5819
|
+
const abs = inside(repo, path10.join(dir, file));
|
|
5259
5820
|
if (!abs || !fs9.existsSync(abs)) return c.notFound();
|
|
5260
5821
|
return new Response(fs9.readFileSync(abs), { headers: { "content-type": abs.endsWith(".jpg") || abs.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
|
|
5261
5822
|
});
|
|
5262
5823
|
api.get("/sheet", (c) => {
|
|
5263
5824
|
const file = c.req.query("f") ?? "";
|
|
5264
|
-
const ws = c.req.query("run") ? wsOfRun(c.req.query("run")) : workspaces.list().find((w) => fs9.existsSync(
|
|
5825
|
+
const ws = c.req.query("run") ? wsOfRun(c.req.query("run")) : workspaces.list().find((w) => fs9.existsSync(path10.join(w.path, file)));
|
|
5265
5826
|
if (!ws) return c.notFound();
|
|
5266
5827
|
const abs = inside(ws.path, file);
|
|
5267
5828
|
if (!abs || !abs.includes(STATE_DIR) || !fs9.existsSync(abs)) return c.notFound();
|
|
@@ -5330,7 +5891,7 @@ api.post("/config", async (c) => {
|
|
|
5330
5891
|
configErrors.delete(ws.id);
|
|
5331
5892
|
const w = watchOf(ws.id);
|
|
5332
5893
|
try {
|
|
5333
|
-
w.accepted = JSON.parse(fs9.readFileSync(
|
|
5894
|
+
w.accepted = JSON.parse(fs9.readFileSync(path10.join(ws.path, ".pi", "loops.json"), "utf8"));
|
|
5334
5895
|
} catch {
|
|
5335
5896
|
}
|
|
5336
5897
|
notifyProposals();
|
|
@@ -5381,7 +5942,7 @@ api.get("/meta", (c) => c.json({
|
|
|
5381
5942
|
app.route("/api", api);
|
|
5382
5943
|
app.route("/api/v1", api);
|
|
5383
5944
|
app.all("/api/*", (c) => c.json({ error: `unknown API route: ${c.req.path}` }, 404));
|
|
5384
|
-
var DIST =
|
|
5945
|
+
var DIST = path10.join(APP_ROOT, "dist");
|
|
5385
5946
|
app.get("*", async (c) => {
|
|
5386
5947
|
const url = c.req.path;
|
|
5387
5948
|
if (DEV) {
|
|
@@ -5391,11 +5952,11 @@ app.get("*", async (c) => {
|
|
|
5391
5952
|
}
|
|
5392
5953
|
const abs = inside(DIST, url === "/" ? "index.html" : url.replace(/^\//, ""));
|
|
5393
5954
|
if (abs && fs9.existsSync(abs) && fs9.statSync(abs).isFile()) {
|
|
5394
|
-
const ext =
|
|
5955
|
+
const ext = path10.extname(abs);
|
|
5395
5956
|
const type = ext === ".js" ? "text/javascript" : ext === ".css" ? "text/css" : ext === ".woff2" ? "font/woff2" : ext === ".svg" ? "image/svg+xml" : ext === ".png" ? "image/png" : "text/html";
|
|
5396
5957
|
return new Response(fs9.readFileSync(abs), { headers: { "content-type": type } });
|
|
5397
5958
|
}
|
|
5398
|
-
const index =
|
|
5959
|
+
const index = path10.join(DIST, "index.html");
|
|
5399
5960
|
if (!fs9.existsSync(index)) return c.text("run `npm run build` first", 500);
|
|
5400
5961
|
return new Response(fs9.readFileSync(index), { headers: { "content-type": "text/html" } });
|
|
5401
5962
|
});
|
|
@@ -5438,6 +5999,7 @@ server.listen(PORT, HOST, () => {
|
|
|
5438
5999
|
});
|
|
5439
6000
|
var shutdown = async () => {
|
|
5440
6001
|
stopAllLive();
|
|
6002
|
+
await providerAuth.close();
|
|
5441
6003
|
await manager.closeAll();
|
|
5442
6004
|
server.close(() => process.exit(0));
|
|
5443
6005
|
setTimeout(() => process.exit(0), 1500);
|