@aexol/spectral 0.9.81 → 0.9.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/agent/index.js +2 -2
  2. package/dist/memory/tools/compact-context.d.ts.map +1 -1
  3. package/dist/memory/tools/compact-context.js +7 -4
  4. package/dist/sdk/agent-core/harness/agent-harness.js +1 -1
  5. package/dist/sdk/agent-core/harness/types.d.ts +2 -0
  6. package/dist/sdk/agent-core/harness/types.d.ts.map +1 -1
  7. package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
  8. package/dist/sdk/ai/providers/openai-completions.js +435 -92
  9. package/dist/sdk/ai/types.d.ts +4 -0
  10. package/dist/sdk/ai/types.d.ts.map +1 -1
  11. package/dist/sdk/coding-agent/core/agent-session.d.ts +1 -1
  12. package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
  13. package/dist/sdk/coding-agent/core/agent-session.js +1 -1
  14. package/dist/sdk/coding-agent/core/extensions/runner.d.ts +3 -1
  15. package/dist/sdk/coding-agent/core/extensions/runner.d.ts.map +1 -1
  16. package/dist/sdk/coding-agent/core/extensions/runner.js +6 -2
  17. package/dist/sdk/coding-agent/core/extensions/types.d.ts +2 -0
  18. package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
  19. package/dist/sdk/coding-agent/core/sdk.d.ts +2 -2
  20. package/dist/sdk/coding-agent/core/sdk.js +2 -2
  21. package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
  22. package/dist/sdk/coding-agent/core/system-prompt.js +22 -4
  23. package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts +27 -0
  24. package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts.map +1 -0
  25. package/dist/sdk/coding-agent/core/tools/apply-patch.js +353 -0
  26. package/dist/sdk/coding-agent/core/tools/edit-diff.d.ts +1 -6
  27. package/dist/sdk/coding-agent/core/tools/edit-diff.d.ts.map +1 -1
  28. package/dist/sdk/coding-agent/core/tools/edit-diff.js +179 -100
  29. package/dist/sdk/coding-agent/core/tools/edit.d.ts.map +1 -1
  30. package/dist/sdk/coding-agent/core/tools/edit.js +9 -5
  31. package/dist/sdk/coding-agent/core/tools/index.d.ts +4 -1
  32. package/dist/sdk/coding-agent/core/tools/index.d.ts.map +1 -1
  33. package/dist/sdk/coding-agent/core/tools/index.js +11 -0
  34. package/dist/sdk/coding-agent/index.d.ts +1 -1
  35. package/dist/sdk/coding-agent/index.d.ts.map +1 -1
  36. package/dist/sdk/coding-agent/index.js +1 -1
  37. package/dist/server/agent-bridge.d.ts.map +1 -1
  38. package/dist/server/agent-bridge.js +274 -54
  39. package/dist/server/handlers/queue.d.ts +3 -11
  40. package/dist/server/handlers/queue.d.ts.map +1 -1
  41. package/dist/server/handlers/queue.js +48 -24
  42. package/dist/server/session-stream.d.ts.map +1 -1
  43. package/dist/server/session-stream.js +43 -9
  44. package/dist/server/storage.d.ts +2 -1
  45. package/dist/server/storage.d.ts.map +1 -1
  46. package/dist/server/storage.js +16 -8
  47. package/dist/server/wire.d.ts +11 -1
  48. package/dist/server/wire.d.ts.map +1 -1
  49. 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
