@xopcai/xopc 0.0.158 → 0.0.159

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.
Files changed (105) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/dashscope/src/image-generation-provider.js +1 -1
  3. package/dist/extensions/fal/src/image-generation-provider.js +1 -1
  4. package/dist/extensions/google/src/image-generation-provider.js +1 -1
  5. package/dist/extensions/telegram/xopc.extension.json +1 -1
  6. package/dist/gateway/static/root/assets/{action-boundary-settings-panel-2p1f4-KA.js → action-boundary-settings-panel-CaRK2vIy.js} +1 -1
  7. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BIl6X2b_.js → agent-avatar-dicebear-BeVf03hx.js} +1 -1
  8. package/dist/gateway/static/root/assets/{agents-bnkKH9EI.js → agents-P05C4A-3.js} +1 -1
  9. package/dist/gateway/static/root/assets/{app-management-settings-panel-CZxw161D.js → app-management-settings-panel-BG2ulMYw.js} +1 -1
  10. package/dist/gateway/static/root/assets/{appearance-settings-CRXNlDhH.js → appearance-settings-0Kjenr2T.js} +1 -1
  11. package/dist/gateway/static/root/assets/{apps-page-l4ripRNG.js → apps-page-DANGrBpN.js} +1 -1
  12. package/dist/gateway/static/root/assets/{archive-plugin-CpVshjXu.js → archive-plugin-ChiNnH1e.js} +1 -1
  13. package/dist/gateway/static/root/assets/{attachment-load-M9WqqJnQ.js → attachment-load-pbBlQR-g.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automation-suggestion-card-CYMsAsYf.js → automation-suggestion-card-CTZDOyDy.js} +1 -1
  15. package/dist/gateway/static/root/assets/{automations-page-Kny7tLiF.js → automations-page-BXfGL_cm.js} +1 -1
  16. package/dist/gateway/static/root/assets/{binary-plugins-Cjn3zb4u.js → binary-plugins-BeZGzpGF.js} +1 -1
  17. package/dist/gateway/static/root/assets/{block-editor-BPKdquaV.js → block-editor-BnDmhN5Z.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-settings-page-vr1tXAab.js → browser-settings-page-534luV0B.js} +1 -1
  19. package/dist/gateway/static/root/assets/{capability-presets-api-Bzkcsyzn.js → capability-presets-api-Ii36j_7v.js} +1 -1
  20. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-D4rH4Dax.js → capability-presets-settings-panel-Cl3dw4yh.js} +1 -1
  21. package/dist/gateway/static/root/assets/{channels-settings-Tlhbi5eh.js → channels-settings-M91zFJVi.js} +1 -1
  22. package/dist/gateway/static/root/assets/{connector-logo-ljPKdYKh.js → connector-logo-CiLxuZ8S.js} +1 -1
  23. package/dist/gateway/static/root/assets/{connectors-page-_XfvjXoY.js → connectors-page-CV5UulW6.js} +1 -1
  24. package/dist/gateway/static/root/assets/{desktop-pet-DKUlAgm7.js → desktop-pet-Cil2eSpW.js} +1 -1
  25. package/dist/gateway/static/root/assets/{desktop-pet-settings-aWWzjdza.js → desktop-pet-settings-DwsC6l5F.js} +1 -1
  26. package/dist/gateway/static/root/assets/{directory-picker-path-field-Dmy1035j.js → directory-picker-path-field-CC2VL5Mc.js} +1 -1
  27. package/dist/gateway/static/root/assets/{dreaming-settings-tT4WkBkd.js → dreaming-settings-KY7ZfVsy.js} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-debug-page-CL79q8AH.js → extension-debug-page-C8KBeucs.js} +1 -1
  29. package/dist/gateway/static/root/assets/{extension-page-hq80wkFI.js → extension-page-CV-5Kcyk.js} +1 -1
  30. package/dist/gateway/static/root/assets/{extension-settings-page-BV-9GDfT.js → extension-settings-page-DvUtDrAu.js} +1 -1
  31. package/dist/gateway/static/root/assets/{gateway-config-api-CFqoN2NW.js → gateway-config-api-DXfVeJLc.js} +1 -1
  32. package/dist/gateway/static/root/assets/{gateway-settings-DzR-kM91.js → gateway-settings-BpJ0v56l.js} +1 -1
  33. package/dist/gateway/static/root/assets/{goal-create-dialog-BLjHRtqe.js → goal-create-dialog-lMHD-2CX.js} +1 -1
  34. package/dist/gateway/static/root/assets/{goal-detail-page-BRxeujXh.js → goal-detail-page-yPmF-G7b.js} +1 -1
  35. package/dist/gateway/static/root/assets/{goals-config-api--qJ4qlRq.js → goals-config-api-P8VhLqYF.js} +1 -1
  36. package/dist/gateway/static/root/assets/{goals-page-BaZ3aw4u.js → goals-page-BHTCKobG.js} +1 -1
  37. package/dist/gateway/static/root/assets/{goals-settings-h7x9h5lb.js → goals-settings-DHXyaH_O.js} +1 -1
  38. package/dist/gateway/static/root/assets/{heartbeat-settings-71IhhM3s.js → heartbeat-settings-CNckP_Dv.js} +1 -1
  39. package/dist/gateway/static/root/assets/index-BgZhEOkN.css +1 -0
  40. package/dist/gateway/static/root/assets/{index-EXa_i6BT.js → index-BuqGh2SW.js} +65 -65
  41. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BZGmHvLu.js → keyboard-shortcuts-settings-jXmv1RVX.js} +1 -1
  42. package/dist/gateway/static/root/assets/{local-app-workbench-page-lUpd6lSo.js → local-app-workbench-page-zpp_wciE.js} +1 -1
  43. package/dist/gateway/static/root/assets/{local-apps-page-CiLqrMIh.js → local-apps-page-Cs5LKjtc.js} +1 -1
  44. package/dist/gateway/static/root/assets/{logs-page-FPDTmcWe.js → logs-page-B4CRYqYR.js} +1 -1
  45. package/dist/gateway/static/root/assets/{markdown-split-r1HPWfCU.js → markdown-split-BFqeSNte.js} +1 -1
  46. package/dist/gateway/static/root/assets/{media-plugins-D74kd3hn.js → media-plugins-Cc8379gq.js} +1 -1
  47. package/dist/gateway/static/root/assets/{models-hub-panel-BXrdePhv.js → models-hub-panel-1hdoAoB_.js} +1 -1
  48. package/dist/gateway/static/root/assets/{notes-page-Dgu02514.js → notes-page-DlGU4ipo.js} +1 -1
  49. package/dist/gateway/static/root/assets/{notes-workbench-D-EAhXv1.js → notes-workbench-DQEAZTA5.js} +2 -2
  50. package/dist/gateway/static/root/assets/{page-tabs-hinRdDy3.js → page-tabs-kFOswGlG.js} +1 -1
  51. package/dist/gateway/static/root/assets/{preview-open-alternatives-C7fw3oGy.js → preview-open-alternatives-CvjuvABg.js} +1 -1
  52. package/dist/gateway/static/root/assets/{project-detail-page-BjpB1wrp.js → project-detail-page-Bp9bC7A6.js} +1 -1
  53. package/dist/gateway/static/root/assets/{projects-page-BxyjqOfC.js → projects-page-RNorFXTw.js} +1 -1
  54. package/dist/gateway/static/root/assets/{remote-access-hub-BFv6Upke.js → remote-access-hub-BqRTcM_6.js} +1 -1
  55. package/dist/gateway/static/root/assets/{save-bar-controls-C3aU8-vO.js → save-bar-controls-DwrD7ODx.js} +1 -1
  56. package/dist/gateway/static/root/assets/{schedule-field-DjegExFD.js → schedule-field-Ba4WzTmE.js} +1 -1
  57. package/dist/gateway/static/root/assets/{schema-form-DLubnsql.js → schema-form-DvydL8eF.js} +1 -1
  58. package/dist/gateway/static/root/assets/{sessions-page-BX4DH8J7.js → sessions-page-B2EfW1cm.js} +1 -1
  59. package/dist/gateway/static/root/assets/settings-advanced-gate-CklL85T4.js +1 -0
  60. package/dist/gateway/static/root/assets/{settings-collapsible-section-3syQHsNR.js → settings-collapsible-section-LaQqGGej.js} +1 -1
  61. package/dist/gateway/static/root/assets/{settings-form-section-D4DCZzKg.js → settings-form-section-DruI__qO.js} +1 -1
  62. package/dist/gateway/static/root/assets/{settings-loading-skeleton-c5K85ylS.js → settings-loading-skeleton-UvA-eDhF.js} +1 -1
  63. package/dist/gateway/static/root/assets/{settings-page-DNiDlaRq.js → settings-page-5oCGSebm.js} +1 -1
  64. package/dist/gateway/static/root/assets/{settings-page-layout-iTRY1Hy-.js → settings-page-layout-DG-XMIPu.js} +1 -1
  65. package/dist/gateway/static/root/assets/{setup-status-panel-BH4xQKM9.js → setup-status-panel-D3my1HgB.js} +1 -1
  66. package/dist/gateway/static/root/assets/{share-preview-page-B88Afrhe.js → share-preview-page-FLVeTrRt.js} +1 -1
  67. package/dist/gateway/static/root/assets/{shares-settings-B6kKkvOb.js → shares-settings-DVxgjrsP.js} +1 -1
  68. package/dist/gateway/static/root/assets/{skill-reload-api-DtjS-nUK.js → skill-reload-api-Bcu58ZaF.js} +1 -1
  69. package/dist/gateway/static/root/assets/{skills-page-CQEoPv0u.js → skills-page-CDd-84FE.js} +1 -1
  70. package/dist/gateway/static/root/assets/{system-settings-panel-DtpTq837.js → system-settings-panel-CeK93xVi.js} +1 -1
  71. package/dist/gateway/static/root/assets/{typed-models-lib-ndEnUP3b.js → typed-models-lib-D1Lr2gG0.js} +1 -1
  72. package/dist/gateway/static/root/assets/{user-context-page-Dn2T3itn.js → user-context-page-1OyJnTyR.js} +1 -1
  73. package/dist/gateway/static/root/assets/{voice-api-key-field-wtWc3SfN.js → voice-api-key-field-B2ncRwHS.js} +1 -1
  74. package/dist/gateway/static/root/assets/{work-discovery-page-Ckxhj66e.js → work-discovery-page-BZlY4PZL.js} +1 -1
  75. package/dist/gateway/static/root/assets/{work-item-detail-page-_1QgGPNw.js → work-item-detail-page-DPXMrneY.js} +1 -1
  76. package/dist/gateway/static/root/assets/{work-page-BPdf2iuv.js → work-page-CTERoqyv.js} +1 -1
  77. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-ukmpkj66.js → workflow-run-setup-panel-Bynqe4kV.js} +1 -1
  78. package/dist/gateway/static/root/assets/{workflows-page-DdKTx0Hs.js → workflows-page-CLgsnqFQ.js} +1 -1
  79. package/dist/gateway/static/root/index.html +2 -2
  80. package/dist/package.js +1 -1
  81. package/dist/src/agent/embedded/session-tool-result-guard.js +14 -0
  82. package/dist/src/agent/image/generation/openai-compatible-image-provider.js +1 -1
  83. package/dist/src/agent/service/process-direct-streaming.js +1 -1
  84. package/dist/src/agent/tools/image-generate-tool.js +10 -3
  85. package/dist/src/agent/tools/send-media.js +20 -70
  86. package/dist/src/agent/tools/tool-media.d.ts +9 -0
  87. package/dist/src/agent/tools/tool-media.js +58 -0
  88. package/dist/src/config/agent-profile.js +1 -1
  89. package/dist/src/gateway/agent-run-relay.d.ts +2 -0
  90. package/dist/src/gateway/agent-run-relay.js +114 -14
  91. package/dist/src/gateway/agents-admin.js +1 -1
  92. package/dist/src/gateway/chat-stream/mapper.d.ts +1 -1
  93. package/dist/src/gateway/chat-stream/mapper.js +27 -42
  94. package/dist/src/gateway/chat-stream/thinking-delta-coalescer.d.ts +7 -0
  95. package/dist/src/gateway/chat-stream/thinking-delta-coalescer.js +103 -0
  96. package/dist/src/gateway/hono/sse.d.ts +1 -1
  97. package/dist/src/gateway/hono/sse.js +1 -1
  98. package/dist/src/gateway/service/run-gateway-agent.js +5 -1
  99. package/dist/src/media/session-references.js +9 -6
  100. package/dist/src/session/client-history.d.ts +1 -0
  101. package/dist/src/session/client-history.js +56 -12
  102. package/dist/src/session/types.d.ts +1 -0
  103. package/package.json +1 -1
  104. package/dist/gateway/static/root/assets/index-gpwk8Nro.css +0 -1
  105. package/dist/gateway/static/root/assets/settings-advanced-gate-CJ9MbRP8.js +0 -1
