@adelie/server 0.5.6 → 0.5.8

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