- { prefix: "claude-opus-4", input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.50 },
146
- { prefix: "claude-sonnet-4", input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
147
- { prefix: "claude-3-5-sonnet", input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
148
- { prefix: "claude-3-5-haiku", input: 0.80, output: 4, cacheWrite: 1, cacheRead: 0.08 },
149
- { prefix: "claude-3-opus", input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.50 },
150
- { prefix: "claude-3-sonnet", input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
151
- { prefix: "claude-3-haiku", input: 0.25, output: 1.25, cacheWrite: 1.25, cacheRead: 0.025 },
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
- { prefix: "gpt-5.5", input: 1.75, output: 14, cacheWrite: 14, cacheRead: 0.35 },
154
- { prefix: "gpt-5.4", input: 1.75, output: 14, cacheWrite: 14, cacheRead: 0.35 },
155
- { prefix: "gpt-5.3", input: 1.75, output: 14, cacheWrite: 14, cacheRead: 0.35 },
156
- { prefix: "gpt-5.2", input: 1.75, output: 14, cacheWrite: 14, cacheRead: 0.35 },
157
- { prefix: "gpt-5.1", input: 2, output: 8, cacheWrite: 8, cacheRead: 0.50 },
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.50 },
160
- { prefix: "gpt-4o", input: 2.50, output: 10, cacheWrite: 10, cacheRead: 1.25 },
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
- { prefix: "gpt-3.5-turbo", input: 0.50, output: 1.50, cacheWrite: 0, cacheRead: 0 },
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.10, output: 4.40, cacheWrite: 0, cacheRead: 0 },
166
- { prefix: "o4-mini", input: 1.10, output: 4.40, cacheWrite: 0, cacheRead: 0 },
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
- { prefix: "gemini-2.5-pro", input: 1.25, output: 10, cacheWrite: 0, cacheRead: 0 },
169
- { prefix: "gemini-2.5-flash", input: 0.15, output: 0.60, cacheWrite: 0, cacheRead: 0 },
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
- { prefix: "deepseek-v3", input: 0.27, output: 1.10, cacheWrite: 0, cacheRead: 0 },
172
- { prefix: "deepseek-r1", input: 0.55, output: 2.19, cacheWrite: 0, cacheRead: 0 },
173
- { prefix: "deepseek/deepseek-v3", input: 0.27, output: 1.10, cacheWrite: 0, cacheRead: 0 },
174
- { prefix: "deepseek/deepseek-r1", input: 0.55, output: 2.19, cacheWrite: 0, cacheRead: 0 },
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
- { prefix: "meta-llama/llama-4", input: 0.20, output: 0.80, cacheWrite: 0, cacheRead: 0 },
177
- { prefix: "meta-llama/llama-3.3", input: 0.20, output: 0.50, cacheWrite: 0, cacheRead: 0 },
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" && Number.isFinite(model.contextWindow)) {
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
- ? { input: pricing.input, output: pricing.output, cacheRead: pricing.cacheRead, cacheWrite: pricing.cacheWrite }
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 && outputRate === 0 && cacheReadRate === 0 && cacheWriteRate === 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) * cachedInputRate;
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, kanbanBridgeExtension, async (ext) => { spectralVisionExtension(ext); }, async (ext) => { designerExtension(ext); }, async (ext) => { observationalMemory(ext); }
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 ?? loadAgentSettings(this.opts.cwd, this.opts.agentDir);
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) => { mcpAdapterFactory(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, [".opencode/skills", ".aexol/skills"]);
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: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
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 { /* best-effort */ }
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" || lower.includes("reflector") || lower.includes("reflecting")) {
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" || lower.includes("pruner") || lower.includes("pruning") || lower.includes("prune")) {
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" || lower.includes("compaction") || lower.includes("compact")) {
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 ? "chain"
1278
- : args?.tasks?.length > 0 ? "parallel"
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.partialResult;
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" ? runningResult.thinkingText : undefined;
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" ? details.results[details.results.length - 1]?.step : undefined,
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
- }), { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, cost: 0, turns: 0 });
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.stopReason;
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;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGlD,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,qBAAqB,GAC1B,aAAa,CAgBf;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,GAChB,aAAa,EAAE,CAUjB;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI,CAMN;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN"}
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
- export function handleEnqueuePrompt(store, manager, sessionId, body) {
13
- if (typeof body.content !== "string" || !body.content.trim()) {
14
- throw new BadRequestError("content (non-empty string) is required");
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
- const session = store.getSession(sessionId);
17
- if (!session)
18
- throw new NotFoundError("Session not found");
19
- const row = store.enqueuePrompt(sessionId, body.content);
20
- manager.pushQueueState(sessionId);
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((r) => ({
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);