@adelie/server 0.5.6 → 0.5.8
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/api/types.d.ts +92 -4
- package/dist/api/types.js +1 -1
- package/dist/{chunk-CXGWSWJA.js → chunk-3Y7I7J3W.js} +13 -1
- package/dist/{chunk-CXGWSWJA.js.map → chunk-3Y7I7J3W.js.map} +1 -1
- package/dist/{chunk-FC7VCPLY.js → chunk-6IXN3YH4.js} +1 -1
- package/dist/chunk-6IXN3YH4.js.map +1 -0
- package/dist/index.js +803 -467
- package/dist/index.js.map +1 -1
- package/dist/install.ps1 +1 -1
- package/dist/install.sh +1 -1
- package/dist/plugin/index.d.ts +5 -2
- package/dist/reset-admin-password.js +1 -1
- package/package.json +4 -4
- package/web-dist/assets/{addon-clipboard-Bw3Lpj1i.js → addon-clipboard-Cr1IGZn2.js} +1 -1
- package/web-dist/assets/{addon-fit-0cr5GD_d.js → addon-fit-C_pn1ddy.js} +1 -1
- package/web-dist/assets/{addon-web-links-tK8gZ-ju.js → addon-web-links-CqNDWe_S.js} +1 -1
- package/web-dist/assets/{highlighter-oOvU_JEd.js → highlighter-D222XDsD.js} +2 -2
- package/web-dist/assets/{highlighter-core-B4OQxQnF.js → highlighter-core-B2uV9rSe.js} +1 -1
- package/web-dist/assets/index-Bi9AMLAl.js +1090 -0
- package/web-dist/assets/index-DiDPf1zX.css +1 -0
- package/web-dist/assets/{xterm-EMDPfXxC.js → xterm-C3by7jJ-.js} +1 -1
- package/web-dist/index.html +2 -2
- package/dist/chunk-FC7VCPLY.js.map +0 -1
- package/web-dist/assets/index-CJYYshdE.css +0 -1
- package/web-dist/assets/index-DlM6oy5i.js +0 -1080
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-GO6G445C.js";
|
|
9
9
|
import {
|
|
10
10
|
TICKET_SLUG_PATTERN
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-6IXN3YH4.js";
|
|
12
12
|
import {
|
|
13
13
|
MachinesRepo
|
|
14
14
|
} from "./chunk-HMTT2PX4.js";
|
|
@@ -25,9 +25,10 @@ import {
|
|
|
25
25
|
isMissingCredential,
|
|
26
26
|
mintApiToken,
|
|
27
27
|
modelCredentialMissing,
|
|
28
|
+
normalizeError,
|
|
28
29
|
storeApiToken,
|
|
29
30
|
toUserInfo
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-3Y7I7J3W.js";
|
|
31
32
|
import {
|
|
32
33
|
acquireServerLock,
|
|
33
34
|
liveServerLock,
|
|
@@ -77,6 +78,7 @@ var MIN_ATTACHMENT_MB = 1;
|
|
|
77
78
|
var MAX_ATTACHMENT_MB = 200;
|
|
78
79
|
var MAX_ATTACHMENT_COUNT = 20;
|
|
79
80
|
var INLINE_IMAGE_MAX_MB = 20;
|
|
81
|
+
var INLINE_VIDEO_MAX_MB = 32;
|
|
80
82
|
function base64Length(bytes) {
|
|
81
83
|
return Math.ceil(bytes / 3) * 4;
|
|
82
84
|
}
|
|
@@ -89,6 +91,7 @@ function toAttachmentLimits(mb) {
|
|
|
89
91
|
};
|
|
90
92
|
}
|
|
91
93
|
var INLINE_IMAGE_MAX_BYTES = INLINE_IMAGE_MAX_MB * MB;
|
|
94
|
+
var INLINE_VIDEO_MAX_BYTES = INLINE_VIDEO_MAX_MB * MB;
|
|
92
95
|
function bodyLimitBytes(mb) {
|
|
93
96
|
return base64Length(mb.attachmentTotalMb * MB) + BODY_HEADROOM_BYTES;
|
|
94
97
|
}
|
|
@@ -2035,7 +2038,7 @@ async function probe(instance, path210) {
|
|
|
2035
2038
|
}
|
|
2036
2039
|
}
|
|
2037
2040
|
var json = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2038
|
-
var bad = (
|
|
2041
|
+
var bad = (message3) => json(400, { error: { code: "bad_request", message: message3 } });
|
|
2039
2042
|
function parseUpgradeTarget(contentType, body, readBlob = () => null) {
|
|
2040
2043
|
const type2 = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
2041
2044
|
if (type2 !== "application/gzip" && type2 !== "application/octet-stream") {
|
|
@@ -2888,8 +2891,8 @@ import { Hono } from "hono";
|
|
|
2888
2891
|
import fs6 from "fs/promises";
|
|
2889
2892
|
import path9 from "path";
|
|
2890
2893
|
import { isValidId } from "@adelie/core";
|
|
2891
|
-
function badRequest(
|
|
2892
|
-
return new HttpError(400, "bad_request",
|
|
2894
|
+
function badRequest(message3) {
|
|
2895
|
+
return new HttpError(400, "bad_request", message3);
|
|
2893
2896
|
}
|
|
2894
2897
|
function pathParam(c, name) {
|
|
2895
2898
|
const v = c.req.param(name);
|
|
@@ -3680,7 +3683,7 @@ import { DEFAULT_PROJECT_ID as DEFAULT_PROJECT_ID3, DEFAULT_PROJECT_NAME as DEFA
|
|
|
3680
3683
|
|
|
3681
3684
|
// src/ifaces.json
|
|
3682
3685
|
var ifaces_default = {
|
|
3683
|
-
hash: "
|
|
3686
|
+
hash: "0b0f68e407b030585d1a39c05a849df1a4d025bd5b85d34c5246b92a42670a96",
|
|
3684
3687
|
ifaces: {
|
|
3685
3688
|
"@adelie/server#Access": {
|
|
3686
3689
|
name: "Access",
|
|
@@ -18771,7 +18774,7 @@ var ifaces_default = {
|
|
|
18771
18774
|
}
|
|
18772
18775
|
}
|
|
18773
18776
|
},
|
|
18774
|
-
|
|
18777
|
+
readTraceMedia: {
|
|
18775
18778
|
params: [
|
|
18776
18779
|
{
|
|
18777
18780
|
data: "string"
|
|
@@ -18790,6 +18793,9 @@ var ifaces_default = {
|
|
|
18790
18793
|
},
|
|
18791
18794
|
{
|
|
18792
18795
|
data: "undefined|number"
|
|
18796
|
+
},
|
|
18797
|
+
{
|
|
18798
|
+
data: "'image'|'video'"
|
|
18793
18799
|
}
|
|
18794
18800
|
],
|
|
18795
18801
|
returns: {
|
|
@@ -19110,7 +19116,7 @@ var ifaces_default = {
|
|
|
19110
19116
|
}
|
|
19111
19117
|
}
|
|
19112
19118
|
},
|
|
19113
|
-
|
|
19119
|
+
readTraceMedia: {
|
|
19114
19120
|
params: [
|
|
19115
19121
|
{
|
|
19116
19122
|
data: "string"
|
|
@@ -19129,6 +19135,9 @@ var ifaces_default = {
|
|
|
19129
19135
|
},
|
|
19130
19136
|
{
|
|
19131
19137
|
data: "undefined|number"
|
|
19138
|
+
},
|
|
19139
|
+
{
|
|
19140
|
+
data: "'image'|'video'"
|
|
19132
19141
|
}
|
|
19133
19142
|
],
|
|
19134
19143
|
returns: {
|
|
@@ -21878,6 +21887,7 @@ var ifaces_default = {
|
|
|
21878
21887
|
"client_type?": "string",
|
|
21879
21888
|
"display_name?": "string",
|
|
21880
21889
|
"vision?": "boolean",
|
|
21890
|
+
"video?": "boolean",
|
|
21881
21891
|
"max_tokens?": "number",
|
|
21882
21892
|
"fast_mode?": "boolean",
|
|
21883
21893
|
"pricing?": {
|
|
@@ -21964,22 +21974,28 @@ var ifaces_default = {
|
|
|
21964
21974
|
[
|
|
21965
21975
|
[
|
|
21966
21976
|
[
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21977
|
+
[
|
|
21978
|
+
{
|
|
21979
|
+
$ref: "@adelie/core#ToolCallPayload"
|
|
21980
|
+
},
|
|
21981
|
+
"|",
|
|
21982
|
+
{
|
|
21983
|
+
$ref: "@adelie/core#SessionMetaPayload"
|
|
21984
|
+
}
|
|
21985
|
+
],
|
|
21970
21986
|
"|",
|
|
21971
21987
|
{
|
|
21972
|
-
$ref: "@adelie/core#
|
|
21988
|
+
$ref: "@adelie/core#TextPayload"
|
|
21973
21989
|
}
|
|
21974
21990
|
],
|
|
21975
21991
|
"|",
|
|
21976
21992
|
{
|
|
21977
|
-
$ref: "@adelie/core#
|
|
21993
|
+
$ref: "@adelie/core#ImageUrlPayload"
|
|
21978
21994
|
}
|
|
21979
21995
|
],
|
|
21980
21996
|
"|",
|
|
21981
21997
|
{
|
|
21982
|
-
$ref: "@adelie/core#
|
|
21998
|
+
$ref: "@adelie/core#VideoUrlPayload"
|
|
21983
21999
|
}
|
|
21984
22000
|
],
|
|
21985
22001
|
"|",
|
|
@@ -22334,6 +22350,14 @@ var ifaces_default = {
|
|
|
22334
22350
|
}
|
|
22335
22351
|
},
|
|
22336
22352
|
"@adelie/core#ToolPermission": "'r'|'rw'",
|
|
22353
|
+
"@adelie/core#VideoUrlPayload": {
|
|
22354
|
+
type: "'video_url'",
|
|
22355
|
+
role: "'user'",
|
|
22356
|
+
video_url: "string",
|
|
22357
|
+
"stop_reason?": {
|
|
22358
|
+
$ref: "@adelie/core#StopReason"
|
|
22359
|
+
}
|
|
22360
|
+
},
|
|
22337
22361
|
"@adelie/server#AccessibleProjectRow": {
|
|
22338
22362
|
role: {
|
|
22339
22363
|
$ref: "@adelie/server#ProjectRole"
|
|
@@ -23130,6 +23154,7 @@ var ifaces_default = {
|
|
|
23130
23154
|
"contextWindow?": "number",
|
|
23131
23155
|
"clientType?": "string",
|
|
23132
23156
|
"vision?": "boolean",
|
|
23157
|
+
"video?": "boolean",
|
|
23133
23158
|
"maxTokens?": "number",
|
|
23134
23159
|
"fastMode?": "boolean",
|
|
23135
23160
|
"pricing?": {
|
|
@@ -23953,12 +23978,14 @@ var ifaces_default = {
|
|
|
23953
23978
|
id: "string",
|
|
23954
23979
|
text: "string",
|
|
23955
23980
|
images: "number",
|
|
23981
|
+
videos: "number",
|
|
23956
23982
|
files: "number"
|
|
23957
23983
|
},
|
|
23958
23984
|
"@adelie/server#PendingSteeringInfo": {
|
|
23959
23985
|
id: "string",
|
|
23960
23986
|
text: "string",
|
|
23961
23987
|
images: "number",
|
|
23988
|
+
videos: "number",
|
|
23962
23989
|
files: "number"
|
|
23963
23990
|
},
|
|
23964
23991
|
"@adelie/server#PlatformAuthFlowErrorCode": "'upstream_failed'|'unreachable'|'apply_failed'|'invalid_key'|'expired'|'locked'|'already_delivered'",
|
|
@@ -24238,6 +24265,9 @@ var ifaces_default = {
|
|
|
24238
24265
|
images: {
|
|
24239
24266
|
$ref: "typescript#Array<string>"
|
|
24240
24267
|
},
|
|
24268
|
+
videos: {
|
|
24269
|
+
$ref: "typescript#Array<string>"
|
|
24270
|
+
},
|
|
24241
24271
|
files: {
|
|
24242
24272
|
$ref: "typescript#Array<RecallableFile>"
|
|
24243
24273
|
}
|
|
@@ -31714,10 +31744,10 @@ var StreamErrorWatcher = class {
|
|
|
31714
31744
|
this.pending.delete(key2);
|
|
31715
31745
|
const spec = retried && entry.status === "retryable" ? LLM_RETRIED : LLM_FAILURES[entry.status];
|
|
31716
31746
|
const trimmed = reason?.trim();
|
|
31717
|
-
const
|
|
31747
|
+
const message3 = trimmed && !isUserAbortReason(trimmed) ? trimmed : this.composeMessage(entry, retried, spec);
|
|
31718
31748
|
this.errors.record({
|
|
31719
31749
|
source: "llm",
|
|
31720
|
-
err:
|
|
31750
|
+
err: message3,
|
|
31721
31751
|
ctx: this.ctxFor(key2),
|
|
31722
31752
|
code: spec.code,
|
|
31723
31753
|
kind: spec.kind
|
|
@@ -32006,7 +32036,7 @@ var SandboxService = class {
|
|
|
32006
32036
|
);
|
|
32007
32037
|
}
|
|
32008
32038
|
failedSuffix() {
|
|
32009
|
-
const parts = [...this.loadErrors].map(([name,
|
|
32039
|
+
const parts = [...this.loadErrors].map(([name, message3]) => `${name} (${message3})`);
|
|
32010
32040
|
if (this.declinedNames.length > 0) {
|
|
32011
32041
|
parts.push(`${this.declinedNames.join(", ")} (not for this host)`);
|
|
32012
32042
|
}
|
|
@@ -32322,8 +32352,8 @@ var UNSUPPORTED_PATTERNS = [
|
|
|
32322
32352
|
"cannot process image",
|
|
32323
32353
|
"image not supported"
|
|
32324
32354
|
];
|
|
32325
|
-
function isImageRejection(
|
|
32326
|
-
const m =
|
|
32355
|
+
function isImageRejection(message3) {
|
|
32356
|
+
const m = message3.toLowerCase();
|
|
32327
32357
|
return UNSUPPORTED_PATTERNS.some((p) => m.includes(p));
|
|
32328
32358
|
}
|
|
32329
32359
|
function classifyVisionProbe(outcome, sawContent) {
|
|
@@ -32333,8 +32363,8 @@ function classifyVisionProbe(outcome, sawContent) {
|
|
|
32333
32363
|
return isImageRejection(String(detail)) ? "unsupported" : "failed";
|
|
32334
32364
|
}
|
|
32335
32365
|
function classifyVisionProbeError(err) {
|
|
32336
|
-
const
|
|
32337
|
-
return isImageRejection(
|
|
32366
|
+
const message3 = err instanceof Error ? err.message : String(err);
|
|
32367
|
+
return isImageRejection(message3) ? "unsupported" : "failed";
|
|
32338
32368
|
}
|
|
32339
32369
|
|
|
32340
32370
|
// src/services/project-config-service.ts
|
|
@@ -33121,6 +33151,7 @@ var ProjectConfigService = class {
|
|
|
33121
33151
|
const fallback = modelEnvFallback({ provider, modelId, clientType, baseUrl: credBaseUrl });
|
|
33122
33152
|
const envKey = fallback?.envKey;
|
|
33123
33153
|
const vision = typeof m.vision === "boolean" ? m.vision : cat?.supportsVision;
|
|
33154
|
+
const video = m.video === true || cat?.supportsVideo === true ? true : void 0;
|
|
33124
33155
|
const maxTokens = optNum(m.max_tokens);
|
|
33125
33156
|
const fastMode = m.fast_mode === true ? true : void 0;
|
|
33126
33157
|
const displayName = m.display_name === "" ? "" : optStr(m.display_name) ?? cat?.displayName;
|
|
@@ -33142,6 +33173,7 @@ var ProjectConfigService = class {
|
|
|
33142
33173
|
...optNum(m.context_window) !== void 0 ? { contextWindow: optNum(m.context_window) } : {},
|
|
33143
33174
|
...clientType ? { clientType } : {},
|
|
33144
33175
|
...vision !== void 0 ? { vision } : {},
|
|
33176
|
+
...video !== void 0 ? { video } : {},
|
|
33145
33177
|
...maxTokens !== void 0 ? { maxTokens } : {},
|
|
33146
33178
|
...fastMode !== void 0 ? { fastMode } : {},
|
|
33147
33179
|
...envKey ? { envKey } : {},
|
|
@@ -34099,8 +34131,8 @@ function maskValues(schema2, values) {
|
|
|
34099
34131
|
return out;
|
|
34100
34132
|
}
|
|
34101
34133
|
var PluginConfigError = class extends Error {
|
|
34102
|
-
constructor(field,
|
|
34103
|
-
super(
|
|
34134
|
+
constructor(field, message3) {
|
|
34135
|
+
super(message3);
|
|
34104
34136
|
this.field = field;
|
|
34105
34137
|
this.name = "PluginConfigError";
|
|
34106
34138
|
}
|
|
@@ -35860,13 +35892,13 @@ function compactUnavailable(why) {
|
|
|
35860
35892
|
message: "The context was just compacted and there is no new conversation since; no need to compact again."
|
|
35861
35893
|
}
|
|
35862
35894
|
};
|
|
35863
|
-
const { code, message:
|
|
35864
|
-
return new HttpError(409, code,
|
|
35895
|
+
const { code, message: message3 } = reasons[why];
|
|
35896
|
+
return new HttpError(409, code, message3);
|
|
35865
35897
|
}
|
|
35866
35898
|
function switchRefusal(err, ref) {
|
|
35867
35899
|
if (!(err instanceof ModelSwitchRefusedError)) throw err;
|
|
35868
|
-
const
|
|
35869
|
-
return new HttpError(409, err.reason,
|
|
35900
|
+
const message3 = err.reason === "model_unavailable" && isMissingCredential(err) ? `Model ${ref.modelId} has no API key yet. Configure it on the Models page first.` : err.message;
|
|
35901
|
+
return new HttpError(409, err.reason, message3);
|
|
35870
35902
|
}
|
|
35871
35903
|
async function* resumeFrom(head, rest) {
|
|
35872
35904
|
yield head;
|
|
@@ -35962,19 +35994,24 @@ function followUpInfo(f) {
|
|
|
35962
35994
|
id: f.id,
|
|
35963
35995
|
text: f.recall.text,
|
|
35964
35996
|
images: f.recall.images.length,
|
|
35997
|
+
videos: f.recall.videos.length,
|
|
35965
35998
|
files: f.recall.files.length
|
|
35966
35999
|
};
|
|
35967
36000
|
}
|
|
35968
36001
|
function recallStoreOf(input) {
|
|
35969
36002
|
const text2 = input.filter(isPlainText("user")).map((m) => m.payload.text).join("\n");
|
|
35970
36003
|
const images = [];
|
|
36004
|
+
const videos = [];
|
|
35971
36005
|
for (const msg of input) {
|
|
35972
36006
|
const payload = msg.payload;
|
|
35973
|
-
if (msg.type
|
|
36007
|
+
if (msg.type !== "model_msg") continue;
|
|
36008
|
+
if (payload.type === "image_url" && typeof payload.image_url === "string") {
|
|
35974
36009
|
images.push(payload.image_url);
|
|
36010
|
+
} else if (payload.type === "video_url" && typeof payload.video_url === "string") {
|
|
36011
|
+
videos.push(payload.video_url);
|
|
35975
36012
|
}
|
|
35976
36013
|
}
|
|
35977
|
-
return { text: text2, images, files: [] };
|
|
36014
|
+
return { text: text2, images, videos, files: [] };
|
|
35978
36015
|
}
|
|
35979
36016
|
function isDeliveredSteering(msg) {
|
|
35980
36017
|
const p = msg.payload;
|
|
@@ -36732,6 +36769,7 @@ var SessionManager = class {
|
|
|
36732
36769
|
id: randomUUID5(),
|
|
36733
36770
|
text: recall.text,
|
|
36734
36771
|
images: recall.images.length,
|
|
36772
|
+
videos: recall.videos.length,
|
|
36735
36773
|
files: recall.files.length
|
|
36736
36774
|
},
|
|
36737
36775
|
input,
|
|
@@ -37689,8 +37727,8 @@ __runInitializers(_init44, 1, DefaultTitleGenerators);
|
|
|
37689
37727
|
|
|
37690
37728
|
// src/runtime/messaging/connector.ts
|
|
37691
37729
|
var MessagingChannelError = class extends Error {
|
|
37692
|
-
constructor(
|
|
37693
|
-
super(
|
|
37730
|
+
constructor(message3, recovers) {
|
|
37731
|
+
super(message3);
|
|
37694
37732
|
this.recovers = recovers;
|
|
37695
37733
|
this.name = "MessagingChannelError";
|
|
37696
37734
|
}
|
|
@@ -37742,8 +37780,8 @@ var MessagingMediaTooLargeError = class extends Error {
|
|
|
37742
37780
|
}
|
|
37743
37781
|
};
|
|
37744
37782
|
var MessagingPermissionError = class extends Error {
|
|
37745
|
-
constructor(scopes, grantUrl,
|
|
37746
|
-
super(
|
|
37783
|
+
constructor(scopes, grantUrl, message3) {
|
|
37784
|
+
super(message3);
|
|
37747
37785
|
this.scopes = scopes;
|
|
37748
37786
|
this.grantUrl = grantUrl;
|
|
37749
37787
|
this.name = "MessagingPermissionError";
|
|
@@ -37752,14 +37790,14 @@ var MessagingPermissionError = class extends Error {
|
|
|
37752
37790
|
grantUrl;
|
|
37753
37791
|
};
|
|
37754
37792
|
var MessagingUnsupportedError = class extends Error {
|
|
37755
|
-
constructor(
|
|
37756
|
-
super(
|
|
37793
|
+
constructor(message3) {
|
|
37794
|
+
super(message3);
|
|
37757
37795
|
this.name = "MessagingUnsupportedError";
|
|
37758
37796
|
}
|
|
37759
37797
|
};
|
|
37760
37798
|
var MessagingOutboundCapError = class extends Error {
|
|
37761
|
-
constructor(
|
|
37762
|
-
super(
|
|
37799
|
+
constructor(message3) {
|
|
37800
|
+
super(message3);
|
|
37763
37801
|
this.name = "MessagingOutboundCapError";
|
|
37764
37802
|
}
|
|
37765
37803
|
};
|
|
@@ -37800,8 +37838,8 @@ function closeCodeIsRoutine(code) {
|
|
|
37800
37838
|
return code === 4009 || code >= 4900 && code <= 4913 || code === 1001 || code === 1006 || code >= 1011 && code <= 1014;
|
|
37801
37839
|
}
|
|
37802
37840
|
var MessagingConnectionClosedError = class extends MessagingChannelError {
|
|
37803
|
-
constructor(
|
|
37804
|
-
super(
|
|
37841
|
+
constructor(message3, closeCode, recovers) {
|
|
37842
|
+
super(message3, recovers);
|
|
37805
37843
|
this.closeCode = closeCode;
|
|
37806
37844
|
this.name = "MessagingConnectionClosedError";
|
|
37807
37845
|
}
|
|
@@ -37828,21 +37866,21 @@ var QQ_BODY_INDEPENDENT_SEND_CODES = /* @__PURE__ */ new Set([
|
|
|
37828
37866
|
40034105,
|
|
37829
37867
|
40054013
|
|
37830
37868
|
]);
|
|
37831
|
-
function qqSendErrorText(errCode,
|
|
37869
|
+
function qqSendErrorText(errCode, message3) {
|
|
37832
37870
|
if (errCode === 40034128) {
|
|
37833
|
-
return `${
|
|
37871
|
+
return `${message3} \u2014 QQ allows only a few replies to one message, within minutes of it`;
|
|
37834
37872
|
}
|
|
37835
37873
|
if (errCode === 40034005 || errCode === 304103 || errCode === 40034024) {
|
|
37836
|
-
return `${
|
|
37874
|
+
return `${message3} \u2014 the QQ message being replied to has expired`;
|
|
37837
37875
|
}
|
|
37838
37876
|
if (errCode === 40034105 || errCode === 40054013) {
|
|
37839
|
-
return `${
|
|
37877
|
+
return `${message3} \u2014 this QQ user has turned off messages from bots`;
|
|
37840
37878
|
}
|
|
37841
|
-
return
|
|
37879
|
+
return message3;
|
|
37842
37880
|
}
|
|
37843
37881
|
var QQApiError = class extends MessagingChannelError {
|
|
37844
|
-
constructor(
|
|
37845
|
-
super(
|
|
37882
|
+
constructor(message3, code, recovers = false) {
|
|
37883
|
+
super(message3, recovers);
|
|
37846
37884
|
this.code = code;
|
|
37847
37885
|
this.name = "QQApiError";
|
|
37848
37886
|
}
|
|
@@ -39087,8 +39125,8 @@ function scopeDenialDetail(msg) {
|
|
|
39087
39125
|
return { scopes, grantUrl: url !== null && url.length <= GRANT_URL_MAX ? url : null };
|
|
39088
39126
|
}
|
|
39089
39127
|
var FeishuApiError = class extends Error {
|
|
39090
|
-
constructor(
|
|
39091
|
-
super(
|
|
39128
|
+
constructor(message3, code) {
|
|
39129
|
+
super(message3);
|
|
39092
39130
|
this.code = code;
|
|
39093
39131
|
this.name = "FeishuApiError";
|
|
39094
39132
|
}
|
|
@@ -39666,8 +39704,8 @@ var TELEGRAM_MAX_DOWNLOAD_BYTES = 20 * 1024 * 1024;
|
|
|
39666
39704
|
var CALL_TIMEOUT_MS3 = 15e3;
|
|
39667
39705
|
var TRANSFER_TIMEOUT_MS = 6e4;
|
|
39668
39706
|
var TelegramApiError = class extends MessagingChannelError {
|
|
39669
|
-
constructor(
|
|
39670
|
-
super(
|
|
39707
|
+
constructor(message3, errorCode, recovers = httpStatusRecovers(errorCode)) {
|
|
39708
|
+
super(message3, recovers);
|
|
39671
39709
|
this.errorCode = errorCode;
|
|
39672
39710
|
this.name = "TelegramApiError";
|
|
39673
39711
|
}
|
|
@@ -40835,8 +40873,8 @@ var AES_KEY_BYTES = 16;
|
|
|
40835
40873
|
var WECHAT_VIDEO_FILE_NAME = "video.mp4";
|
|
40836
40874
|
var WECHAT_STALE_TOKEN_CODE = -14;
|
|
40837
40875
|
var WeChatApiError = class extends MessagingChannelError {
|
|
40838
|
-
constructor(ret,
|
|
40839
|
-
super(
|
|
40876
|
+
constructor(ret, message3, flags = {}) {
|
|
40877
|
+
super(message3, flags.recovers ?? false);
|
|
40840
40878
|
this.ret = ret;
|
|
40841
40879
|
this.name = "WeChatApiError";
|
|
40842
40880
|
this.authenticated = flags.authenticated ?? false;
|
|
@@ -42530,9 +42568,9 @@ function messagingFilesMissingLog(names) {
|
|
|
42530
42568
|
function filesSkippedError(channel, fileNames) {
|
|
42531
42569
|
const n = fileNames.length;
|
|
42532
42570
|
const files = n === 1 ? "1 more file the reply mentioned was" : `${n} more files the reply mentioned were`;
|
|
42533
|
-
const
|
|
42571
|
+
const message3 = (list) => `${files} not sent to the ${channel} chat: at most ${MESSAGING_OUTBOUND_FILE_MAX_COUNT} ride along with one reply (${list})`;
|
|
42534
42572
|
return new MessagingOutboundCapError(
|
|
42535
|
-
|
|
42573
|
+
message3(quotedNames(fileNames, MESSAGE_MAX - message3("").length))
|
|
42536
42574
|
);
|
|
42537
42575
|
}
|
|
42538
42576
|
var MESSAGING_INBOUND_IMAGE_BUDGET_BYTES = 2 * INLINE_IMAGE_MAX_BYTES;
|
|
@@ -44323,9 +44361,9 @@ var HarnessHistoryStore = class {
|
|
|
44323
44361
|
await this.push(id);
|
|
44324
44362
|
return true;
|
|
44325
44363
|
} catch (err) {
|
|
44326
|
-
const
|
|
44327
|
-
this.lastRollback = { id, error:
|
|
44328
|
-
this.log.line(`[harness-history] rollback to ${id} failed: ${
|
|
44364
|
+
const message3 = err instanceof Error ? err.message : String(err);
|
|
44365
|
+
this.lastRollback = { id, error: message3, at: this.clock.now().toISOString() };
|
|
44366
|
+
this.log.line(`[harness-history] rollback to ${id} failed: ${message3}`);
|
|
44329
44367
|
throw err;
|
|
44330
44368
|
}
|
|
44331
44369
|
}
|
|
@@ -49159,8 +49197,8 @@ async function scanMessages(state, messages, onBoundary, expandChild, fromOrdina
|
|
|
49159
49197
|
if (msg.origin !== void 0 && msg.origin.length > 0) continue;
|
|
49160
49198
|
const ms = tsMs(msg.timestamp);
|
|
49161
49199
|
const p = msg.payload;
|
|
49162
|
-
const
|
|
49163
|
-
if (!
|
|
49200
|
+
const isUserMedia = msg.type === "model_msg" && (p.type === "image_url" || p.type === "video_url");
|
|
49201
|
+
if (!isUserMedia) state.steeringOpen = false;
|
|
49164
49202
|
if (msg.type === "model_msg") {
|
|
49165
49203
|
if (state.compactionActive) {
|
|
49166
49204
|
touchTask(state, ms);
|
|
@@ -49192,7 +49230,7 @@ async function scanMessages(state, messages, onBoundary, expandChild, fromOrdina
|
|
|
49192
49230
|
);
|
|
49193
49231
|
continue;
|
|
49194
49232
|
}
|
|
49195
|
-
if (p.type === "image_url") {
|
|
49233
|
+
if (p.type === "image_url" || p.type === "video_url") {
|
|
49196
49234
|
if (state.steeringOpen) {
|
|
49197
49235
|
touchTask(state, ms);
|
|
49198
49236
|
continue;
|
|
@@ -49448,6 +49486,7 @@ function buildContextBreakdown(messages) {
|
|
|
49448
49486
|
switch (p.type) {
|
|
49449
49487
|
case "text":
|
|
49450
49488
|
case "image_url":
|
|
49489
|
+
case "video_url":
|
|
49451
49490
|
case "inline_data":
|
|
49452
49491
|
if (p.role === "assistant") out.assistantMessages += size;
|
|
49453
49492
|
else out.userMessages += size;
|
|
@@ -49655,43 +49694,63 @@ var TraceLineIndex = class {
|
|
|
49655
49694
|
}
|
|
49656
49695
|
};
|
|
49657
49696
|
|
|
49658
|
-
// src/services/trace-
|
|
49697
|
+
// src/services/trace-media.ts
|
|
49659
49698
|
var REFERENCED_IMAGE_MIMES = /* @__PURE__ */ new Set([
|
|
49660
49699
|
"image/png",
|
|
49661
49700
|
"image/jpeg",
|
|
49662
49701
|
"image/gif",
|
|
49663
49702
|
"image/webp"
|
|
49664
49703
|
]);
|
|
49665
|
-
|
|
49704
|
+
var REFERENCED_VIDEO_MIMES = /* @__PURE__ */ new Set([
|
|
49705
|
+
"video/mp4",
|
|
49706
|
+
"video/quicktime",
|
|
49707
|
+
"video/webm",
|
|
49708
|
+
"video/x-matroska",
|
|
49709
|
+
"video/x-msvideo"
|
|
49710
|
+
]);
|
|
49711
|
+
function referableDataUrl(url, mimes) {
|
|
49666
49712
|
if (!url.startsWith("data:")) return null;
|
|
49667
49713
|
const comma = url.indexOf(",");
|
|
49668
49714
|
if (comma < 0 || comma === url.length - 1) return null;
|
|
49669
49715
|
const header = url.slice("data:".length, comma).toLowerCase();
|
|
49670
49716
|
if (!header.endsWith(";base64")) return null;
|
|
49671
49717
|
const mime = header.slice(0, header.indexOf(";")).trim();
|
|
49672
|
-
return
|
|
49718
|
+
return mimes.has(mime) ? { mime, dataStart: comma + 1 } : null;
|
|
49673
49719
|
}
|
|
49674
49720
|
function traceImageUrl(sessionId, at, slot) {
|
|
49675
49721
|
const query = `file=${at.fileIndex}&ordinal=${at.ordinal}${slot !== void 0 ? `&i=${slot}` : ""}`;
|
|
49676
49722
|
return `/api/sessions/${encodeURIComponent(sessionId)}/trace-image?${query}`;
|
|
49677
49723
|
}
|
|
49678
|
-
function
|
|
49724
|
+
function traceVideoUrl(sessionId, at) {
|
|
49725
|
+
const query = `file=${at.fileIndex}&ordinal=${at.ordinal}`;
|
|
49726
|
+
return `/api/sessions/${encodeURIComponent(sessionId)}/trace-video?${query}`;
|
|
49727
|
+
}
|
|
49728
|
+
function withMediaByReference(sessionId, msg) {
|
|
49679
49729
|
const at = msg.tracePosition;
|
|
49680
49730
|
if (at === void 0 || msg.type !== "model_msg") return msg;
|
|
49681
49731
|
const kind = msg.payload.type;
|
|
49682
49732
|
if (kind === "image_url") {
|
|
49683
49733
|
const payload = msg.payload;
|
|
49684
|
-
if (typeof payload.image_url !== "string" ||
|
|
49734
|
+
if (typeof payload.image_url !== "string" || referableDataUrl(payload.image_url, REFERENCED_IMAGE_MIMES) === null) {
|
|
49685
49735
|
return msg;
|
|
49686
49736
|
}
|
|
49687
49737
|
return { ...msg, payload: { ...payload, image_url: traceImageUrl(sessionId, at) } };
|
|
49688
49738
|
}
|
|
49739
|
+
if (kind === "video_url") {
|
|
49740
|
+
const payload = msg.payload;
|
|
49741
|
+
if (typeof payload.video_url !== "string" || referableDataUrl(payload.video_url, REFERENCED_VIDEO_MIMES) === null) {
|
|
49742
|
+
return msg;
|
|
49743
|
+
}
|
|
49744
|
+
return { ...msg, payload: { ...payload, video_url: traceVideoUrl(sessionId, at) } };
|
|
49745
|
+
}
|
|
49689
49746
|
if (kind === "tool_call_output") {
|
|
49690
49747
|
const payload = msg.payload;
|
|
49691
49748
|
if (!Array.isArray(payload.images)) return msg;
|
|
49692
49749
|
let replaced = false;
|
|
49693
49750
|
const images = payload.images.map((image, k) => {
|
|
49694
|
-
if (typeof image !== "string" ||
|
|
49751
|
+
if (typeof image !== "string" || referableDataUrl(image, REFERENCED_IMAGE_MIMES) === null) {
|
|
49752
|
+
return image;
|
|
49753
|
+
}
|
|
49695
49754
|
replaced = true;
|
|
49696
49755
|
return traceImageUrl(sessionId, at, k);
|
|
49697
49756
|
});
|
|
@@ -49699,19 +49758,21 @@ function withImagesByReference(sessionId, msg) {
|
|
|
49699
49758
|
}
|
|
49700
49759
|
return msg;
|
|
49701
49760
|
}
|
|
49702
|
-
function
|
|
49761
|
+
function traceRecordMedia(record, slot, kind) {
|
|
49703
49762
|
if (record.type !== "model_msg") return null;
|
|
49704
49763
|
const p = record.payload;
|
|
49764
|
+
const mimes = kind === "video" ? REFERENCED_VIDEO_MIMES : REFERENCED_IMAGE_MIMES;
|
|
49705
49765
|
let url;
|
|
49706
49766
|
if (slot === void 0) {
|
|
49707
|
-
|
|
49767
|
+
const wanted = kind === "video" ? "video_url" : "image_url";
|
|
49768
|
+
url = p.type === wanted ? kind === "video" ? p.video_url : p.image_url : void 0;
|
|
49708
49769
|
} else if (p.type === "tool_call_output" && Array.isArray(p.images)) {
|
|
49709
49770
|
url = p.images[slot];
|
|
49710
49771
|
}
|
|
49711
49772
|
if (typeof url !== "string") return null;
|
|
49712
|
-
const
|
|
49713
|
-
if (
|
|
49714
|
-
return { mime:
|
|
49773
|
+
const media = referableDataUrl(url, mimes);
|
|
49774
|
+
if (media === null) return null;
|
|
49775
|
+
return { mime: media.mime, bytes: Buffer.from(url.slice(media.dataStart), "base64") };
|
|
49715
49776
|
}
|
|
49716
49777
|
|
|
49717
49778
|
// src/services/trace-service.ts
|
|
@@ -49719,7 +49780,7 @@ import { Component as Component36, Use as Use38 } from "@adelie/core/kernel";
|
|
|
49719
49780
|
var IMPORT_SESSION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
49720
49781
|
var MAX_TRACE_BUNDLE_SHARDS = 200;
|
|
49721
49782
|
function parseTraceShard(content, what) {
|
|
49722
|
-
const invalid3 = (
|
|
49783
|
+
const invalid3 = (message3) => new HttpError(400, "invalid_trace", message3);
|
|
49723
49784
|
let records;
|
|
49724
49785
|
try {
|
|
49725
49786
|
records = parseTraceLines2(content, { onMalformed: "throw" });
|
|
@@ -49749,7 +49810,7 @@ function parseTraceBundle(content) {
|
|
|
49749
49810
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
49750
49811
|
const envelope = parsed;
|
|
49751
49812
|
if (envelope.format !== "adelie-trace-bundle") return null;
|
|
49752
|
-
const invalid3 = (
|
|
49813
|
+
const invalid3 = (message3) => new HttpError(400, "invalid_trace", message3);
|
|
49753
49814
|
if (envelope.version !== 1) {
|
|
49754
49815
|
throw invalid3(`Unsupported Trace bundle version ${String(envelope.version)}.`);
|
|
49755
49816
|
}
|
|
@@ -50164,7 +50225,7 @@ var TraceService = class {
|
|
|
50164
50225
|
let unitBytes = 0;
|
|
50165
50226
|
for (let i = starts[k].at; i < unitEnd; i++) {
|
|
50166
50227
|
unitBytes += Buffer.byteLength(
|
|
50167
|
-
JSON.stringify(
|
|
50228
|
+
JSON.stringify(withMediaByReference(sessionId, spanRaw[i])),
|
|
50168
50229
|
"utf8"
|
|
50169
50230
|
);
|
|
50170
50231
|
}
|
|
@@ -50211,7 +50272,7 @@ var TraceService = class {
|
|
|
50211
50272
|
* resumes from the selected shard exactly as it would after a restart.
|
|
50212
50273
|
*/
|
|
50213
50274
|
async forkSessionTrace(projectId, agentId, sourceSessionId, position) {
|
|
50214
|
-
const invalid3 = (
|
|
50275
|
+
const invalid3 = (message3) => new HttpError(400, "invalid_fork_position", message3);
|
|
50215
50276
|
const files = await this.locateAll(projectId, agentId, sourceSessionId);
|
|
50216
50277
|
const targetFilePos = files.findIndex((file) => file.index === position.fileIndex);
|
|
50217
50278
|
if (targetFilePos < 0) throw invalid3("The selected reply no longer exists in this Session.");
|
|
@@ -50255,7 +50316,7 @@ var TraceService = class {
|
|
|
50255
50316
|
}
|
|
50256
50317
|
if (!completedRequest) throw invalid3("The selected assistant reply has not completed yet.");
|
|
50257
50318
|
const resumed = resumeTrace(targetShard.slice(0, cutOrdinal));
|
|
50258
|
-
if (resumed.contextClosed || resumed.carryOver.length > 0 || !resumed.history.some((
|
|
50319
|
+
if (resumed.contextClosed || resumed.carryOver.length > 0 || !resumed.history.some((message3) => message3 === target)) {
|
|
50259
50320
|
throw invalid3("The selected reply is not a complete resumable Task boundary.");
|
|
50260
50321
|
}
|
|
50261
50322
|
const firstMeta = shards.flat().find(isSessionMeta6);
|
|
@@ -50384,20 +50445,20 @@ var TraceService = class {
|
|
|
50384
50445
|
return { events, offset, limit, total };
|
|
50385
50446
|
}
|
|
50386
50447
|
/**
|
|
50387
|
-
* One image of one Trace record, decoded: the `image_url` of the
|
|
50388
|
-
* (`fileIndex`, `ordinal`), or its tool output's `images[slot]`. This is what a
|
|
50389
|
-
* history page's
|
|
50390
|
-
* file, the record or the slot holds no
|
|
50448
|
+
* One image or video of one Trace record, decoded: the `image_url` / `video_url` of the
|
|
50449
|
+
* record at (`fileIndex`, `ordinal`), or its tool output's `images[slot]`. This is what a
|
|
50450
|
+
* windowed history page's media URL names (trace-media.ts). 404, with a code per kind, when
|
|
50451
|
+
* the file, the record or the slot holds no media the route serves.
|
|
50391
50452
|
*/
|
|
50392
|
-
async
|
|
50393
|
-
const notFound2 = () => new HttpError(404,
|
|
50453
|
+
async readTraceMedia(projectId, agentId, sessionId, fileIndex, ordinal, slot, kind) {
|
|
50454
|
+
const notFound2 = () => new HttpError(404, `trace_${kind}_not_found`, `This Trace record holds no such ${kind}.`);
|
|
50394
50455
|
const files = await this.locateAll(projectId, agentId, sessionId);
|
|
50395
50456
|
const file = files.find((f) => f.index === fileIndex);
|
|
50396
50457
|
if (file === void 0) throw notFound2();
|
|
50397
50458
|
const { events } = await this.lineIndex.readRange(file.path, ordinal, 1);
|
|
50398
|
-
const
|
|
50399
|
-
if (
|
|
50400
|
-
return
|
|
50459
|
+
const media = events[0] !== void 0 ? traceRecordMedia(events[0], slot, kind) : null;
|
|
50460
|
+
if (media === null) throw notFound2();
|
|
50461
|
+
return media;
|
|
50401
50462
|
}
|
|
50402
50463
|
/**
|
|
50403
50464
|
* What the analysis prices a file's Requests on: the paired reference named by the file's own
|
|
@@ -54596,12 +54657,14 @@ import path27 from "path";
|
|
|
54596
54657
|
import { Hono as Hono22 } from "hono";
|
|
54597
54658
|
import {
|
|
54598
54659
|
THINKING_LEVEL_NAMES as THINKING_LEVEL_NAMES2,
|
|
54660
|
+
catalogSupportsVideo,
|
|
54599
54661
|
imageUrlMessage as imageUrlMessage3,
|
|
54600
54662
|
isValidId as isValidId4,
|
|
54601
54663
|
scratchpadDir as scratchpadDir3,
|
|
54602
54664
|
sessionScratchpadDir,
|
|
54603
54665
|
stripLeadingMarkerBlocks,
|
|
54604
|
-
userText as userText5
|
|
54666
|
+
userText as userText5,
|
|
54667
|
+
videoUrlMessage
|
|
54605
54668
|
} from "@adelie/core";
|
|
54606
54669
|
|
|
54607
54670
|
// src/services/workspace-guard.ts
|
|
@@ -55049,13 +55112,13 @@ var ModelBalances = class {
|
|
|
55049
55112
|
}
|
|
55050
55113
|
return { ok: true, provider, ...figures, fetchedAt: new Date(this.now()).toISOString() };
|
|
55051
55114
|
}
|
|
55052
|
-
failure(provider, error,
|
|
55115
|
+
failure(provider, error, message3, status) {
|
|
55053
55116
|
return {
|
|
55054
55117
|
ok: false,
|
|
55055
55118
|
provider,
|
|
55056
55119
|
error,
|
|
55057
55120
|
...status !== void 0 ? { status } : {},
|
|
55058
|
-
message:
|
|
55121
|
+
message: message3,
|
|
55059
55122
|
fetchedAt: new Date(this.now()).toISOString()
|
|
55060
55123
|
};
|
|
55061
55124
|
}
|
|
@@ -55413,8 +55476,8 @@ function modelOAuthCallbackRoutes(deps) {
|
|
|
55413
55476
|
try {
|
|
55414
55477
|
deps.modelOAuth.deposit({ flowId: flowId3, projectId, code });
|
|
55415
55478
|
} catch (err) {
|
|
55416
|
-
const
|
|
55417
|
-
return c.html(resultPage("Authorization failed",
|
|
55479
|
+
const message3 = err instanceof HttpError ? err.message : "Something went wrong. Return to Adelie and start a new authorization.";
|
|
55480
|
+
return c.html(resultPage("Authorization failed", message3), 400);
|
|
55418
55481
|
}
|
|
55419
55482
|
return c.html(
|
|
55420
55483
|
resultPage(
|
|
@@ -56089,12 +56152,12 @@ function pageLimit(raw, name) {
|
|
|
56089
56152
|
}
|
|
56090
56153
|
function appendPendingInputs(messages, pending) {
|
|
56091
56154
|
if (pending.length === 0) return messages;
|
|
56092
|
-
const envelopeKey = (
|
|
56093
|
-
const { tracePosition: _tracePosition, ...envelope } =
|
|
56155
|
+
const envelopeKey = (message3) => {
|
|
56156
|
+
const { tracePosition: _tracePosition, ...envelope } = message3;
|
|
56094
56157
|
return JSON.stringify(envelope);
|
|
56095
56158
|
};
|
|
56096
56159
|
const tail = new Set(messages.slice(-50).map(envelopeKey));
|
|
56097
|
-
const missing = pending.filter((
|
|
56160
|
+
const missing = pending.filter((message3) => !tail.has(envelopeKey(message3)));
|
|
56098
56161
|
return missing.length > 0 ? [...messages, ...missing] : messages;
|
|
56099
56162
|
}
|
|
56100
56163
|
function messagesPageQuery(c) {
|
|
@@ -56156,6 +56219,49 @@ function requireImageUrl(url, field) {
|
|
|
56156
56219
|
`${field} must be an http(s) URL or a base64 data: URL (data:<mime>;base64,<bytes>).`
|
|
56157
56220
|
);
|
|
56158
56221
|
}
|
|
56222
|
+
var VIDEO_DATA_URL = /^data:video\/[^;,]+;base64,[A-Za-z0-9+/=\s]+$/;
|
|
56223
|
+
function requireVideoUrl(url, field) {
|
|
56224
|
+
if (typeof url === "string") {
|
|
56225
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
56226
|
+
if (VIDEO_DATA_URL.test(url)) {
|
|
56227
|
+
const bytes = dataUrlByteLength(url);
|
|
56228
|
+
if (bytes > INLINE_VIDEO_MAX_BYTES) {
|
|
56229
|
+
throw new HttpError(
|
|
56230
|
+
413,
|
|
56231
|
+
"video_too_large",
|
|
56232
|
+
`${field} exceeds the ${INLINE_VIDEO_MAX_MB}MB inline video limit.`
|
|
56233
|
+
);
|
|
56234
|
+
}
|
|
56235
|
+
return url;
|
|
56236
|
+
}
|
|
56237
|
+
}
|
|
56238
|
+
throw badRequest(
|
|
56239
|
+
`${field} must be an http(s) URL or a base64 video data: URL (data:video/<type>;base64,<bytes>).`
|
|
56240
|
+
);
|
|
56241
|
+
}
|
|
56242
|
+
function decodeVideoDataUrl(url, field) {
|
|
56243
|
+
const match = /^data:([^;,]+);base64,([A-Za-z0-9+/=\s]+)$/.exec(url);
|
|
56244
|
+
if (!match) {
|
|
56245
|
+
throw badRequest(`${field} must be a base64 data: URL for the video to be written to disk.`);
|
|
56246
|
+
}
|
|
56247
|
+
return { bytes: Buffer.from(match[2], "base64"), mime: match[1] };
|
|
56248
|
+
}
|
|
56249
|
+
var VIDEO_MIME_EXTENSIONS = {
|
|
56250
|
+
"video/mp4": ".mp4",
|
|
56251
|
+
"video/quicktime": ".mov",
|
|
56252
|
+
"video/webm": ".webm",
|
|
56253
|
+
"video/x-matroska": ".mkv",
|
|
56254
|
+
"video/x-msvideo": ".avi"
|
|
56255
|
+
};
|
|
56256
|
+
function videoFileName(given, mime) {
|
|
56257
|
+
if (typeof given === "string" && given.length > 0) return given;
|
|
56258
|
+
const ext = VIDEO_MIME_EXTENSIONS[mime.toLowerCase()] ?? "";
|
|
56259
|
+
return `video${ext || ".mp4"}`;
|
|
56260
|
+
}
|
|
56261
|
+
function videoAttachment(url, given, field) {
|
|
56262
|
+
const { bytes, mime } = decodeVideoDataUrl(url, field);
|
|
56263
|
+
return { fileName: videoFileName(given, mime), bytes, mime };
|
|
56264
|
+
}
|
|
56159
56265
|
function dataUrlByteLength(dataUrl) {
|
|
56160
56266
|
const payload = dataUrl.slice(dataUrl.indexOf(",") + 1).replace(/[=\s]+/g, "");
|
|
56161
56267
|
return Math.floor(payload.length * 3 / 4);
|
|
@@ -56169,7 +56275,19 @@ function resolveScratchpadFile(dir, fileName) {
|
|
|
56169
56275
|
const resolved = path27.resolve(dir, fileName);
|
|
56170
56276
|
return path27.dirname(resolved) === path27.resolve(dir) ? resolved : null;
|
|
56171
56277
|
}
|
|
56172
|
-
function
|
|
56278
|
+
async function sessionTakesVideo(deps, row) {
|
|
56279
|
+
if (catalogSupportsVideo(row.provider, row.modelId)) return true;
|
|
56280
|
+
try {
|
|
56281
|
+
const project = await deps.projectConfigService.loadConfig(row.projectId);
|
|
56282
|
+
const entry = (project.models ?? []).find(
|
|
56283
|
+
(m) => m.provider === row.provider && m.model_id === row.modelId
|
|
56284
|
+
);
|
|
56285
|
+
return entry?.video === true;
|
|
56286
|
+
} catch {
|
|
56287
|
+
return false;
|
|
56288
|
+
}
|
|
56289
|
+
}
|
|
56290
|
+
function parseTaskInput(body, limits, videoInput) {
|
|
56173
56291
|
const input = body.input;
|
|
56174
56292
|
if (!Array.isArray(input) || input.length === 0) {
|
|
56175
56293
|
throw badRequest("input must be an array with at least one item.");
|
|
@@ -56178,6 +56296,7 @@ function parseTaskInput(body, limits) {
|
|
|
56178
56296
|
const attachments = [];
|
|
56179
56297
|
const texts = [];
|
|
56180
56298
|
const images = [];
|
|
56299
|
+
const videos = [];
|
|
56181
56300
|
input.forEach((item, i) => {
|
|
56182
56301
|
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
56183
56302
|
throw badRequest(`input[${i}] must be an object.`);
|
|
@@ -56197,19 +56316,31 @@ function parseTaskInput(body, limits) {
|
|
|
56197
56316
|
images.push(url);
|
|
56198
56317
|
return;
|
|
56199
56318
|
}
|
|
56319
|
+
if (part.type === "video_url") {
|
|
56320
|
+
const url = requireVideoUrl(part.videoUrl, `input[${i}].videoUrl`);
|
|
56321
|
+
if (videoInput) {
|
|
56322
|
+
messages.push(videoUrlMessage(url));
|
|
56323
|
+
videos.push(url);
|
|
56324
|
+
} else {
|
|
56325
|
+
attachments.push(videoAttachment(url, part.fileName, `input[${i}].videoUrl`));
|
|
56326
|
+
assertAttachmentBudget(attachments, limits);
|
|
56327
|
+
}
|
|
56328
|
+
return;
|
|
56329
|
+
}
|
|
56200
56330
|
if (part.type === "file") {
|
|
56201
56331
|
attachments.push(parseAttachmentPart(part, i, limits));
|
|
56202
56332
|
assertAttachmentBudget(attachments, limits);
|
|
56203
56333
|
return;
|
|
56204
56334
|
}
|
|
56205
|
-
throw badRequest(`input[${i}].type must be one of text / image_url / file.`);
|
|
56335
|
+
throw badRequest(`input[${i}].type must be one of text / image_url / video_url / file.`);
|
|
56206
56336
|
});
|
|
56207
|
-
return { messages, attachments, texts, images };
|
|
56337
|
+
return { messages, attachments, texts, images, videos };
|
|
56208
56338
|
}
|
|
56209
|
-
function recallStore(text2, images, attachments, written) {
|
|
56339
|
+
function recallStore(text2, images, videos, attachments, written) {
|
|
56210
56340
|
return {
|
|
56211
56341
|
text: text2,
|
|
56212
56342
|
images,
|
|
56343
|
+
videos,
|
|
56213
56344
|
files: written.map((p, i) => ({
|
|
56214
56345
|
fileName: attachments[i].fileName,
|
|
56215
56346
|
path: p,
|
|
@@ -56220,7 +56351,7 @@ function recallStore(text2, images, attachments, written) {
|
|
|
56220
56351
|
async function recalledResponse(recall) {
|
|
56221
56352
|
const files = await readRecalledFiles(recall.files);
|
|
56222
56353
|
await removeAttachments(recall.files.map((f) => f.path));
|
|
56223
|
-
return { text: recall.text, images: recall.images, files };
|
|
56354
|
+
return { text: recall.text, images: recall.images, videos: recall.videos, files };
|
|
56224
56355
|
}
|
|
56225
56356
|
function parseSteerImages(body) {
|
|
56226
56357
|
const images = body.images;
|
|
@@ -56228,6 +56359,17 @@ function parseSteerImages(body) {
|
|
|
56228
56359
|
if (!Array.isArray(images)) throw badRequest("images must be an array.");
|
|
56229
56360
|
return images.map((url, i) => requireImageUrl(url, `images[${i}]`));
|
|
56230
56361
|
}
|
|
56362
|
+
function parseSteerVideos(body, videoInput) {
|
|
56363
|
+
const videos = body.videos;
|
|
56364
|
+
if (videos === void 0) return [];
|
|
56365
|
+
if (!Array.isArray(videos)) throw badRequest("videos must be an array.");
|
|
56366
|
+
if (!videoInput) {
|
|
56367
|
+
throw badRequest(
|
|
56368
|
+
"This Session's model takes no video input; send the video as a file attachment instead."
|
|
56369
|
+
);
|
|
56370
|
+
}
|
|
56371
|
+
return videos.map((url, i) => requireVideoUrl(url, `videos[${i}]`));
|
|
56372
|
+
}
|
|
56231
56373
|
function parseSteerFiles(body, limits) {
|
|
56232
56374
|
const files = body.files;
|
|
56233
56375
|
if (files === void 0) return [];
|
|
@@ -56654,13 +56796,14 @@ function sessionsRoutes(deps) {
|
|
|
56654
56796
|
if (fileIndex === void 0 || ordinal === void 0) {
|
|
56655
56797
|
throw badRequest("file and ordinal are required.");
|
|
56656
56798
|
}
|
|
56657
|
-
const image = await deps.traceService.
|
|
56799
|
+
const image = await deps.traceService.readTraceMedia(
|
|
56658
56800
|
row.projectId,
|
|
56659
56801
|
row.agentId,
|
|
56660
56802
|
row.sessionId,
|
|
56661
56803
|
fileIndex,
|
|
56662
56804
|
ordinal,
|
|
56663
|
-
intQuery("i", 0)
|
|
56805
|
+
intQuery("i", 0),
|
|
56806
|
+
"image"
|
|
56664
56807
|
);
|
|
56665
56808
|
return c.body(new Uint8Array(image.bytes), 200, {
|
|
56666
56809
|
"content-type": image.mime,
|
|
@@ -56668,6 +56811,28 @@ function sessionsRoutes(deps) {
|
|
|
56668
56811
|
"cache-control": "private, max-age=31536000, immutable"
|
|
56669
56812
|
});
|
|
56670
56813
|
});
|
|
56814
|
+
app.get("/:sessionId/trace-video", async (c) => {
|
|
56815
|
+
const row = resolveSession(c);
|
|
56816
|
+
const fileIndex = parseNonNegativeInt(c.req.query("file") ?? "");
|
|
56817
|
+
const ordinal = parseNonNegativeInt(c.req.query("ordinal") ?? "");
|
|
56818
|
+
if (fileIndex === null || fileIndex < 1 || ordinal === null) {
|
|
56819
|
+
throw badRequest("file (at least 1) and ordinal are required.");
|
|
56820
|
+
}
|
|
56821
|
+
const video = await deps.traceService.readTraceMedia(
|
|
56822
|
+
row.projectId,
|
|
56823
|
+
row.agentId,
|
|
56824
|
+
row.sessionId,
|
|
56825
|
+
fileIndex,
|
|
56826
|
+
ordinal,
|
|
56827
|
+
void 0,
|
|
56828
|
+
"video"
|
|
56829
|
+
);
|
|
56830
|
+
return c.body(new Uint8Array(video.bytes), 200, {
|
|
56831
|
+
"content-type": video.mime,
|
|
56832
|
+
"x-content-type-options": "nosniff",
|
|
56833
|
+
"cache-control": "private, max-age=31536000, immutable"
|
|
56834
|
+
});
|
|
56835
|
+
});
|
|
56671
56836
|
app.get("/:sessionId/messages", async (c) => {
|
|
56672
56837
|
const row = resolveSession(c);
|
|
56673
56838
|
const page = messagesPageQuery(c);
|
|
@@ -56707,7 +56872,7 @@ function sessionsRoutes(deps) {
|
|
|
56707
56872
|
...result.agentState !== void 0 ? { agentState: result.agentState } : {}
|
|
56708
56873
|
};
|
|
56709
56874
|
const messages2 = appendPendingInputs(result.messages, pendingInputs).map(
|
|
56710
|
-
(m) =>
|
|
56875
|
+
(m) => withMediaByReference(row.sessionId, m)
|
|
56711
56876
|
);
|
|
56712
56877
|
return c.json({
|
|
56713
56878
|
messages: messages2,
|
|
@@ -56763,14 +56928,15 @@ function sessionsRoutes(deps) {
|
|
|
56763
56928
|
const body = await readJson(c);
|
|
56764
56929
|
const goal = parseGoalField(body);
|
|
56765
56930
|
const limits = toAttachmentLimits(deps.serverSettingsRepo.getAttachmentLimitsMb());
|
|
56931
|
+
const videoInput = await sessionTakesVideo(deps, row);
|
|
56766
56932
|
if (goal) {
|
|
56767
|
-
const { messages, attachments } = parseTaskInput(body, limits);
|
|
56933
|
+
const { messages, attachments } = parseTaskInput(body, limits, videoInput);
|
|
56768
56934
|
const text2 = messages.filter((m) => m.payload.type === "text").map((m) => m.payload.text).join("\n").trim();
|
|
56769
56935
|
if (!text2) {
|
|
56770
56936
|
throw badRequest("goal mode requires a non-empty text objective.");
|
|
56771
56937
|
}
|
|
56772
56938
|
if (attachments.length > 0) {
|
|
56773
|
-
throw badRequest("goal mode accepts text and
|
|
56939
|
+
throw badRequest("goal mode accepts text, images and videos only (no file attachments).");
|
|
56774
56940
|
}
|
|
56775
56941
|
const objective = stripLeadingMarkerBlocks(text2).trim() || text2;
|
|
56776
56942
|
const { sessionId } = await deps.manager.startGoal(row.sessionId, {
|
|
@@ -56780,7 +56946,7 @@ function sessionsRoutes(deps) {
|
|
|
56780
56946
|
});
|
|
56781
56947
|
return c.json({ sessionId }, 202);
|
|
56782
56948
|
}
|
|
56783
|
-
const parsed = parseTaskInput(body, limits);
|
|
56949
|
+
const parsed = parseTaskInput(body, limits, videoInput);
|
|
56784
56950
|
const queueIfBusy = body.queueIfBusy === true;
|
|
56785
56951
|
deps.manager.assertCanAcceptTask(row.sessionId, { queueIfBusy });
|
|
56786
56952
|
const { input, written } = await attachFilesToInput(
|
|
@@ -56794,7 +56960,13 @@ function sessionsRoutes(deps) {
|
|
|
56794
56960
|
queueIfBusy,
|
|
56795
56961
|
// Original content, kept while the input waits in the follow-up queue so a recall
|
|
56796
56962
|
// (DELETE /follow-ups/:id) can hand it back; unused when the task starts directly.
|
|
56797
|
-
recall: recallStore(
|
|
56963
|
+
recall: recallStore(
|
|
56964
|
+
parsed.texts.join("\n"),
|
|
56965
|
+
parsed.images,
|
|
56966
|
+
parsed.videos,
|
|
56967
|
+
parsed.attachments,
|
|
56968
|
+
written
|
|
56969
|
+
)
|
|
56798
56970
|
});
|
|
56799
56971
|
return c.json({ sessionId, queued }, 202);
|
|
56800
56972
|
} catch (err) {
|
|
@@ -56807,21 +56979,26 @@ function sessionsRoutes(deps) {
|
|
|
56807
56979
|
const body = await readJson(c);
|
|
56808
56980
|
const text2 = typeof body.text === "string" ? body.text.trim() : "";
|
|
56809
56981
|
const images = parseSteerImages(body);
|
|
56982
|
+
const videos = parseSteerVideos(body, await sessionTakesVideo(deps, row));
|
|
56810
56983
|
const files = parseSteerFiles(
|
|
56811
56984
|
body,
|
|
56812
56985
|
toAttachmentLimits(deps.serverSettingsRepo.getAttachmentLimitsMb())
|
|
56813
56986
|
);
|
|
56814
|
-
if (!text2 && images.length === 0 && files.length === 0) {
|
|
56815
|
-
throw badRequest("text, images or files must carry the steering message.");
|
|
56987
|
+
if (!text2 && images.length === 0 && videos.length === 0 && files.length === 0) {
|
|
56988
|
+
throw badRequest("text, images, videos or files must carry the steering message.");
|
|
56816
56989
|
}
|
|
56817
56990
|
const { input, written } = await attachFilesToInput(
|
|
56818
|
-
[
|
|
56991
|
+
[
|
|
56992
|
+
...text2 ? [userText5(text2)] : [],
|
|
56993
|
+
...images.map((url) => imageUrlMessage3(url)),
|
|
56994
|
+
...videos.map((url) => videoUrlMessage(url))
|
|
56995
|
+
],
|
|
56819
56996
|
files,
|
|
56820
56997
|
scratchpadDir3(deps.config.root, row.projectId, row.agentId),
|
|
56821
56998
|
row.sessionId
|
|
56822
56999
|
);
|
|
56823
57000
|
try {
|
|
56824
|
-
deps.manager.steer(row.sessionId, input, recallStore(text2, images, files, written));
|
|
57001
|
+
deps.manager.steer(row.sessionId, input, recallStore(text2, images, videos, files, written));
|
|
56825
57002
|
} catch (err) {
|
|
56826
57003
|
await removeAttachments(written);
|
|
56827
57004
|
throw err;
|
|
@@ -60717,13 +60894,13 @@ function dialThroughSocks(socksPort, host, port, timeoutMs = SOCKS_HANDSHAKE_TIM
|
|
|
60717
60894
|
let buffer = Buffer.alloc(0);
|
|
60718
60895
|
let stage = "greet";
|
|
60719
60896
|
let settled = false;
|
|
60720
|
-
const fail2 = (
|
|
60897
|
+
const fail2 = (message3) => {
|
|
60721
60898
|
if (settled) return;
|
|
60722
60899
|
settled = true;
|
|
60723
60900
|
socket.removeListener("data", onData);
|
|
60724
60901
|
socket.removeListener("close", onClose);
|
|
60725
60902
|
socket.destroy();
|
|
60726
|
-
reject(new Error(
|
|
60903
|
+
reject(new Error(message3));
|
|
60727
60904
|
};
|
|
60728
60905
|
const onData = (chunk) => {
|
|
60729
60906
|
buffer = Buffer.concat([buffer, chunk]);
|
|
@@ -61411,8 +61588,8 @@ var APPLY_TIMEOUT_MS = 10 * 6e4;
|
|
|
61411
61588
|
function refusalDetail(status, text2) {
|
|
61412
61589
|
try {
|
|
61413
61590
|
const body = JSON.parse(text2);
|
|
61414
|
-
const
|
|
61415
|
-
if (typeof
|
|
61591
|
+
const message3 = body.error?.message;
|
|
61592
|
+
if (typeof message3 === "string" && message3 !== "") return message3;
|
|
61416
61593
|
} catch {
|
|
61417
61594
|
}
|
|
61418
61595
|
const said = text2.trim();
|
|
@@ -65665,10 +65842,10 @@ ${serializeTicket(ticket.doc).trimEnd()}`
|
|
|
65665
65842
|
|
|
65666
65843
|
// src/runtime/organization/reconcile.ts
|
|
65667
65844
|
var nowOf = (deps) => deps.now?.() ?? Date.now();
|
|
65668
|
-
function recordError(deps, org, code,
|
|
65845
|
+
function recordError(deps, org, code, message3, agentId) {
|
|
65669
65846
|
deps.errors.record({
|
|
65670
65847
|
source: "organization",
|
|
65671
|
-
err: new Error(
|
|
65848
|
+
err: new Error(message3),
|
|
65672
65849
|
code,
|
|
65673
65850
|
ctx: { projectId: org.projectId, ...agentId !== void 0 ? { agentId } : {} }
|
|
65674
65851
|
});
|
|
@@ -66447,7 +66624,7 @@ var notAMember = (channelId, principal, why) => new HttpError(
|
|
|
66447
66624
|
"not_a_member",
|
|
66448
66625
|
why ?? `${principal} is not a member of the channel ${channelId}.`
|
|
66449
66626
|
);
|
|
66450
|
-
var allHandsImmutable = (
|
|
66627
|
+
var allHandsImmutable = (message3) => new HttpError(400, "all_hands_immutable", message3);
|
|
66451
66628
|
var requireChainLimit = (value) => {
|
|
66452
66629
|
if (!Number.isInteger(value) || value < 0 || value > MENTION_CHAIN_LIMIT_MAX) {
|
|
66453
66630
|
throw badRequest(
|
|
@@ -66497,14 +66674,14 @@ var OrganizationService = class {
|
|
|
66497
66674
|
);
|
|
66498
66675
|
}
|
|
66499
66676
|
/** One dead end of an id proposal, in the log and in the Project's errors panel. */
|
|
66500
|
-
recordIdSuggestFailure(projectId,
|
|
66501
|
-
this.deps.log?.(`org: id suggestion fell back for ${projectId}: ${
|
|
66677
|
+
recordIdSuggestFailure(projectId, message3) {
|
|
66678
|
+
this.deps.log?.(`org: id suggestion fell back for ${projectId}: ${message3}`);
|
|
66502
66679
|
this.deps.errors.record({
|
|
66503
66680
|
source: "organization",
|
|
66504
66681
|
code: "id_suggest_failed",
|
|
66505
66682
|
kind: "expected",
|
|
66506
66683
|
ctx: { projectId },
|
|
66507
|
-
err: new Error(
|
|
66684
|
+
err: new Error(message3)
|
|
66508
66685
|
});
|
|
66509
66686
|
}
|
|
66510
66687
|
async requireOrg(projectId, orgId) {
|
|
@@ -69100,7 +69277,7 @@ function organizationRoutes(deps) {
|
|
|
69100
69277
|
member(c, projectId);
|
|
69101
69278
|
const body = await readJson(c).catch(() => ({}));
|
|
69102
69279
|
const agentId = optionalString(body, "agentId", { minLen: 2, maxLen: 64 });
|
|
69103
|
-
const
|
|
69280
|
+
const message3 = optionalString(body, "message", { maxLen: 1e5 });
|
|
69104
69281
|
const workspace = optionalString(body, "workspace", { minLen: 1, maxLen: 4096 });
|
|
69105
69282
|
const res = await deps.orgService.startTicket(
|
|
69106
69283
|
projectId,
|
|
@@ -69108,7 +69285,7 @@ function organizationRoutes(deps) {
|
|
|
69108
69285
|
ticketId,
|
|
69109
69286
|
{
|
|
69110
69287
|
...agentId !== void 0 ? { agentId } : {},
|
|
69111
|
-
...
|
|
69288
|
+
...message3 !== void 0 ? { message: message3 } : {},
|
|
69112
69289
|
...workspace !== void 0 ? { workspace } : {}
|
|
69113
69290
|
},
|
|
69114
69291
|
// Who asks decides whether it may: an employee starts sessions only on its own tickets.
|
|
@@ -70292,8 +70469,8 @@ var DRAFT_SHORTCUT_MAX_COUNT = 3;
|
|
|
70292
70469
|
var DRAFT_SHORTCUT_TITLE_MAX = 40;
|
|
70293
70470
|
var DRAFT_SHORTCUT_PROMPT_MAX = 4e3;
|
|
70294
70471
|
var DRAFT_SHORTCUT_ID_MAX = 64;
|
|
70295
|
-
function invalid(
|
|
70296
|
-
return new HttpError(400, "invalid_draft_shortcuts",
|
|
70472
|
+
function invalid(message3) {
|
|
70473
|
+
return new HttpError(400, "invalid_draft_shortcuts", message3);
|
|
70297
70474
|
}
|
|
70298
70475
|
function shortcutField(entry, key2, index, maxLen) {
|
|
70299
70476
|
const value = entry[key2];
|
|
@@ -70337,8 +70514,8 @@ var KEYBINDINGS_BYTES_MAX = 8 * 1024;
|
|
|
70337
70514
|
var SECTIONS = ["mac", "windows", "linux"];
|
|
70338
70515
|
var ID_RE = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$/;
|
|
70339
70516
|
var CHORD_RE = /^(Mod\+)?(Ctrl\+)?(Alt\+)?(Shift\+)?[A-Za-z][A-Za-z0-9]*$/;
|
|
70340
|
-
function invalid2(
|
|
70341
|
-
return new HttpError(400, "invalid_keybindings",
|
|
70517
|
+
function invalid2(message3) {
|
|
70518
|
+
return new HttpError(400, "invalid_keybindings", message3);
|
|
70342
70519
|
}
|
|
70343
70520
|
function validateKeybindings(value) {
|
|
70344
70521
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -70437,6 +70614,7 @@ function meRoutes(deps) {
|
|
|
70437
70614
|
...deps.serverSettingsRepo.getAttachmentLimitsMb(),
|
|
70438
70615
|
attachmentMaxCount: MAX_ATTACHMENT_COUNT,
|
|
70439
70616
|
imageMaxMb: INLINE_IMAGE_MAX_MB,
|
|
70617
|
+
videoMaxMb: INLINE_VIDEO_MAX_MB,
|
|
70440
70618
|
attachmentLimitMinMb: MIN_ATTACHMENT_MB,
|
|
70441
70619
|
attachmentLimitMaxMb: MAX_ATTACHMENT_MB
|
|
70442
70620
|
},
|
|
@@ -70738,13 +70916,399 @@ InstallRoutes = __decorateElement(_init131, 0, "InstallRoutes", _InstallRoutes_d
|
|
|
70738
70916
|
__runInitializers(_init131, 1, InstallRoutes);
|
|
70739
70917
|
|
|
70740
70918
|
// src/http/routes/themes.ts
|
|
70741
|
-
import { readdir, readFile } from "fs/promises";
|
|
70919
|
+
import { mkdir, readdir, readFile, rm as rm2, writeFile } from "fs/promises";
|
|
70742
70920
|
import { join as join2 } from "path";
|
|
70743
70921
|
import { Hono as Hono41 } from "hono";
|
|
70744
|
-
import { externalThemesCss, readExternalTheme } from "@adelie/core";
|
|
70922
|
+
import { externalThemesCss, MAX_THEME_FILE_BYTES as MAX_THEME_FILE_BYTES2, readExternalTheme } from "@adelie/core";
|
|
70745
70923
|
import { Bind as Bind25, Component as Component65, Use as Use67 } from "@adelie/core/kernel";
|
|
70924
|
+
|
|
70925
|
+
// src/services/theme-download.ts
|
|
70926
|
+
import { MAX_THEME_FILE_BYTES } from "@adelie/core";
|
|
70927
|
+
|
|
70928
|
+
// src/services/plugin-download.ts
|
|
70929
|
+
import { createHash as createHash9, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
70930
|
+
import { isIP } from "net";
|
|
70931
|
+
var MAX_PLUGIN_ARCHIVE_BYTES = 32 * 1024 * 1024;
|
|
70932
|
+
var DOWNLOAD_TIMEOUT_MS = 6e4;
|
|
70933
|
+
function normalizePluginUrl(raw) {
|
|
70934
|
+
const trimmed = raw.trim();
|
|
70935
|
+
if (trimmed === "") throw badRequest("url must not be empty.");
|
|
70936
|
+
let url;
|
|
70937
|
+
try {
|
|
70938
|
+
url = new URL(trimmed);
|
|
70939
|
+
} catch {
|
|
70940
|
+
throw new HttpError(400, "unsupported_url", "url must be an absolute http(s) URL.");
|
|
70941
|
+
}
|
|
70942
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
70943
|
+
throw new HttpError(400, "unsupported_url", "url must be an http(s) URL.");
|
|
70944
|
+
}
|
|
70945
|
+
assertHostAllowed(url);
|
|
70946
|
+
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
70947
|
+
if (host !== "github.com") {
|
|
70948
|
+
const file = url.pathname.split("/").filter(Boolean).pop() ?? "";
|
|
70949
|
+
const suggested = archiveStem(file);
|
|
70950
|
+
return { fetchUrl: url.toString(), subdir: "", ...suggested ? { name: suggested } : {} };
|
|
70951
|
+
}
|
|
70952
|
+
const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
70953
|
+
const [owner3, repo, kind, ...rest] = segments;
|
|
70954
|
+
if (!owner3 || !repo) {
|
|
70955
|
+
throw new HttpError(
|
|
70956
|
+
400,
|
|
70957
|
+
"unsupported_url",
|
|
70958
|
+
"Not a GitHub repository URL: expected /<owner>/<repo>."
|
|
70959
|
+
);
|
|
70960
|
+
}
|
|
70961
|
+
const repoUrl = `https://github.com/${owner3}/${repo}`;
|
|
70962
|
+
if (kind === void 0) {
|
|
70963
|
+
return { fetchUrl: `${repoUrl}/archive/HEAD.zip`, subdir: "", name: repo };
|
|
70964
|
+
}
|
|
70965
|
+
if (kind !== "tree") {
|
|
70966
|
+
return { fetchUrl: url.toString(), subdir: "", name: repo };
|
|
70967
|
+
}
|
|
70968
|
+
const [ref, ...sub] = rest;
|
|
70969
|
+
if (!ref) throw new HttpError(400, "unsupported_url", "A GitHub tree URL needs a branch or tag.");
|
|
70970
|
+
const subdir = sub.join("/");
|
|
70971
|
+
return {
|
|
70972
|
+
fetchUrl: `${repoUrl}/archive/${encodeURIComponent(ref)}.zip`,
|
|
70973
|
+
subdir,
|
|
70974
|
+
// The plugin directory is the last segment of the subdirectory — that is its name.
|
|
70975
|
+
name: sub.length > 0 ? sub[sub.length - 1] : repo
|
|
70976
|
+
};
|
|
70977
|
+
}
|
|
70978
|
+
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
70979
|
+
var NPM_NAME = /^(?:@[A-Za-z0-9._~!*'()-]+\/)?[A-Za-z0-9._~!*'()-]+$/;
|
|
70980
|
+
var NPM_VERSION = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
|
|
70981
|
+
function parseNpmPluginSpec(raw) {
|
|
70982
|
+
const trimmed = raw.trim();
|
|
70983
|
+
if (trimmed === "") return null;
|
|
70984
|
+
if (trimmed.toLowerCase().startsWith("npm:")) {
|
|
70985
|
+
return parseNpmName(trimmed.slice("npm:".length).trim());
|
|
70986
|
+
}
|
|
70987
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
70988
|
+
let url;
|
|
70989
|
+
try {
|
|
70990
|
+
url = new URL(trimmed);
|
|
70991
|
+
} catch {
|
|
70992
|
+
return null;
|
|
70993
|
+
}
|
|
70994
|
+
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
70995
|
+
if (host === "npmjs.com") {
|
|
70996
|
+
const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
70997
|
+
if (segments[0] !== "package") return null;
|
|
70998
|
+
const scoped = segments[1] === void 0 ? void 0 : segments[1].startsWith("@");
|
|
70999
|
+
const name = scoped ? `${segments[1]}/${segments[2] ?? ""}` : segments[1];
|
|
71000
|
+
if (name === void 0 || name.endsWith("/")) return null;
|
|
71001
|
+
const marker = scoped ? 3 : 2;
|
|
71002
|
+
const version = segments[marker] === "v" ? segments[marker + 1] : void 0;
|
|
71003
|
+
return parseNpmName(version === void 0 ? name : `${name}@${version}`);
|
|
71004
|
+
}
|
|
71005
|
+
if (host === "registry.npmjs.org") {
|
|
71006
|
+
const rest = decodeURIComponent(url.pathname.replace(/^\//, ""));
|
|
71007
|
+
const segments = rest.split("/");
|
|
71008
|
+
if (segments[0] === void 0 || segments[0] === "") return null;
|
|
71009
|
+
const scoped = segments[0].startsWith("@");
|
|
71010
|
+
const name = scoped ? `${segments[0]}/${segments[1] ?? ""}` : segments[0];
|
|
71011
|
+
if (name.endsWith("/") || segments.length > (scoped ? 2 : 1)) return null;
|
|
71012
|
+
return parseNpmName(name);
|
|
71013
|
+
}
|
|
71014
|
+
return null;
|
|
71015
|
+
}
|
|
71016
|
+
return parseNpmName(trimmed);
|
|
71017
|
+
}
|
|
71018
|
+
function parseNpmName(spec) {
|
|
71019
|
+
if (spec === "") return null;
|
|
71020
|
+
const at = spec.lastIndexOf("@");
|
|
71021
|
+
const name = at > 0 ? spec.slice(0, at) : spec;
|
|
71022
|
+
const version = at > 0 ? spec.slice(at + 1) : void 0;
|
|
71023
|
+
if (name.length > 214 || !NPM_NAME.test(name)) return null;
|
|
71024
|
+
if (version !== void 0 && !NPM_VERSION.test(version)) return null;
|
|
71025
|
+
return version === void 0 ? { name } : { name, version };
|
|
71026
|
+
}
|
|
71027
|
+
async function resolvePluginSource(raw, options = {}) {
|
|
71028
|
+
const spec = parseNpmPluginSpec(raw);
|
|
71029
|
+
if (spec === null) return normalizePluginUrl(raw);
|
|
71030
|
+
const found = await fetchPackument(spec, options);
|
|
71031
|
+
const entry = pickVersion(spec, found);
|
|
71032
|
+
const tarball = entry.tarball;
|
|
71033
|
+
let url;
|
|
71034
|
+
try {
|
|
71035
|
+
url = new URL(tarball);
|
|
71036
|
+
} catch {
|
|
71037
|
+
throw new HttpError(400, "download_failed", "The registry named an unreadable tarball URL.");
|
|
71038
|
+
}
|
|
71039
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
71040
|
+
throw new HttpError(400, "download_failed", "The registry named an unsupported tarball URL.");
|
|
71041
|
+
}
|
|
71042
|
+
assertHostAllowed(url);
|
|
71043
|
+
return {
|
|
71044
|
+
fetchUrl: url.toString(),
|
|
71045
|
+
subdir: "",
|
|
71046
|
+
// The package's own name, minus the scope: the scope names a publisher, and the plugin is
|
|
71047
|
+
// installed under the name its own repository gives it (`@scope/use-firecrawl` → `use-firecrawl`).
|
|
71048
|
+
name: spec.name.slice(spec.name.startsWith("@") ? spec.name.indexOf("/") + 1 : 0),
|
|
71049
|
+
...entry.integrity !== void 0 ? { integrity: entry.integrity } : {}
|
|
71050
|
+
};
|
|
71051
|
+
}
|
|
71052
|
+
async function fetchPackument(spec, options) {
|
|
71053
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
71054
|
+
const encoded = spec.name.startsWith("@") ? `@${encodeURIComponent(spec.name.slice(1))}` : encodeURIComponent(spec.name);
|
|
71055
|
+
const url = `${NPM_REGISTRY}/${encoded}`;
|
|
71056
|
+
let res;
|
|
71057
|
+
try {
|
|
71058
|
+
res = await doFetch(url, {
|
|
71059
|
+
method: "GET",
|
|
71060
|
+
redirect: "follow",
|
|
71061
|
+
// The abbreviated document: dist-tags and each version's dist, which is all this reads, and a
|
|
71062
|
+
// fraction of the full packument for a package with a long history.
|
|
71063
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
71064
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS)
|
|
71065
|
+
});
|
|
71066
|
+
} catch (err) {
|
|
71067
|
+
throw new HttpError(400, "npm_registry_failed", `Could not reach npm: ${message(err)}`);
|
|
71068
|
+
}
|
|
71069
|
+
if (res.url) assertHostAllowed(new URL(res.url));
|
|
71070
|
+
if (res.status === 404) {
|
|
71071
|
+
throw new HttpError(404, "npm_package_not_found", `npm has no package ${spec.name}.`);
|
|
71072
|
+
}
|
|
71073
|
+
if (!res.ok) {
|
|
71074
|
+
await res.body?.cancel().catch(() => void 0);
|
|
71075
|
+
throw new HttpError(
|
|
71076
|
+
400,
|
|
71077
|
+
"npm_registry_failed",
|
|
71078
|
+
`npm answered HTTP ${res.status} for ${spec.name}.`
|
|
71079
|
+
);
|
|
71080
|
+
}
|
|
71081
|
+
try {
|
|
71082
|
+
const data = await res.json();
|
|
71083
|
+
if (typeof data !== "object" || data === null) throw new Error("not an object");
|
|
71084
|
+
return data;
|
|
71085
|
+
} catch {
|
|
71086
|
+
throw new HttpError(
|
|
71087
|
+
400,
|
|
71088
|
+
"npm_registry_failed",
|
|
71089
|
+
`npm returned no usable data for ${spec.name}.`
|
|
71090
|
+
);
|
|
71091
|
+
}
|
|
71092
|
+
}
|
|
71093
|
+
function pickVersion(spec, packument) {
|
|
71094
|
+
const versions = packument["versions"];
|
|
71095
|
+
const table = typeof versions === "object" && versions !== null ? versions : {};
|
|
71096
|
+
const distTags = packument["dist-tags"];
|
|
71097
|
+
const tags = typeof distTags === "object" && distTags !== null ? distTags : {};
|
|
71098
|
+
const version = spec.version ?? (typeof tags["latest"] === "string" ? tags["latest"] : void 0);
|
|
71099
|
+
if (version === void 0) {
|
|
71100
|
+
throw new HttpError(404, "npm_version_not_found", `${spec.name} has no published version.`);
|
|
71101
|
+
}
|
|
71102
|
+
const entry = table[version];
|
|
71103
|
+
if (typeof entry !== "object" || entry === null) {
|
|
71104
|
+
throw new HttpError(
|
|
71105
|
+
404,
|
|
71106
|
+
"npm_version_not_found",
|
|
71107
|
+
`npm has no version ${version} of ${spec.name}; use an exact version or a dist-tag.`
|
|
71108
|
+
);
|
|
71109
|
+
}
|
|
71110
|
+
const dist = entry["dist"];
|
|
71111
|
+
const tarball = typeof dist === "object" && dist !== null ? dist["tarball"] : void 0;
|
|
71112
|
+
if (typeof tarball !== "string" || tarball === "") {
|
|
71113
|
+
throw new HttpError(404, "npm_version_not_found", `${spec.name}@${version} has no tarball.`);
|
|
71114
|
+
}
|
|
71115
|
+
const integrity = dist["integrity"];
|
|
71116
|
+
const entryOut = { tarball };
|
|
71117
|
+
if (typeof integrity === "string" && integrity !== "") entryOut.integrity = integrity;
|
|
71118
|
+
return entryOut;
|
|
71119
|
+
}
|
|
71120
|
+
function archiveStem(file) {
|
|
71121
|
+
const lower = file.toLowerCase();
|
|
71122
|
+
for (const suffix of [".tar.gz", ".tgz", ".zip"]) {
|
|
71123
|
+
if (lower.endsWith(suffix)) return file.slice(0, -suffix.length);
|
|
71124
|
+
}
|
|
71125
|
+
return void 0;
|
|
71126
|
+
}
|
|
71127
|
+
function isBlockedHost(hostname) {
|
|
71128
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
71129
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
71130
|
+
if (host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
71131
|
+
const version = isIP(host);
|
|
71132
|
+
if (version === 4) {
|
|
71133
|
+
const octets = host.split(".").map(Number);
|
|
71134
|
+
const [a, b] = octets;
|
|
71135
|
+
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a >= 224;
|
|
71136
|
+
}
|
|
71137
|
+
if (version === 6) {
|
|
71138
|
+
return /^(0*:)*0*1$/.test(host) || host.startsWith("fe8") || host.startsWith("fe9") || host.startsWith("fea") || host.startsWith("feb") || host.startsWith("fc") || host.startsWith("fd");
|
|
71139
|
+
}
|
|
71140
|
+
return false;
|
|
71141
|
+
}
|
|
71142
|
+
function assertHostAllowed(url) {
|
|
71143
|
+
if (isBlockedHost(url.hostname)) {
|
|
71144
|
+
throw new HttpError(
|
|
71145
|
+
400,
|
|
71146
|
+
"blocked_url",
|
|
71147
|
+
`Refusing to fetch from a local or private host: ${url.hostname}`
|
|
71148
|
+
);
|
|
71149
|
+
}
|
|
71150
|
+
}
|
|
71151
|
+
async function fetchPluginArchive(source, options = {}) {
|
|
71152
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
71153
|
+
let res;
|
|
71154
|
+
try {
|
|
71155
|
+
res = await doFetch(source.fetchUrl, {
|
|
71156
|
+
method: "GET",
|
|
71157
|
+
redirect: "follow",
|
|
71158
|
+
headers: { accept: "application/zip, application/octet-stream, */*" },
|
|
71159
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS)
|
|
71160
|
+
});
|
|
71161
|
+
} catch (err) {
|
|
71162
|
+
throw new HttpError(400, "download_failed", `Could not download: ${message(err)}`);
|
|
71163
|
+
}
|
|
71164
|
+
if (res.url) {
|
|
71165
|
+
try {
|
|
71166
|
+
assertHostAllowed(new URL(res.url));
|
|
71167
|
+
} catch (err) {
|
|
71168
|
+
if (err instanceof HttpError) throw err;
|
|
71169
|
+
throw new HttpError(400, "download_failed", "Could not read the URL that answered.");
|
|
71170
|
+
}
|
|
71171
|
+
}
|
|
71172
|
+
if (!res.ok) {
|
|
71173
|
+
await res.body?.cancel().catch(() => void 0);
|
|
71174
|
+
throw new HttpError(400, "download_failed", `Download failed with HTTP ${res.status}.`);
|
|
71175
|
+
}
|
|
71176
|
+
const declared = Number(res.headers.get("content-length") ?? "");
|
|
71177
|
+
if (Number.isFinite(declared) && declared > MAX_PLUGIN_ARCHIVE_BYTES) {
|
|
71178
|
+
await res.body?.cancel().catch(() => void 0);
|
|
71179
|
+
throw pluginArchiveTooLarge(MAX_PLUGIN_ARCHIVE_BYTES);
|
|
71180
|
+
}
|
|
71181
|
+
const archive = await readBounded(res);
|
|
71182
|
+
if (source.integrity !== void 0) checkIntegrity(source.integrity, archive);
|
|
71183
|
+
return archive;
|
|
71184
|
+
}
|
|
71185
|
+
var INTEGRITY_PATTERN = /^(sha512|sha384|sha256|sha1)-([A-Za-z0-9+/]+={0,2})$/;
|
|
71186
|
+
function checkIntegrity(integrity, archive) {
|
|
71187
|
+
const parts = INTEGRITY_PATTERN.exec(integrity);
|
|
71188
|
+
if (parts === null) return;
|
|
71189
|
+
const expected = Buffer.from(parts[2], "base64");
|
|
71190
|
+
const actual = createHash9(parts[1]).update(archive).digest();
|
|
71191
|
+
if (actual.length !== expected.length || !timingSafeEqual3(actual, expected)) {
|
|
71192
|
+
throw new HttpError(
|
|
71193
|
+
400,
|
|
71194
|
+
"integrity_failed",
|
|
71195
|
+
"The downloaded archive does not match the checksum the registry published for it."
|
|
71196
|
+
);
|
|
71197
|
+
}
|
|
71198
|
+
}
|
|
71199
|
+
async function readBounded(res) {
|
|
71200
|
+
const reader = res.body?.getReader();
|
|
71201
|
+
if (!reader) return Buffer.from(await res.arrayBuffer());
|
|
71202
|
+
const chunks = [];
|
|
71203
|
+
let total = 0;
|
|
71204
|
+
for (; ; ) {
|
|
71205
|
+
const { done, value } = await reader.read();
|
|
71206
|
+
if (done) break;
|
|
71207
|
+
total += value.byteLength;
|
|
71208
|
+
if (total > MAX_PLUGIN_ARCHIVE_BYTES) {
|
|
71209
|
+
await reader.cancel().catch(() => void 0);
|
|
71210
|
+
throw pluginArchiveTooLarge(MAX_PLUGIN_ARCHIVE_BYTES);
|
|
71211
|
+
}
|
|
71212
|
+
chunks.push(value);
|
|
71213
|
+
}
|
|
71214
|
+
return Buffer.concat(chunks);
|
|
71215
|
+
}
|
|
71216
|
+
function pluginArchiveTooLarge(limitBytes) {
|
|
71217
|
+
return new HttpError(
|
|
71218
|
+
413,
|
|
71219
|
+
"plugin_too_large",
|
|
71220
|
+
`The archive exceeds the ${Math.floor(limitBytes / (1024 * 1024))}MB limit.`
|
|
71221
|
+
);
|
|
71222
|
+
}
|
|
71223
|
+
function message(err) {
|
|
71224
|
+
return err instanceof Error ? err.message : String(err);
|
|
71225
|
+
}
|
|
71226
|
+
|
|
71227
|
+
// src/services/theme-download.ts
|
|
71228
|
+
var DOWNLOAD_TIMEOUT_MS2 = 3e4;
|
|
71229
|
+
function assertHostAllowed2(url) {
|
|
71230
|
+
if (isBlockedHost(url.hostname)) {
|
|
71231
|
+
throw new HttpError(
|
|
71232
|
+
400,
|
|
71233
|
+
"blocked_url",
|
|
71234
|
+
`Refusing to fetch from a local or private host: ${url.hostname}`
|
|
71235
|
+
);
|
|
71236
|
+
}
|
|
71237
|
+
}
|
|
71238
|
+
function themeTooLarge() {
|
|
71239
|
+
return new HttpError(
|
|
71240
|
+
413,
|
|
71241
|
+
"theme_too_large",
|
|
71242
|
+
`The theme file exceeds the ${Math.floor(MAX_THEME_FILE_BYTES / 1024)}KB limit.`
|
|
71243
|
+
);
|
|
71244
|
+
}
|
|
71245
|
+
async function fetchThemeFile(rawUrl, options = {}) {
|
|
71246
|
+
let url;
|
|
71247
|
+
try {
|
|
71248
|
+
url = new URL(rawUrl.trim());
|
|
71249
|
+
} catch {
|
|
71250
|
+
throw new HttpError(400, "unsupported_url", "url must be an absolute http(s) URL.");
|
|
71251
|
+
}
|
|
71252
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
71253
|
+
throw new HttpError(400, "unsupported_url", "url must be an http(s) URL.");
|
|
71254
|
+
}
|
|
71255
|
+
assertHostAllowed2(url);
|
|
71256
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
71257
|
+
let res;
|
|
71258
|
+
try {
|
|
71259
|
+
res = await doFetch(url.toString(), {
|
|
71260
|
+
method: "GET",
|
|
71261
|
+
redirect: "follow",
|
|
71262
|
+
// A theme is JSON, but a host may serve it as text/plain (a raw file link, a gist) — the
|
|
71263
|
+
// bytes decide, so the accept header asks without insisting.
|
|
71264
|
+
headers: { accept: "application/json, text/plain, */*" },
|
|
71265
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS2)
|
|
71266
|
+
});
|
|
71267
|
+
} catch (err) {
|
|
71268
|
+
throw new HttpError(400, "download_failed", `Could not download: ${message2(err)}`);
|
|
71269
|
+
}
|
|
71270
|
+
if (res.url) {
|
|
71271
|
+
try {
|
|
71272
|
+
assertHostAllowed2(new URL(res.url));
|
|
71273
|
+
} catch (err) {
|
|
71274
|
+
if (err instanceof HttpError) throw err;
|
|
71275
|
+
throw new HttpError(400, "download_failed", "Could not read the URL that answered.");
|
|
71276
|
+
}
|
|
71277
|
+
}
|
|
71278
|
+
if (!res.ok) {
|
|
71279
|
+
await res.body?.cancel().catch(() => void 0);
|
|
71280
|
+
throw new HttpError(400, "download_failed", `Download failed with HTTP ${res.status}.`);
|
|
71281
|
+
}
|
|
71282
|
+
const declared = Number(res.headers.get("content-length") ?? "");
|
|
71283
|
+
if (Number.isFinite(declared) && declared > MAX_THEME_FILE_BYTES) {
|
|
71284
|
+
await res.body?.cancel().catch(() => void 0);
|
|
71285
|
+
throw themeTooLarge();
|
|
71286
|
+
}
|
|
71287
|
+
return readBounded2(res);
|
|
71288
|
+
}
|
|
71289
|
+
async function readBounded2(res) {
|
|
71290
|
+
const reader = res.body?.getReader();
|
|
71291
|
+
if (!reader) return Buffer.from(await res.arrayBuffer());
|
|
71292
|
+
const chunks = [];
|
|
71293
|
+
let total = 0;
|
|
71294
|
+
for (; ; ) {
|
|
71295
|
+
const { done, value } = await reader.read();
|
|
71296
|
+
if (done) break;
|
|
71297
|
+
total += value.byteLength;
|
|
71298
|
+
if (total > MAX_THEME_FILE_BYTES) {
|
|
71299
|
+
await reader.cancel().catch(() => void 0);
|
|
71300
|
+
throw themeTooLarge();
|
|
71301
|
+
}
|
|
71302
|
+
chunks.push(value);
|
|
71303
|
+
}
|
|
71304
|
+
return Buffer.concat(chunks);
|
|
71305
|
+
}
|
|
71306
|
+
function message2(err) {
|
|
71307
|
+
return err instanceof Error ? err.message : String(err);
|
|
71308
|
+
}
|
|
71309
|
+
|
|
71310
|
+
// src/http/routes/themes.ts
|
|
70746
71311
|
var THEMES_DIR = "themes";
|
|
70747
|
-
var MAX_FILE_BYTES2 = 256 * 1024;
|
|
70748
71312
|
async function readImportedThemes(root) {
|
|
70749
71313
|
const dir = join2(root, THEMES_DIR);
|
|
70750
71314
|
let names;
|
|
@@ -70763,8 +71327,8 @@ async function readImportedThemes(root) {
|
|
|
70763
71327
|
rejected.push({ file, errors: [`\u8BFB\u4E0D\u4E86\u8FD9\u4E2A\u6587\u4EF6\uFF1A${error.message}`] });
|
|
70764
71328
|
continue;
|
|
70765
71329
|
}
|
|
70766
|
-
if (text2.length >
|
|
70767
|
-
rejected.push({ file, errors: [`\u6587\u4EF6\u8D85\u8FC7 ${
|
|
71330
|
+
if (text2.length > MAX_THEME_FILE_BYTES2) {
|
|
71331
|
+
rejected.push({ file, errors: [`\u6587\u4EF6\u8D85\u8FC7 ${MAX_THEME_FILE_BYTES2} \u5B57\u8282`] });
|
|
70768
71332
|
continue;
|
|
70769
71333
|
}
|
|
70770
71334
|
let parsed;
|
|
@@ -70787,18 +71351,6 @@ async function readImportedThemes(root) {
|
|
|
70787
71351
|
}
|
|
70788
71352
|
return { dir, themes, rejected };
|
|
70789
71353
|
}
|
|
70790
|
-
function themesRoutes(deps) {
|
|
70791
|
-
const routes = new Hono41();
|
|
70792
|
-
routes.get("/", async (c) => {
|
|
70793
|
-
const { dir, themes, rejected } = await readImportedThemes(deps.root);
|
|
70794
|
-
return c.json({
|
|
70795
|
-
dir,
|
|
70796
|
-
themes: themes.map(({ file, theme }) => ({ file, theme })),
|
|
70797
|
-
rejected: rejected.map(({ file, errors }) => ({ file, errors: [...errors] }))
|
|
70798
|
-
});
|
|
70799
|
-
});
|
|
70800
|
-
return routes;
|
|
70801
|
-
}
|
|
70802
71354
|
function themeStylesheetRoutes(deps) {
|
|
70803
71355
|
const routes = new Hono41();
|
|
70804
71356
|
routes.get("/", async (c) => {
|
|
@@ -70812,6 +71364,87 @@ function themeStylesheetRoutes(deps) {
|
|
|
70812
71364
|
});
|
|
70813
71365
|
return routes;
|
|
70814
71366
|
}
|
|
71367
|
+
function requireAdmin2(c) {
|
|
71368
|
+
if (!c.var.user.isAdmin) {
|
|
71369
|
+
throw new HttpError(403, "admin_required", "Only an admin can import themes.");
|
|
71370
|
+
}
|
|
71371
|
+
}
|
|
71372
|
+
function readThemeText(bytes) {
|
|
71373
|
+
try {
|
|
71374
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
|
|
71375
|
+
} catch {
|
|
71376
|
+
throw new HttpError(400, "theme_invalid", "The file is not UTF-8 text.");
|
|
71377
|
+
}
|
|
71378
|
+
}
|
|
71379
|
+
function readThemeTextAsTheme(text2) {
|
|
71380
|
+
let parsed;
|
|
71381
|
+
try {
|
|
71382
|
+
parsed = JSON.parse(text2);
|
|
71383
|
+
} catch (error) {
|
|
71384
|
+
throw new HttpError(400, "theme_invalid", `\u4E0D\u662F\u5408\u6CD5\u7684 JSON\uFF1A${error.message}`);
|
|
71385
|
+
}
|
|
71386
|
+
const read = readExternalTheme(parsed);
|
|
71387
|
+
if (!read.ok) throw new HttpError(400, "theme_invalid", read.errors.join("\uFF1B"));
|
|
71388
|
+
return read.theme;
|
|
71389
|
+
}
|
|
71390
|
+
async function installTheme(root, text2, overwrite) {
|
|
71391
|
+
const theme = readThemeTextAsTheme(text2);
|
|
71392
|
+
const file = `${theme.id}.json`;
|
|
71393
|
+
const claimed = (await readImportedThemes(root)).themes.find(
|
|
71394
|
+
(entry) => entry.theme.id === theme.id
|
|
71395
|
+
)?.file;
|
|
71396
|
+
if (claimed !== void 0 && !overwrite) {
|
|
71397
|
+
throw new HttpError(
|
|
71398
|
+
409,
|
|
71399
|
+
"theme_exists",
|
|
71400
|
+
`A theme named ${theme.id} is already imported as ${claimed}.`
|
|
71401
|
+
);
|
|
71402
|
+
}
|
|
71403
|
+
const dir = join2(root, THEMES_DIR);
|
|
71404
|
+
await mkdir(dir, { recursive: true });
|
|
71405
|
+
if (claimed !== void 0 && claimed !== file) await rm2(join2(dir, claimed));
|
|
71406
|
+
await writeFile(join2(dir, file), text2, "utf8");
|
|
71407
|
+
return { file, theme };
|
|
71408
|
+
}
|
|
71409
|
+
function themesRoutes(deps) {
|
|
71410
|
+
const routes = new Hono41();
|
|
71411
|
+
routes.get("/", async (c) => {
|
|
71412
|
+
const { dir, themes, rejected } = await readImportedThemes(deps.root);
|
|
71413
|
+
return c.json({
|
|
71414
|
+
dir,
|
|
71415
|
+
themes: themes.map(({ file, theme }) => ({ file, theme })),
|
|
71416
|
+
rejected: rejected.map(({ file, errors }) => ({ file, errors: [...errors] }))
|
|
71417
|
+
});
|
|
71418
|
+
});
|
|
71419
|
+
routes.post("/upload", async (c) => {
|
|
71420
|
+
requireAdmin2(c);
|
|
71421
|
+
const body = await readJson(c);
|
|
71422
|
+
const dataBase64 = requireString(body, "dataBase64", { minLen: 1, label: "dataBase64" });
|
|
71423
|
+
const bytes = Buffer.from(dataBase64, "base64");
|
|
71424
|
+
if (bytes.byteLength === 0) throw badRequest("The file is empty.");
|
|
71425
|
+
if (bytes.byteLength > MAX_THEME_FILE_BYTES2) throw themeTooLarge();
|
|
71426
|
+
const { file, theme } = await installTheme(
|
|
71427
|
+
deps.root,
|
|
71428
|
+
readThemeText(bytes),
|
|
71429
|
+
optionalBoolean(body, "overwrite") ?? false
|
|
71430
|
+
);
|
|
71431
|
+
return c.json({ file, theme }, 201);
|
|
71432
|
+
});
|
|
71433
|
+
routes.post("/download", async (c) => {
|
|
71434
|
+
requireAdmin2(c);
|
|
71435
|
+
const body = await readJson(c);
|
|
71436
|
+
const url = requireString(body, "url", { minLen: 1, maxLen: 2048 });
|
|
71437
|
+
const bytes = await fetchThemeFile(url);
|
|
71438
|
+
if (bytes.byteLength === 0) throw badRequest("The download is empty.");
|
|
71439
|
+
const { file, theme } = await installTheme(
|
|
71440
|
+
deps.root,
|
|
71441
|
+
readThemeText(bytes),
|
|
71442
|
+
optionalBoolean(body, "overwrite") ?? false
|
|
71443
|
+
);
|
|
71444
|
+
return c.json({ file, theme }, 201);
|
|
71445
|
+
});
|
|
71446
|
+
return routes;
|
|
71447
|
+
}
|
|
70815
71448
|
var _routes_dec17, _config_dec15, _ThemesRoutes_decorators, _init132;
|
|
70816
71449
|
_ThemesRoutes_decorators = [Component65({
|
|
70817
71450
|
contributes: {
|
|
@@ -72056,305 +72689,6 @@ function builtinPluginRegistry(deps = {}) {
|
|
|
72056
72689
|
};
|
|
72057
72690
|
}
|
|
72058
72691
|
|
|
72059
|
-
// src/services/plugin-download.ts
|
|
72060
|
-
import { createHash as createHash9, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
72061
|
-
import { isIP } from "net";
|
|
72062
|
-
var MAX_PLUGIN_ARCHIVE_BYTES = 32 * 1024 * 1024;
|
|
72063
|
-
var DOWNLOAD_TIMEOUT_MS = 6e4;
|
|
72064
|
-
function normalizePluginUrl(raw) {
|
|
72065
|
-
const trimmed = raw.trim();
|
|
72066
|
-
if (trimmed === "") throw badRequest("url must not be empty.");
|
|
72067
|
-
let url;
|
|
72068
|
-
try {
|
|
72069
|
-
url = new URL(trimmed);
|
|
72070
|
-
} catch {
|
|
72071
|
-
throw new HttpError(400, "unsupported_url", "url must be an absolute http(s) URL.");
|
|
72072
|
-
}
|
|
72073
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
72074
|
-
throw new HttpError(400, "unsupported_url", "url must be an http(s) URL.");
|
|
72075
|
-
}
|
|
72076
|
-
assertHostAllowed(url);
|
|
72077
|
-
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
72078
|
-
if (host !== "github.com") {
|
|
72079
|
-
const file = url.pathname.split("/").filter(Boolean).pop() ?? "";
|
|
72080
|
-
const suggested = archiveStem(file);
|
|
72081
|
-
return { fetchUrl: url.toString(), subdir: "", ...suggested ? { name: suggested } : {} };
|
|
72082
|
-
}
|
|
72083
|
-
const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
72084
|
-
const [owner3, repo, kind, ...rest] = segments;
|
|
72085
|
-
if (!owner3 || !repo) {
|
|
72086
|
-
throw new HttpError(
|
|
72087
|
-
400,
|
|
72088
|
-
"unsupported_url",
|
|
72089
|
-
"Not a GitHub repository URL: expected /<owner>/<repo>."
|
|
72090
|
-
);
|
|
72091
|
-
}
|
|
72092
|
-
const repoUrl = `https://github.com/${owner3}/${repo}`;
|
|
72093
|
-
if (kind === void 0) {
|
|
72094
|
-
return { fetchUrl: `${repoUrl}/archive/HEAD.zip`, subdir: "", name: repo };
|
|
72095
|
-
}
|
|
72096
|
-
if (kind !== "tree") {
|
|
72097
|
-
return { fetchUrl: url.toString(), subdir: "", name: repo };
|
|
72098
|
-
}
|
|
72099
|
-
const [ref, ...sub] = rest;
|
|
72100
|
-
if (!ref) throw new HttpError(400, "unsupported_url", "A GitHub tree URL needs a branch or tag.");
|
|
72101
|
-
const subdir = sub.join("/");
|
|
72102
|
-
return {
|
|
72103
|
-
fetchUrl: `${repoUrl}/archive/${encodeURIComponent(ref)}.zip`,
|
|
72104
|
-
subdir,
|
|
72105
|
-
// The plugin directory is the last segment of the subdirectory — that is its name.
|
|
72106
|
-
name: sub.length > 0 ? sub[sub.length - 1] : repo
|
|
72107
|
-
};
|
|
72108
|
-
}
|
|
72109
|
-
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
72110
|
-
var NPM_NAME = /^(?:@[A-Za-z0-9._~!*'()-]+\/)?[A-Za-z0-9._~!*'()-]+$/;
|
|
72111
|
-
var NPM_VERSION = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
|
|
72112
|
-
function parseNpmPluginSpec(raw) {
|
|
72113
|
-
const trimmed = raw.trim();
|
|
72114
|
-
if (trimmed === "") return null;
|
|
72115
|
-
if (trimmed.toLowerCase().startsWith("npm:")) {
|
|
72116
|
-
return parseNpmName(trimmed.slice("npm:".length).trim());
|
|
72117
|
-
}
|
|
72118
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
72119
|
-
let url;
|
|
72120
|
-
try {
|
|
72121
|
-
url = new URL(trimmed);
|
|
72122
|
-
} catch {
|
|
72123
|
-
return null;
|
|
72124
|
-
}
|
|
72125
|
-
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
72126
|
-
if (host === "npmjs.com") {
|
|
72127
|
-
const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
72128
|
-
if (segments[0] !== "package") return null;
|
|
72129
|
-
const scoped = segments[1] === void 0 ? void 0 : segments[1].startsWith("@");
|
|
72130
|
-
const name = scoped ? `${segments[1]}/${segments[2] ?? ""}` : segments[1];
|
|
72131
|
-
if (name === void 0 || name.endsWith("/")) return null;
|
|
72132
|
-
const marker = scoped ? 3 : 2;
|
|
72133
|
-
const version = segments[marker] === "v" ? segments[marker + 1] : void 0;
|
|
72134
|
-
return parseNpmName(version === void 0 ? name : `${name}@${version}`);
|
|
72135
|
-
}
|
|
72136
|
-
if (host === "registry.npmjs.org") {
|
|
72137
|
-
const rest = decodeURIComponent(url.pathname.replace(/^\//, ""));
|
|
72138
|
-
const segments = rest.split("/");
|
|
72139
|
-
if (segments[0] === void 0 || segments[0] === "") return null;
|
|
72140
|
-
const scoped = segments[0].startsWith("@");
|
|
72141
|
-
const name = scoped ? `${segments[0]}/${segments[1] ?? ""}` : segments[0];
|
|
72142
|
-
if (name.endsWith("/") || segments.length > (scoped ? 2 : 1)) return null;
|
|
72143
|
-
return parseNpmName(name);
|
|
72144
|
-
}
|
|
72145
|
-
return null;
|
|
72146
|
-
}
|
|
72147
|
-
return parseNpmName(trimmed);
|
|
72148
|
-
}
|
|
72149
|
-
function parseNpmName(spec) {
|
|
72150
|
-
if (spec === "") return null;
|
|
72151
|
-
const at = spec.lastIndexOf("@");
|
|
72152
|
-
const name = at > 0 ? spec.slice(0, at) : spec;
|
|
72153
|
-
const version = at > 0 ? spec.slice(at + 1) : void 0;
|
|
72154
|
-
if (name.length > 214 || !NPM_NAME.test(name)) return null;
|
|
72155
|
-
if (version !== void 0 && !NPM_VERSION.test(version)) return null;
|
|
72156
|
-
return version === void 0 ? { name } : { name, version };
|
|
72157
|
-
}
|
|
72158
|
-
async function resolvePluginSource(raw, options = {}) {
|
|
72159
|
-
const spec = parseNpmPluginSpec(raw);
|
|
72160
|
-
if (spec === null) return normalizePluginUrl(raw);
|
|
72161
|
-
const found = await fetchPackument(spec, options);
|
|
72162
|
-
const entry = pickVersion(spec, found);
|
|
72163
|
-
const tarball = entry.tarball;
|
|
72164
|
-
let url;
|
|
72165
|
-
try {
|
|
72166
|
-
url = new URL(tarball);
|
|
72167
|
-
} catch {
|
|
72168
|
-
throw new HttpError(400, "download_failed", "The registry named an unreadable tarball URL.");
|
|
72169
|
-
}
|
|
72170
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
72171
|
-
throw new HttpError(400, "download_failed", "The registry named an unsupported tarball URL.");
|
|
72172
|
-
}
|
|
72173
|
-
assertHostAllowed(url);
|
|
72174
|
-
return {
|
|
72175
|
-
fetchUrl: url.toString(),
|
|
72176
|
-
subdir: "",
|
|
72177
|
-
// The package's own name, minus the scope: the scope names a publisher, and the plugin is
|
|
72178
|
-
// installed under the name its own repository gives it (`@scope/use-firecrawl` → `use-firecrawl`).
|
|
72179
|
-
name: spec.name.slice(spec.name.startsWith("@") ? spec.name.indexOf("/") + 1 : 0),
|
|
72180
|
-
...entry.integrity !== void 0 ? { integrity: entry.integrity } : {}
|
|
72181
|
-
};
|
|
72182
|
-
}
|
|
72183
|
-
async function fetchPackument(spec, options) {
|
|
72184
|
-
const doFetch = options.fetchImpl ?? fetch;
|
|
72185
|
-
const encoded = spec.name.startsWith("@") ? `@${encodeURIComponent(spec.name.slice(1))}` : encodeURIComponent(spec.name);
|
|
72186
|
-
const url = `${NPM_REGISTRY}/${encoded}`;
|
|
72187
|
-
let res;
|
|
72188
|
-
try {
|
|
72189
|
-
res = await doFetch(url, {
|
|
72190
|
-
method: "GET",
|
|
72191
|
-
redirect: "follow",
|
|
72192
|
-
// The abbreviated document: dist-tags and each version's dist, which is all this reads, and a
|
|
72193
|
-
// fraction of the full packument for a package with a long history.
|
|
72194
|
-
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
72195
|
-
signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS)
|
|
72196
|
-
});
|
|
72197
|
-
} catch (err) {
|
|
72198
|
-
throw new HttpError(400, "npm_registry_failed", `Could not reach npm: ${message(err)}`);
|
|
72199
|
-
}
|
|
72200
|
-
if (res.url) assertHostAllowed(new URL(res.url));
|
|
72201
|
-
if (res.status === 404) {
|
|
72202
|
-
throw new HttpError(404, "npm_package_not_found", `npm has no package ${spec.name}.`);
|
|
72203
|
-
}
|
|
72204
|
-
if (!res.ok) {
|
|
72205
|
-
await res.body?.cancel().catch(() => void 0);
|
|
72206
|
-
throw new HttpError(
|
|
72207
|
-
400,
|
|
72208
|
-
"npm_registry_failed",
|
|
72209
|
-
`npm answered HTTP ${res.status} for ${spec.name}.`
|
|
72210
|
-
);
|
|
72211
|
-
}
|
|
72212
|
-
try {
|
|
72213
|
-
const data = await res.json();
|
|
72214
|
-
if (typeof data !== "object" || data === null) throw new Error("not an object");
|
|
72215
|
-
return data;
|
|
72216
|
-
} catch {
|
|
72217
|
-
throw new HttpError(
|
|
72218
|
-
400,
|
|
72219
|
-
"npm_registry_failed",
|
|
72220
|
-
`npm returned no usable data for ${spec.name}.`
|
|
72221
|
-
);
|
|
72222
|
-
}
|
|
72223
|
-
}
|
|
72224
|
-
function pickVersion(spec, packument) {
|
|
72225
|
-
const versions = packument["versions"];
|
|
72226
|
-
const table = typeof versions === "object" && versions !== null ? versions : {};
|
|
72227
|
-
const distTags = packument["dist-tags"];
|
|
72228
|
-
const tags = typeof distTags === "object" && distTags !== null ? distTags : {};
|
|
72229
|
-
const version = spec.version ?? (typeof tags["latest"] === "string" ? tags["latest"] : void 0);
|
|
72230
|
-
if (version === void 0) {
|
|
72231
|
-
throw new HttpError(404, "npm_version_not_found", `${spec.name} has no published version.`);
|
|
72232
|
-
}
|
|
72233
|
-
const entry = table[version];
|
|
72234
|
-
if (typeof entry !== "object" || entry === null) {
|
|
72235
|
-
throw new HttpError(
|
|
72236
|
-
404,
|
|
72237
|
-
"npm_version_not_found",
|
|
72238
|
-
`npm has no version ${version} of ${spec.name}; use an exact version or a dist-tag.`
|
|
72239
|
-
);
|
|
72240
|
-
}
|
|
72241
|
-
const dist = entry["dist"];
|
|
72242
|
-
const tarball = typeof dist === "object" && dist !== null ? dist["tarball"] : void 0;
|
|
72243
|
-
if (typeof tarball !== "string" || tarball === "") {
|
|
72244
|
-
throw new HttpError(404, "npm_version_not_found", `${spec.name}@${version} has no tarball.`);
|
|
72245
|
-
}
|
|
72246
|
-
const integrity = dist["integrity"];
|
|
72247
|
-
const entryOut = { tarball };
|
|
72248
|
-
if (typeof integrity === "string" && integrity !== "") entryOut.integrity = integrity;
|
|
72249
|
-
return entryOut;
|
|
72250
|
-
}
|
|
72251
|
-
function archiveStem(file) {
|
|
72252
|
-
const lower = file.toLowerCase();
|
|
72253
|
-
for (const suffix of [".tar.gz", ".tgz", ".zip"]) {
|
|
72254
|
-
if (lower.endsWith(suffix)) return file.slice(0, -suffix.length);
|
|
72255
|
-
}
|
|
72256
|
-
return void 0;
|
|
72257
|
-
}
|
|
72258
|
-
function isBlockedHost(hostname) {
|
|
72259
|
-
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
72260
|
-
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
72261
|
-
if (host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
72262
|
-
const version = isIP(host);
|
|
72263
|
-
if (version === 4) {
|
|
72264
|
-
const octets = host.split(".").map(Number);
|
|
72265
|
-
const [a, b] = octets;
|
|
72266
|
-
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a >= 224;
|
|
72267
|
-
}
|
|
72268
|
-
if (version === 6) {
|
|
72269
|
-
return /^(0*:)*0*1$/.test(host) || host.startsWith("fe8") || host.startsWith("fe9") || host.startsWith("fea") || host.startsWith("feb") || host.startsWith("fc") || host.startsWith("fd");
|
|
72270
|
-
}
|
|
72271
|
-
return false;
|
|
72272
|
-
}
|
|
72273
|
-
function assertHostAllowed(url) {
|
|
72274
|
-
if (isBlockedHost(url.hostname)) {
|
|
72275
|
-
throw new HttpError(
|
|
72276
|
-
400,
|
|
72277
|
-
"blocked_url",
|
|
72278
|
-
`Refusing to fetch from a local or private host: ${url.hostname}`
|
|
72279
|
-
);
|
|
72280
|
-
}
|
|
72281
|
-
}
|
|
72282
|
-
async function fetchPluginArchive(source, options = {}) {
|
|
72283
|
-
const doFetch = options.fetchImpl ?? fetch;
|
|
72284
|
-
let res;
|
|
72285
|
-
try {
|
|
72286
|
-
res = await doFetch(source.fetchUrl, {
|
|
72287
|
-
method: "GET",
|
|
72288
|
-
redirect: "follow",
|
|
72289
|
-
headers: { accept: "application/zip, application/octet-stream, */*" },
|
|
72290
|
-
signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS)
|
|
72291
|
-
});
|
|
72292
|
-
} catch (err) {
|
|
72293
|
-
throw new HttpError(400, "download_failed", `Could not download: ${message(err)}`);
|
|
72294
|
-
}
|
|
72295
|
-
if (res.url) {
|
|
72296
|
-
try {
|
|
72297
|
-
assertHostAllowed(new URL(res.url));
|
|
72298
|
-
} catch (err) {
|
|
72299
|
-
if (err instanceof HttpError) throw err;
|
|
72300
|
-
throw new HttpError(400, "download_failed", "Could not read the URL that answered.");
|
|
72301
|
-
}
|
|
72302
|
-
}
|
|
72303
|
-
if (!res.ok) {
|
|
72304
|
-
await res.body?.cancel().catch(() => void 0);
|
|
72305
|
-
throw new HttpError(400, "download_failed", `Download failed with HTTP ${res.status}.`);
|
|
72306
|
-
}
|
|
72307
|
-
const declared = Number(res.headers.get("content-length") ?? "");
|
|
72308
|
-
if (Number.isFinite(declared) && declared > MAX_PLUGIN_ARCHIVE_BYTES) {
|
|
72309
|
-
await res.body?.cancel().catch(() => void 0);
|
|
72310
|
-
throw pluginArchiveTooLarge(MAX_PLUGIN_ARCHIVE_BYTES);
|
|
72311
|
-
}
|
|
72312
|
-
const archive = await readBounded(res);
|
|
72313
|
-
if (source.integrity !== void 0) checkIntegrity(source.integrity, archive);
|
|
72314
|
-
return archive;
|
|
72315
|
-
}
|
|
72316
|
-
var INTEGRITY_PATTERN = /^(sha512|sha384|sha256|sha1)-([A-Za-z0-9+/]+={0,2})$/;
|
|
72317
|
-
function checkIntegrity(integrity, archive) {
|
|
72318
|
-
const parts = INTEGRITY_PATTERN.exec(integrity);
|
|
72319
|
-
if (parts === null) return;
|
|
72320
|
-
const expected = Buffer.from(parts[2], "base64");
|
|
72321
|
-
const actual = createHash9(parts[1]).update(archive).digest();
|
|
72322
|
-
if (actual.length !== expected.length || !timingSafeEqual3(actual, expected)) {
|
|
72323
|
-
throw new HttpError(
|
|
72324
|
-
400,
|
|
72325
|
-
"integrity_failed",
|
|
72326
|
-
"The downloaded archive does not match the checksum the registry published for it."
|
|
72327
|
-
);
|
|
72328
|
-
}
|
|
72329
|
-
}
|
|
72330
|
-
async function readBounded(res) {
|
|
72331
|
-
const reader = res.body?.getReader();
|
|
72332
|
-
if (!reader) return Buffer.from(await res.arrayBuffer());
|
|
72333
|
-
const chunks = [];
|
|
72334
|
-
let total = 0;
|
|
72335
|
-
for (; ; ) {
|
|
72336
|
-
const { done, value } = await reader.read();
|
|
72337
|
-
if (done) break;
|
|
72338
|
-
total += value.byteLength;
|
|
72339
|
-
if (total > MAX_PLUGIN_ARCHIVE_BYTES) {
|
|
72340
|
-
await reader.cancel().catch(() => void 0);
|
|
72341
|
-
throw pluginArchiveTooLarge(MAX_PLUGIN_ARCHIVE_BYTES);
|
|
72342
|
-
}
|
|
72343
|
-
chunks.push(value);
|
|
72344
|
-
}
|
|
72345
|
-
return Buffer.concat(chunks);
|
|
72346
|
-
}
|
|
72347
|
-
function pluginArchiveTooLarge(limitBytes) {
|
|
72348
|
-
return new HttpError(
|
|
72349
|
-
413,
|
|
72350
|
-
"plugin_too_large",
|
|
72351
|
-
`The archive exceeds the ${Math.floor(limitBytes / (1024 * 1024))}MB limit.`
|
|
72352
|
-
);
|
|
72353
|
-
}
|
|
72354
|
-
function message(err) {
|
|
72355
|
-
return err instanceof Error ? err.message : String(err);
|
|
72356
|
-
}
|
|
72357
|
-
|
|
72358
72692
|
// src/services/tar-archive.ts
|
|
72359
72693
|
import { gunzipSync } from "zlib";
|
|
72360
72694
|
var BLOCK = 512;
|
|
@@ -72521,7 +72855,7 @@ function libraryResponse(root) {
|
|
|
72521
72855
|
}))
|
|
72522
72856
|
};
|
|
72523
72857
|
}
|
|
72524
|
-
function
|
|
72858
|
+
function requireAdmin3(c) {
|
|
72525
72859
|
if (!c.var.user.isAdmin) {
|
|
72526
72860
|
throw new HttpError(403, "admin_required", "Only an admin can manage the plugin directory.");
|
|
72527
72861
|
}
|
|
@@ -72646,7 +72980,7 @@ function pluginLibraryRoutes(deps) {
|
|
|
72646
72980
|
})
|
|
72647
72981
|
);
|
|
72648
72982
|
app.post("/upload", async (c) => {
|
|
72649
|
-
|
|
72983
|
+
requireAdmin3(c);
|
|
72650
72984
|
const body = await readJson(c);
|
|
72651
72985
|
const dataBase64 = requireString(body, "dataBase64", { minLen: 1, label: "dataBase64" });
|
|
72652
72986
|
const archive = Buffer.from(dataBase64, "base64");
|
|
@@ -72664,7 +72998,7 @@ function pluginLibraryRoutes(deps) {
|
|
|
72664
72998
|
);
|
|
72665
72999
|
});
|
|
72666
73000
|
app.post("/download", async (c) => {
|
|
72667
|
-
|
|
73001
|
+
requireAdmin3(c);
|
|
72668
73002
|
const body = await readJson(c);
|
|
72669
73003
|
const url = requireString(body, "url", { minLen: 1, maxLen: 2048 });
|
|
72670
73004
|
const requestedName = optionalString(body, "name", { maxLen: 128 });
|
|
@@ -72714,7 +73048,7 @@ function pluginLibraryRoutes(deps) {
|
|
|
72714
73048
|
});
|
|
72715
73049
|
});
|
|
72716
73050
|
app.delete("/:plugin", async (c) => {
|
|
72717
|
-
|
|
73051
|
+
requireAdmin3(c);
|
|
72718
73052
|
const pluginName = c.req.param("plugin");
|
|
72719
73053
|
if (!PLUGIN_NAME_PATTERN4.test(pluginName)) {
|
|
72720
73054
|
throw new HttpError(404, "unknown_plugin", `No such plugin: ${pluginName}`);
|
|
@@ -72942,7 +73276,7 @@ function installedPluginRoutes(deps) {
|
|
|
72942
73276
|
};
|
|
72943
73277
|
};
|
|
72944
73278
|
app.get("/", async (c) => c.json(await view(scope(c))));
|
|
72945
|
-
const
|
|
73279
|
+
const requireAdmin4 = (c) => {
|
|
72946
73280
|
if (!c.var.user.isAdmin) {
|
|
72947
73281
|
throw new HttpError(403, "admin_required", "Only an admin can perform this operation.");
|
|
72948
73282
|
}
|
|
@@ -72993,7 +73327,7 @@ function installedPluginRoutes(deps) {
|
|
|
72993
73327
|
});
|
|
72994
73328
|
};
|
|
72995
73329
|
app.post("/", async (c) => {
|
|
72996
|
-
|
|
73330
|
+
requireAdmin4(c);
|
|
72997
73331
|
const projectId = scope(c);
|
|
72998
73332
|
const body = await readJson(c);
|
|
72999
73333
|
const specifier = specifierOf(body.specifier);
|
|
@@ -73016,7 +73350,7 @@ function installedPluginRoutes(deps) {
|
|
|
73016
73350
|
return c.json(await view(projectId));
|
|
73017
73351
|
});
|
|
73018
73352
|
app.delete("/", async (c) => {
|
|
73019
|
-
|
|
73353
|
+
requireAdmin4(c);
|
|
73020
73354
|
const projectId = scope(c);
|
|
73021
73355
|
const specifier = specifierOf(c.req.query("specifier"));
|
|
73022
73356
|
const machineId = machineOf(c.req.query("machineId"));
|
|
@@ -73041,7 +73375,7 @@ function installedPluginRoutes(deps) {
|
|
|
73041
73375
|
return c.json(await view(projectId));
|
|
73042
73376
|
});
|
|
73043
73377
|
app.put("/", async (c) => {
|
|
73044
|
-
|
|
73378
|
+
requireAdmin4(c);
|
|
73045
73379
|
const projectId = scope(c);
|
|
73046
73380
|
const body = await readJson(c);
|
|
73047
73381
|
const list = body.plugins;
|
|
@@ -74227,7 +74561,7 @@ var WorkflowService = class {
|
|
|
74227
74561
|
state = next ?? null;
|
|
74228
74562
|
await writeState(folder.dir, state);
|
|
74229
74563
|
},
|
|
74230
|
-
log: (
|
|
74564
|
+
log: (message3) => service.log.line(`[workflow ${folder.id}] ${message3}`)
|
|
74231
74565
|
};
|
|
74232
74566
|
}
|
|
74233
74567
|
/** A workflow reaches the Sessions of its own Project and no others. */
|
|
@@ -74902,12 +75236,13 @@ var HttpModule = class {
|
|
|
74902
75236
|
const app = new Hono49();
|
|
74903
75237
|
app.onError((err, c) => {
|
|
74904
75238
|
const projectId = attributedProjectId(c, { access });
|
|
75239
|
+
const normalized = normalizeError(err);
|
|
74905
75240
|
errors.record({
|
|
74906
75241
|
source: "http",
|
|
74907
|
-
err,
|
|
75242
|
+
err: normalized,
|
|
74908
75243
|
...projectId !== void 0 ? { ctx: { projectId } } : {}
|
|
74909
75244
|
});
|
|
74910
|
-
return handleError(
|
|
75245
|
+
return handleError(normalized, c);
|
|
74911
75246
|
});
|
|
74912
75247
|
app.notFound(() => declined());
|
|
74913
75248
|
app.use("*", async (c, next) => {
|
|
@@ -75163,8 +75498,8 @@ import os10 from "os";
|
|
|
75163
75498
|
// src/builtin-browser/shell-link.ts
|
|
75164
75499
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
75165
75500
|
var ShellLinkError = class extends Error {
|
|
75166
|
-
constructor(kind,
|
|
75167
|
-
super(
|
|
75501
|
+
constructor(kind, message3) {
|
|
75502
|
+
super(message3);
|
|
75168
75503
|
this.kind = kind;
|
|
75169
75504
|
this.name = "ShellLinkError";
|
|
75170
75505
|
}
|
|
@@ -75436,8 +75771,8 @@ var LOAD_POLL_MS = 250;
|
|
|
75436
75771
|
var LOAD_SETTLE_MS = 300;
|
|
75437
75772
|
var CONTEXT_LOST = /Execution context was destroyed|Inspected target navigated or closed|Cannot find context/i;
|
|
75438
75773
|
var PageScriptError = class extends Error {
|
|
75439
|
-
constructor(
|
|
75440
|
-
super(
|
|
75774
|
+
constructor(message3) {
|
|
75775
|
+
super(message3);
|
|
75441
75776
|
this.name = "PageScriptError";
|
|
75442
75777
|
}
|
|
75443
75778
|
};
|
|
@@ -75545,9 +75880,9 @@ ${code}
|
|
|
75545
75880
|
}
|
|
75546
75881
|
const res = raw ?? {};
|
|
75547
75882
|
if (res.exceptionDetails !== void 0) {
|
|
75548
|
-
const
|
|
75549
|
-
if (CONTEXT_LOST.test(
|
|
75550
|
-
throw new PageScriptError(
|
|
75883
|
+
const message3 = exceptionMessage(res.exceptionDetails);
|
|
75884
|
+
if (CONTEXT_LOST.test(message3)) return { reloaded: true };
|
|
75885
|
+
throw new PageScriptError(message3);
|
|
75551
75886
|
}
|
|
75552
75887
|
const result = res.result;
|
|
75553
75888
|
if (result === void 0) return { value: void 0 };
|
|
@@ -76934,8 +77269,8 @@ var BrowserActions = class {
|
|
|
76934
77269
|
if (method !== DIALOG_EVENT) return;
|
|
76935
77270
|
const type2 = DIALOG_TYPES.find((t) => t === params.type) ?? "alert";
|
|
76936
77271
|
const accepted = type2 === "alert" || accept;
|
|
76937
|
-
const
|
|
76938
|
-
into.push({ type: type2, message:
|
|
77272
|
+
const message3 = typeof params.message === "string" ? params.message : "";
|
|
77273
|
+
into.push({ type: type2, message: message3, accepted });
|
|
76939
77274
|
const answer = { accept: accepted };
|
|
76940
77275
|
if (accepted && type2 === "prompt" && typeof params.defaultPrompt === "string") {
|
|
76941
77276
|
answer.promptText = params.defaultPrompt;
|
|
@@ -79564,7 +79899,7 @@ import { Hono as Hono52 } from "hono";
|
|
|
79564
79899
|
import { isValidId as isValidId9 } from "@adelie/core";
|
|
79565
79900
|
var agentNotFound = () => new HttpError(404, "agent_not_found", "No Agent with this id has its API enabled.");
|
|
79566
79901
|
var sessionNotFound = () => new HttpError(404, "session_not_found", "No API Session with this id.");
|
|
79567
|
-
var unauthorized = (
|
|
79902
|
+
var unauthorized = (message3) => new HttpError(401, "unauthorized", message3);
|
|
79568
79903
|
function requireAgentApiOn(settings) {
|
|
79569
79904
|
if (!settings.getAgentApiEnabled()) {
|
|
79570
79905
|
throw new HttpError(403, "agent_api_disabled", "The Agent API is turned off on this server.");
|
|
@@ -80243,8 +80578,8 @@ async function runStream(c, deps) {
|
|
|
80243
80578
|
}
|
|
80244
80579
|
} catch (err) {
|
|
80245
80580
|
if (stream.aborted) return;
|
|
80246
|
-
const
|
|
80247
|
-
deps.log.line(`[amsp] run stream of ${actual} failed: ${
|
|
80581
|
+
const message3 = err instanceof Error ? err.message : String(err);
|
|
80582
|
+
deps.log.line(`[amsp] run stream of ${actual} failed: ${message3}`);
|
|
80248
80583
|
deps.errors.record({
|
|
80249
80584
|
source: "session",
|
|
80250
80585
|
err,
|
|
@@ -80255,7 +80590,7 @@ async function runStream(c, deps) {
|
|
|
80255
80590
|
if (!ended) {
|
|
80256
80591
|
deps.manager.abortTask(actual);
|
|
80257
80592
|
try {
|
|
80258
|
-
await end(translator.fail({ code: "internal", message:
|
|
80593
|
+
await end(translator.fail({ code: "internal", message: message3 }));
|
|
80259
80594
|
} catch {
|
|
80260
80595
|
}
|
|
80261
80596
|
}
|
|
@@ -81220,12 +81555,13 @@ function createApp(boot3) {
|
|
|
81220
81555
|
const app = new Hono55();
|
|
81221
81556
|
app.onError((err, c) => {
|
|
81222
81557
|
const projectId = attributedProjectId(c, { access: access() });
|
|
81558
|
+
const normalized = normalizeError(err);
|
|
81223
81559
|
errors().record({
|
|
81224
81560
|
source: "http",
|
|
81225
|
-
err,
|
|
81561
|
+
err: normalized,
|
|
81226
81562
|
...projectId !== void 0 ? { ctx: { projectId } } : {}
|
|
81227
81563
|
});
|
|
81228
|
-
return handleError(
|
|
81564
|
+
return handleError(normalized, c);
|
|
81229
81565
|
});
|
|
81230
81566
|
app.notFound((c) => c.json(errorBody("not_found", "Endpoint does not exist."), 404));
|
|
81231
81567
|
app.use("*", async (c, next) => {
|