@@ -11,7 +11,7 @@
11
11
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=20260713-1" />
12
12
  <link rel="manifest" href="/site.webmanifest?v=20260713-1" />
13
13
  <title>xopc</title>
14
- <script type="module" crossorigin src="/assets/index-EXa_i6BT.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-BuqGh2SW.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-aKtaBQYM.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-zJ_50EbN.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BOUWij0V.js">
@@ -32,7 +32,7 @@
32
32
  <link rel="modulepreload" crossorigin href="/assets/gateway-startup-retry-cxQ94qMm.js">
33
33
  <link rel="modulepreload" crossorigin href="/assets/activity-store-C-57rvkX.js">
34
34
  <link rel="modulepreload" crossorigin href="/assets/theme-store-C8ohIHhw.js">
35
- <link rel="stylesheet" crossorigin href="/assets/index-gpwk8Nro.css">
35
+ <link rel="stylesheet" crossorigin href="/assets/index-BgZhEOkN.css">
36
36
  </head>
37
37
  <body>
38
38
  <div id="root"></div>
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.0.158";
2
+ var version = "0.0.159";
3
3
  //#endregion
4
4
  export { version };
@@ -173,6 +173,20 @@ function sanitizeToolResultDetailsForPersistence(details) {
173
173
  out.sessions = src.sessions.slice(0, MAX_PERSISTED_DETAIL_SESSION_COUNT).map(sanitizePersistedSessionDetail);
174
174
  if (src.sessions.length > MAX_PERSISTED_DETAIL_SESSION_COUNT) out.sessionsTruncated = src.sessions.length - MAX_PERSISTED_DETAIL_SESSION_COUNT;
175
175
  }
