@syengup/friday-channel-next 0.0.34 → 0.0.37
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/attachments/0768c9b1-53b0-44df-83e8-be15c4ea188f.jpg +0 -0
- package/dist/attachments/0a379d01-116b-4da1-bf15-77cb2cbb0093.jpg +0 -0
- package/dist/attachments/181caab2-64a7-4004-a057-225a144f949e.mp3 +0 -0
- package/dist/attachments/19662331-e527-47d2-bc0e-0e19a7a91419.jpg +0 -0
- package/dist/attachments/26a23b2b-52df-4572-a5e1-15b34fb87e44.jpg +0 -0
- package/dist/attachments/2f9282c5-8db4-4c4a-a060-e65104f6f9ff.jpg +0 -0
- package/dist/attachments/3929ec3d-ea15-4de6-96bc-97e8b0b658a7.jpg +0 -0
- package/dist/attachments/403c0cbc-4e3c-4146-a3be-ff3746ee7cda.jpg +0 -0
- package/dist/attachments/441977f5-0f7b-4aa2-841a-1d63e787ea53.jpg +0 -0
- package/dist/attachments/453e8aa2-76e3-498d-8d6f-d7b96d6bf45b.jpg +0 -0
- package/dist/attachments/538cde71-d26e-4d3d-b901-e8dd905e668c.mp3 +0 -0
- package/dist/attachments/55c7f628-4ba2-4252-aa4b-4f3eb6045a8a.mp3 +0 -0
- package/dist/attachments/5f7683f5-8194-4698-b077-31d209525379.jpg +0 -0
- package/dist/attachments/60614a35-8f44-4197-b783-2f58f5a72ac8.jpeg +0 -0
- package/dist/attachments/62830489-8814-48b1-851c-3845e514f35e.mp3 +0 -0
- package/dist/attachments/66f4a62d-1531-4f38-a531-7456f9edf221.png +0 -0
- package/dist/attachments/6735d749-769e-483a-9b84-43b9338a720b.png +0 -0
- package/dist/attachments/6d1766b1-05e4-4b04-b3c8-1c25e9d182a1.png +0 -0
- package/dist/attachments/782b077b-06e3-484b-baf5-33e7160234ed.png +0 -0
- package/dist/attachments/7ad638b2-1f56-4d93-9ad8-b40346e0650f.jpg +0 -0
- package/dist/attachments/89f6fb15-e652-4111-a60c-baa414659052.png +0 -0
- package/dist/attachments/8a88b14f-442f-45fb-b01d-e51bab8f800d.mp3 +0 -0
- package/dist/attachments/92292034-9cf6-4f26-8d77-fddca3deb638.png +0 -0
- package/dist/attachments/92c2b414-d33d-4d93-bcb6-013da7bec9a4.jpg +0 -0
- package/dist/attachments/9664f69e-3c05-45ca-9a52-f2d0b9f9bf7e.jpg +0 -0
- package/dist/attachments/977d28c1-43c0-40e0-95e3-defe0f41afe8.jpg +0 -0
- package/dist/attachments/9df40f1a-c6e1-4177-8a03-06757a30b19e.png +0 -0
- package/dist/attachments/a68e6815-6163-4421-a70f-34493aa9a217.jpg +0 -0
- package/dist/attachments/aab32fea-6d99-47ec-ab1f-2340f31312eb.jpg +0 -0
- package/dist/attachments/ab403224-2fb1-49c1-8738-ea194ab65d44.png +0 -0
- package/dist/attachments/ac3da190-d6ee-4038-a673-8b893035a687.png +0 -0
- package/dist/attachments/af02be9c-87f7-4c5a-9969-7db32039bb58.png +0 -0
- package/dist/attachments/b011d42a-00e5-4f77-86bc-08da6112e6e1.mp3 +0 -0
- package/dist/attachments/b7d7df40-c627-4b1f-9b09-167b88545c25.mp3 +0 -0
- package/dist/attachments/c5e9bf09-a718-422c-bcb3-94c173e3755b.mp3 +0 -0
- package/dist/attachments/d5449e13-1995-44ba-9392-ecbfe5f9876f.jpg +0 -0
- package/dist/attachments/ea0069f5-01cf-4ea1-985e-3a1e426399c3.png +0 -0
- package/dist/attachments/f3989ff2-7b70-4a80-a896-74a6b197f7d8.png +0 -0
- package/dist/attachments/f64a4a14-e3aa-4eed-a8d9-1603f04baa5b.jpg +0 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +176 -0
- package/dist/src/agent/abort-run.d.ts +1 -0
- package/dist/src/agent/abort-run.js +11 -0
- package/dist/src/agent/active-runs.d.ts +9 -0
- package/dist/src/agent/active-runs.js +20 -0
- package/dist/src/agent/dispatch-bridge.d.ts +5 -0
- package/dist/src/agent/dispatch-bridge.js +12 -0
- package/dist/src/agent/media-bridge.d.ts +4 -0
- package/dist/src/agent/media-bridge.js +21 -0
- package/dist/src/agent/subagent-registry.d.ts +68 -0
- package/dist/src/agent/subagent-registry.js +142 -0
- package/dist/src/agent-forward-runtime.d.ts +17 -0
- package/dist/src/agent-forward-runtime.js +16 -0
- package/dist/src/agent-run-context-bridge.d.ts +13 -0
- package/dist/src/agent-run-context-bridge.js +23 -0
- package/dist/src/channel-actions.d.ts +13 -0
- package/dist/src/channel-actions.js +101 -0
- package/dist/src/channel.d.ts +6 -0
- package/dist/src/channel.js +248 -0
- package/dist/src/collect-message-media-paths.d.ts +11 -0
- package/dist/src/collect-message-media-paths.js +143 -0
- package/dist/src/config.d.ts +15 -0
- package/dist/src/config.js +39 -0
- package/dist/src/friday-inbound-stats.d.ts +2 -0
- package/dist/src/friday-inbound-stats.js +8 -0
- package/dist/src/friday-session.d.ts +40 -0
- package/dist/src/friday-session.js +395 -0
- package/dist/src/host-config.d.ts +1 -0
- package/dist/src/host-config.js +15 -0
- package/dist/src/http/handlers/cancel.d.ts +2 -0
- package/dist/src/http/handlers/cancel.js +33 -0
- package/dist/src/http/handlers/device-approve.d.ts +2 -0
- package/dist/src/http/handlers/device-approve.js +125 -0
- package/dist/src/http/handlers/device-token.d.ts +2 -0
- package/dist/src/http/handlers/device-token.js +43 -0
- package/dist/src/http/handlers/files-download.d.ts +10 -0
- package/dist/src/http/handlers/files-download.js +210 -0
- package/dist/src/http/handlers/files-upload.d.ts +8 -0
- package/dist/src/http/handlers/files-upload.js +136 -0
- package/dist/src/http/handlers/files.d.ts +75 -0
- package/dist/src/http/handlers/files.js +305 -0
- package/dist/src/http/handlers/import.d.ts +7 -0
- package/dist/src/http/handlers/import.js +69 -0
- package/dist/src/http/handlers/info.d.ts +2 -0
- package/dist/src/http/handlers/info.js +13 -0
- package/dist/src/http/handlers/messages-list.d.ts +7 -0
- package/dist/src/http/handlers/messages-list.js +44 -0
- package/dist/src/http/handlers/messages.d.ts +34 -0
- package/dist/src/http/handlers/messages.js +476 -0
- package/dist/src/http/handlers/models-list.d.ts +10 -0
- package/dist/src/http/handlers/models-list.js +113 -0
- package/dist/src/http/handlers/nodes-approve.d.ts +2 -0
- package/dist/src/http/handlers/nodes-approve.js +146 -0
- package/dist/src/http/handlers/pair.d.ts +2 -0
- package/dist/src/http/handlers/pair.js +39 -0
- package/dist/src/http/handlers/sessions-delete.d.ts +2 -0
- package/dist/src/http/handlers/sessions-delete.js +49 -0
- package/dist/src/http/handlers/sessions-list.d.ts +8 -0
- package/dist/src/http/handlers/sessions-list.js +24 -0
- package/dist/src/http/handlers/sessions-messages-get.d.ts +2 -0
- package/dist/src/http/handlers/sessions-messages-get.js +55 -0
- package/dist/src/http/handlers/sessions-messages-post.d.ts +2 -0
- package/dist/src/http/handlers/sessions-messages-post.js +92 -0
- package/dist/src/http/handlers/sessions-messages.d.ts +2 -0
- package/dist/src/http/handlers/sessions-messages.js +135 -0
- package/dist/src/http/handlers/sessions-settings.d.ts +2 -0
- package/dist/src/http/handlers/sessions-settings.js +71 -0
- package/dist/src/http/handlers/sse.d.ts +2 -0
- package/dist/src/http/handlers/sse.js +70 -0
- package/dist/src/http/handlers/status.d.ts +2 -0
- package/dist/src/http/handlers/status.js +29 -0
- package/dist/src/http/handlers/sync.d.ts +7 -0
- package/dist/src/http/handlers/sync.js +56 -0
- package/dist/src/http/middleware/auth.d.ts +13 -0
- package/dist/src/http/middleware/auth.js +29 -0
- package/dist/src/http/middleware/body.d.ts +2 -0
- package/dist/src/http/middleware/body.js +24 -0
- package/dist/src/http/middleware/cors.d.ts +2 -0
- package/dist/src/http/middleware/cors.js +11 -0
- package/dist/src/http/server.d.ts +19 -0
- package/dist/src/http/server.js +87 -0
- package/dist/src/logging.d.ts +7 -0
- package/dist/src/logging.js +28 -0
- package/dist/src/push/apns.d.ts +15 -0
- package/dist/src/push/apns.js +56 -0
- package/dist/src/push/device-tokens.d.ts +3 -0
- package/dist/src/push/device-tokens.js +39 -0
- package/dist/src/run-metadata.d.ts +25 -0
- package/dist/src/run-metadata.js +139 -0
- package/dist/src/runtime.d.ts +13 -0
- package/dist/src/runtime.js +5 -0
- package/dist/src/session/session-manager.d.ts +22 -0
- package/dist/src/session/session-manager.js +190 -0
- package/dist/src/session-usage-snapshot.d.ts +23 -0
- package/dist/src/session-usage-snapshot.js +65 -0
- package/dist/src/sse/emitter.d.ts +59 -0
- package/dist/src/sse/emitter.js +219 -0
- package/dist/src/sse/offline-queue.d.ts +26 -0
- package/dist/src/sse/offline-queue.js +134 -0
- package/dist/src/sync/account-identity.d.ts +14 -0
- package/dist/src/sync/account-identity.js +101 -0
- package/dist/src/sync/archive.d.ts +9 -0
- package/dist/src/sync/archive.js +25 -0
- package/dist/src/sync/database.d.ts +66 -0
- package/dist/src/sync/database.js +364 -0
- package/dist/src/sync/init.d.ts +3 -0
- package/dist/src/sync/init.js +14 -0
- package/dist/src/sync/installation-id.d.ts +1 -0
- package/dist/src/sync/installation-id.js +41 -0
- package/dist/src/sync/message-accumulator.d.ts +29 -0
- package/dist/src/sync/message-accumulator.js +188 -0
- package/dist/src/sync/message-store.d.ts +68 -0
- package/dist/src/sync/message-store.js +262 -0
- package/dist/src/sync/push-store.d.ts +5 -0
- package/dist/src/sync/push-store.js +54 -0
- package/dist/src/sync/session-key.d.ts +12 -0
- package/dist/src/sync/session-key.js +47 -0
- package/dist/src/sync/sync-state.d.ts +5 -0
- package/dist/src/sync/sync-state.js +54 -0
- package/dist/src/sync/transcript-archive.d.ts +13 -0
- package/dist/src/sync/transcript-archive.js +37 -0
- package/dist/src/sync/transcript-store.d.ts +35 -0
- package/dist/src/sync/transcript-store.js +221 -0
- package/dist/src/sync/translate.d.ts +42 -0
- package/dist/src/sync/translate.js +171 -0
- package/dist/src/vendor/runtime-store.d.ts +26 -0
- package/dist/src/vendor/runtime-store.js +60 -0
- package/install.js +2 -2
- package/package.json +11 -10
- package/src/agent/subagent-registry.ts +195 -0
- package/src/channel.ts +6 -4
- package/src/e2e/subagent-smoke.e2e.test.ts +223 -0
- package/src/e2e/subagent.e2e.test.ts +502 -0
- package/src/friday-session.ts +140 -1
- package/src/http/handlers/device-approve.test.ts +0 -1
- package/src/http/handlers/device-approve.ts +0 -2
- package/src/http/handlers/files-download.ts +4 -1
- package/src/http/handlers/files.ts +7 -4
- package/src/http/handlers/messages.ts +54 -4
- package/src/http/handlers/models-list.ts +24 -2
- package/src/http/handlers/nodes-approve.test.ts +288 -0
- package/src/http/handlers/nodes-approve.ts +189 -0
- package/src/http/server.ts +5 -0
- package/src/openclaw.d.ts +5 -0
- package/src/sse/emitter.ts +1 -1
- package/src/test-support/mock-runtime.ts +2 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { readJsonBody } from "../middleware/body.js";
|
|
3
|
+
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
+
import { createFridayNextLogger } from "../../logging.js";
|
|
5
|
+
const EXEC_ENV = process.platform === "win32"
|
|
6
|
+
? process.env
|
|
7
|
+
: { ...process.env, PATH: `/opt/homebrew/bin:/usr/local/bin:/home/linuxbrew/.linuxbrew/bin:${process.env.PATH ?? ""}` };
|
|
8
|
+
function execAsync(command, timeoutMs) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const child = exec(command, { encoding: "utf-8", timeout: timeoutMs, maxBuffer: 1024 * 1024, env: EXEC_ENV }, (error, stdout, stderr) => {
|
|
11
|
+
if (error) {
|
|
12
|
+
reject(error);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
resolve({ stdout, stderr });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
child.stdout?.on("data", () => { });
|
|
19
|
+
child.stderr?.on("data", () => { });
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export async function handleNodesApprove(req, res) {
|
|
23
|
+
const log = createFridayNextLogger("nodes-approve");
|
|
24
|
+
if (req.method !== "POST") {
|
|
25
|
+
res.statusCode = 405;
|
|
26
|
+
res.setHeader("Content-Type", "application/json");
|
|
27
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const token = extractBearerToken(req);
|
|
31
|
+
if (!token) {
|
|
32
|
+
res.statusCode = 401;
|
|
33
|
+
res.setHeader("Content-Type", "application/json");
|
|
34
|
+
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const body = await readJsonBody(req);
|
|
38
|
+
if (!body) {
|
|
39
|
+
res.statusCode = 400;
|
|
40
|
+
res.setHeader("Content-Type", "application/json");
|
|
41
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const rawNodeId = typeof body.nodeId === "string" ? body.nodeId : "";
|
|
45
|
+
if (!rawNodeId.trim()) {
|
|
46
|
+
res.statusCode = 400;
|
|
47
|
+
res.setHeader("Content-Type", "application/json");
|
|
48
|
+
res.end(JSON.stringify({ error: "Missing required field: nodeId" }));
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
const normalizedNodeId = rawNodeId.trim().toUpperCase();
|
|
52
|
+
let listStdout;
|
|
53
|
+
try {
|
|
54
|
+
const result = await execAsync("openclaw nodes list --json", 15000);
|
|
55
|
+
listStdout = result.stdout;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const stderr = err?.stderr?.trim();
|
|
59
|
+
log.error(`nodes list failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
60
|
+
res.statusCode = 502;
|
|
61
|
+
res.setHeader("Content-Type", "application/json");
|
|
62
|
+
res.end(JSON.stringify({ error: "Failed to list nodes from gateway", detail: stderr || undefined }));
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
let listData;
|
|
66
|
+
try {
|
|
67
|
+
listData = JSON.parse(listStdout);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
log.error(`nodes list returned invalid JSON: ${listStdout.slice(0, 200)}`);
|
|
71
|
+
res.statusCode = 502;
|
|
72
|
+
res.setHeader("Content-Type", "application/json");
|
|
73
|
+
res.end(JSON.stringify({ error: "Unexpected response from gateway node list" }));
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const pending = listData.pending ?? [];
|
|
77
|
+
const pendingMatch = pending.find((entry) => entry.nodeId.trim().toUpperCase() === normalizedNodeId);
|
|
78
|
+
if (pendingMatch) {
|
|
79
|
+
const requestId = pendingMatch.requestId;
|
|
80
|
+
log.info(`approving nodeId=${normalizedNodeId} requestId=${requestId}`);
|
|
81
|
+
let approveStdout;
|
|
82
|
+
try {
|
|
83
|
+
const result = await execAsync(`openclaw nodes approve ${requestId} --json`, 15000);
|
|
84
|
+
approveStdout = result.stdout;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const stderr = err?.stderr?.trim();
|
|
88
|
+
log.error(`nodes approve failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
89
|
+
res.statusCode = 502;
|
|
90
|
+
res.setHeader("Content-Type", "application/json");
|
|
91
|
+
res.end(JSON.stringify({
|
|
92
|
+
error: "Node approval command failed",
|
|
93
|
+
detail: stderr || (err instanceof Error ? err.message : "Unknown error"),
|
|
94
|
+
}));
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
let approveData;
|
|
98
|
+
try {
|
|
99
|
+
approveData = JSON.parse(approveStdout);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
log.error(`nodes approve returned non-JSON: ${approveStdout.slice(0, 200)}`);
|
|
103
|
+
res.statusCode = 502;
|
|
104
|
+
res.setHeader("Content-Type", "application/json");
|
|
105
|
+
res.end(JSON.stringify({ error: "Unexpected response from node approval" }));
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
res.statusCode = 200;
|
|
109
|
+
res.setHeader("Content-Type", "application/json");
|
|
110
|
+
res.end(JSON.stringify({
|
|
111
|
+
ok: true,
|
|
112
|
+
nodeId: normalizedNodeId,
|
|
113
|
+
requestId: approveData.requestId,
|
|
114
|
+
approvedAtMs: approveData.node?.approvedAtMs,
|
|
115
|
+
}));
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
// Not in pending — check if already paired with non-empty caps/commands
|
|
119
|
+
const paired = listData.paired ?? [];
|
|
120
|
+
const pairedMatch = paired.find((entry) => entry.nodeId.trim().toUpperCase() === normalizedNodeId);
|
|
121
|
+
if (pairedMatch) {
|
|
122
|
+
const caps = pairedMatch.caps ?? [];
|
|
123
|
+
const commands = pairedMatch.commands ?? [];
|
|
124
|
+
if (caps.length > 0 || commands.length > 0) {
|
|
125
|
+
log.info(`nodeId=${normalizedNodeId} already paired with caps=${caps.length} commands=${commands.length}`);
|
|
126
|
+
res.statusCode = 200;
|
|
127
|
+
res.setHeader("Content-Type", "application/json");
|
|
128
|
+
res.end(JSON.stringify({
|
|
129
|
+
ok: true,
|
|
130
|
+
nodeId: normalizedNodeId,
|
|
131
|
+
alreadyApproved: true,
|
|
132
|
+
approvedAtMs: pairedMatch.approvedAtMs,
|
|
133
|
+
caps,
|
|
134
|
+
commands,
|
|
135
|
+
}));
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
res.statusCode = 404;
|
|
140
|
+
res.setHeader("Content-Type", "application/json");
|
|
141
|
+
res.end(JSON.stringify({
|
|
142
|
+
error: "No pending node found for this nodeId",
|
|
143
|
+
nodeId: normalizedNodeId,
|
|
144
|
+
}));
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getOrCreateAccountId } from "../../sync/account-identity.js";
|
|
2
|
+
import { getInstallationId } from "../../sync/installation-id.js";
|
|
3
|
+
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
+
import { readJsonBody } from "../middleware/body.js";
|
|
5
|
+
export async function handlePair(req, res) {
|
|
6
|
+
if (req.method !== "POST") {
|
|
7
|
+
res.statusCode = 405;
|
|
8
|
+
res.setHeader("Content-Type", "application/json");
|
|
9
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
if (!extractBearerToken(req)) {
|
|
13
|
+
res.statusCode = 401;
|
|
14
|
+
res.setHeader("Content-Type", "application/json");
|
|
15
|
+
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const body = await readJsonBody(req);
|
|
19
|
+
const deviceId = typeof body?.deviceId === "string" ? body.deviceId.trim() : "";
|
|
20
|
+
if (!deviceId) {
|
|
21
|
+
res.statusCode = 400;
|
|
22
|
+
res.setHeader("Content-Type", "application/json");
|
|
23
|
+
res.end(JSON.stringify({ error: "Missing required field: deviceId" }));
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const claimAccountId = typeof body?.claimAccountId === "string" ? body.claimAccountId.trim() : undefined;
|
|
27
|
+
const claim = body?.claim && typeof body.claim === "object" && !Array.isArray(body.claim)
|
|
28
|
+
? body.claim
|
|
29
|
+
: {};
|
|
30
|
+
const result = getOrCreateAccountId({
|
|
31
|
+
deviceId,
|
|
32
|
+
claimAccountId,
|
|
33
|
+
claimConfirmed: claim.confirmed === true,
|
|
34
|
+
});
|
|
35
|
+
res.statusCode = 200;
|
|
36
|
+
res.setHeader("Content-Type", "application/json");
|
|
37
|
+
res.end(JSON.stringify({ ok: true, installationId: getInstallationId(), ...result }));
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { deleteFridaySession, toSessionStoreKey } from "../../session/session-manager.js";
|
|
2
|
+
import { getActiveRunIds } from "../../agent/active-runs.js";
|
|
3
|
+
import { abortRun } from "../../agent/abort-run.js";
|
|
4
|
+
import { getRunRoute } from "../../run-metadata.js";
|
|
5
|
+
import { sseEmitter } from "../../sse/emitter.js";
|
|
6
|
+
import { readJsonBody } from "../middleware/body.js";
|
|
7
|
+
import { extractBearerToken } from "../middleware/auth.js";
|
|
8
|
+
async function cancelActiveRunsForSession(sessionKey) {
|
|
9
|
+
const storeKey = toSessionStoreKey(sessionKey);
|
|
10
|
+
const cancelled = [];
|
|
11
|
+
for (const runId of getActiveRunIds()) {
|
|
12
|
+
const route = getRunRoute(runId);
|
|
13
|
+
if (route?.sessionKey === storeKey) {
|
|
14
|
+
await abortRun(runId);
|
|
15
|
+
sseEmitter.untrackRun(runId);
|
|
16
|
+
cancelled.push(runId);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return cancelled;
|
|
20
|
+
}
|
|
21
|
+
export async function handleSessionsDelete(req, res) {
|
|
22
|
+
if (req.method !== "DELETE") {
|
|
23
|
+
res.statusCode = 405;
|
|
24
|
+
res.setHeader("Content-Type", "application/json");
|
|
25
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const token = extractBearerToken(req);
|
|
29
|
+
if (!token) {
|
|
30
|
+
res.statusCode = 401;
|
|
31
|
+
res.setHeader("Content-Type", "application/json");
|
|
32
|
+
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const body = await readJsonBody(req);
|
|
36
|
+
const sessionKey = typeof body?.sessionKey === "string" ? body.sessionKey.trim() : "";
|
|
37
|
+
if (!sessionKey) {
|
|
38
|
+
res.statusCode = 400;
|
|
39
|
+
res.setHeader("Content-Type", "application/json");
|
|
40
|
+
res.end(JSON.stringify({ error: "Missing required field: sessionKey" }));
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
const cancelledRuns = await cancelActiveRunsForSession(sessionKey);
|
|
44
|
+
const result = deleteFridaySession(sessionKey);
|
|
45
|
+
res.statusCode = 200;
|
|
46
|
+
res.setHeader("Content-Type", "application/json");
|
|
47
|
+
res.end(JSON.stringify({ ok: true, ...result, cancelledRuns }));
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /friday-next/sessions?deviceId=...
|
|
3
|
+
*
|
|
4
|
+
* List sessions. If deviceId is provided, filters by device.
|
|
5
|
+
* If deviceId is omitted, returns all sessions (for app reinstall recovery when deviceId changed).
|
|
6
|
+
*/
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
+
export declare function handleSessionsList(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { extractBearerToken } from "../middleware/auth.js";
|
|
2
|
+
import { listSessions, listAllSessionsList } from "../../sync/message-store.js";
|
|
3
|
+
export async function handleSessionsList(req, res) {
|
|
4
|
+
if (req.method !== "GET") {
|
|
5
|
+
res.statusCode = 405;
|
|
6
|
+
res.setHeader("Content-Type", "application/json");
|
|
7
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
const token = extractBearerToken(req);
|
|
11
|
+
if (!token) {
|
|
12
|
+
res.statusCode = 401;
|
|
13
|
+
res.setHeader("Content-Type", "application/json");
|
|
14
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
18
|
+
const deviceId = url.searchParams.get("deviceId")?.trim();
|
|
19
|
+
const sessions = deviceId ? listSessions(deviceId) : listAllSessionsList();
|
|
20
|
+
res.statusCode = 200;
|
|
21
|
+
res.setHeader("Content-Type", "application/json");
|
|
22
|
+
res.end(JSON.stringify({ sessions }));
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { resolveAccountIdFromRequest } from "../../sync/account-identity.js";
|
|
2
|
+
import { getTranscriptStore } from "../../sync/transcript-store.js";
|
|
3
|
+
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
+
function sessionKeyFromPath(pathname) {
|
|
5
|
+
const prefix = "/friday-next/sessions/";
|
|
6
|
+
const suffix = "/messages";
|
|
7
|
+
if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix))
|
|
8
|
+
return "";
|
|
9
|
+
return decodeURIComponent(pathname.slice(prefix.length, -suffix.length));
|
|
10
|
+
}
|
|
11
|
+
export async function handleSessionMessagesGet(req, res) {
|
|
12
|
+
if (req.method !== "GET") {
|
|
13
|
+
res.statusCode = 405;
|
|
14
|
+
res.setHeader("Content-Type", "application/json");
|
|
15
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (!extractBearerToken(req)) {
|
|
19
|
+
res.statusCode = 401;
|
|
20
|
+
res.setHeader("Content-Type", "application/json");
|
|
21
|
+
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const accountId = resolveAccountIdFromRequest(req);
|
|
25
|
+
if (!accountId) {
|
|
26
|
+
res.statusCode = 403;
|
|
27
|
+
res.setHeader("Content-Type", "application/json");
|
|
28
|
+
res.end(JSON.stringify({ error: "Unknown or missing X-Account-Id" }));
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
32
|
+
const sessionKey = sessionKeyFromPath(url.pathname);
|
|
33
|
+
if (!sessionKey) {
|
|
34
|
+
res.statusCode = 400;
|
|
35
|
+
res.setHeader("Content-Type", "application/json");
|
|
36
|
+
res.end(JSON.stringify({ error: "Missing session key in path" }));
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (!sessionKey.includes(`friday:account:${accountId}:`) && !sessionKey.includes(`friday%3Aaccount%3A${accountId}%3A`)) {
|
|
40
|
+
const legacyAllowed = sessionKey.startsWith("friday:direct:") || sessionKey.startsWith("agent:main:friday:direct:");
|
|
41
|
+
if (!legacyAllowed) {
|
|
42
|
+
res.statusCode = 403;
|
|
43
|
+
res.setHeader("Content-Type", "application/json");
|
|
44
|
+
res.end(JSON.stringify({ error: "Session does not belong to account" }));
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const since = Number.parseInt(url.searchParams.get("since") ?? "0", 10);
|
|
49
|
+
const limit = Number.parseInt(url.searchParams.get("limit") ?? "200", 10);
|
|
50
|
+
const events = getTranscriptStore().readSince(accountId, sessionKey, Number.isFinite(since) ? since : 0, limit);
|
|
51
|
+
res.statusCode = 200;
|
|
52
|
+
res.setHeader("Content-Type", "application/json");
|
|
53
|
+
res.end(JSON.stringify({ ok: true, events, nextSince: events.at(-1)?.seq ?? since }));
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { resolveAccountIdFromRequest } from "../../sync/account-identity.js";
|
|
2
|
+
import { getTranscriptStore } from "../../sync/transcript-store.js";
|
|
3
|
+
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
+
import { readJsonBody } from "../middleware/body.js";
|
|
5
|
+
const allowedKinds = new Set([
|
|
6
|
+
"user_message",
|
|
7
|
+
"assistant_message",
|
|
8
|
+
"thought_event",
|
|
9
|
+
"tool_event",
|
|
10
|
+
"outbound_text",
|
|
11
|
+
"outbound_media",
|
|
12
|
+
"sync_upsert",
|
|
13
|
+
]);
|
|
14
|
+
function sessionKeyFromPath(pathname) {
|
|
15
|
+
const prefix = "/friday-next/sessions/";
|
|
16
|
+
const suffix = "/messages";
|
|
17
|
+
if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix))
|
|
18
|
+
return "";
|
|
19
|
+
return decodeURIComponent(pathname.slice(prefix.length, -suffix.length));
|
|
20
|
+
}
|
|
21
|
+
function parseEvent(raw, accountId) {
|
|
22
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
23
|
+
return null;
|
|
24
|
+
const obj = raw;
|
|
25
|
+
const kind = typeof obj.kind === "string" && allowedKinds.has(obj.kind)
|
|
26
|
+
? obj.kind
|
|
27
|
+
: null;
|
|
28
|
+
if (!kind)
|
|
29
|
+
return null;
|
|
30
|
+
const payload = obj.payload && typeof obj.payload === "object" && !Array.isArray(obj.payload)
|
|
31
|
+
? obj.payload
|
|
32
|
+
: null;
|
|
33
|
+
if (!payload)
|
|
34
|
+
return null;
|
|
35
|
+
return {
|
|
36
|
+
eventId: typeof obj.eventId === "string" ? obj.eventId.trim() : undefined,
|
|
37
|
+
clientId: typeof obj.clientId === "string" && obj.clientId.trim() ? obj.clientId.trim() : accountId,
|
|
38
|
+
kind,
|
|
39
|
+
payload,
|
|
40
|
+
ts: typeof obj.ts === "number" ? obj.ts : undefined,
|
|
41
|
+
lamportTs: typeof obj.lamportTs === "number" ? obj.lamportTs : undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export async function handleSessionMessagesPost(req, res) {
|
|
45
|
+
if (req.method !== "POST") {
|
|
46
|
+
res.statusCode = 405;
|
|
47
|
+
res.setHeader("Content-Type", "application/json");
|
|
48
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (!extractBearerToken(req)) {
|
|
52
|
+
res.statusCode = 401;
|
|
53
|
+
res.setHeader("Content-Type", "application/json");
|
|
54
|
+
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
const accountId = resolveAccountIdFromRequest(req);
|
|
58
|
+
if (!accountId) {
|
|
59
|
+
res.statusCode = 403;
|
|
60
|
+
res.setHeader("Content-Type", "application/json");
|
|
61
|
+
res.end(JSON.stringify({ error: "Unknown or missing X-Account-Id" }));
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
65
|
+
const sessionKey = sessionKeyFromPath(url.pathname);
|
|
66
|
+
if (!sessionKey) {
|
|
67
|
+
res.statusCode = 400;
|
|
68
|
+
res.setHeader("Content-Type", "application/json");
|
|
69
|
+
res.end(JSON.stringify({ error: "Missing session key in path" }));
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
if (!sessionKey.includes(`friday:account:${accountId}:`) && !sessionKey.startsWith("friday:direct:")) {
|
|
73
|
+
res.statusCode = 403;
|
|
74
|
+
res.setHeader("Content-Type", "application/json");
|
|
75
|
+
res.end(JSON.stringify({ error: "Session does not belong to account" }));
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const body = await readJsonBody(req, 8 * 1024 * 1024);
|
|
79
|
+
const rawEvents = Array.isArray(body?.events) ? body.events : [];
|
|
80
|
+
const events = rawEvents.map((e) => parseEvent(e, accountId)).filter((e) => e !== null);
|
|
81
|
+
if (events.length !== rawEvents.length) {
|
|
82
|
+
res.statusCode = 400;
|
|
83
|
+
res.setHeader("Content-Type", "application/json");
|
|
84
|
+
res.end(JSON.stringify({ error: "Invalid transcript event batch" }));
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
const appended = getTranscriptStore().appendEvents(accountId, sessionKey, events);
|
|
88
|
+
res.statusCode = 200;
|
|
89
|
+
res.setHeader("Content-Type", "application/json");
|
|
90
|
+
res.end(JSON.stringify({ ok: true, appended: appended.length, events: appended }));
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { resolveFridayNextConfig } from "../../config.js";
|
|
2
|
+
import { getHostOpenClawConfigSnapshot } from "../../host-config.js";
|
|
3
|
+
import { getFridayNextRuntime } from "../../runtime.js";
|
|
4
|
+
import { accountExists, requireAccountId } from "../../sync/account-identity.js";
|
|
5
|
+
import { sessionBelongsToAccount } from "../../sync/session-key.js";
|
|
6
|
+
import { readSince, upsertEvents } from "../../sync/transcript-store.js";
|
|
7
|
+
import { extractBearerToken } from "../middleware/auth.js";
|
|
8
|
+
import { readJsonBody } from "../middleware/body.js";
|
|
9
|
+
const VALID_KINDS = new Set([
|
|
10
|
+
"user_message",
|
|
11
|
+
"assistant_message",
|
|
12
|
+
"thought_event",
|
|
13
|
+
"tool_event",
|
|
14
|
+
"outbound_text",
|
|
15
|
+
"outbound_media",
|
|
16
|
+
]);
|
|
17
|
+
function decodeSessionKeyFromPath(pathname) {
|
|
18
|
+
const prefix = "/friday-next/sessions/";
|
|
19
|
+
if (!pathname.startsWith(prefix))
|
|
20
|
+
return null;
|
|
21
|
+
const rest = pathname.slice(prefix.length);
|
|
22
|
+
const messagesIdx = rest.indexOf("/messages");
|
|
23
|
+
if (messagesIdx < 0)
|
|
24
|
+
return null;
|
|
25
|
+
const encoded = rest.slice(0, messagesIdx);
|
|
26
|
+
if (!encoded)
|
|
27
|
+
return null;
|
|
28
|
+
try {
|
|
29
|
+
return decodeURIComponent(encoded);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function handleSessionsMessages(req, res) {
|
|
36
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
37
|
+
const sessionKey = decodeSessionKeyFromPath(url.pathname);
|
|
38
|
+
if (!sessionKey)
|
|
39
|
+
return false;
|
|
40
|
+
if (!extractBearerToken(req)) {
|
|
41
|
+
res.statusCode = 401;
|
|
42
|
+
res.setHeader("Content-Type", "application/json");
|
|
43
|
+
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const accountId = requireAccountId(req);
|
|
47
|
+
if (!accountId) {
|
|
48
|
+
res.statusCode = 400;
|
|
49
|
+
res.setHeader("Content-Type", "application/json");
|
|
50
|
+
res.end(JSON.stringify({ error: "Missing or invalid X-Account-Id header" }));
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(getFridayNextRuntime().config));
|
|
54
|
+
if (!accountExists(cfg.historyDir, accountId)) {
|
|
55
|
+
res.statusCode = 403;
|
|
56
|
+
res.setHeader("Content-Type", "application/json");
|
|
57
|
+
res.end(JSON.stringify({ error: "Unknown account" }));
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (!sessionBelongsToAccount(sessionKey, accountId)) {
|
|
61
|
+
res.statusCode = 403;
|
|
62
|
+
res.setHeader("Content-Type", "application/json");
|
|
63
|
+
res.end(JSON.stringify({ error: "Session does not belong to account" }));
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (req.method === "GET") {
|
|
67
|
+
const since = Math.max(0, Number.parseInt(url.searchParams.get("since") ?? "0", 10) || 0);
|
|
68
|
+
const limit = Math.min(cfg.syncMaxBatchSize, Math.max(1, Number.parseInt(url.searchParams.get("limit") ?? String(cfg.syncMaxBatchSize), 10) || cfg.syncMaxBatchSize));
|
|
69
|
+
const direction = url.searchParams.get("direction") === "desc" ? "desc" : "asc";
|
|
70
|
+
const events = readSince({
|
|
71
|
+
accountId,
|
|
72
|
+
sessionKey,
|
|
73
|
+
sinceSeq: since,
|
|
74
|
+
limit,
|
|
75
|
+
direction,
|
|
76
|
+
historyDir: cfg.historyDir,
|
|
77
|
+
});
|
|
78
|
+
res.statusCode = 200;
|
|
79
|
+
res.setHeader("Content-Type", "application/json");
|
|
80
|
+
res.end(JSON.stringify({ ok: true, accountId, sessionKey, events }));
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (req.method === "POST") {
|
|
84
|
+
const body = (await readJsonBody(req));
|
|
85
|
+
const rawEvents = Array.isArray(body?.events) ? body.events : [];
|
|
86
|
+
if (rawEvents.length > cfg.syncMaxBatchSize) {
|
|
87
|
+
res.statusCode = 400;
|
|
88
|
+
res.setHeader("Content-Type", "application/json");
|
|
89
|
+
res.end(JSON.stringify({ error: `Too many events (max ${cfg.syncMaxBatchSize})` }));
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
const parsed = rawEvents
|
|
93
|
+
.map((item) => {
|
|
94
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
95
|
+
return null;
|
|
96
|
+
const o = item;
|
|
97
|
+
const kind = typeof o.kind === "string" ? o.kind : "";
|
|
98
|
+
if (!VALID_KINDS.has(kind))
|
|
99
|
+
return null;
|
|
100
|
+
const clientId = typeof o.clientId === "string" ? o.clientId : accountId;
|
|
101
|
+
const payload = o.payload && typeof o.payload === "object" && !Array.isArray(o.payload)
|
|
102
|
+
? o.payload
|
|
103
|
+
: {};
|
|
104
|
+
return {
|
|
105
|
+
kind: kind,
|
|
106
|
+
clientId,
|
|
107
|
+
payload,
|
|
108
|
+
messageId: typeof o.messageId === "string" ? o.messageId : undefined,
|
|
109
|
+
lamport: typeof o.lamport === "number" ? o.lamport : undefined,
|
|
110
|
+
ts: typeof o.ts === "number" ? o.ts : undefined,
|
|
111
|
+
};
|
|
112
|
+
})
|
|
113
|
+
.filter((x) => x !== null);
|
|
114
|
+
const result = upsertEvents({
|
|
115
|
+
accountId,
|
|
116
|
+
sessionKey,
|
|
117
|
+
events: parsed,
|
|
118
|
+
historyDir: cfg.historyDir,
|
|
119
|
+
});
|
|
120
|
+
res.statusCode = 200;
|
|
121
|
+
res.setHeader("Content-Type", "application/json");
|
|
122
|
+
res.end(JSON.stringify({
|
|
123
|
+
ok: true,
|
|
124
|
+
accountId,
|
|
125
|
+
sessionKey,
|
|
126
|
+
applied: result.applied,
|
|
127
|
+
events: result.events,
|
|
128
|
+
}));
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
res.statusCode = 405;
|
|
132
|
+
res.setHeader("Content-Type", "application/json");
|
|
133
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { setSessionSettings, getSessionSettings, splitModelRef, } from "../../session/session-manager.js";
|
|
2
|
+
import { readJsonBody } from "../middleware/body.js";
|
|
3
|
+
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
+
const VALID_REASONING = new Set(["on", "off", "stream"]);
|
|
5
|
+
const VALID_THINKING = new Set(["off", "minimal", "low", "medium", "high"]);
|
|
6
|
+
export async function handleSessionsSettings(req, res) {
|
|
7
|
+
if (req.method !== "PUT" && req.method !== "GET") {
|
|
8
|
+
res.statusCode = 405;
|
|
9
|
+
res.setHeader("Content-Type", "application/json");
|
|
10
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
const token = extractBearerToken(req);
|
|
14
|
+
if (!token) {
|
|
15
|
+
res.statusCode = 401;
|
|
16
|
+
res.setHeader("Content-Type", "application/json");
|
|
17
|
+
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (req.method === "GET") {
|
|
21
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
22
|
+
const sessionKey = (url.searchParams.get("sessionKey") ?? "").trim();
|
|
23
|
+
if (!sessionKey) {
|
|
24
|
+
res.statusCode = 400;
|
|
25
|
+
res.setHeader("Content-Type", "application/json");
|
|
26
|
+
res.end(JSON.stringify({ error: "Missing required query param: sessionKey" }));
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const settings = getSessionSettings(sessionKey);
|
|
30
|
+
res.statusCode = 200;
|
|
31
|
+
res.setHeader("Content-Type", "application/json");
|
|
32
|
+
res.end(JSON.stringify({ ok: true, sessionKey, ...settings }));
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// PUT
|
|
36
|
+
const body = await readJsonBody(req);
|
|
37
|
+
const sessionKey = typeof body?.sessionKey === "string" ? body.sessionKey.trim() : "";
|
|
38
|
+
if (!sessionKey) {
|
|
39
|
+
res.statusCode = 400;
|
|
40
|
+
res.setHeader("Content-Type", "application/json");
|
|
41
|
+
res.end(JSON.stringify({ error: "Missing required field: sessionKey" }));
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const reasoningLevel = typeof body?.reasoningLevel === "string" ? body.reasoningLevel : undefined;
|
|
45
|
+
const thinkingLevel = typeof body?.thinkingLevel === "string" ? body.thinkingLevel : undefined;
|
|
46
|
+
const modelRef = typeof body?.modelRef === "string" ? body.modelRef : undefined;
|
|
47
|
+
const errors = [];
|
|
48
|
+
if (reasoningLevel !== undefined && !VALID_REASONING.has(reasoningLevel)) {
|
|
49
|
+
errors.push(`reasoningLevel must be one of: ${[...VALID_REASONING].join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
if (thinkingLevel !== undefined && !VALID_THINKING.has(thinkingLevel)) {
|
|
52
|
+
errors.push(`thinkingLevel must be one of: ${[...VALID_THINKING].join(", ")}`);
|
|
53
|
+
}
|
|
54
|
+
if (errors.length > 0) {
|
|
55
|
+
res.statusCode = 400;
|
|
56
|
+
res.setHeader("Content-Type", "application/json");
|
|
57
|
+
res.end(JSON.stringify({ error: errors.join("; ") }));
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const settings = { reasoningLevel, thinkingLevel, modelRef };
|
|
61
|
+
if (modelRef) {
|
|
62
|
+
const split = splitModelRef(modelRef);
|
|
63
|
+
settings["providerOverride"] = split.provider;
|
|
64
|
+
settings["modelOverride"] = split.modelId;
|
|
65
|
+
}
|
|
66
|
+
const result = setSessionSettings(sessionKey, settings);
|
|
67
|
+
res.statusCode = 200;
|
|
68
|
+
res.setHeader("Content-Type", "application/json");
|
|
69
|
+
res.end(JSON.stringify({ ok: true, sessionKey, ...result }));
|
|
70
|
+
return true;
|
|
71
|
+
}
|