@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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/dashscope/src/image-generation-provider.js +1 -1
- package/dist/extensions/fal/src/image-generation-provider.js +1 -1
- package/dist/extensions/google/src/image-generation-provider.js +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{action-boundary-settings-panel-2p1f4-KA.js → action-boundary-settings-panel-CaRK2vIy.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BIl6X2b_.js → agent-avatar-dicebear-BeVf03hx.js} +1 -1
- package/dist/gateway/static/root/assets/{agents-bnkKH9EI.js → agents-P05C4A-3.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-CZxw161D.js → app-management-settings-panel-BG2ulMYw.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-CRXNlDhH.js → appearance-settings-0Kjenr2T.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-l4ripRNG.js → apps-page-DANGrBpN.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-CpVshjXu.js → archive-plugin-ChiNnH1e.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-M9WqqJnQ.js → attachment-load-pbBlQR-g.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-CYMsAsYf.js → automation-suggestion-card-CTZDOyDy.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-Kny7tLiF.js → automations-page-BXfGL_cm.js} +1 -1
- package/dist/gateway/static/root/assets/{binary-plugins-Cjn3zb4u.js → binary-plugins-BeZGzpGF.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-BPKdquaV.js → block-editor-BnDmhN5Z.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-settings-page-vr1tXAab.js → browser-settings-page-534luV0B.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-presets-api-Bzkcsyzn.js → capability-presets-api-Ii36j_7v.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-D4rH4Dax.js → capability-presets-settings-panel-Cl3dw4yh.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-Tlhbi5eh.js → channels-settings-M91zFJVi.js} +1 -1
- package/dist/gateway/static/root/assets/{connector-logo-ljPKdYKh.js → connector-logo-CiLxuZ8S.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-page-_XfvjXoY.js → connectors-page-CV5UulW6.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-DKUlAgm7.js → desktop-pet-Cil2eSpW.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-aWWzjdza.js → desktop-pet-settings-DwsC6l5F.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-Dmy1035j.js → directory-picker-path-field-CC2VL5Mc.js} +1 -1
- package/dist/gateway/static/root/assets/{dreaming-settings-tT4WkBkd.js → dreaming-settings-KY7ZfVsy.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CL79q8AH.js → extension-debug-page-C8KBeucs.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-hq80wkFI.js → extension-page-CV-5Kcyk.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BV-9GDfT.js → extension-settings-page-DvUtDrAu.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-CFqoN2NW.js → gateway-config-api-DXfVeJLc.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-DzR-kM91.js → gateway-settings-BpJ0v56l.js} +1 -1
- package/dist/gateway/static/root/assets/{goal-create-dialog-BLjHRtqe.js → goal-create-dialog-lMHD-2CX.js} +1 -1
- package/dist/gateway/static/root/assets/{goal-detail-page-BRxeujXh.js → goal-detail-page-yPmF-G7b.js} +1 -1
- package/dist/gateway/static/root/assets/{goals-config-api--qJ4qlRq.js → goals-config-api-P8VhLqYF.js} +1 -1
- package/dist/gateway/static/root/assets/{goals-page-BaZ3aw4u.js → goals-page-BHTCKobG.js} +1 -1
- package/dist/gateway/static/root/assets/{goals-settings-h7x9h5lb.js → goals-settings-DHXyaH_O.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-71IhhM3s.js → heartbeat-settings-CNckP_Dv.js} +1 -1
- package/dist/gateway/static/root/assets/index-BgZhEOkN.css +1 -0
- package/dist/gateway/static/root/assets/{index-EXa_i6BT.js → index-BuqGh2SW.js} +65 -65
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BZGmHvLu.js → keyboard-shortcuts-settings-jXmv1RVX.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-lUpd6lSo.js → local-app-workbench-page-zpp_wciE.js} +1 -1
- package/dist/gateway/static/root/assets/{local-apps-page-CiLqrMIh.js → local-apps-page-Cs5LKjtc.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-FPDTmcWe.js → logs-page-B4CRYqYR.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-split-r1HPWfCU.js → markdown-split-BFqeSNte.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-D74kd3hn.js → media-plugins-Cc8379gq.js} +1 -1
- package/dist/gateway/static/root/assets/{models-hub-panel-BXrdePhv.js → models-hub-panel-1hdoAoB_.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-Dgu02514.js → notes-page-DlGU4ipo.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-workbench-D-EAhXv1.js → notes-workbench-DQEAZTA5.js} +2 -2
- package/dist/gateway/static/root/assets/{page-tabs-hinRdDy3.js → page-tabs-kFOswGlG.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-C7fw3oGy.js → preview-open-alternatives-CvjuvABg.js} +1 -1
- package/dist/gateway/static/root/assets/{project-detail-page-BjpB1wrp.js → project-detail-page-Bp9bC7A6.js} +1 -1
- package/dist/gateway/static/root/assets/{projects-page-BxyjqOfC.js → projects-page-RNorFXTw.js} +1 -1
- package/dist/gateway/static/root/assets/{remote-access-hub-BFv6Upke.js → remote-access-hub-BqRTcM_6.js} +1 -1
- package/dist/gateway/static/root/assets/{save-bar-controls-C3aU8-vO.js → save-bar-controls-DwrD7ODx.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-DjegExFD.js → schedule-field-Ba4WzTmE.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-DLubnsql.js → schema-form-DvydL8eF.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-BX4DH8J7.js → sessions-page-B2EfW1cm.js} +1 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-CklL85T4.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-3syQHsNR.js → settings-collapsible-section-LaQqGGej.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-D4DCZzKg.js → settings-form-section-DruI__qO.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-c5K85ylS.js → settings-loading-skeleton-UvA-eDhF.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-DNiDlaRq.js → settings-page-5oCGSebm.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-iTRY1Hy-.js → settings-page-layout-DG-XMIPu.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-BH4xQKM9.js → setup-status-panel-D3my1HgB.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-B88Afrhe.js → share-preview-page-FLVeTrRt.js} +1 -1
- package/dist/gateway/static/root/assets/{shares-settings-B6kKkvOb.js → shares-settings-DVxgjrsP.js} +1 -1
- package/dist/gateway/static/root/assets/{skill-reload-api-DtjS-nUK.js → skill-reload-api-Bcu58ZaF.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-CQEoPv0u.js → skills-page-CDd-84FE.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-DtpTq837.js → system-settings-panel-CeK93xVi.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-ndEnUP3b.js → typed-models-lib-D1Lr2gG0.js} +1 -1
- package/dist/gateway/static/root/assets/{user-context-page-Dn2T3itn.js → user-context-page-1OyJnTyR.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-wtWc3SfN.js → voice-api-key-field-B2ncRwHS.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-page-Ckxhj66e.js → work-discovery-page-BZlY4PZL.js} +1 -1
- package/dist/gateway/static/root/assets/{work-item-detail-page-_1QgGPNw.js → work-item-detail-page-DPXMrneY.js} +1 -1
- package/dist/gateway/static/root/assets/{work-page-BPdf2iuv.js → work-page-CTERoqyv.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-ukmpkj66.js → workflow-run-setup-panel-Bynqe4kV.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-DdKTx0Hs.js → workflows-page-CLgsnqFQ.js} +1 -1
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/embedded/session-tool-result-guard.js +14 -0
- package/dist/src/agent/image/generation/openai-compatible-image-provider.js +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +1 -1
- package/dist/src/agent/tools/image-generate-tool.js +10 -3
- package/dist/src/agent/tools/send-media.js +20 -70
- package/dist/src/agent/tools/tool-media.d.ts +9 -0
- package/dist/src/agent/tools/tool-media.js +58 -0
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/gateway/agent-run-relay.d.ts +2 -0
- package/dist/src/gateway/agent-run-relay.js +114 -14
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/chat-stream/mapper.d.ts +1 -1
- package/dist/src/gateway/chat-stream/mapper.js +27 -42
- package/dist/src/gateway/chat-stream/thinking-delta-coalescer.d.ts +7 -0
- package/dist/src/gateway/chat-stream/thinking-delta-coalescer.js +103 -0
- package/dist/src/gateway/hono/sse.d.ts +1 -1
- package/dist/src/gateway/hono/sse.js +1 -1
- package/dist/src/gateway/service/run-gateway-agent.js +5 -1
- package/dist/src/media/session-references.js +9 -6
- package/dist/src/session/client-history.d.ts +1 -0
- package/dist/src/session/client-history.js +56 -12
- package/dist/src/session/types.d.ts +1 -0
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/index-gpwk8Nro.css +0 -1
- 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-
|
|
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-
|
|
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
|
@@ -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");
|
|
@@ -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
|
|
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
|
|
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
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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:
|
|
63
|
+
text: `Media attached: ${basename(resolved)} (${media.type})`
|
|
114
64
|
}],
|
|
115
65
|
details: {
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
max
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 {};
|