@aexol/spectral 0.9.81 → 0.9.82
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/agent/index.js +2 -2
- package/dist/memory/tools/compact-context.d.ts.map +1 -1
- package/dist/memory/tools/compact-context.js +7 -4
- package/dist/sdk/agent-core/harness/agent-harness.js +1 -1
- package/dist/sdk/agent-core/harness/types.d.ts +2 -0
- package/dist/sdk/agent-core/harness/types.d.ts.map +1 -1
- package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
- package/dist/sdk/ai/providers/openai-completions.js +435 -92
- package/dist/sdk/ai/types.d.ts +4 -0
- package/dist/sdk/ai/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.d.ts +1 -1
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +1 -1
- package/dist/sdk/coding-agent/core/extensions/runner.d.ts +3 -1
- package/dist/sdk/coding-agent/core/extensions/runner.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/runner.js +6 -2
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +2 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/sdk.d.ts +2 -2
- package/dist/sdk/coding-agent/core/sdk.js +2 -2
- package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/system-prompt.js +22 -4
- package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts +27 -0
- package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/tools/apply-patch.js +353 -0
- package/dist/sdk/coding-agent/core/tools/edit-diff.d.ts +1 -6
- package/dist/sdk/coding-agent/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/tools/edit-diff.js +179 -100
- package/dist/sdk/coding-agent/core/tools/edit.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/tools/edit.js +9 -5
- package/dist/sdk/coding-agent/core/tools/index.d.ts +4 -1
- package/dist/sdk/coding-agent/core/tools/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/tools/index.js +11 -0
- package/dist/sdk/coding-agent/index.d.ts +1 -1
- package/dist/sdk/coding-agent/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/index.js +1 -1
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +274 -54
- package/dist/server/handlers/queue.d.ts +3 -11
- package/dist/server/handlers/queue.d.ts.map +1 -1
- package/dist/server/handlers/queue.js +48 -24
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +43 -9
- package/dist/server/storage.d.ts +2 -1
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +16 -8
- package/dist/server/wire.d.ts +11 -1
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -54,8 +54,8 @@ import { existsSync, statSync } from "node:fs";
|
|
|
54
54
|
import { dirname, join, resolve } from "node:path";
|
|
55
55
|
import aexolMcpExtension from "../extensions/aexol-mcp.js";
|
|
56
56
|
import mcpAdapterFactory from "../mcp/index.js";
|
|
57
|
-
import { discoverPrimaryAgent, injectPrimaryAgentPrompt } from "../agent/agents.js";
|
|
58
|
-
import { loadAgentSettings } from "./handlers/agent-settings.js";
|
|
57
|
+
import { discoverPrimaryAgent, injectPrimaryAgentPrompt, } from "../agent/agents.js";
|
|
58
|
+
import { loadAgentSettings, } from "./handlers/agent-settings.js";
|
|
59
59
|
import kanbanBridgeExtension from "../extensions/kanban-bridge.js";
|
|
60
60
|
import spectralVisionExtension from "../extensions/spectral-vision-fallback.js";
|
|
61
61
|
import subagentExt from "../agent/index.js";
|
|
@@ -142,39 +142,159 @@ function extractTextFromContent(content) {
|
|
|
142
142
|
*/
|
|
143
143
|
const MODEL_PRICING = [
|
|
144
144
|
// Anthropic Claude models
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
{
|
|
146
|
+
prefix: "claude-opus-4",
|
|
147
|
+
input: 15,
|
|
148
|
+
output: 75,
|
|
149
|
+
cacheWrite: 18.75,
|
|
150
|
+
cacheRead: 1.5,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
prefix: "claude-sonnet-4",
|
|
154
|
+
input: 3,
|
|
155
|
+
output: 15,
|
|
156
|
+
cacheWrite: 3.75,
|
|
157
|
+
cacheRead: 0.3,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
prefix: "claude-3-5-sonnet",
|
|
161
|
+
input: 3,
|
|
162
|
+
output: 15,
|
|
163
|
+
cacheWrite: 3.75,
|
|
164
|
+
cacheRead: 0.3,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
prefix: "claude-3-5-haiku",
|
|
168
|
+
input: 0.8,
|
|
169
|
+
output: 4,
|
|
170
|
+
cacheWrite: 1,
|
|
171
|
+
cacheRead: 0.08,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
prefix: "claude-3-opus",
|
|
175
|
+
input: 15,
|
|
176
|
+
output: 75,
|
|
177
|
+
cacheWrite: 18.75,
|
|
178
|
+
cacheRead: 1.5,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
prefix: "claude-3-sonnet",
|
|
182
|
+
input: 3,
|
|
183
|
+
output: 15,
|
|
184
|
+
cacheWrite: 3.75,
|
|
185
|
+
cacheRead: 0.3,
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
prefix: "claude-3-haiku",
|
|
189
|
+
input: 0.25,
|
|
190
|
+
output: 1.25,
|
|
191
|
+
cacheWrite: 1.25,
|
|
192
|
+
cacheRead: 0.025,
|
|
193
|
+
},
|
|
152
194
|
// OpenAI models
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
195
|
+
{
|
|
196
|
+
prefix: "gpt-5.5",
|
|
197
|
+
input: 1.75,
|
|
198
|
+
output: 14,
|
|
199
|
+
cacheWrite: 14,
|
|
200
|
+
cacheRead: 0.35,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
prefix: "gpt-5.4",
|
|
204
|
+
input: 1.75,
|
|
205
|
+
output: 14,
|
|
206
|
+
cacheWrite: 14,
|
|
207
|
+
cacheRead: 0.35,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
prefix: "gpt-5.3",
|
|
211
|
+
input: 1.75,
|
|
212
|
+
output: 14,
|
|
213
|
+
cacheWrite: 14,
|
|
214
|
+
cacheRead: 0.35,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
prefix: "gpt-5.2",
|
|
218
|
+
input: 1.75,
|
|
219
|
+
output: 14,
|
|
220
|
+
cacheWrite: 14,
|
|
221
|
+
cacheRead: 0.35,
|
|
222
|
+
},
|
|
223
|
+
{ prefix: "gpt-5.1", input: 2, output: 8, cacheWrite: 8, cacheRead: 0.5 },
|
|
158
224
|
{ prefix: "gpt-5", input: 1.25, output: 10, cacheWrite: 10, cacheRead: 0.25 },
|
|
159
|
-
{ prefix: "gpt-4.1", input: 2, output: 8, cacheWrite: 8, cacheRead: 0.
|
|
160
|
-
{ prefix: "gpt-4o", input: 2.
|
|
225
|
+
{ prefix: "gpt-4.1", input: 2, output: 8, cacheWrite: 8, cacheRead: 0.5 },
|
|
226
|
+
{ prefix: "gpt-4o", input: 2.5, output: 10, cacheWrite: 10, cacheRead: 1.25 },
|
|
161
227
|
{ prefix: "gpt-4-turbo", input: 10, output: 30, cacheWrite: 0, cacheRead: 0 },
|
|
162
228
|
{ prefix: "gpt-4", input: 30, output: 60, cacheWrite: 0, cacheRead: 0 },
|
|
163
|
-
{
|
|
229
|
+
{
|
|
230
|
+
prefix: "gpt-3.5-turbo",
|
|
231
|
+
input: 0.5,
|
|
232
|
+
output: 1.5,
|
|
233
|
+
cacheWrite: 0,
|
|
234
|
+
cacheRead: 0,
|
|
235
|
+
},
|
|
164
236
|
{ prefix: "o1", input: 15, output: 60, cacheWrite: 0, cacheRead: 0 },
|
|
165
|
-
{ prefix: "o3-mini", input: 1.
|
|
166
|
-
{ prefix: "o4-mini", input: 1.
|
|
237
|
+
{ prefix: "o3-mini", input: 1.1, output: 4.4, cacheWrite: 0, cacheRead: 0 },
|
|
238
|
+
{ prefix: "o4-mini", input: 1.1, output: 4.4, cacheWrite: 0, cacheRead: 0 },
|
|
167
239
|
// Google Gemini models
|
|
168
|
-
{
|
|
169
|
-
|
|
240
|
+
{
|
|
241
|
+
prefix: "gemini-2.5-pro",
|
|
242
|
+
input: 1.25,
|
|
243
|
+
output: 10,
|
|
244
|
+
cacheWrite: 0,
|
|
245
|
+
cacheRead: 0,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
prefix: "gemini-2.5-flash",
|
|
249
|
+
input: 0.15,
|
|
250
|
+
output: 0.6,
|
|
251
|
+
cacheWrite: 0,
|
|
252
|
+
cacheRead: 0,
|
|
253
|
+
},
|
|
170
254
|
// DeepSeek models
|
|
171
|
-
{
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
255
|
+
{
|
|
256
|
+
prefix: "deepseek-v3",
|
|
257
|
+
input: 0.27,
|
|
258
|
+
output: 1.1,
|
|
259
|
+
cacheWrite: 0,
|
|
260
|
+
cacheRead: 0,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
prefix: "deepseek-r1",
|
|
264
|
+
input: 0.55,
|
|
265
|
+
output: 2.19,
|
|
266
|
+
cacheWrite: 0,
|
|
267
|
+
cacheRead: 0,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
prefix: "deepseek/deepseek-v3",
|
|
271
|
+
input: 0.27,
|
|
272
|
+
output: 1.1,
|
|
273
|
+
cacheWrite: 0,
|
|
274
|
+
cacheRead: 0,
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
prefix: "deepseek/deepseek-r1",
|
|
278
|
+
input: 0.55,
|
|
279
|
+
output: 2.19,
|
|
280
|
+
cacheWrite: 0,
|
|
281
|
+
cacheRead: 0,
|
|
282
|
+
},
|
|
175
283
|
// Meta Llama models (common via OpenRouter-compatible endpoints)
|
|
176
|
-
{
|
|
177
|
-
|
|
284
|
+
{
|
|
285
|
+
prefix: "meta-llama/llama-4",
|
|
286
|
+
input: 0.2,
|
|
287
|
+
output: 0.8,
|
|
288
|
+
cacheWrite: 0,
|
|
289
|
+
cacheRead: 0,
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
prefix: "meta-llama/llama-3.3",
|
|
293
|
+
input: 0.2,
|
|
294
|
+
output: 0.5,
|
|
295
|
+
cacheWrite: 0,
|
|
296
|
+
cacheRead: 0,
|
|
297
|
+
},
|
|
178
298
|
];
|
|
179
299
|
/**
|
|
180
300
|
* Strip vendor prefix to extract the bare model name.
|
|
@@ -186,7 +306,8 @@ function bareModelId(modelId) {
|
|
|
186
306
|
return idx === -1 ? modelId : modelId.slice(idx + 1);
|
|
187
307
|
}
|
|
188
308
|
function inferSyntheticContextWindow(model) {
|
|
189
|
-
if (typeof model.contextWindow === "number" &&
|
|
309
|
+
if (typeof model.contextWindow === "number" &&
|
|
310
|
+
Number.isFinite(model.contextWindow)) {
|
|
190
311
|
return model.contextWindow;
|
|
191
312
|
}
|
|
192
313
|
const modelId = model.modelId.toLowerCase();
|
|
@@ -216,7 +337,12 @@ function getModelCost(m) {
|
|
|
216
337
|
// Fall back to hardcoded pricing table when credits aren't configured
|
|
217
338
|
const pricing = lookupPricing(m.modelId);
|
|
218
339
|
return pricing
|
|
219
|
-
? {
|
|
340
|
+
? {
|
|
341
|
+
input: pricing.input,
|
|
342
|
+
output: pricing.output,
|
|
343
|
+
cacheRead: pricing.cacheRead,
|
|
344
|
+
cacheWrite: pricing.cacheWrite,
|
|
345
|
+
}
|
|
220
346
|
: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
221
347
|
}
|
|
222
348
|
/** Look up pricing for a modelId. Returns null when unknown. */
|
|
@@ -234,6 +360,24 @@ function lookupPricing(modelId) {
|
|
|
234
360
|
}
|
|
235
361
|
return null;
|
|
236
362
|
}
|
|
363
|
+
function inferSyntheticThinkingLevelMap(model) {
|
|
364
|
+
if (!model.supportsReasoning)
|
|
365
|
+
return undefined;
|
|
366
|
+
const modelId = model.modelId.toLowerCase();
|
|
367
|
+
const bare = bareModelId(modelId);
|
|
368
|
+
if (model.provider === "built-in" &&
|
|
369
|
+
(modelId.includes("gpt-5") || bare.includes("gpt-5"))) {
|
|
370
|
+
return {
|
|
371
|
+
off: "none",
|
|
372
|
+
minimal: "minimal",
|
|
373
|
+
low: "low",
|
|
374
|
+
medium: "medium",
|
|
375
|
+
high: "high",
|
|
376
|
+
xhigh: "xhigh",
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
237
381
|
function inferSyntheticOpenAICompat(model) {
|
|
238
382
|
const bare = bareModelId(model.modelId);
|
|
239
383
|
const isDeepSeek = model.provider === "deepseek" ||
|
|
@@ -246,6 +390,15 @@ function inferSyntheticOpenAICompat(model) {
|
|
|
246
390
|
supportsDeveloperRole: false,
|
|
247
391
|
};
|
|
248
392
|
}
|
|
393
|
+
if (model.provider === "built-in" &&
|
|
394
|
+
(model.modelId.toLowerCase().includes("gpt-5") ||
|
|
395
|
+
bare.toLowerCase().includes("gpt-5"))) {
|
|
396
|
+
return {
|
|
397
|
+
thinkingFormat: "openai",
|
|
398
|
+
supportsReasoningEffort: true,
|
|
399
|
+
supportsStore: false,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
249
402
|
if (model.provider === "openrouter") {
|
|
250
403
|
const compat = {
|
|
251
404
|
thinkingFormat: "openrouter",
|
|
@@ -274,9 +427,13 @@ function calculateCredits(inputTokens, outputTokens, cacheReadTokens = 0, cacheW
|
|
|
274
427
|
(outputTokens / 1_000_000) * outputRate +
|
|
275
428
|
(cacheReadTokens / 1_000_000) * cacheReadRate +
|
|
276
429
|
(cacheWriteTokens / 1_000_000) * cacheWriteRate;
|
|
277
|
-
if (inputRate === 0 &&
|
|
430
|
+
if (inputRate === 0 &&
|
|
431
|
+
outputRate === 0 &&
|
|
432
|
+
cacheReadRate === 0 &&
|
|
433
|
+
cacheWriteRate === 0) {
|
|
278
434
|
if (cachedInputRate > 0) {
|
|
279
|
-
return ((inputTokens + cacheReadTokens + cacheWriteTokens) / 1_000_000) *
|
|
435
|
+
return (((inputTokens + cacheReadTokens + cacheWriteTokens) / 1_000_000) *
|
|
436
|
+
cachedInputRate);
|
|
280
437
|
}
|
|
281
438
|
return 0;
|
|
282
439
|
}
|
|
@@ -441,11 +598,22 @@ export class AgentBridge {
|
|
|
441
598
|
return { systemPrompt: injected };
|
|
442
599
|
});
|
|
443
600
|
},
|
|
444
|
-
aexolMcpExtension,
|
|
601
|
+
aexolMcpExtension,
|
|
602
|
+
kanbanBridgeExtension,
|
|
603
|
+
async (ext) => {
|
|
604
|
+
spectralVisionExtension(ext);
|
|
605
|
+
},
|
|
606
|
+
async (ext) => {
|
|
607
|
+
designerExtension(ext);
|
|
608
|
+
},
|
|
609
|
+
async (ext) => {
|
|
610
|
+
observationalMemory(ext);
|
|
611
|
+
},
|
|
445
612
|
];
|
|
446
613
|
// Agent-behavior toggles. Loaded once at bridge construction so changes
|
|
447
614
|
// take effect on the next session (not mid-turn).
|
|
448
|
-
const agentSettings = this.opts.agentSettings ??
|
|
615
|
+
const agentSettings = this.opts.agentSettings ??
|
|
616
|
+
loadAgentSettings(this.opts.cwd, this.opts.agentDir);
|
|
449
617
|
// Only register the subagent tool when the toggle is enabled.
|
|
450
618
|
if (agentSettings.subagentsEnabled) {
|
|
451
619
|
extensionFactories.splice(4, 0, async (ext) => {
|
|
@@ -459,7 +627,9 @@ export class AgentBridge {
|
|
|
459
627
|
// adapter is included in compiled standalone binaries (Bun build), where
|
|
460
628
|
// filesystem-based discovery via jiti fails because the source files are
|
|
461
629
|
// embedded in the binary rather than present on disk.
|
|
462
|
-
extensionFactories.push(async (ext) => {
|
|
630
|
+
extensionFactories.push(async (ext) => {
|
|
631
|
+
mcpAdapterFactory(ext);
|
|
632
|
+
});
|
|
463
633
|
// ResourceLoader with extensions wired in via factories.
|
|
464
634
|
// Each factory's signature `(ext: ExtensionAPI) => Promise<void>` matches
|
|
465
635
|
// the ExtensionFactory type exactly, so we can pass them directly.
|
|
@@ -469,7 +639,10 @@ export class AgentBridge {
|
|
|
469
639
|
// (ancestor-walked). We additionally walk ancestors for
|
|
470
640
|
// .opencode/skills and .aexol/skills so OpenCode/Codex/Aexol skills
|
|
471
641
|
// work out of the box.
|
|
472
|
-
const extraSkillPaths = collectAncestorSkillDirs(this.opts.cwd, [
|
|
642
|
+
const extraSkillPaths = collectAncestorSkillDirs(this.opts.cwd, [
|
|
643
|
+
".opencode/skills",
|
|
644
|
+
".aexol/skills",
|
|
645
|
+
]);
|
|
473
646
|
// Primary-agent prompt injection happens per-turn via the before_agent_start
|
|
474
647
|
// extension registered above, so the primaryAgentEnabled toggle takes effect
|
|
475
648
|
// mid-session (on the next user message) rather than only at session start.
|
|
@@ -548,7 +721,13 @@ export class AgentBridge {
|
|
|
548
721
|
cacheRead: 0,
|
|
549
722
|
cacheWrite: 0,
|
|
550
723
|
totalTokens: 0,
|
|
551
|
-
cost: {
|
|
724
|
+
cost: {
|
|
725
|
+
input: 0,
|
|
726
|
+
output: 0,
|
|
727
|
+
cacheRead: 0,
|
|
728
|
+
cacheWrite: 0,
|
|
729
|
+
total: 0,
|
|
730
|
+
},
|
|
552
731
|
},
|
|
553
732
|
stopReason: toolCalls.length > 0
|
|
554
733
|
? "toolUse"
|
|
@@ -663,7 +842,9 @@ export class AgentBridge {
|
|
|
663
842
|
try {
|
|
664
843
|
this.opts.emit(event);
|
|
665
844
|
}
|
|
666
|
-
catch {
|
|
845
|
+
catch {
|
|
846
|
+
/* best-effort */
|
|
847
|
+
}
|
|
667
848
|
});
|
|
668
849
|
// Emit session_start so extensions can initialize (e.g. spectral-mcp-adapter
|
|
669
850
|
// connects to MCP servers, loads configs from ~/.config/mcp/mcp.json etc.).
|
|
@@ -797,6 +978,7 @@ export class AgentBridge {
|
|
|
797
978
|
provider: SPECTRAL_PROXY_ANTHROPIC,
|
|
798
979
|
baseUrl,
|
|
799
980
|
reasoning: m.supportsReasoning ?? false,
|
|
981
|
+
thinkingLevelMap: inferSyntheticThinkingLevelMap(m),
|
|
800
982
|
input: this.modelInput(m),
|
|
801
983
|
cost: getModelCost(m),
|
|
802
984
|
contextWindow: inferSyntheticContextWindow(m),
|
|
@@ -824,6 +1006,7 @@ export class AgentBridge {
|
|
|
824
1006
|
provider: SPECTRAL_PROXY_OPENAI,
|
|
825
1007
|
baseUrl,
|
|
826
1008
|
reasoning: m.supportsReasoning ?? false,
|
|
1009
|
+
thinkingLevelMap: inferSyntheticThinkingLevelMap(m),
|
|
827
1010
|
input: this.modelInput(m),
|
|
828
1011
|
cost: getModelCost(m),
|
|
829
1012
|
contextWindow: inferSyntheticContextWindow(m),
|
|
@@ -853,6 +1036,7 @@ export class AgentBridge {
|
|
|
853
1036
|
provider: SPECTRAL_PROXY_USER_MODEL,
|
|
854
1037
|
baseUrl,
|
|
855
1038
|
reasoning: m.supportsReasoning ?? false,
|
|
1039
|
+
thinkingLevelMap: inferSyntheticThinkingLevelMap(m),
|
|
856
1040
|
input: this.modelInput(m),
|
|
857
1041
|
cost: getModelCost(m),
|
|
858
1042
|
contextWindow: inferSyntheticContextWindow(m),
|
|
@@ -915,15 +1099,22 @@ export class AgentBridge {
|
|
|
915
1099
|
}
|
|
916
1100
|
updateMemoryPhase(message, system) {
|
|
917
1101
|
const lower = message.toLowerCase();
|
|
918
|
-
if (system === "memory_reflection" ||
|
|
1102
|
+
if (system === "memory_reflection" ||
|
|
1103
|
+
lower.includes("reflector") ||
|
|
1104
|
+
lower.includes("reflecting")) {
|
|
919
1105
|
this.memoryPhase = "reflecting";
|
|
920
1106
|
return;
|
|
921
1107
|
}
|
|
922
|
-
if (system === "memory_pruner" ||
|
|
1108
|
+
if (system === "memory_pruner" ||
|
|
1109
|
+
lower.includes("pruner") ||
|
|
1110
|
+
lower.includes("pruning") ||
|
|
1111
|
+
lower.includes("prune")) {
|
|
923
1112
|
this.memoryPhase = "pruning";
|
|
924
1113
|
return;
|
|
925
1114
|
}
|
|
926
|
-
if (system === "memory_compaction" ||
|
|
1115
|
+
if (system === "memory_compaction" ||
|
|
1116
|
+
lower.includes("compaction") ||
|
|
1117
|
+
lower.includes("compact")) {
|
|
927
1118
|
this.memoryPhase = "compacting";
|
|
928
1119
|
return;
|
|
929
1120
|
}
|
|
@@ -955,9 +1146,7 @@ export class AgentBridge {
|
|
|
955
1146
|
if (!model) {
|
|
956
1147
|
try {
|
|
957
1148
|
await this.refreshAllowedModels({ bypassCache: true });
|
|
958
|
-
model = this.modelRegistry
|
|
959
|
-
.getAvailable()
|
|
960
|
-
.find((m) => m.id === modelId);
|
|
1149
|
+
model = this.modelRegistry.getAvailable().find((m) => m.id === modelId);
|
|
961
1150
|
}
|
|
962
1151
|
catch (err) {
|
|
963
1152
|
refreshError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -1015,7 +1204,6 @@ export class AgentBridge {
|
|
|
1015
1204
|
return undefined;
|
|
1016
1205
|
switch (effort) {
|
|
1017
1206
|
case "xhigh":
|
|
1018
|
-
return "high";
|
|
1019
1207
|
case "high":
|
|
1020
1208
|
case "medium":
|
|
1021
1209
|
case "low":
|
|
@@ -1187,7 +1375,9 @@ export class AgentBridge {
|
|
|
1187
1375
|
// the framing on the wire.
|
|
1188
1376
|
const finalContent = content ?? text;
|
|
1189
1377
|
const hasMeaningfulEvent = wireEvents.some((e) => e.type === "text_delta" ||
|
|
1378
|
+
e.type === "thinking_start" ||
|
|
1190
1379
|
e.type === "thinking_delta" ||
|
|
1380
|
+
e.type === "thinking_end" ||
|
|
1191
1381
|
e.type === "tool_call" ||
|
|
1192
1382
|
e.type === "tool_result");
|
|
1193
1383
|
if (!finalContent && !hasMeaningfulEvent) {
|
|
@@ -1258,11 +1448,26 @@ export class AgentBridge {
|
|
|
1258
1448
|
delta: inner.delta,
|
|
1259
1449
|
});
|
|
1260
1450
|
}
|
|
1451
|
+
else if (inner.type === "thinking_start") {
|
|
1452
|
+
this.emitAndBuffer({
|
|
1453
|
+
type: "thinking_start",
|
|
1454
|
+
messageId: this.pending.messageId,
|
|
1455
|
+
metadata: inner.metadata,
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1261
1458
|
else if (inner.type === "thinking_delta") {
|
|
1262
1459
|
this.emitAndBuffer({
|
|
1263
1460
|
type: "thinking_delta",
|
|
1264
1461
|
messageId: this.pending.messageId,
|
|
1265
1462
|
delta: inner.delta,
|
|
1463
|
+
metadata: inner.metadata,
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
else if (inner.type === "thinking_end") {
|
|
1467
|
+
this.emitAndBuffer({
|
|
1468
|
+
type: "thinking_end",
|
|
1469
|
+
messageId: this.pending.messageId,
|
|
1470
|
+
metadata: inner.metadata,
|
|
1266
1471
|
});
|
|
1267
1472
|
}
|
|
1268
1473
|
// Other inner types (text_start/end, toolcall_*, done, error) we
|
|
@@ -1274,8 +1479,10 @@ export class AgentBridge {
|
|
|
1274
1479
|
// Emit subagent_start for subagent tool invocations (Option B frontend panels).
|
|
1275
1480
|
if (ev.toolName === "subagent") {
|
|
1276
1481
|
const args = ev.args;
|
|
1277
|
-
const mode = args?.chain?.length > 0
|
|
1278
|
-
|
|
1482
|
+
const mode = args?.chain?.length > 0
|
|
1483
|
+
? "chain"
|
|
1484
|
+
: args?.tasks?.length > 0
|
|
1485
|
+
? "parallel"
|
|
1279
1486
|
: "single";
|
|
1280
1487
|
const se = {
|
|
1281
1488
|
type: "subagent_start",
|
|
@@ -1304,14 +1511,15 @@ export class AgentBridge {
|
|
|
1304
1511
|
case "tool_execution_update": {
|
|
1305
1512
|
// Emit subagent_progress for subagent tool updates.
|
|
1306
1513
|
if (ev.toolName === "subagent") {
|
|
1307
|
-
const partialResult = ev
|
|
1514
|
+
const partialResult = ev
|
|
1515
|
+
.partialResult;
|
|
1308
1516
|
const details = partialResult?.details;
|
|
1309
1517
|
if (details?.results) {
|
|
1310
1518
|
const mode = (details.mode ?? "single");
|
|
1311
1519
|
const total = details.results.length;
|
|
1312
|
-
const done = details.results.filter(r => r.exitCode !== undefined && r.exitCode !== -1).length;
|
|
1520
|
+
const done = details.results.filter((r) => r.exitCode !== undefined && r.exitCode !== -1).length;
|
|
1313
1521
|
const running = total - done;
|
|
1314
|
-
const runningResult = details.results.find(r => r.exitCode === -1 || r.exitCode === undefined);
|
|
1522
|
+
const runningResult = details.results.find((r) => r.exitCode === -1 || r.exitCode === undefined);
|
|
1315
1523
|
const currentAgent = runningResult?.agent;
|
|
1316
1524
|
const extractedStreamingText = extractTextFromContent(partialResult?.content);
|
|
1317
1525
|
const fallbackStreamingText = extractedStreamingText &&
|
|
@@ -1324,14 +1532,18 @@ export class AgentBridge {
|
|
|
1324
1532
|
const streamingText = typeof runningResult?.streamingText === "string"
|
|
1325
1533
|
? runningResult.streamingText
|
|
1326
1534
|
: fallbackStreamingText;
|
|
1327
|
-
const thinkingText = typeof runningResult?.thinkingText === "string"
|
|
1535
|
+
const thinkingText = typeof runningResult?.thinkingText === "string"
|
|
1536
|
+
? runningResult.thinkingText
|
|
1537
|
+
: undefined;
|
|
1328
1538
|
const se = {
|
|
1329
1539
|
type: "subagent_progress",
|
|
1330
1540
|
toolCallId: ev.toolCallId,
|
|
1331
1541
|
mode,
|
|
1332
1542
|
completedCount: done,
|
|
1333
1543
|
totalCount: total,
|
|
1334
|
-
currentStep: mode === "chain"
|
|
1544
|
+
currentStep: mode === "chain"
|
|
1545
|
+
? details.results[details.results.length - 1]?.step
|
|
1546
|
+
: undefined,
|
|
1335
1547
|
currentAgent,
|
|
1336
1548
|
message: mode === "parallel"
|
|
1337
1549
|
? `Parallel: ${done}/${total} done, ${running} running`
|
|
@@ -1369,7 +1581,7 @@ export class AgentBridge {
|
|
|
1369
1581
|
}
|
|
1370
1582
|
return "(no output)";
|
|
1371
1583
|
};
|
|
1372
|
-
const results = details.results.map(r => ({
|
|
1584
|
+
const results = details.results.map((r) => ({
|
|
1373
1585
|
agent: r.agent,
|
|
1374
1586
|
agentSource: r.agentSource ?? "unknown",
|
|
1375
1587
|
task: r.task,
|
|
@@ -1395,7 +1607,14 @@ export class AgentBridge {
|
|
|
1395
1607
|
cacheWriteTokens: acc.cacheWriteTokens + r.usage.cacheWriteTokens,
|
|
1396
1608
|
cost: acc.cost + r.usage.cost,
|
|
1397
1609
|
turns: acc.turns + r.usage.turns,
|
|
1398
|
-
}), {
|
|
1610
|
+
}), {
|
|
1611
|
+
inputTokens: 0,
|
|
1612
|
+
outputTokens: 0,
|
|
1613
|
+
cacheReadTokens: 0,
|
|
1614
|
+
cacheWriteTokens: 0,
|
|
1615
|
+
cost: 0,
|
|
1616
|
+
turns: 0,
|
|
1617
|
+
});
|
|
1399
1618
|
const se = {
|
|
1400
1619
|
type: "subagent_end",
|
|
1401
1620
|
toolCallId: ev.toolCallId,
|
|
@@ -1427,7 +1646,8 @@ export class AgentBridge {
|
|
|
1427
1646
|
// turns that legitimately have no text.
|
|
1428
1647
|
const assembled = extractTextFromContent(ev.message.content);
|
|
1429
1648
|
const content = assembled || text;
|
|
1430
|
-
const stopReason = ev.message
|
|
1649
|
+
const stopReason = ev.message
|
|
1650
|
+
.stopReason;
|
|
1431
1651
|
const endEvent = stopReason
|
|
1432
1652
|
? {
|
|
1433
1653
|
type: "message_end",
|
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure REST handlers for `/api/sessions/:sessionId/queue/*`.
|
|
3
|
-
*
|
|
4
|
-
* Same contract as sessions/projects handlers — pure functions that take
|
|
5
|
-
* `store` + `manager` dependencies, throw `BadRequestError`/`NotFoundError`
|
|
6
|
-
* on invalid input, and return typed responses.
|
|
7
|
-
*
|
|
8
|
-
* After mutating the queue, each handler calls `manager.pushQueueState()`
|
|
9
|
-
* to broadcast the updated queue to all subscribers via `queue_changed`.
|
|
10
|
-
*/
|
|
11
1
|
import type { SessionStreamManager } from "../session-stream.js";
|
|
12
2
|
import type { SessionStore } from "../storage.js";
|
|
3
|
+
import type { ImageAttachment } from "../wire.js";
|
|
13
4
|
export interface EnqueuePromptInputRaw {
|
|
14
5
|
content?: unknown;
|
|
6
|
+
images?: unknown;
|
|
15
7
|
}
|
|
16
|
-
/** Wire shape for a single queue item returned to the frontend. */
|
|
17
8
|
export interface WireQueueItem {
|
|
18
9
|
id: string;
|
|
19
10
|
content: string;
|
|
11
|
+
images?: ImageAttachment[];
|
|
20
12
|
position: number;
|
|
21
13
|
createdAt: string;
|
|
22
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/server/handlers/queue.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/server/handlers/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAgDD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,qBAAqB,GAC1B,aAAa,CAWf;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,GAChB,aAAa,EAAE,CAIjB;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI,CAKN;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,GAChB,IAAI,CAKN"}
|
|
@@ -1,40 +1,64 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure REST handlers for `/api/sessions/:sessionId/queue/*`.
|
|
3
|
-
*
|
|
4
|
-
* Same contract as sessions/projects handlers — pure functions that take
|
|
5
|
-
* `store` + `manager` dependencies, throw `BadRequestError`/`NotFoundError`
|
|
6
|
-
* on invalid input, and return typed responses.
|
|
7
|
-
*
|
|
8
|
-
* After mutating the queue, each handler calls `manager.pushQueueState()`
|
|
9
|
-
* to broadcast the updated queue to all subscribers via `queue_changed`.
|
|
10
|
-
*/
|
|
11
1
|
import { BadRequestError, NotFoundError } from "./errors.js";
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
|
|
2
|
+
function coerceImages(raw) {
|
|
3
|
+
if (!Array.isArray(raw))
|
|
4
|
+
return undefined;
|
|
5
|
+
const images = [];
|
|
6
|
+
for (const item of raw) {
|
|
7
|
+
if (item &&
|
|
8
|
+
typeof item === "object" &&
|
|
9
|
+
typeof item.data === "string" &&
|
|
10
|
+
typeof item.mimeType === "string") {
|
|
11
|
+
images.push({
|
|
12
|
+
data: item.data,
|
|
13
|
+
mimeType: item.mimeType,
|
|
14
|
+
width: typeof item.width === "number"
|
|
15
|
+
? item.width
|
|
16
|
+
: undefined,
|
|
17
|
+
height: typeof item.height === "number"
|
|
18
|
+
? item.height
|
|
19
|
+
: undefined,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
15
22
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
return images.length > 0 ? images : undefined;
|
|
24
|
+
}
|
|
25
|
+
function parseImagesJson(raw) {
|
|
26
|
+
if (!raw)
|
|
27
|
+
return undefined;
|
|
28
|
+
try {
|
|
29
|
+
return coerceImages(JSON.parse(raw));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function toWireQueueItem(row) {
|
|
21
36
|
return {
|
|
22
37
|
id: row.id,
|
|
23
38
|
content: row.content,
|
|
39
|
+
images: parseImagesJson(row.images_json),
|
|
24
40
|
position: row.position,
|
|
25
41
|
createdAt: row.created_at,
|
|
26
42
|
};
|
|
27
43
|
}
|
|
44
|
+
export function handleEnqueuePrompt(store, manager, sessionId, body) {
|
|
45
|
+
const content = typeof body.content === "string" ? body.content.trim() : "";
|
|
46
|
+
const images = coerceImages(body.images);
|
|
47
|
+
if (!content && !images?.length) {
|
|
48
|
+
throw new BadRequestError("content or images are required");
|
|
49
|
+
}
|
|
50
|
+
const session = store.getSession(sessionId);
|
|
51
|
+
if (!session)
|
|
52
|
+
throw new NotFoundError("Session not found");
|
|
53
|
+
const row = store.enqueuePrompt(sessionId, content, images);
|
|
54
|
+
manager.pushQueueState(sessionId);
|
|
55
|
+
return toWireQueueItem(row);
|
|
56
|
+
}
|
|
28
57
|
export function handleGetPromptQueue(store, sessionId) {
|
|
29
58
|
const session = store.getSession(sessionId);
|
|
30
59
|
if (!session)
|
|
31
60
|
throw new NotFoundError("Session not found");
|
|
32
|
-
return store.getPromptQueue(sessionId).map(
|
|
33
|
-
id: r.id,
|
|
34
|
-
content: r.content,
|
|
35
|
-
position: r.position,
|
|
36
|
-
createdAt: r.created_at,
|
|
37
|
-
}));
|
|
61
|
+
return store.getPromptQueue(sessionId).map(toWireQueueItem);
|
|
38
62
|
}
|
|
39
63
|
export function handleRemovePrompt(store, manager, sessionId, itemId) {
|
|
40
64
|
const session = store.getSession(sessionId);
|