176
+ if (Array.isArray(src.media)) out.media = src.media.slice(0, 12).map((item) => {
177
+ if (!item || typeof item !== "object") return item;
178
+ const media = item;
179
+ return Object.fromEntries([
180
+ "id",
181
+ "bucket",
182
+ "type",
183
+ "mimeType",
184
+ "name",
185
+ "size",
186
+ "uri",
187
+ "path"
188
+ ].filter((key) => media[key] !== void 0).map((key) => [key, media[key]]));
189
+ });
176
190
  return enforcePersistedDetailsByteCap(out, src, originalSize);
177
191
  }
178
192
  function capToolResultDetails(msg) {
@@ -1,11 +1,11 @@
1
1
  import { createLogger } from "../../../utils/logger/index.js";
2
2
  import { init_logger } from "../../../utils/logger.js";
3
+ import { imageAssetFromBase64, imageFileExtensionForMimeType } from "./image-assets.js";
3
4
  import { pickTimeoutMsOrFallback } from "../../../media-shared/http/deadline.js";
4
5
  import { privateNetworkPolicyToSsrfGuardOptions } from "../../../media-shared/http/private-network.js";
5
6
  import { resolveProviderHttpRequestConfig } from "../../../media-shared/http/resolve-provider-http-request-config.js";
6
7
  import { postJsonRequest, postMultipartRequest } from "../../../media-shared/http/provider-http.js";
7
8
  import "../../../media-shared/http/index.js";
8
- import { imageAssetFromBase64, imageFileExtensionForMimeType } from "./image-assets.js";
9
9
  //#region src/agent/image/generation/openai-compatible-image-provider.ts
10
10
  init_logger();
11
11
  const log = createLogger("ImageGen:OpenAICompat");
@@ -102,7 +102,7 @@ function pushAssistantReceipt(queue, text, runId, metadata) {
102
102
  message
103
103
  });
