@aexol/spectral 0.9.13 → 0.9.14
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/extensions/spectral-vision-fallback.d.ts +10 -15
- package/dist/extensions/spectral-vision-fallback.d.ts.map +1 -1
- package/dist/extensions/spectral-vision-fallback.js +231 -134
- package/dist/sdk/agent-core/harness/compaction/utils.d.ts.map +1 -1
- package/dist/sdk/agent-core/harness/compaction/utils.js +31 -2
- package/dist/sdk/coding-agent/core/compaction/utils.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/utils.js +39 -2
- package/dist/server/agent-bridge.d.ts +13 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +25 -3
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +13 -6
- package/dist/server/storage.d.ts +10 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +25 -0
- package/package.json +1 -1
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Spectral Vision Extension
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* when images are attached. This allows non-vision models to "see" images
|
|
6
|
-
* and saves main agent context by using a potentially smaller/cheaper
|
|
7
|
-
* vision model for image understanding.
|
|
4
|
+
* Converts attached images into text analysis before the main agent call.
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
* - If the main model
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Hooks into the `context` event to intercept ALL images before they reach
|
|
19
|
-
* the LLM (covers user-attached images and tool-result images alike).
|
|
6
|
+
* Pipeline:
|
|
7
|
+
* - If the current main model can process images, use it as the analyzer.
|
|
8
|
+
* - Otherwise use the admin-configured default vision model.
|
|
9
|
+
* - Otherwise use the best available authenticated image-capable model.
|
|
10
|
+
* - Replace image blocks with the text analysis before persistence when
|
|
11
|
+
* possible (`message_end`), so image data is deleted from conversation
|
|
12
|
+
* history and is not re-analyzed on later turns.
|
|
13
|
+
* - Keep a `context` hook as a defense-in-depth fallback for older sessions
|
|
14
|
+
* or messages that entered context before the replacement hook existed.
|
|
20
15
|
*/
|
|
21
16
|
import type { ExtensionAPI } from "../sdk/coding-agent/index.js";
|
|
22
17
|
export default function spectralVisionExtension(ext: ExtensionAPI): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spectral-vision-fallback.d.ts","sourceRoot":"","sources":["../../src/extensions/spectral-vision-fallback.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"spectral-vision-fallback.d.ts","sourceRoot":"","sources":["../../src/extensions/spectral-vision-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAsWjE,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CA6DvE"}
|
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Spectral Vision Extension
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* when images are attached. This allows non-vision models to "see" images
|
|
6
|
-
* and saves main agent context by using a potentially smaller/cheaper
|
|
7
|
-
* vision model for image understanding.
|
|
4
|
+
* Converts attached images into text analysis before the main agent call.
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
* - If the main model
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Hooks into the `context` event to intercept ALL images before they reach
|
|
19
|
-
* the LLM (covers user-attached images and tool-result images alike).
|
|
6
|
+
* Pipeline:
|
|
7
|
+
* - If the current main model can process images, use it as the analyzer.
|
|
8
|
+
* - Otherwise use the admin-configured default vision model.
|
|
9
|
+
* - Otherwise use the best available authenticated image-capable model.
|
|
10
|
+
* - Replace image blocks with the text analysis before persistence when
|
|
11
|
+
* possible (`message_end`), so image data is deleted from conversation
|
|
12
|
+
* history and is not re-analyzed on later turns.
|
|
13
|
+
* - Keep a `context` hook as a defense-in-depth fallback for older sessions
|
|
14
|
+
* or messages that entered context before the replacement hook existed.
|
|
20
15
|
*/
|
|
21
16
|
import { streamSimple } from "../sdk/ai/index.js";
|
|
22
17
|
// ---------------------------------------------------------------------------
|
|
23
18
|
// Helpers
|
|
24
19
|
// ---------------------------------------------------------------------------
|
|
20
|
+
const EXTENSION_NAME = "spectral-vision";
|
|
21
|
+
const IMAGE_DESCRIPTION_PATTERN = /^\[Image analysis from /;
|
|
22
|
+
const MAX_CACHE_ENTRIES = 500;
|
|
23
|
+
function withImageInput(model) {
|
|
24
|
+
if (model.input.includes("image"))
|
|
25
|
+
return model;
|
|
26
|
+
return {
|
|
27
|
+
...model,
|
|
28
|
+
input: [...model.input, "image"],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function sameModel(a, b) {
|
|
32
|
+
return !!a && !!b && a.provider === b.provider && a.id === b.id;
|
|
33
|
+
}
|
|
34
|
+
function getCurrentImageAnalyzer(ctx) {
|
|
35
|
+
const currentModel = ctx.model;
|
|
36
|
+
if (!currentModel)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (!ctx.modelRegistry.hasConfiguredAuth(currentModel))
|
|
39
|
+
return undefined;
|
|
40
|
+
if (currentModel.input.includes("image")) {
|
|
41
|
+
return { model: withImageInput(currentModel), source: "main" };
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
25
45
|
/**
|
|
26
46
|
* Check if the SettingsManager has an admin-configured default vision model
|
|
27
47
|
* with working auth. Returns the model if found and auth-ready, or undefined.
|
|
@@ -36,75 +56,126 @@ function getAdminVisionModel(ctx) {
|
|
|
36
56
|
return undefined;
|
|
37
57
|
const allModels = ctx.modelRegistry.getAll();
|
|
38
58
|
const match = allModels.find((m) => m.provider === defaultVisionProvider && m.id === defaultVisionModel);
|
|
39
|
-
if (!match
|
|
59
|
+
if (!match)
|
|
40
60
|
return undefined;
|
|
41
61
|
if (!ctx.modelRegistry.hasConfiguredAuth(match))
|
|
42
62
|
return undefined;
|
|
43
|
-
|
|
63
|
+
// Trust the admin's explicit choice: if they marked this model as the
|
|
64
|
+
// default vision model, it should be used even when the backend's
|
|
65
|
+
// capabilities data doesn't report image support.
|
|
66
|
+
return withImageInput(match);
|
|
44
67
|
}
|
|
45
|
-
|
|
46
|
-
* Resolve the best vision model to use for image descriptions.
|
|
47
|
-
*
|
|
48
|
-
* Priority:
|
|
49
|
-
* 1. Current main model if it supports images — passthrough, no interception
|
|
50
|
-
* 2. Admin-configured default vision model (isVisionDefault from backend)
|
|
51
|
-
* 3. Best available vision model by provider priority
|
|
52
|
-
* 4. First available vision model
|
|
53
|
-
*/
|
|
54
|
-
function resolveVisionModel(ctx) {
|
|
55
|
-
// 1. If the main model supports vision natively, skip interception entirely
|
|
56
|
-
// and let the main model process images directly.
|
|
57
|
-
const currentModel = ctx.model;
|
|
58
|
-
if (currentModel?.input.includes("image") && ctx.modelRegistry.hasConfiguredAuth(currentModel)) {
|
|
59
|
-
process.stderr.write(`[spectral-vision] Main model supports vision — passing images through natively (${currentModel.provider}/${currentModel.id})\n`);
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
// 2. Admin-configured default vision model (fallback for non-vision main models)
|
|
68
|
+
function getFallbackVisionModel(ctx, exclude) {
|
|
63
69
|
const adminModel = getAdminVisionModel(ctx);
|
|
64
|
-
if (adminModel) {
|
|
65
|
-
|
|
66
|
-
return adminModel;
|
|
70
|
+
if (adminModel && !sameModel(adminModel, exclude)) {
|
|
71
|
+
return { model: adminModel, source: "default" };
|
|
67
72
|
}
|
|
68
|
-
// 3. Fall back to best available vision model by provider priority
|
|
69
73
|
const allModels = ctx.modelRegistry.getAll();
|
|
70
|
-
const visionModels = allModels.filter((m) => m.input.includes("image") &&
|
|
74
|
+
const visionModels = allModels.filter((m) => m.input.includes("image") &&
|
|
75
|
+
ctx.modelRegistry.hasConfiguredAuth(m) &&
|
|
76
|
+
!sameModel(m, exclude));
|
|
71
77
|
if (visionModels.length === 0)
|
|
72
78
|
return undefined;
|
|
73
79
|
const providerPriority = ["anthropic", "openai", "google", "openrouter"];
|
|
74
80
|
for (const provider of providerPriority) {
|
|
75
81
|
const match = visionModels.find((m) => m.provider === provider);
|
|
76
|
-
if (match)
|
|
77
|
-
|
|
78
|
-
return match;
|
|
79
|
-
}
|
|
82
|
+
if (match)
|
|
83
|
+
return { model: match, source: "fallback" };
|
|
80
84
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
return { model: visionModels[0], source: "fallback" };
|
|
86
|
+
}
|
|
87
|
+
function resolvePrimaryAnalyzer(ctx) {
|
|
88
|
+
const currentAnalyzer = getCurrentImageAnalyzer(ctx);
|
|
89
|
+
if (currentAnalyzer) {
|
|
90
|
+
process.stderr.write(`[${EXTENSION_NAME}] Using main model for image analysis: ${currentAnalyzer.model.provider}/${currentAnalyzer.model.id}\n`);
|
|
91
|
+
return currentAnalyzer;
|
|
92
|
+
}
|
|
93
|
+
const fallback = getFallbackVisionModel(ctx);
|
|
94
|
+
if (fallback) {
|
|
95
|
+
const label = fallback.source === "default" ? "admin-configured default" : "fallback vision model";
|
|
96
|
+
process.stderr.write(`[${EXTENSION_NAME}] Using ${label}: ${fallback.model.provider}/${fallback.model.id}\n`);
|
|
97
|
+
return fallback;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
85
100
|
}
|
|
86
101
|
/** Check if any content block in an array is an image */
|
|
87
102
|
function hasImageContent(content) {
|
|
88
103
|
return Array.isArray(content) && content.some((c) => c?.type === "image");
|
|
89
104
|
}
|
|
105
|
+
function removeImageContent(content) {
|
|
106
|
+
return content.filter((block) => block.type === "text");
|
|
107
|
+
}
|
|
90
108
|
/** Count images in a message */
|
|
91
109
|
function countImages(msg) {
|
|
92
110
|
if (!Array.isArray(msg.content))
|
|
93
111
|
return 0;
|
|
94
112
|
return msg.content.filter((c) => c.type === "image").length;
|
|
95
113
|
}
|
|
114
|
+
function isProcessableImageMessage(msg) {
|
|
115
|
+
return (msg.role === "user" || msg.role === "toolResult") && Array.isArray(msg.content) && hasImageContent(msg.content);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if a message has already been processed by our vision fallback.
|
|
119
|
+
*/
|
|
120
|
+
function isAlreadyDescribed(msg) {
|
|
121
|
+
if (!Array.isArray(msg.content))
|
|
122
|
+
return false;
|
|
123
|
+
return msg.content.some((c) => c.type === "text" && IMAGE_DESCRIPTION_PATTERN.test(String(c.text)));
|
|
124
|
+
}
|
|
125
|
+
function contextTextFor(content) {
|
|
126
|
+
return content
|
|
127
|
+
.filter((c) => c.type === "text")
|
|
128
|
+
.map((c) => c.text)
|
|
129
|
+
.join(" ")
|
|
130
|
+
.substring(0, 500);
|
|
131
|
+
}
|
|
132
|
+
function imageMessageCacheKey(msg) {
|
|
133
|
+
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
134
|
+
const text = content
|
|
135
|
+
.filter((c) => c.type === "text")
|
|
136
|
+
.map((c) => String(c.text))
|
|
137
|
+
.join("\n")
|
|
138
|
+
.slice(0, 1000);
|
|
139
|
+
const images = content
|
|
140
|
+
.filter((c) => c.type === "image")
|
|
141
|
+
.map((c) => `${c.mimeType ?? "image"}:${String(c.data ?? "").length}:${String(c.data ?? "").slice(0, 64)}`)
|
|
142
|
+
.join("|");
|
|
143
|
+
return `${msg.role}:${msg.timestamp ?? "no-ts"}:${text}:${images}`;
|
|
144
|
+
}
|
|
145
|
+
function rememberAnalysis(cache, key, content) {
|
|
146
|
+
cache.set(key, content);
|
|
147
|
+
if (cache.size <= MAX_CACHE_ENTRIES)
|
|
148
|
+
return;
|
|
149
|
+
const first = cache.keys().next().value;
|
|
150
|
+
if (first)
|
|
151
|
+
cache.delete(first);
|
|
152
|
+
}
|
|
153
|
+
function omittedContent(msg, reason) {
|
|
154
|
+
const textBlocks = Array.isArray(msg.content)
|
|
155
|
+
? msg.content.filter((c) => c.type === "text")
|
|
156
|
+
: [];
|
|
157
|
+
const imageCount = countImages(msg);
|
|
158
|
+
return [
|
|
159
|
+
...textBlocks,
|
|
160
|
+
{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `[${imageCount} image(s) omitted — ${reason}]`,
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
}
|
|
96
166
|
// ---------------------------------------------------------------------------
|
|
97
167
|
// Core: call vision model to describe images
|
|
98
168
|
// ---------------------------------------------------------------------------
|
|
99
169
|
async function describeImages(visionModel, content, contextText, ctx) {
|
|
170
|
+
const model = withImageInput(visionModel);
|
|
100
171
|
const userContent = [
|
|
101
172
|
{
|
|
102
173
|
type: "text",
|
|
103
|
-
text: `Please
|
|
174
|
+
text: `Please analyze the following image(s). ` +
|
|
104
175
|
(contextText ? `Context: ${contextText}. ` : "") +
|
|
105
176
|
"Focus on what is visually visible — text content, UI elements, diagrams, " +
|
|
106
|
-
"code structure, layout, colors,
|
|
107
|
-
"If multiple images are provided,
|
|
177
|
+
"code structure, layout, colors, errors, and any details needed to answer the user's request. " +
|
|
178
|
+
"Be concise but thorough. If multiple images are provided, analyze each one separately with a heading.",
|
|
108
179
|
},
|
|
109
180
|
];
|
|
110
181
|
const textPrefixBlocks = [];
|
|
@@ -120,115 +191,141 @@ async function describeImages(visionModel, content, contextText, ctx) {
|
|
|
120
191
|
}
|
|
121
192
|
if (imageCount === 0)
|
|
122
193
|
return content;
|
|
123
|
-
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(
|
|
194
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
124
195
|
if (!auth.ok) {
|
|
125
|
-
|
|
196
|
+
throw new Error(`No API key for vision model ${model.provider}/${model.id}: ${auth.error}`);
|
|
197
|
+
}
|
|
198
|
+
const result = await streamSimple(model, {
|
|
199
|
+
systemPrompt: "You are an image analysis assistant. Analyze images accurately and concisely. Return only the visual analysis, not commentary about being an assistant.",
|
|
200
|
+
messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
|
|
201
|
+
tools: [],
|
|
202
|
+
}, {
|
|
203
|
+
apiKey: auth.apiKey,
|
|
204
|
+
headers: auth.headers,
|
|
205
|
+
});
|
|
206
|
+
let description = "";
|
|
207
|
+
for await (const event of result) {
|
|
208
|
+
if (event.type === "text_delta") {
|
|
209
|
+
description += event.delta;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const textNote = description.trim()
|
|
213
|
+
? `[Image analysis from ${model.provider}/${model.id} (${imageCount} image(s)):\n${description.trim()}\n]`
|
|
214
|
+
: `[${imageCount} image(s) — vision model returned empty analysis]`;
|
|
215
|
+
try {
|
|
216
|
+
ctx.ui.notify(`[${EXTENSION_NAME}] Analyzed ${imageCount} image(s) with ${model.provider}/${model.id}`, "info");
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// UI context may not be available in headless modes.
|
|
220
|
+
}
|
|
221
|
+
return [
|
|
222
|
+
...textPrefixBlocks,
|
|
223
|
+
{ type: "text", text: textNote },
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
async function analyzeMessageImages(msg, ctx, cache) {
|
|
227
|
+
const key = imageMessageCacheKey(msg);
|
|
228
|
+
const cached = cache.get(key);
|
|
229
|
+
if (cached)
|
|
230
|
+
return cached;
|
|
231
|
+
if (isAlreadyDescribed(msg)) {
|
|
232
|
+
const withoutImages = removeImageContent(msg.content);
|
|
233
|
+
rememberAnalysis(cache, key, withoutImages);
|
|
234
|
+
return withoutImages;
|
|
235
|
+
}
|
|
236
|
+
const primary = resolvePrimaryAnalyzer(ctx);
|
|
237
|
+
if (!primary) {
|
|
238
|
+
const content = omittedContent(msg, "no authenticated vision-capable model is configured");
|
|
239
|
+
rememberAnalysis(cache, key, content);
|
|
126
240
|
return content;
|
|
127
241
|
}
|
|
242
|
+
const contextText = contextTextFor(msg.content);
|
|
243
|
+
const imageCount = countImages(msg);
|
|
128
244
|
try {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}, {
|
|
134
|
-
apiKey: auth.apiKey,
|
|
135
|
-
headers: auth.headers,
|
|
136
|
-
});
|
|
137
|
-
let description = "";
|
|
138
|
-
for await (const event of result) {
|
|
139
|
-
if (event.type === "text_delta") {
|
|
140
|
-
description += event.delta;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
const textNote = description.trim()
|
|
144
|
-
? `[Image description from ${visionModel.provider}/${visionModel.id} (${imageCount} image(s)):\n${description}\n]`
|
|
145
|
-
: `[${imageCount} image(s) — vision model returned empty description]`;
|
|
146
|
-
// Notify the frontend that the vision extension analyzed the image(s)
|
|
147
|
-
try {
|
|
148
|
-
ctx.ui.notify(`[spectral-vision] Analyzed ${imageCount} image(s) with ${visionModel.provider}/${visionModel.id}`, "info");
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// UI context may not be available in headless modes
|
|
152
|
-
}
|
|
153
|
-
return [
|
|
154
|
-
...textPrefixBlocks,
|
|
155
|
-
{ type: "text", text: textNote },
|
|
156
|
-
];
|
|
245
|
+
const described = await describeImages(primary.model, msg.content, contextText, ctx);
|
|
246
|
+
const withoutImages = removeImageContent(described);
|
|
247
|
+
rememberAnalysis(cache, key, withoutImages);
|
|
248
|
+
return withoutImages;
|
|
157
249
|
}
|
|
158
250
|
catch (err) {
|
|
159
|
-
const
|
|
160
|
-
process.stderr.write(`[
|
|
161
|
-
|
|
251
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
252
|
+
process.stderr.write(`[${EXTENSION_NAME}] Image analysis with ${primary.model.provider}/${primary.model.id} failed: ${message}\n`);
|
|
253
|
+
const fallback = primary.source === "main" ? getFallbackVisionModel(ctx, primary.model) : undefined;
|
|
254
|
+
if (fallback) {
|
|
255
|
+
process.stderr.write(`[${EXTENSION_NAME}] Retrying image analysis with ${fallback.model.provider}/${fallback.model.id}\n`);
|
|
256
|
+
try {
|
|
257
|
+
const described = await describeImages(fallback.model, msg.content, contextText, ctx);
|
|
258
|
+
const withoutImages = removeImageContent(described);
|
|
259
|
+
rememberAnalysis(cache, key, withoutImages);
|
|
260
|
+
return withoutImages;
|
|
261
|
+
}
|
|
262
|
+
catch (fallbackErr) {
|
|
263
|
+
const fallbackMessage = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
|
|
264
|
+
process.stderr.write(`[${EXTENSION_NAME}] Fallback image analysis with ${fallback.model.provider}/${fallback.model.id} failed: ${fallbackMessage}\n`);
|
|
265
|
+
try {
|
|
266
|
+
ctx.ui.notify(`[${EXTENSION_NAME}] Failed to analyze ${imageCount} image(s): ${fallbackMessage}`, "warning");
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// UI context may not be available in headless modes.
|
|
270
|
+
}
|
|
271
|
+
const content = omittedContent(msg, `vision analysis failed: ${fallbackMessage}`);
|
|
272
|
+
rememberAnalysis(cache, key, content);
|
|
273
|
+
return content;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
162
276
|
try {
|
|
163
|
-
ctx.ui.notify(`[
|
|
277
|
+
ctx.ui.notify(`[${EXTENSION_NAME}] Failed to analyze ${imageCount} image(s): ${message}`, "warning");
|
|
164
278
|
}
|
|
165
279
|
catch {
|
|
166
|
-
// UI context may not be available in headless modes
|
|
280
|
+
// UI context may not be available in headless modes.
|
|
167
281
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
type: "text",
|
|
172
|
-
text: `[${imageCount} image(s) omitted — vision fallback failed: ${msg}]`,
|
|
173
|
-
},
|
|
174
|
-
];
|
|
282
|
+
const content = omittedContent(msg, `vision analysis failed: ${message}`);
|
|
283
|
+
rememberAnalysis(cache, key, content);
|
|
284
|
+
return content;
|
|
175
285
|
}
|
|
176
286
|
}
|
|
177
287
|
// ---------------------------------------------------------------------------
|
|
178
288
|
// Extension entry point
|
|
179
289
|
// ---------------------------------------------------------------------------
|
|
180
290
|
export default function spectralVisionExtension(ext) {
|
|
181
|
-
|
|
291
|
+
const analysisCache = new Map();
|
|
182
292
|
ext.on("session_start", (_event, ctx) => {
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
else if (ctx.model?.input.includes("image")) {
|
|
188
|
-
process.stderr.write(`[spectral-vision] Ready — main model ${ctx.model.provider}/${ctx.model.id} handles vision natively, passthrough enabled.\n`);
|
|
293
|
+
const analyzer = resolvePrimaryAnalyzer(ctx);
|
|
294
|
+
if (analyzer) {
|
|
295
|
+
const label = analyzer.source === "main" ? "main model" : analyzer.source === "default" ? "default vision model" : "fallback vision model";
|
|
296
|
+
process.stderr.write(`[${EXTENSION_NAME}] Ready — ${label} ${analyzer.model.provider}/${analyzer.model.id} will analyze images before agent calls.\n`);
|
|
189
297
|
}
|
|
190
298
|
else {
|
|
191
|
-
process.stderr.write(
|
|
299
|
+
process.stderr.write(`[${EXTENSION_NAME}] No vision model with auth configured. Images will be removed with an explanatory note.\n`);
|
|
192
300
|
}
|
|
193
301
|
});
|
|
302
|
+
// Primary path: replace user/tool-result image blocks before AgentSession
|
|
303
|
+
// persists the message. This deletes raw base64 image data from conversation
|
|
304
|
+
// history and prevents repeat analysis on later turns.
|
|
305
|
+
ext.on("message_end", async (event, ctx) => {
|
|
306
|
+
const msg = event.message;
|
|
307
|
+
if (!isProcessableImageMessage(msg))
|
|
308
|
+
return;
|
|
309
|
+
process.stderr.write(`[${EXTENSION_NAME}] Analyzing ${countImages(msg)} image(s) before persisting ${msg.role} message...\n`);
|
|
310
|
+
const content = await analyzeMessageImages(msg, ctx, analysisCache);
|
|
311
|
+
return { message: { ...msg, content } };
|
|
312
|
+
});
|
|
313
|
+
// Defense-in-depth path: handle images still present in context from older
|
|
314
|
+
// sessions or from messages that bypassed message_end replacement. Results are
|
|
315
|
+
// cached by message/image fingerprint so old images are not re-analyzed every
|
|
316
|
+
// turn; cached analysis is substituted instead.
|
|
194
317
|
ext.on("context", async (event, ctx) => {
|
|
195
318
|
const messages = event.messages;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
for (const msg of messages) {
|
|
199
|
-
totalImages += countImages(msg);
|
|
200
|
-
}
|
|
201
|
-
if (totalImages === 0)
|
|
319
|
+
const imageMessages = messages.filter(isProcessableImageMessage);
|
|
320
|
+
if (imageMessages.length === 0)
|
|
202
321
|
return;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// returns undefined and we pass images through without interception.
|
|
206
|
-
visionModel = resolveVisionModel(ctx);
|
|
207
|
-
if (!visionModel) {
|
|
208
|
-
// Check if we're in passthrough mode (main model has native vision)
|
|
209
|
-
if (ctx.model?.input.includes("image")) {
|
|
210
|
-
process.stderr.write(`[spectral-vision] Passthrough — ${totalImages} image(s) flowing to main model natively\n`);
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
process.stderr.write("[spectral-vision] No vision model available, images will be omitted\n");
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
process.stderr.write(`[spectral-vision] Describing ${totalImages} image(s) with ${visionModel.provider}/${visionModel.id}...\n`);
|
|
218
|
-
// Process each message — intercept both user messages and tool results
|
|
219
|
-
// that contain images (e.g. read tool returning a screenshot).
|
|
322
|
+
const totalImages = imageMessages.reduce((sum, msg) => sum + countImages(msg), 0);
|
|
323
|
+
process.stderr.write(`[${EXTENSION_NAME}] Context contains ${totalImages} image(s); replacing with analysis text before provider request...\n`);
|
|
220
324
|
const processed = await Promise.all(messages.map(async (msg) => {
|
|
221
|
-
if (
|
|
325
|
+
if (!isProcessableImageMessage(msg))
|
|
222
326
|
return msg;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const textBlocks = msg.content
|
|
226
|
-
.filter((c) => c.type === "text")
|
|
227
|
-
.map((c) => c.text)
|
|
228
|
-
.join(" ");
|
|
229
|
-
const contextText = textBlocks.substring(0, 500); // limit context
|
|
230
|
-
const described = await describeImages(visionModel, msg.content, contextText, ctx);
|
|
231
|
-
return { ...msg, content: described };
|
|
327
|
+
const content = await analyzeMessageImages(msg, ctx, analysisCache);
|
|
328
|
+
return { ...msg, content };
|
|
232
329
|
}));
|
|
233
330
|
return { messages: processed };
|
|
234
331
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/agent-core/harness/compaction/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,+CAA+C;IAC/C,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,mDAAmD;IACnD,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,yCAAyC;IACzC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,kDAAkD;AAClD,wBAAgB,aAAa,IAAI,cAAc,CAM9C;AAED,uEAAuE;AACvE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CA2B9F;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAK1G;AAED,kDAAkD;AAClD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAUzF;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/agent-core/harness/compaction/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,+CAA+C;IAC/C,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,mDAAmD;IACnD,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,yCAAyC;IACzC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,kDAAkD;AAClD,wBAAgB,aAAa,IAAI,cAAc,CAM9C;AAED,uEAAuE;AACvE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CA2B9F;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAK1G;AAED,kDAAkD;AAClD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAUzF;AAiDD,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAuDjE"}
|
|
@@ -59,6 +59,33 @@ export function formatFileOperations(readFiles, modifiedFiles) {
|
|
|
59
59
|
return `\n\n${sections.join("\n\n")}`;
|
|
60
60
|
}
|
|
61
61
|
const TOOL_RESULT_MAX_CHARS = 2000;
|
|
62
|
+
const VISION_DESCRIPTION_PREFIX = "[Image description from ";
|
|
63
|
+
const VISION_EMPTY_PREFIX_RE = /^\[\d+ image\(s\) — vision model returned empty description\]$/m;
|
|
64
|
+
const VISION_FAILED_PREFIX_RE = /^\[\d+ image\(s\) omitted — vision fallback failed: .*\]$/m;
|
|
65
|
+
function stripVisionDescriptionBlocks(text) {
|
|
66
|
+
if (!text.includes(VISION_DESCRIPTION_PREFIX) && !VISION_EMPTY_PREFIX_RE.test(text) && !VISION_FAILED_PREFIX_RE.test(text)) {
|
|
67
|
+
return text;
|
|
68
|
+
}
|
|
69
|
+
const lines = text.split("\n");
|
|
70
|
+
const kept = [];
|
|
71
|
+
let skippingVisionBlock = false;
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
if (!skippingVisionBlock && line.startsWith(VISION_DESCRIPTION_PREFIX)) {
|
|
74
|
+
skippingVisionBlock = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (skippingVisionBlock) {
|
|
78
|
+
if (line === "]")
|
|
79
|
+
skippingVisionBlock = false;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (VISION_EMPTY_PREFIX_RE.test(line) || VISION_FAILED_PREFIX_RE.test(line)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
kept.push(line);
|
|
86
|
+
}
|
|
87
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
88
|
+
}
|
|
62
89
|
function safeJsonStringify(value) {
|
|
63
90
|
try {
|
|
64
91
|
return JSON.stringify(value) ?? "undefined";
|
|
@@ -78,12 +105,13 @@ export function serializeConversation(messages) {
|
|
|
78
105
|
const parts = [];
|
|
79
106
|
for (const msg of messages) {
|
|
80
107
|
if (msg.role === "user") {
|
|
81
|
-
const
|
|
108
|
+
const rawContent = typeof msg.content === "string"
|
|
82
109
|
? msg.content
|
|
83
110
|
: msg.content
|
|
84
111
|
.filter((c) => c.type === "text")
|
|
85
112
|
.map((c) => c.text)
|
|
86
113
|
.join("");
|
|
114
|
+
const content = stripVisionDescriptionBlocks(rawContent);
|
|
87
115
|
if (content)
|
|
88
116
|
parts.push(`[User]: ${content}`);
|
|
89
117
|
}
|
|
@@ -117,10 +145,11 @@ export function serializeConversation(messages) {
|
|
|
117
145
|
}
|
|
118
146
|
}
|
|
119
147
|
else if (msg.role === "toolResult") {
|
|
120
|
-
const
|
|
148
|
+
const rawContent = msg.content
|
|
121
149
|
.filter((c) => c.type === "text")
|
|
122
150
|
.map((c) => c.text)
|
|
123
151
|
.join("");
|
|
152
|
+
const content = stripVisionDescriptionBlocks(rawContent);
|
|
124
153
|
if (content) {
|
|
125
154
|
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
|
126
155
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/compaction/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAMpD,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,wBAAgB,aAAa,IAAI,cAAc,CAM9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CA2B9F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAK1G;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAUzF;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/compaction/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAMpD,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,wBAAgB,aAAa,IAAI,cAAc,CAM9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CA2B9F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAK1G;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAUzF;AA2DD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAuDjE;AAMD,eAAO,MAAM,2BAA2B,oUAEmF,CAAC"}
|
|
@@ -72,6 +72,41 @@ export function formatFileOperations(readFiles, modifiedFiles) {
|
|
|
72
72
|
// ============================================================================
|
|
73
73
|
/** Maximum characters for a tool result in serialized summaries. */
|
|
74
74
|
const TOOL_RESULT_MAX_CHARS = 2000;
|
|
75
|
+
const VISION_DESCRIPTION_PREFIX = "[Image description from ";
|
|
76
|
+
const VISION_EMPTY_PREFIX_RE = /^\[\d+ image\(s\) — vision model returned empty description\]$/m;
|
|
77
|
+
const VISION_FAILED_PREFIX_RE = /^\[\d+ image\(s\) omitted — vision fallback failed: .*\]$/m;
|
|
78
|
+
/**
|
|
79
|
+
* Remove synthetic vision-fallback description blocks from summarized text.
|
|
80
|
+
*
|
|
81
|
+
* These blocks are transport glue for non-vision main models. They should help
|
|
82
|
+
* the active turn see images, but they should not become durable conversation
|
|
83
|
+
* memory or dominate compaction summaries.
|
|
84
|
+
*/
|
|
85
|
+
function stripVisionDescriptionBlocks(text) {
|
|
86
|
+
if (!text.includes(VISION_DESCRIPTION_PREFIX) && !VISION_EMPTY_PREFIX_RE.test(text) && !VISION_FAILED_PREFIX_RE.test(text)) {
|
|
87
|
+
return text;
|
|
88
|
+
}
|
|
89
|
+
const lines = text.split("\n");
|
|
90
|
+
const kept = [];
|
|
91
|
+
let skippingVisionBlock = false;
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
if (!skippingVisionBlock && line.startsWith(VISION_DESCRIPTION_PREFIX)) {
|
|
94
|
+
skippingVisionBlock = true;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (skippingVisionBlock) {
|
|
98
|
+
if (line === "]") {
|
|
99
|
+
skippingVisionBlock = false;
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (VISION_EMPTY_PREFIX_RE.test(line) || VISION_FAILED_PREFIX_RE.test(line)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
kept.push(line);
|
|
107
|
+
}
|
|
108
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
109
|
+
}
|
|
75
110
|
/**
|
|
76
111
|
* Truncate text to a maximum character length for summarization.
|
|
77
112
|
* Keeps the beginning and appends a truncation marker.
|
|
@@ -94,12 +129,13 @@ export function serializeConversation(messages) {
|
|
|
94
129
|
const parts = [];
|
|
95
130
|
for (const msg of messages) {
|
|
96
131
|
if (msg.role === "user") {
|
|
97
|
-
const
|
|
132
|
+
const rawContent = typeof msg.content === "string"
|
|
98
133
|
? msg.content
|
|
99
134
|
: msg.content
|
|
100
135
|
.filter((c) => c.type === "text")
|
|
101
136
|
.map((c) => c.text)
|
|
102
137
|
.join("");
|
|
138
|
+
const content = stripVisionDescriptionBlocks(rawContent);
|
|
103
139
|
if (content)
|
|
104
140
|
parts.push(`[User]: ${content}`);
|
|
105
141
|
}
|
|
@@ -133,10 +169,11 @@ export function serializeConversation(messages) {
|
|
|
133
169
|
}
|
|
134
170
|
}
|
|
135
171
|
else if (msg.role === "toolResult") {
|
|
136
|
-
const
|
|
172
|
+
const rawContent = msg.content
|
|
137
173
|
.filter((c) => c.type === "text")
|
|
138
174
|
.map((c) => c.text)
|
|
139
175
|
.join("");
|
|
176
|
+
const content = stripVisionDescriptionBlocks(rawContent);
|
|
140
177
|
if (content) {
|
|
141
178
|
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
|
142
179
|
}
|
|
@@ -168,6 +168,19 @@ export declare class AgentBridge {
|
|
|
168
168
|
* The `id` we register is the raw `modelId` (e.g. `claude-3-5-haiku-latest`),
|
|
169
169
|
* which is exactly what the backend expects in `body.model`.
|
|
170
170
|
*/
|
|
171
|
+
/**
|
|
172
|
+
* Determine the input modalities for a model.
|
|
173
|
+
*
|
|
174
|
+
* When the admin explicitly marks a model as the default vision model
|
|
175
|
+
* (isVisionDefault = true), we always include "image" in the input array
|
|
176
|
+
* regardless of what the backend's capabilities-derived `supportsImages`
|
|
177
|
+
* flag reports. Custom/built-in models may lack proper modality metadata,
|
|
178
|
+
* but the admin knows this model can handle images.
|
|
179
|
+
*
|
|
180
|
+
* If `supportsImages` is explicitly false, the vision extension will use
|
|
181
|
+
* the configured default/fallback vision model before the main model call.
|
|
182
|
+
*/
|
|
183
|
+
private modelInput;
|
|
171
184
|
private registerSyntheticProviders;
|
|
172
185
|
/**
|
|
173
186
|
* Apply a sticky model selection to the underlying spectral session, if it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAcH,OAAO,EAQL,KAAK,iBAAiB,EAEvB,MAAM,8BAA8B,CAAC;AAkBtC,OAAO,EACL,kBAAkB,IAAI,yBAAyB,EAEhD,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAiB3E,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,OAAO,yBAAyB,CAAC;AAmCpE,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,0BAA0B,EAAE,CAAC,GAAG,EAAE;QAChC,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC9C;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;CAC3C;AAmXD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAC,CAAe;IAC/B,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,OAAO,CAAC,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC;;;;OAIG;IACH,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB,CAAC,CAAS;IACpC,sFAAsF;IACtF,OAAO,CAAC,iBAAiB,CAQT;IAChB,OAAO,CAAC,WAAW,CAA0E;gBAEjF,IAAI,EAAE,kBAAkB;IAIpC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmR5B,OAAO,CAAC,4BAA4B;YAUtB,oBAAoB;IAmBlC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,0BAA0B;IAqGlC;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;OAQG;IACH,wBAAwB,IAAI,MAAM,GAAG,SAAS;IAI9C;;;;OAIG;IACH,eAAe,IAAI;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,SAAS;IAIvG,gBAAgB,IAAI,KAAK,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IAgBF,iBAAiB,IAAI;QACnB,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;QACtE,QAAQ,EAAE;YACR,QAAQ,EAAE,OAAO,CAAC;YAClB,UAAU,EAAE,OAAO,CAAC;YACpB,UAAU,EAAE,OAAO,CAAC;YACpB,MAAM,EAAE,OAAO,CAAC;SACjB,CAAC;KACH;IAYD,OAAO,CAAC,iBAAiB;IAsBnB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IA6DpE;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,iCAAiC;IAmBzC;;;;;;OAMG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAoBpD;;;;;;;;;;;OAWG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BrE;;;;;;;;OAQG;IACG,OAAO,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYzD,OAAO,IAAI,IAAI;IA0Bf;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAOrB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,sBAAsB;IAkD9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -688,6 +688,26 @@ export class AgentBridge {
|
|
|
688
688
|
* The `id` we register is the raw `modelId` (e.g. `claude-3-5-haiku-latest`),
|
|
689
689
|
* which is exactly what the backend expects in `body.model`.
|
|
690
690
|
*/
|
|
691
|
+
/**
|
|
692
|
+
* Determine the input modalities for a model.
|
|
693
|
+
*
|
|
694
|
+
* When the admin explicitly marks a model as the default vision model
|
|
695
|
+
* (isVisionDefault = true), we always include "image" in the input array
|
|
696
|
+
* regardless of what the backend's capabilities-derived `supportsImages`
|
|
697
|
+
* flag reports. Custom/built-in models may lack proper modality metadata,
|
|
698
|
+
* but the admin knows this model can handle images.
|
|
699
|
+
*
|
|
700
|
+
* If `supportsImages` is explicitly false, the vision extension will use
|
|
701
|
+
* the configured default/fallback vision model before the main model call.
|
|
702
|
+
*/
|
|
703
|
+
modelInput(m) {
|
|
704
|
+
if (m.isVisionDefault || m.supportsImages !== false) {
|
|
705
|
+
return ["text", "image"];
|
|
706
|
+
}
|
|
707
|
+
console.info(`[agent-bridge] Model ${m.modelId} (${m.provider}) reports supportsImages: false ` +
|
|
708
|
+
`— not adding image to input modalities (vision extension will analyze images before provider requests)`);
|
|
709
|
+
return ["text"];
|
|
710
|
+
}
|
|
691
711
|
registerSyntheticProviders(allowedModels) {
|
|
692
712
|
if (!this.modelRegistry)
|
|
693
713
|
return;
|
|
@@ -714,7 +734,7 @@ export class AgentBridge {
|
|
|
714
734
|
provider: SPECTRAL_PROXY_ANTHROPIC,
|
|
715
735
|
baseUrl,
|
|
716
736
|
reasoning: m.supportsReasoning ?? false,
|
|
717
|
-
input: m
|
|
737
|
+
input: this.modelInput(m),
|
|
718
738
|
cost: getModelCost(m),
|
|
719
739
|
contextWindow: m.contextWindow ?? 0,
|
|
720
740
|
maxTokens: 0,
|
|
@@ -740,7 +760,7 @@ export class AgentBridge {
|
|
|
740
760
|
provider: SPECTRAL_PROXY_OPENAI,
|
|
741
761
|
baseUrl,
|
|
742
762
|
reasoning: m.supportsReasoning ?? false,
|
|
743
|
-
input: m
|
|
763
|
+
input: this.modelInput(m),
|
|
744
764
|
cost: getModelCost(m),
|
|
745
765
|
contextWindow: m.contextWindow ?? 0,
|
|
746
766
|
maxTokens: 0,
|
|
@@ -768,7 +788,7 @@ export class AgentBridge {
|
|
|
768
788
|
provider: SPECTRAL_PROXY_USER_MODEL,
|
|
769
789
|
baseUrl,
|
|
770
790
|
reasoning: m.supportsReasoning ?? false,
|
|
771
|
-
input: m
|
|
791
|
+
input: this.modelInput(m),
|
|
772
792
|
cost: getModelCost(m),
|
|
773
793
|
contextWindow: m.contextWindow ?? 0,
|
|
774
794
|
maxTokens: 0,
|
|
@@ -1393,6 +1413,7 @@ export class AgentBridge {
|
|
|
1393
1413
|
// Forward compaction lifecycle events to the wire so the browser
|
|
1394
1414
|
// can surface a "Compacting…" indicator and track compaction
|
|
1395
1415
|
// activity.
|
|
1416
|
+
this.memoryPhase = "compacting";
|
|
1396
1417
|
this.opts.emit({
|
|
1397
1418
|
type: "compaction_start",
|
|
1398
1419
|
reason: ev.reason,
|
|
@@ -1410,6 +1431,7 @@ export class AgentBridge {
|
|
|
1410
1431
|
return;
|
|
1411
1432
|
}
|
|
1412
1433
|
case "compaction_end": {
|
|
1434
|
+
this.memoryPhase = "idle";
|
|
1413
1435
|
this.opts.emit({
|
|
1414
1436
|
type: "compaction_end",
|
|
1415
1437
|
summary: ev.result?.summary ?? "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -1240,12 +1240,19 @@ export class SessionStreamManager {
|
|
|
1240
1240
|
? `Compaction complete${tokensMsg}. Summary: ${event.summary}`
|
|
1241
1241
|
: `Compaction complete${tokensMsg}.`;
|
|
1242
1242
|
try {
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1243
|
+
// Update the existing "compacting…" system message in-place rather than
|
|
1244
|
+
// appending a second system row. This keeps history replay at one entry
|
|
1245
|
+
// per compaction instead of two (start + end). Falls back to append if
|
|
1246
|
+
// no system message exists (shouldn't happen, but defensive).
|
|
1247
|
+
const updated = this.store.updateLastSystemMessage(stream.sessionId, sysMsg);
|
|
1248
|
+
if (!updated) {
|
|
1249
|
+
this.store.appendMessage(stream.sessionId, {
|
|
1250
|
+
role: "system",
|
|
1251
|
+
content: sysMsg,
|
|
1252
|
+
eventsJsonl: "",
|
|
1253
|
+
createdAt: Date.now(),
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1249
1256
|
}
|
|
1250
1257
|
catch {
|
|
1251
1258
|
// best-effort
|
package/dist/server/storage.d.ts
CHANGED
|
@@ -94,6 +94,7 @@ export declare class SessionStore {
|
|
|
94
94
|
private stmtAppendMessage;
|
|
95
95
|
private stmtDeleteMessage;
|
|
96
96
|
private stmtDeleteMessagesBySession;
|
|
97
|
+
private stmtUpdateLastSystemMessage;
|
|
97
98
|
private stmtTouchSession;
|
|
98
99
|
private stmtRenameSession;
|
|
99
100
|
private stmtSessionWithCount;
|
|
@@ -156,6 +157,15 @@ export declare class SessionStore {
|
|
|
156
157
|
/** Delete all persisted messages for a session. */
|
|
157
158
|
deleteMessagesBySession(sessionId: string): void;
|
|
158
159
|
appendMessage(sessionId: string, msg: AppendMessageInput): StoredMessage;
|
|
160
|
+
/**
|
|
161
|
+
* Update the content of the most-recent system message for a session.
|
|
162
|
+
* Used by `compaction_end` to update the initial "compacting…" message in
|
|
163
|
+
* place rather than appending a second system row, so history replay shows
|
|
164
|
+
* a single compaction entry instead of two.
|
|
165
|
+
*
|
|
166
|
+
* Returns true if a row was updated, false if no system message exists yet.
|
|
167
|
+
*/
|
|
168
|
+
updateLastSystemMessage(sessionId: string, content: string): boolean;
|
|
159
169
|
/**
|
|
160
170
|
* Rename a session. Bumps `updated_at`. Returns the updated summary, or
|
|
161
171
|
* null if the session doesn't exist. Title is trimmed; an empty title falls
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/server/storage.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/server/storage.ts"],"names":[],"mappings":"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"}
|
package/dist/server/storage.js
CHANGED
|
@@ -180,6 +180,7 @@ export class SessionStore {
|
|
|
180
180
|
stmtAppendMessage;
|
|
181
181
|
stmtDeleteMessage;
|
|
182
182
|
stmtDeleteMessagesBySession;
|
|
183
|
+
stmtUpdateLastSystemMessage;
|
|
183
184
|
stmtTouchSession;
|
|
184
185
|
stmtRenameSession;
|
|
185
186
|
stmtSessionWithCount;
|
|
@@ -317,6 +318,13 @@ export class SessionStore {
|
|
|
317
318
|
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
318
319
|
this.stmtDeleteMessage = this.db.prepare(`DELETE FROM messages WHERE id = ?`);
|
|
319
320
|
this.stmtDeleteMessagesBySession = this.db.prepare(`DELETE FROM messages WHERE session_id = ?`);
|
|
321
|
+
this.stmtUpdateLastSystemMessage = this.db.prepare(`UPDATE messages SET content = ?
|
|
322
|
+
WHERE id = (
|
|
323
|
+
SELECT id FROM messages
|
|
324
|
+
WHERE session_id = ? AND role = 'system'
|
|
325
|
+
ORDER BY created_at DESC
|
|
326
|
+
LIMIT 1
|
|
327
|
+
)`);
|
|
320
328
|
this.stmtTouchSession = this.db.prepare(`UPDATE sessions SET updated_at = ? WHERE id = ?`);
|
|
321
329
|
this.stmtRenameSession = this.db.prepare(`UPDATE sessions SET title = ?, updated_at = ? WHERE id = ?`);
|
|
322
330
|
this.stmtSessionWithCount = this.db.prepare(`
|
|
@@ -561,6 +569,23 @@ export class SessionStore {
|
|
|
561
569
|
...(msg.images && msg.images.length > 0 ? { images: msg.images } : {}),
|
|
562
570
|
};
|
|
563
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Update the content of the most-recent system message for a session.
|
|
574
|
+
* Used by `compaction_end` to update the initial "compacting…" message in
|
|
575
|
+
* place rather than appending a second system row, so history replay shows
|
|
576
|
+
* a single compaction entry instead of two.
|
|
577
|
+
*
|
|
578
|
+
* Returns true if a row was updated, false if no system message exists yet.
|
|
579
|
+
*/
|
|
580
|
+
updateLastSystemMessage(sessionId, content) {
|
|
581
|
+
try {
|
|
582
|
+
const info = this.stmtUpdateLastSystemMessage.run(content, sessionId);
|
|
583
|
+
return info.changes > 0;
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
564
589
|
/**
|
|
565
590
|
* Rename a session. Bumps `updated_at`. Returns the updated summary, or
|
|
566
591
|
* null if the session doesn't exist. Title is trimmed; an empty title falls
|