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