104
104
  queue.push({
105
- type: "message_update",
105
+ type: "assistant_snapshot",
106
106
  runId,
107
107
  message
108
108
  });
@@ -1,7 +1,8 @@
1
1
  import { getAgentDefaultImageGenerationModelConfig, init_schema } from "../../config/schema.js";
2
+ import { imageAssetFromDataUrl, imageFileExtensionForMimeType, mimeTypeFromFileName, parseImageDataUrl, sniffImageMimeType } from "../image/generation/image-assets.js";
3
+ import { persistToolMedia } from "./tool-media.js";
2
4
  import { applyImageGenerationModelConfigDefaults } from "../image/image-helpers.js";
3
5
  import { isFailoverError } from "../failover-error.js";
4
- import { imageAssetFromDataUrl, imageFileExtensionForMimeType, mimeTypeFromFileName, parseImageDataUrl, sniffImageMimeType } from "../image/generation/image-assets.js";
5
6
  import { getImageGenerationProvider } from "../image/generation/provider-registry.js";
6
7
  import { generateImage, listImageGenerationProvidersSummary } from "../image/generation/runtime.js";
7
8
  import path from "node:path";
@@ -174,7 +175,7 @@ function createImageGenerateTool(options) {
174
175
  return {
175
176
  name: "image_generate",
176
177
  label: "Image Generation",
177
- description: "Generate images with the configured image-generation model (default OpenAI). Use action=\"list\" to see providers. Saves files under workspace/media/generated/.",
178
+ description: "Generate and attach images with the configured image-generation model. Use action=\"list\" to see providers. Also saves files under workspace/media/generated/. Do not call send_media for generated images.",
178
179
  parameters: ImageGenerateToolSchema,
179
180
  async execute(_toolCallId, args) {
180
181
  const params = args;
@@ -254,7 +255,12 @@ function createImageGenerateTool(options) {
254
255
  ...filename ? { filenameHint: filename } : {}
255
256
  });
256
257
  const workspaceRelativePaths = paths.map((p) => path.relative(options.workspace, p).split(path.sep).join("/"));
257
- const lines = [`Generated ${paths.length} image(s) with ${result.provider}/${result.model}.`, ...paths.map((p) => `Saved: ${p}`)];
258
+ const media = await Promise.all(result.images.map((image, index) => persistToolMedia({
259
+ buffer: image.buffer,
260
+ filePath: paths[index],
261
+ mediaType: "photo"
262
+ })));
263
+ const lines = [`Generated and attached ${paths.length} image(s) with ${result.provider}/${result.model}.`, ...paths.map((p) => `Saved: ${p}`)];
258
264
  for (const note of buildNormalizationNotes(result.normalization, result.ignoredOverrides)) lines.push(note);
259
265
  return {
260
266
  content: [{
@@ -266,6 +272,7 @@ function createImageGenerateTool(options) {
266
272
  model: result.model,
267
273
  paths,
268
274
  workspaceRelativePaths,
275
+ media,
269
276
  attempts: result.attempts,
270
277
  ...result.normalization ? { normalization: result.normalization } : {},
271
278
  ...result.ignoredOverrides.length > 0 ? { ignoredOverrides: result.ignoredOverrides } : {},
@@ -1,5 +1,6 @@
1
1
  import { checkFileSafety } from "../prompt/safety.js";
2
2
  import { resolvePathUnderWorkspace } from "./tool-paths.js";
3
+ import { persistToolMedia } from "./tool-media.js";
3
4
  import { basename } from "node:path";
4
5
  import { readFile } from "fs/promises";
5
6
  import { Type } from "@sinclair/typebox";
@@ -14,61 +15,6 @@ const SendMediaSchema = Type.Object({
14
15
  }, { description: "Type of media to send (auto-detected if not specified)" })),
15
16
  caption: Type.Optional(Type.String({ description: "Caption for the media" }))
16
17
  });
17
- function detectMediaType(filePath) {
18
- const ext = filePath.split(".").pop()?.toLowerCase();
19
- const imageExts = [
20
- "jpg",
21
- "jpeg",
22
- "png",
23
- "gif",
24
- "webp",
25
- "bmp"
26
- ];
27
- const videoExts = [
28
- "mp4",
29
- "mov",
30
- "avi",
31
- "webm",
32
- "mkv"
33
- ];
34
- const audioExts = [
35
- "mp3",
36
- "wav",
37
- "ogg",
38
- "m4a",
39
- "flac"
40
- ];
41
- if (imageExts.includes(ext || "")) return "photo";
42
- if (videoExts.includes(ext || "")) return "video";
43
- if (audioExts.includes(ext || "")) return "audio";
44
- return "document";
45
- }
46
- function detectMimeType(filePath) {
47
- return {
48
- jpg: "image/jpeg",
49
- jpeg: "image/jpeg",
50
- png: "image/png",
51
- gif: "image/gif",
52
- webp: "image/webp",
53
- bmp: "image/bmp",
54
- svg: "image/svg+xml",
55
- mp4: "video/mp4",
56
- mov: "video/quicktime",
57
- avi: "video/x-msvideo",
58
- webm: "video/webm",
59
- mp3: "audio/mpeg",
60
- wav: "audio/wav",
61
- ogg: "audio/ogg",
62
- m4a: "audio/mp4",
63
- flac: "audio/flac",
64
- pdf: "application/pdf",
65
- doc: "application/msword",
66
- docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
67
- txt: "text/plain",
68
- zip: "application/zip",
69
- rar: "application/x-rar-compressed"
70
- }[filePath.split(".").pop()?.toLowerCase() || ""] || "application/octet-stream";
71
- }
72
18
  function createSendMediaTool(workspace, bus, getContext) {
73
19
  return {
74
20
  name: "send_media",
@@ -95,26 +41,30 @@ function createSendMediaTool(workspace, bus, getContext) {
95
41
  details: {}
96
42
  };
97
43
  try {
98
- const base64 = (await readFile(resolved)).toString("base64");
99
- const mimeType = detectMimeType(resolved);
100
- const mediaType = p.mediaType || detectMediaType(resolved);
101
- const dataUrl = `data:${mimeType};base64,${base64}`;
102
- const msg = {
103
- channel: ctx.channel,
104
- chat_id: ctx.chatId,
105
- content: p.caption || "",
106
- mediaUrl: dataUrl,
107
- mediaType
108
- };
109
- await bus.publishOutbound(msg);
44
+ const fileBuffer = await readFile(resolved);
45
+ const media = await persistToolMedia({
46
+ buffer: fileBuffer,
47
+ filePath: resolved,
48
+ ...p.mediaType ? { mediaType: p.mediaType } : {}
49
+ });
50
+ if (ctx.channel !== "webchat") {
51
+ const msg = {
52
+ channel: ctx.channel,
53
+ chat_id: ctx.chatId,
54
+ content: p.caption || "",
55
+ mediaUrl: `data:${media.mimeType};base64,${fileBuffer.toString("base64")}`,
56
+ mediaType: media.type
57
+ };
58
+ await bus.publishOutbound(msg);
59
+ }
110
60
  return {
111
61
  content: [{
112
62
  type: "text",
113
- text: `✅ Media sent: ${basename(resolved)} (${mediaType})`
63
+ text: `Media attached: ${basename(resolved)} (${media.type})`
114
64
  }],
115
65
  details: {
116
- filePath: resolved,
117
- mediaType
66
+ media: [media],
67
+ ...p.caption ? { caption: p.caption } : {}
118
68
  }
119
69
  };
120
70
  } catch (error) {
@@ -0,0 +1,9 @@
1
+ import type { MediaRef } from '../../media/types.js';
2
+ export type ToolMediaType = 'photo' | 'video' | 'audio' | 'document';
3
+ export declare function detectToolMediaMimeType(buffer: Buffer, filePath: string): string;
4
+ export declare function mediaTypeFromMimeType(mimeType: string): ToolMediaType;
5
+ export declare function persistToolMedia(params: {
6
+ buffer: Buffer;
7
+ filePath: string;
8
+ mediaType?: ToolMediaType;
9
+ }): Promise<MediaRef>;
@@ -0,0 +1,58 @@
1
+ import { sniffImageMimeType } from "../image/generation/image-assets.js";
2
+ import { mimeTypeFromMediaPath, saveMediaBuffer } from "../../media/store.js";
3
+ import { basename } from "node:path";
4
+ //#region src/agent/tools/tool-media.ts
5
+ const MAX_TOOL_MEDIA_BYTES = 50 * 1024 * 1024;
6
+ function hasImageMagic(buffer) {
7
+ if (buffer.length >= 8 && buffer.subarray(0, 8).equals(Buffer.from([
8
+ 137,
9
+ 80,
10
+ 78,
11
+ 71,
12
+ 13,
13
+ 10,
14
+ 26,
15
+ 10
16
+ ]))) return true;
17
+ if (buffer.length >= 3 && buffer.subarray(0, 3).equals(Buffer.from([
18
+ 255,
19
+ 216,
20
+ 255
21
+ ]))) return true;
22
+ if (buffer.length >= 4 && buffer.subarray(0, 4).toString("ascii") === "GIF8") return true;
23
+ return buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP";
24
+ }
25
+ function detectToolMediaMimeType(buffer, filePath) {
26
+ if (hasImageMagic(buffer)) return sniffImageMimeType(buffer).mimeType;
27
+ if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") return "application/pdf";
28
+ return mimeTypeFromMediaPath(filePath);
29
+ }
30
+ function mediaTypeFromMimeType(mimeType) {
31
+ if (mimeType === "image/svg+xml") return "document";
32
+ if (mimeType.startsWith("image/")) return "photo";
33
+ if (mimeType.startsWith("video/")) return "video";
34
+ if (mimeType.startsWith("audio/")) return "audio";
35
+ return "document";
36
+ }
37
+ async function persistToolMedia(params) {
38
+ const name = basename(params.filePath);
39
+ const mimeType = detectToolMediaMimeType(params.buffer, params.filePath);
40
+ const saved = await saveMediaBuffer(params.buffer, {
41
+ bucket: "outbound",
42
+ contentType: mimeType,
43
+ originalFilename: name,
44
+ maxBytes: MAX_TOOL_MEDIA_BYTES
45
+ });
46
+ return {
47
+ id: saved.id,
48
+ bucket: saved.bucket,
49
+ type: params.mediaType ?? mediaTypeFromMimeType(mimeType),
50
+ mimeType: saved.contentType,
51
+ name,
52
+ size: saved.size,
53
+ uri: saved.uri,
54
+ path: saved.path
55
+ };
56
+ }
57
+ //#endregion
58
+ export { detectToolMediaMimeType, mediaTypeFromMimeType, persistToolMedia };
@@ -1,6 +1,6 @@
1
+ import { init_agent_scope, normalizeAgentId, resolveAgentWorkspaceDir } from "../agent/agent-scope.js";
1
2
  import { resolveEffectiveAgentManifest } from "../agent-manifest/resolver.js";
2
3
  import "../agent-manifest/index.js";
3
- import { init_agent_scope, normalizeAgentId, resolveAgentWorkspaceDir } from "../agent/agent-scope.js";
4
4
  import { init_session_key, parseSessionKey } from "../routing/session-key.js";
5
5
  import { agentExists, getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
6
6
  //#region src/config/agent-profile.ts
@@ -7,7 +7,9 @@ export type RelayEvent = {
7
7
  type: string;
8
8
  } & Record<string, unknown>;
9
9
  export declare class AgentRunRelay {
10
+ private readonly maxEvents;
10
11
  private runs;
12
+ constructor(maxEvents?: number);
11
13
  ensureRun(runId: string, sessionKey: string): void;
12
14
  getSessionKey(runId: string): string | undefined;
13
15
  hasRun(runId: string): boolean;
@@ -5,8 +5,84 @@ init_logger();
5
5
  const log = createLogger("Gateway:AgentRunRelay");
6
6
  const COMPLETE_TTL_MS = 5 * 6e4;
7
7
  const MAX_EVENTS = 8e3;
8
+ const COMPACT_TO_RATIO = .75;
9
+ const MAX_MERGED_DELTA_CHARS = 16 * 1024;
10
+ const REPLAY_DELTA_TYPES = new Set([
11
+ "assistant_delta",
12
+ "thinking_delta",
13
+ "command_output_delta",
14
+ "review_delta"
15
+ ]);
16
+ function eventSeq(event) {
17
+ return typeof event.seq === "number" ? event.seq : void 0;
18
+ }
19
+ function findEventAtOrAfter(events, seq) {
20
+ let low = 0;
21
+ let high = events.length;
22
+ while (low < high) {
23
+ const middle = low + Math.floor((high - low) / 2);
24
+ if ((eventSeq(events[middle]) ?? Number.MAX_SAFE_INTEGER) < seq) low = middle + 1;
25
+ else high = middle;
26
+ }
27
+ return events[low];
28
+ }
29
+ function recordValue(value) {
30
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
31
+ }
32
+ function sameDeltaStream(left, right) {
33
+ if (left.type !== right.type || !REPLAY_DELTA_TYPES.has(left.type)) return false;
34
+ const leftPayload = recordValue(left.payload);
35
+ const rightPayload = recordValue(right.payload);
36
+ if (!leftPayload || !rightPayload) return false;
37
+ return [
38
+ "messageId",
39
+ "toolCallId",
40
+ "reviewId",
41
+ "stream"
42
+ ].every((key) => leftPayload[key] === rightPayload[key]);
43
+ }
44
+ function mergeReplayDelta(events, event) {
45
+ const previous = events.at(-1);
46
+ if (!previous || !sameDeltaStream(previous, event)) return false;
47
+ const previousPayload = recordValue(previous.payload);
48
+ const payload = recordValue(event.payload);
49
+ const previousDelta = previousPayload?.delta;
50
+ const delta = payload?.delta;
51
+ if (typeof previousDelta !== "string" || typeof delta !== "string") return false;
52
+ if (previousDelta.length + delta.length > MAX_MERGED_DELTA_CHARS) return false;
53
+ events[events.length - 1] = {
54
+ ...previous,
55
+ payload: {
56
+ ...previousPayload,
57
+ delta: previousDelta + delta
58
+ }
59
+ };
60
+ return true;
61
+ }
62
+ function compactEvents(events, targetSize) {
63
+ let remaining = Math.max(0, events.length - targetSize);
64
+ const retained = [];
65
+ const removedByType = {};
66
+ for (const event of events) {
67
+ if (remaining > 0 && REPLAY_DELTA_TYPES.has(event.type)) {
68
+ remaining -= 1;
69
+ removedByType[event.type] = (removedByType[event.type] ?? 0) + 1;
70
+ continue;
71
+ }
72
+ retained.push(event);
73
+ }
74
+ if (remaining > 0) for (const event of retained.splice(0, remaining)) removedByType[event.type] = (removedByType[event.type] ?? 0) + 1;
75
+ return {
76
+ events: retained,
77
+ removedByType
78
+ };
79
+ }
8
80
  var AgentRunRelay = class {
9
81
  runs = /* @__PURE__ */ new Map();
82
+ constructor(maxEvents = MAX_EVENTS) {
83
+ this.maxEvents = maxEvents;
84
+ if (!Number.isInteger(maxEvents) || maxEvents < 2) throw new Error("AgentRunRelay maxEvents must be an integer greater than 1");
85
+ }
10
86
  ensureRun(runId, sessionKey) {
11
87
  if (this.runs.has(runId)) return;
12
88
  this.runs.set(runId, {
@@ -14,7 +90,9 @@ var AgentRunRelay = class {
14
90
  events: [],
15
91
  nextSeq: 1,
16
92
  done: false,
17
- waiters: []
93
+ waiters: [],
94
+ subscriberCount: 0,
95
+ compactionWarned: false
18
96
  });
19
97
  }
20
98
  getSessionKey(runId) {
@@ -31,12 +109,22 @@ var AgentRunRelay = class {
31
109
  runId,
32
110
  seq: state.nextSeq++
33
111
  };
34
- if (state.events.length >= MAX_EVENTS) log.warn({
35
- runId,
36
- max: MAX_EVENTS,
37
- droppedType: relayed.type
38
- }, "Relay buffer full; dropping event (resume may miss tool_end/tokens)");
39
- else state.events.push(relayed);
112
+ const merged = state.subscriberCount === 0 && mergeReplayDelta(state.events, relayed);
113
+ if (!merged && state.events.length >= this.maxEvents) {
114
+ const targetSize = Math.max(1, Math.floor(this.maxEvents * COMPACT_TO_RATIO));
115
+ const compacted = compactEvents(state.events, targetSize);
116
+ state.events = compacted.events;
117
+ if (!state.compactionWarned) {
118
+ state.compactionWarned = true;
119
+ log.warn({
120
+ runId,
121
+ max: this.maxEvents,
122
+ retained: state.events.length,
123
+ removedByType: compacted.removedByType
124
+ }, "Relay replay buffer compacted; old delta events are no longer resumable");
125
+ }
126
+ }
127
+ if (!merged) state.events.push(relayed);
40
128
  const waiters = state.waiters.splice(0);
41
129
  for (const w of waiters) w();
42
130
  return relayed;
@@ -55,13 +143,25 @@ var AgentRunRelay = class {
55
143
  async *subscribe(runId) {
56
144
  const state = this.runs.get(runId);
57
145
  if (!state) throw new Error("UNKNOWN_RUN");
58
- let cursor = 0;
59
- while (true) {
60
- while (cursor < state.events.length) yield state.events[cursor++];
61
- if (state.done) break;
62
- await new Promise((resolve) => {
63
- state.waiters.push(resolve);
64
- });
146
+ state.subscriberCount += 1;
147
+ try {
148
+ let nextSeq = eventSeq(state.events[0]) ?? state.nextSeq;
149
+ while (true) {
150
+ const earliestSeq = eventSeq(state.events[0]);
151
+ if (earliestSeq !== void 0 && nextSeq < earliestSeq) nextSeq = earliestSeq;
152
+ const available = findEventAtOrAfter(state.events, nextSeq);
153
+ if (available) {
154
+ nextSeq = (eventSeq(available) ?? nextSeq) + 1;
155
+ yield available;
156
+ continue;
157
+ }
158
+ if (state.done) break;
159
+ await new Promise((resolve) => {
160
+ state.waiters.push(resolve);
161
+ });
162
+ }
163
+ } finally {
164
+ state.subscriberCount -= 1;
65
165
  }
66
166
  }
67
167
  };
@@ -1,5 +1,5 @@
1
- import { resolveEffectiveAgentManifest } from "../agent-manifest/resolver.js";
2
1
  import { init_agent_scope, listAgentEntries, normalizeAgentId, resolveAgentDir, resolveAgentProfileDir, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveUserPath, validateAgentIdForNewAgent } from "../agent/agent-scope.js";
2
+ import { resolveEffectiveAgentManifest } from "../agent-manifest/resolver.js";
3
3
  import { resolveEffectiveAgentProfile } from "../config/agent-profile.js";
4
4
  import { WORKSPACE_FILES, init_paths, resolveUserProfilePath } from "../config/paths.js";
5
5
  import { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES, REQUIRED_AGENT_PROFILE_MARKDOWN_FILE_SET } from "../agent/context/workspace.js";
@@ -14,7 +14,6 @@ export declare class ChatStreamMapper {
14
14
  private assistantIndex;
15
15
  private currentAssistantMessageId;
16
16
  private lastAssistantMessageId;
17
- private currentAssistantText;
18
17
  private currentAssistantReviewEmitted;
19
18
  private toolCallToMessageId;
20
19
  private turnDiffs;
@@ -30,6 +29,7 @@ export declare class ChatStreamMapper {
30
29
  error(message: string, code?: string): ChatStreamEvent[];
31
30
  private mapMessageStart;
32
31
  private mapMessageUpdate;
32
+ private mapAssistantSnapshot;
33
33
  private mapMessageEnd;
34
34
  private mapToolStart;
35
35
  private mapToolUpdate;
@@ -4,7 +4,6 @@ var ChatStreamMapper = class {
4
4
  assistantIndex = 0;
5
5
  currentAssistantMessageId;
6
6
  lastAssistantMessageId;
7
- currentAssistantText = "";
8
7
  currentAssistantReviewEmitted = false;
9
8
  toolCallToMessageId = /* @__PURE__ */ new Map();
10
9
  turnDiffs = [];
@@ -23,6 +22,7 @@ var ChatStreamMapper = class {
23
22
  case "agent_end": return [];
24
23
  case "message_start": return this.mapMessageStart(event.message);
25
24
  case "message_update": return this.mapMessageUpdate(event);
25
+ case "assistant_snapshot": return this.mapAssistantSnapshot(event.message);
26
26
  case "message_end": return this.mapMessageEnd(event.message);
27
27
  case "tool_execution_start": return this.mapToolStart(event);
28
28
  case "tool_execution_update": return this.mapToolUpdate(event);
@@ -44,7 +44,7 @@ var ChatStreamMapper = class {
44
44
  status: event.status === "error" ? "error" : "complete",
45
45
  ...typeof event.message === "string" && event.message ? { message: event.message } : {}
46
46
  })];
47
- case "user_message": return [this.make("user_message", { message: userMessageFromLegacyEvent(event) })];
47
+ case "user_message": return [this.make("user_message", { message: userMessageFromEvent(event) })];
48
48
  case "user_transcript": return [this.make("user_transcript", {
49
49
  text: String(event.text ?? ""),
50
50
  media: event.media
@@ -147,7 +147,6 @@ var ChatStreamMapper = class {
147
147
  if (raw?.role !== "assistant") return [];
148
148
  const messageId = this.nextAssistantMessageId();
149
149
  this.currentAssistantMessageId = messageId;
150
- this.currentAssistantText = "";
151
150
  this.currentAssistantReviewEmitted = false;
152
151
  return [this.make("assistant_message_start", { messageId })];
153
152
  }
@@ -155,39 +154,42 @@ var ChatStreamMapper = class {
155
154
  const message = event.message;
156
155
  if (message?.role !== "assistant") return [];
157
156
  const messageId = this.ensureAssistantMessageId();
157
+ const events = [];
158
158
  const review = extractReview(message);
159
159
  if (review && !this.currentAssistantReviewEmitted) {
160
160
  this.currentAssistantReviewEmitted = true;
161
- return [this.make("review", {
161
+ events.push(this.make("review", {
162
162
  messageId,
163
163
  review
164
- })];
164
+ }));
165
165
  }
166
166
  const delta = event.assistantMessageEvent;
167
- if (delta?.type === "text_delta" && typeof delta.delta === "string" && delta.delta) {
168
- const text = extractTextFromMessage(message);
169
- const suffixFromMessage = appendSuffix(this.currentAssistantText, text);
170
- const suffix = text ? suffixFromMessage : appendSuffix(this.currentAssistantText, delta.delta);
171
- if (!suffix) {
172
- if (text) this.currentAssistantText = text;
173
- return [];
174
- }
175
- this.currentAssistantText = text || `${this.currentAssistantText}${suffix}`;
176
- return [this.make("assistant_delta", {
167
+ if (delta?.type === "text_delta" && typeof delta.delta === "string" && delta.delta) events.push(this.make("assistant_delta", {
168
+ messageId,
169
+ delta: delta.delta
170
+ }));
171
+ if (delta?.type === "thinking_delta" && typeof delta.delta === "string" && delta.delta) events.push(this.make("thinking_delta", {
172
+ messageId,
173
+ delta: delta.delta
174
+ }));
175
+ return events;
176
+ }
177
+ mapAssistantSnapshot(raw) {
178
+ const message = raw;
179
+ if (message?.role !== "assistant") return [];
180
+ const messageId = this.ensureAssistantMessageId();
181
+ const review = extractReview(message);
182
+ if (review && !this.currentAssistantReviewEmitted) {
183
+ this.currentAssistantReviewEmitted = true;
184
+ return [this.make("review", {
177
185
  messageId,
178
- delta: suffix
186
+ review
179
187
  })];
180
188
  }
181
- if (delta?.type === "thinking_delta" && typeof delta.delta === "string" && delta.delta) return [this.make("thinking_delta", {
182
- messageId,
183
- delta: delta.delta
184
- })];
185
189
  const text = extractTextFromMessage(message);
186
- const suffix = appendSuffix(this.currentAssistantText, text);
187
- this.currentAssistantText = text || this.currentAssistantText;
188
- return suffix ? [this.make("assistant_delta", {
190
+ return text ? [this.make("assistant_delta", {
189
191
  messageId,
190
- delta: suffix
192
+ delta: text
191
193
  })] : [];
192
194
  }
193
195
  mapMessageEnd(raw) {
@@ -203,13 +205,6 @@ var ChatStreamMapper = class {
203
205
  review
204
206
  }));
205
207
  }
206
- const text = extractTextFromMessage(message);
207
- const suffix = appendSuffix(this.currentAssistantText, text);
208
- if (suffix) events.push(this.make("assistant_delta", {
209
- messageId,
210
- delta: suffix
211
- }));
212
- this.currentAssistantText = text || this.currentAssistantText;
213
208
  events.push(this.make("thinking_end", { messageId }));
214
209
  if (this.turnDiffs.length > 0) {
215
210
  events.push(this.make("turn_diff", {
@@ -230,7 +225,6 @@ var ChatStreamMapper = class {
230
225
  }));
231
226
  this.lastAssistantMessageId = messageId;
232
227
  this.currentAssistantMessageId = void 0;
233
- this.currentAssistantText = "";
234
228
  this.currentAssistantReviewEmitted = false;
235
229
  return events;
236
230
  }
@@ -368,7 +362,7 @@ var ChatStreamMapper = class {
368
362
  }
369
363
  }
370
364
  };
371
- function userMessageFromLegacyEvent(event) {
365
+ function userMessageFromEvent(event) {
372
366
  return {
373
367
  role: "user",
374
368
  content: event.content ?? [],
@@ -400,15 +394,6 @@ function extractText(value) {
400
394
  return rec.type === "text" && typeof rec.text === "string" ? rec.text : "";
401
395
  }).join("") || void 0;
402
396
  }
403
- function appendSuffix(base, next) {
404
- if (!next) return "";
405
- if (!base) return next;
406
- if (next === base || base.endsWith(next)) return "";
407
- if (next.startsWith(base)) return next.slice(base.length);
408
- const max = Math.min(base.length, next.length, 512);
409
- for (let overlap = max; overlap > 0; overlap--) if (base.slice(-overlap) === next.slice(0, overlap)) return next.slice(overlap);
410
- return next;
411
- }
412
397
  function extractUsage(message) {
413
398
  const usage = message.usage;
414
399
  if (!usage || typeof usage !== "object") return void 0;
@@ -0,0 +1,7 @@
1
+ import type { ChatStreamEvent } from './protocol.js';
2
+ type CoalescerOptions = {
3
+ windowMs?: number;
4
+ maxChars?: number;
5
+ };
6
+ export declare function coalesceThinkingDeltas(source: AsyncIterable<ChatStreamEvent>, options?: CoalescerOptions): AsyncGenerator<ChatStreamEvent>;
7
+ export {};