@aexol/spectral 0.9.221 → 0.9.222

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 (153) hide show
  1. package/dist/agent/index.d.ts +7 -3
  2. package/dist/agent/index.d.ts.map +1 -1
  3. package/dist/agent/index.js +35 -8
  4. package/dist/extensions/aexol-backend-client.d.ts +85 -0
  5. package/dist/extensions/aexol-backend-client.d.ts.map +1 -0
  6. package/dist/extensions/aexol-backend-client.js +159 -0
  7. package/dist/extensions/aexol-mcp.d.ts +10 -6
  8. package/dist/extensions/aexol-mcp.d.ts.map +1 -1
  9. package/dist/extensions/aexol-mcp.js +21 -106
  10. package/dist/extensions/logic-trace-backend-draft.d.ts +68 -0
  11. package/dist/extensions/logic-trace-backend-draft.d.ts.map +1 -0
  12. package/dist/extensions/logic-trace-backend-draft.js +171 -0
  13. package/dist/extensions/logic-trace-validate.d.ts +33 -0
  14. package/dist/extensions/logic-trace-validate.d.ts.map +1 -0
  15. package/dist/extensions/logic-trace-validate.js +73 -0
  16. package/dist/extensions/logic-trace.d.ts +213 -0
  17. package/dist/extensions/logic-trace.d.ts.map +1 -0
  18. package/dist/extensions/logic-trace.js +1098 -0
  19. package/dist/generated/parser/lexer.d.ts +148 -0
  20. package/dist/generated/parser/lexer.d.ts.map +1 -0
  21. package/dist/generated/parser/lexer.js +877 -0
  22. package/dist/generated/parser/mod.d.ts +26 -0
  23. package/dist/generated/parser/mod.d.ts.map +1 -0
  24. package/dist/generated/parser/mod.js +30 -0
  25. package/dist/generated/parser/parser.d.ts +161 -0
  26. package/dist/generated/parser/parser.d.ts.map +1 -0
  27. package/dist/generated/parser/parser.js +754 -0
  28. package/dist/generated/parser/tokenizer.d.ts +107 -0
  29. package/dist/generated/parser/tokenizer.d.ts.map +1 -0
  30. package/dist/generated/parser/tokenizer.js +202 -0
  31. package/dist/generated/parser/validator.d.ts +45 -0
  32. package/dist/generated/parser/validator.d.ts.map +1 -0
  33. package/dist/generated/parser/validator.js +202 -0
  34. package/dist/generated/zeus/const.d.ts.map +1 -1
  35. package/dist/generated/zeus/const.js +4 -170
  36. package/dist/generated/zeus/index.d.ts +8 -873
  37. package/dist/generated/zeus/index.d.ts.map +1 -1
  38. package/dist/generated/zeus/index.js +0 -7
  39. package/dist/mcp/ui-stream-types.d.ts +2 -2
  40. package/dist/memory/tools/share-project-observation.js +1 -1
  41. package/dist/memory/tools/write-project-observation.d.ts.map +1 -1
  42. package/dist/memory/tools/write-project-observation.js +4 -3
  43. package/dist/sdk/agent-core/harness/types.d.ts +0 -1
  44. package/dist/sdk/agent-core/harness/types.d.ts.map +1 -1
  45. package/dist/sdk/agent-core/index.d.ts +0 -8
  46. package/dist/sdk/agent-core/index.d.ts.map +1 -1
  47. package/dist/sdk/agent-core/index.js +0 -9
  48. package/dist/sdk/coding-agent/core/extensions/index.d.ts +1 -1
  49. package/dist/sdk/coding-agent/core/extensions/index.d.ts.map +1 -1
  50. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +2 -2
  51. package/dist/sdk/coding-agent/core/extensions/types.d.ts +20 -1
  52. package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
  53. package/dist/sdk/coding-agent/core/tools/tool-definition-wrapper.d.ts +9 -4
  54. package/dist/sdk/coding-agent/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  55. package/dist/sdk/coding-agent/core/tools/tool-definition-wrapper.js +15 -2
  56. package/dist/sdk/coding-agent/index.d.ts +1 -1
  57. package/dist/sdk/coding-agent/index.d.ts.map +1 -1
  58. package/dist/server/agent-bridge/headless-ui.d.ts +20 -0
  59. package/dist/server/agent-bridge/headless-ui.d.ts.map +1 -0
  60. package/dist/server/agent-bridge/headless-ui.js +85 -0
  61. package/dist/server/agent-bridge/pricing.d.ts +37 -0
  62. package/dist/server/agent-bridge/pricing.d.ts.map +1 -0
  63. package/dist/server/agent-bridge/pricing.js +202 -0
  64. package/dist/server/agent-bridge/rehydrate.d.ts +35 -0
  65. package/dist/server/agent-bridge/rehydrate.d.ts.map +1 -0
  66. package/dist/server/agent-bridge/rehydrate.js +122 -0
  67. package/dist/server/agent-bridge/synthetic-model.d.ts +27 -0
  68. package/dist/server/agent-bridge/synthetic-model.d.ts.map +1 -0
  69. package/dist/server/agent-bridge/synthetic-model.js +92 -0
  70. package/dist/server/agent-bridge.d.ts +11 -1
  71. package/dist/server/agent-bridge.d.ts.map +1 -1
  72. package/dist/server/agent-bridge.js +42 -481
  73. package/dist/server/session-stream/compaction-events.d.ts +13 -0
  74. package/dist/server/session-stream/compaction-events.d.ts.map +1 -0
  75. package/dist/server/session-stream/compaction-events.js +32 -0
  76. package/dist/server/session-stream/history-prune.d.ts +35 -0
  77. package/dist/server/session-stream/history-prune.d.ts.map +1 -0
  78. package/dist/server/session-stream/history-prune.js +182 -0
  79. package/dist/server/session-stream/memory.d.ts +48 -0
  80. package/dist/server/session-stream/memory.d.ts.map +1 -0
  81. package/dist/server/session-stream/memory.js +136 -0
  82. package/dist/server/session-stream/replay-tail.d.ts +4 -0
  83. package/dist/server/session-stream/replay-tail.d.ts.map +1 -0
  84. package/dist/server/session-stream/replay-tail.js +39 -0
  85. package/dist/server/session-stream/turn-events.d.ts +33 -0
  86. package/dist/server/session-stream/turn-events.d.ts.map +1 -0
  87. package/dist/server/session-stream/turn-events.js +65 -0
  88. package/dist/server/session-stream/turn-silence.d.ts +8 -0
  89. package/dist/server/session-stream/turn-silence.d.ts.map +1 -0
  90. package/dist/server/session-stream/turn-silence.js +72 -0
  91. package/dist/server/session-stream.d.ts +137 -36
  92. package/dist/server/session-stream.d.ts.map +1 -1
  93. package/dist/server/session-stream.js +18 -509
  94. package/dist/server/storage/observation-ranking.d.ts +14 -0
  95. package/dist/server/storage/observation-ranking.d.ts.map +1 -0
  96. package/dist/server/storage/observation-ranking.js +37 -0
  97. package/dist/server/storage/preflight.d.ts +20 -0
  98. package/dist/server/storage/preflight.d.ts.map +1 -0
  99. package/dist/server/storage/preflight.js +22 -0
  100. package/dist/server/storage/project-binding.d.ts +5 -0
  101. package/dist/server/storage/project-binding.d.ts.map +1 -0
  102. package/dist/server/storage/project-binding.js +32 -0
  103. package/dist/server/storage/rows.d.ts +104 -0
  104. package/dist/server/storage/rows.d.ts.map +1 -0
  105. package/dist/server/storage/rows.js +2 -0
  106. package/dist/server/storage/schema.d.ts +20 -0
  107. package/dist/server/storage/schema.d.ts.map +1 -0
  108. package/dist/server/storage/schema.js +134 -0
  109. package/dist/server/storage/types.d.ts +66 -0
  110. package/dist/server/storage/types.d.ts.map +1 -0
  111. package/dist/server/storage/types.js +1 -0
  112. package/dist/server/storage.d.ts +7 -94
  113. package/dist/server/storage.d.ts.map +1 -1
  114. package/dist/server/storage.js +6 -224
  115. package/dist/server/wire.d.ts +6 -0
  116. package/dist/server/wire.d.ts.map +1 -1
  117. package/package.json +2 -1
  118. package/dist/sdk/agent-core/harness/agent-harness.d.ts +0 -92
  119. package/dist/sdk/agent-core/harness/agent-harness.d.ts.map +0 -1
  120. package/dist/sdk/agent-core/harness/agent-harness.js +0 -899
  121. package/dist/sdk/agent-core/harness/env/nodejs.d.ts +0 -51
  122. package/dist/sdk/agent-core/harness/env/nodejs.d.ts.map +0 -1
  123. package/dist/sdk/agent-core/harness/env/nodejs.js +0 -485
  124. package/dist/sdk/agent-core/harness/prompt-templates.d.ts +0 -48
  125. package/dist/sdk/agent-core/harness/prompt-templates.d.ts.map +0 -1
  126. package/dist/sdk/agent-core/harness/prompt-templates.js +0 -229
  127. package/dist/sdk/agent-core/harness/session/jsonl-repo.d.ts +0 -26
  128. package/dist/sdk/agent-core/harness/session/jsonl-repo.d.ts.map +0 -1
  129. package/dist/sdk/agent-core/harness/session/jsonl-repo.js +0 -100
  130. package/dist/sdk/agent-core/harness/session/jsonl-storage.d.ts +0 -33
  131. package/dist/sdk/agent-core/harness/session/jsonl-storage.d.ts.map +0 -1
  132. package/dist/sdk/agent-core/harness/session/jsonl-storage.js +0 -230
  133. package/dist/sdk/agent-core/harness/session/memory-repo.d.ts +0 -18
  134. package/dist/sdk/agent-core/harness/session/memory-repo.d.ts.map +0 -1
  135. package/dist/sdk/agent-core/harness/session/memory-repo.js +0 -41
  136. package/dist/sdk/agent-core/harness/session/memory-storage.d.ts +0 -25
  137. package/dist/sdk/agent-core/harness/session/memory-storage.d.ts.map +0 -1
  138. package/dist/sdk/agent-core/harness/session/memory-storage.js +0 -113
  139. package/dist/sdk/agent-core/harness/session/repo-utils.d.ts +0 -11
  140. package/dist/sdk/agent-core/harness/session/repo-utils.d.ts.map +0 -1
  141. package/dist/sdk/agent-core/harness/session/repo-utils.js +0 -38
  142. package/dist/sdk/agent-core/harness/skills.d.ts +0 -44
  143. package/dist/sdk/agent-core/harness/skills.d.ts.map +0 -1
  144. package/dist/sdk/agent-core/harness/skills.js +0 -310
  145. package/dist/sdk/agent-core/harness/utils/shell-output.d.ts +0 -14
  146. package/dist/sdk/agent-core/harness/utils/shell-output.d.ts.map +0 -1
  147. package/dist/sdk/agent-core/harness/utils/shell-output.js +0 -125
  148. package/dist/sdk/agent-core/node.d.ts +0 -3
  149. package/dist/sdk/agent-core/node.d.ts.map +0 -1
  150. package/dist/sdk/agent-core/node.js +0 -2
  151. package/dist/sdk/agent-core/proxy.d.ts +0 -69
  152. package/dist/sdk/agent-core/proxy.d.ts.map +0 -1
  153. package/dist/sdk/agent-core/proxy.js +0 -297
@@ -0,0 +1,85 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Headless UI context
3
+ // ---------------------------------------------------------------------------
4
+ import { createBridgeConfirm, createStdinConfirmIO, } from "../ui/terminal-confirm.js";
5
+ /**
6
+ * Detect the memory subsystem from a notification message text.
7
+ * The memory extension prefixes all messages with "Observational memory:".
8
+ */
9
+ function detectMemorySystem(message) {
10
+ if (!message.startsWith("Observational memory:"))
11
+ return undefined;
12
+ const lower = message.toLowerCase();
13
+ if (lower.includes("observer"))
14
+ return "memory_observer";
15
+ if (lower.includes("compaction") || lower.includes("compact"))
16
+ return "memory_compaction";
17
+ if (lower.includes("reflection") || lower.includes("reflect"))
18
+ return "memory_reflection";
19
+ if (lower.includes("pruner") || lower.includes("prune"))
20
+ return "memory_pruner";
21
+ // Keep generic observational-memory messages visible in the landing badge.
22
+ return "memory_observer";
23
+ }
24
+ /**
25
+ * Detect subsystem from notification messages by known prefix.
26
+ * Returns undefined for generic extension messages without a matching prefix.
27
+ */
28
+ function detectNotifSystem(message) {
29
+ if (message.startsWith("[spectral-vision]"))
30
+ return "vision";
31
+ return detectMemorySystem(message);
32
+ }
33
+ /**
34
+ * Create a minimal ExtensionUIContext that forwards `notify()` calls as
35
+ * `agent_notification` wire events, and renders `confirm()` as a terminal-
36
+ * native permission prompt on serve's stdout (the terminal `spectral serve`
37
+ * runs in) so extensions like desktop-control can request real user approval
38
+ * while the user chats via the web UI. A prompt also emits an
39
+ * `agent_notification` ("Approval required in terminal: …") so web users know
40
+ * to look at the terminal. Confirmations are serialized across sessions and
41
+ * auto-deny on timeout (SPECTRAL_CONFIRM_TIMEOUT_MS, default 5 min) or when
42
+ * stdin is not a TTY. All other interactive UI methods (select/input/custom/
43
+ * editor) remain no-ops resolving `undefined`.
44
+ */
45
+ export function createHeadlessUIContext(emit) {
46
+ const terminalIO = createStdinConfirmIO();
47
+ // Defer to a Proxy so we don't need to stub every method.
48
+ // `notify` and `confirm` are the methods called by extensions in serve mode
49
+ // (observational memory, MCP status bar updates, spectral-vision,
50
+ // desktop-control).
51
+ const handler = {
52
+ get(_target, prop) {
53
+ if (prop === "notify") {
54
+ return (message, type, metadata) => {
55
+ const level = type ?? "info";
56
+ const system = detectNotifSystem(message);
57
+ if (system?.startsWith("memory_")) {
58
+ console.info(`[AgentBridge][memory][${level}] ${message}`);
59
+ }
60
+ else if (system === "vision") {
61
+ console.info(`[AgentBridge][vision][${level}] ${message}`);
62
+ }
63
+ emit({
64
+ type: "agent_notification",
65
+ message,
66
+ level,
67
+ system,
68
+ dcpLiteTelemetry: metadata?.dcpLiteTelemetry,
69
+ });
70
+ };
71
+ }
72
+ // All other methods: return a no-op function or undefined.
73
+ // Methods that return Promises (select, confirm, input, custom, editor)
74
+ if (prop === "confirm") {
75
+ return createBridgeConfirm(emit, terminalIO);
76
+ }
77
+ // resolve to `undefined`. `confirm` above is the one interactive method
78
+ // with a real terminal-native implementation.
79
+ // Methods that set state (setStatus, setWorkingMessage, setWidget, etc.)
80
+ // are safe to no-op.
81
+ return () => undefined;
82
+ },
83
+ };
84
+ return new Proxy({}, handler);
85
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Model pricing and credit calculation helpers for the agent bridge.
3
+ *
4
+ * Extracted verbatim from `agent-bridge.ts`: the hardcoded per-model pricing
5
+ * table (USD per 1M tokens), vendor-prefix stripping, backend-credit-rate
6
+ * resolution and credit computation. Pure functions — no bridge state.
7
+ */
8
+ import type { AllowedModel } from "../../relay/models-fetch.js";
9
+ /**
10
+ * Strip vendor prefix to extract the bare model name.
11
+ * e.g. "openai/gpt-5.3-codex" → "gpt-5.3-codex".
12
+ * Returns the original string when no separator is present.
13
+ */
14
+ export declare function bareModelId(modelId: string): string;
15
+ /** Get model cost from backend credit rates, falling back to hardcoded pricing table. */
16
+ export declare function getModelCost(m: AllowedModel): {
17
+ input: number;
18
+ output: number;
19
+ cacheRead: number;
20
+ cacheWrite: number;
21
+ };
22
+ /** Look up pricing for a modelId. Returns null when unknown. */
23
+ export declare function lookupPricing(modelId: string): {
24
+ input: number;
25
+ output: number;
26
+ cacheWrite: number;
27
+ cacheRead: number;
28
+ } | null;
29
+ /**
30
+ * Calculate credits from token usage using per-model credit rates.
31
+ *
32
+ * NOTE: We intentionally keep higher precision here (no 2-decimal rounding)
33
+ * so small turns don't collapse to 0.00 in live UI aggregation. Rendering
34
+ * layers decide final display precision.
35
+ */
36
+ export declare function calculateCredits(inputTokens: number, outputTokens: number, cacheReadTokens?: number, cacheWriteTokens?: number, creditInputPer1M?: number | null, creditOutputPer1M?: number | null, creditCachedInputPer1M?: number | null, creditCacheReadPer1M?: number | null, creditCacheWritePer1M?: number | null): number;
37
+ //# sourceMappingURL=pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../../src/server/agent-bridge/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AA6HhE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,CAAC,EAAE,YAAY,GAAG;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CA2BA;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,IAAI,CAaP;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,GAAE,MAAU,EAC3B,gBAAgB,GAAE,MAAU,EAC5B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,EAChC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,EACjC,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,EACtC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,EACpC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,GACpC,MAAM,CA6BR"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Model pricing and credit calculation helpers for the agent bridge.
3
+ *
4
+ * Extracted verbatim from `agent-bridge.ts`: the hardcoded per-model pricing
5
+ * table (USD per 1M tokens), vendor-prefix stripping, backend-credit-rate
6
+ * resolution and credit computation. Pure functions — no bridge state.
7
+ */
8
+ /**
9
+ * Token pricing per model (USD per 1M tokens). Matches provider list
10
+ * prices as of May 2026. Used to compute token cost server-side when
11
+ * spectral's own cost field is unavailable (synthetic proxy models are
12
+ * registered with zero cost to avoid agent-side billing).
13
+ *
14
+ * Keys are matched as prefix substrings against modelId, so
15
+ * `"gpt-5"` covers both `gpt-5` and any
16
+ * future point-release.
17
+ */
18
+ const MODEL_PRICING = [
19
+ // OpenAI models
20
+ {
21
+ prefix: "gpt-5.5",
22
+ input: 1.75,
23
+ output: 14,
24
+ cacheWrite: 14,
25
+ cacheRead: 0.35,
26
+ },
27
+ {
28
+ prefix: "gpt-5.4",
29
+ input: 1.75,
30
+ output: 14,
31
+ cacheWrite: 14,
32
+ cacheRead: 0.35,
33
+ },
34
+ {
35
+ prefix: "gpt-5.3",
36
+ input: 1.75,
37
+ output: 14,
38
+ cacheWrite: 14,
39
+ cacheRead: 0.35,
40
+ },
41
+ {
42
+ prefix: "gpt-5.2",
43
+ input: 1.75,
44
+ output: 14,
45
+ cacheWrite: 14,
46
+ cacheRead: 0.35,
47
+ },
48
+ { prefix: "gpt-5.1", input: 2, output: 8, cacheWrite: 8, cacheRead: 0.5 },
49
+ { prefix: "gpt-5", input: 1.25, output: 10, cacheWrite: 10, cacheRead: 0.25 },
50
+ { prefix: "gpt-4.1", input: 2, output: 8, cacheWrite: 8, cacheRead: 0.5 },
51
+ { prefix: "gpt-4o", input: 2.5, output: 10, cacheWrite: 10, cacheRead: 1.25 },
52
+ { prefix: "gpt-4-turbo", input: 10, output: 30, cacheWrite: 0, cacheRead: 0 },
53
+ { prefix: "gpt-4", input: 30, output: 60, cacheWrite: 0, cacheRead: 0 },
54
+ {
55
+ prefix: "gpt-3.5-turbo",
56
+ input: 0.5,
57
+ output: 1.5,
58
+ cacheWrite: 0,
59
+ cacheRead: 0,
60
+ },
61
+ { prefix: "o1", input: 15, output: 60, cacheWrite: 0, cacheRead: 0 },
62
+ { prefix: "o3-mini", input: 1.1, output: 4.4, cacheWrite: 0, cacheRead: 0 },
63
+ { prefix: "o4-mini", input: 1.1, output: 4.4, cacheWrite: 0, cacheRead: 0 },
64
+ // Google Gemini models
65
+ {
66
+ prefix: "gemini-2.5-pro",
67
+ input: 1.25,
68
+ output: 10,
69
+ cacheWrite: 0,
70
+ cacheRead: 0,
71
+ },
72
+ {
73
+ prefix: "gemini-2.5-flash",
74
+ input: 0.15,
75
+ output: 0.6,
76
+ cacheWrite: 0,
77
+ cacheRead: 0,
78
+ },
79
+ // DeepSeek models
80
+ {
81
+ prefix: "deepseek-v3",
82
+ input: 0.27,
83
+ output: 1.1,
84
+ cacheWrite: 0,
85
+ cacheRead: 0,
86
+ },
87
+ {
88
+ prefix: "deepseek-r1",
89
+ input: 0.55,
90
+ output: 2.19,
91
+ cacheWrite: 0,
92
+ cacheRead: 0,
93
+ },
94
+ {
95
+ prefix: "deepseek/deepseek-v3",
96
+ input: 0.27,
97
+ output: 1.1,
98
+ cacheWrite: 0,
99
+ cacheRead: 0,
100
+ },
101
+ {
102
+ prefix: "deepseek/deepseek-r1",
103
+ input: 0.55,
104
+ output: 2.19,
105
+ cacheWrite: 0,
106
+ cacheRead: 0,
107
+ },
108
+ // Meta Llama models (common via OpenRouter-compatible endpoints)
109
+ {
110
+ prefix: "meta-llama/llama-4",
111
+ input: 0.2,
112
+ output: 0.8,
113
+ cacheWrite: 0,
114
+ cacheRead: 0,
115
+ },
116
+ {
117
+ prefix: "meta-llama/llama-3.3",
118
+ input: 0.2,
119
+ output: 0.5,
120
+ cacheWrite: 0,
121
+ cacheRead: 0,
122
+ },
123
+ ];
124
+ /**
125
+ * Strip vendor prefix to extract the bare model name.
126
+ * e.g. "openai/gpt-5.3-codex" → "gpt-5.3-codex".
127
+ * Returns the original string when no separator is present.
128
+ */
129
+ export function bareModelId(modelId) {
130
+ const idx = modelId.lastIndexOf("/");
131
+ return idx === -1 ? modelId : modelId.slice(idx + 1);
132
+ }
133
+ /** Get model cost from backend credit rates, falling back to hardcoded pricing table. */
134
+ export function getModelCost(m) {
135
+ // Prefer backend credit rates when configured
136
+ const hasCredits = m.creditInputPer1M != null ||
137
+ m.creditOutputPer1M != null ||
138
+ m.creditCacheReadPer1M != null ||
139
+ m.creditCacheWritePer1M != null;
140
+ if (hasCredits) {
141
+ return {
142
+ input: m.creditInputPer1M ?? 0,
143
+ output: m.creditOutputPer1M ?? 0,
144
+ cacheRead: m.creditCacheReadPer1M ?? 0,
145
+ cacheWrite: m.creditCacheWritePer1M ?? 0,
146
+ };
147
+ }
148
+ // Fall back to hardcoded pricing table when credits aren't configured
149
+ const pricing = lookupPricing(m.modelId);
150
+ return pricing
151
+ ? {
152
+ input: pricing.input,
153
+ output: pricing.output,
154
+ cacheRead: pricing.cacheRead,
155
+ cacheWrite: pricing.cacheWrite,
156
+ }
157
+ : { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
158
+ }
159
+ /** Look up pricing for a modelId. Returns null when unknown. */
160
+ export function lookupPricing(modelId) {
161
+ const bare = bareModelId(modelId);
162
+ for (const entry of MODEL_PRICING) {
163
+ if (modelId.startsWith(entry.prefix) || bare.startsWith(entry.prefix)) {
164
+ return {
165
+ input: entry.input,
166
+ output: entry.output,
167
+ cacheWrite: entry.cacheWrite,
168
+ cacheRead: entry.cacheRead,
169
+ };
170
+ }
171
+ }
172
+ return null;
173
+ }
174
+ /**
175
+ * Calculate credits from token usage using per-model credit rates.
176
+ *
177
+ * NOTE: We intentionally keep higher precision here (no 2-decimal rounding)
178
+ * so small turns don't collapse to 0.00 in live UI aggregation. Rendering
179
+ * layers decide final display precision.
180
+ */
181
+ export function calculateCredits(inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, creditInputPer1M, creditOutputPer1M, creditCachedInputPer1M, creditCacheReadPer1M, creditCacheWritePer1M) {
182
+ const inputRate = creditInputPer1M ?? 0;
183
+ const outputRate = creditOutputPer1M ?? 0;
184
+ const cachedInputRate = creditCachedInputPer1M ?? 0;
185
+ const cacheReadRate = creditCacheReadPer1M ?? 0;
186
+ const cacheWriteRate = creditCacheWritePer1M ?? 0;
187
+ const credits = (inputTokens / 1_000_000) * inputRate +
188
+ (outputTokens / 1_000_000) * outputRate +
189
+ (cacheReadTokens / 1_000_000) * cacheReadRate +
190
+ (cacheWriteTokens / 1_000_000) * cacheWriteRate;
191
+ if (inputRate === 0 &&
192
+ outputRate === 0 &&
193
+ cacheReadRate === 0 &&
194
+ cacheWriteRate === 0) {
195
+ if (cachedInputRate > 0) {
196
+ return (((inputTokens + cacheReadTokens + cacheWriteTokens) / 1_000_000) *
197
+ cachedInputRate);
198
+ }
199
+ return 0;
200
+ }
201
+ return credits;
202
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Wire-event parsing and content sanitization for session history
3
+ * rehydration. Extracted verbatim from `agent-bridge.ts`. Pure helpers —
4
+ * no bridge state.
5
+ */
6
+ import type { ImageContent, TextContent } from "../../sdk/ai/types.js";
7
+ import type { RestoredMemorySnapshot } from "../../memory/types.js";
8
+ import type { SessionMemorySnapshot } from "../storage.js";
9
+ interface ParsedWireToolEvents {
10
+ toolCalls: Array<{
11
+ id: string;
12
+ name: string;
13
+ arguments: Record<string, unknown> | undefined;
14
+ }>;
15
+ toolResults: Array<{
16
+ id: string;
17
+ result: unknown;
18
+ isError: boolean;
19
+ }>;
20
+ }
21
+ /**
22
+ * Parse the newline-delimited JSON of wire events persisted alongside an
23
+ * assistant message and extract all tool_call / tool_result events.
24
+ *
25
+ * These are used during session rehydration to reconstruct the full tool
26
+ * interaction history (call → result) so the LLM context is complete —
27
+ * without this, reconnected sessions would see only the assistant text
28
+ * and never the tool results, causing the model to get confused about
29
+ * the conversation state.
30
+ */
31
+ export declare function parseWireToolEvents(eventsJsonl: string): ParsedWireToolEvents;
32
+ export declare function sanitizeRehydratedBlock(block: TextContent | ImageContent): TextContent | ImageContent;
33
+ export declare function toRestoredMemorySnapshot(snapshot: SessionMemorySnapshot): RestoredMemorySnapshot;
34
+ export {};
35
+ //# sourceMappingURL=rehydrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydrate.d.ts","sourceRoot":"","sources":["../../../src/server/agent-bridge/rehydrate.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EACV,sBAAsB,EAEvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3D,UAAU,oBAAoB;IAC5B,SAAS,EAAE,KAAK,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;KAChD,CAAC,CAAC;IACH,WAAW,EAAE,KAAK,CAAC;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,OAAO,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;CACJ;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CA8B7E;AAmBD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,WAAW,GAAG,YAAY,GAChC,WAAW,GAAG,YAAY,CA2C5B;AAcD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,qBAAqB,GAC9B,sBAAsB,CAQxB"}
@@ -0,0 +1,122 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Wire event parsing for history rehydration
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Parse the newline-delimited JSON of wire events persisted alongside an
6
+ * assistant message and extract all tool_call / tool_result events.
7
+ *
8
+ * These are used during session rehydration to reconstruct the full tool
9
+ * interaction history (call → result) so the LLM context is complete —
10
+ * without this, reconnected sessions would see only the assistant text
11
+ * and never the tool results, causing the model to get confused about
12
+ * the conversation state.
13
+ */
14
+ export function parseWireToolEvents(eventsJsonl) {
15
+ const toolCalls = [];
16
+ const toolResults = [];
17
+ if (!eventsJsonl)
18
+ return { toolCalls, toolResults };
19
+ for (const line of eventsJsonl.split("\n")) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed)
22
+ continue;
23
+ try {
24
+ const ev = JSON.parse(trimmed);
25
+ if (ev.type === "tool_call") {
26
+ toolCalls.push({
27
+ id: ev.id,
28
+ name: ev.name,
29
+ arguments: ev.args,
30
+ });
31
+ }
32
+ else if (ev.type === "tool_result") {
33
+ toolResults.push({
34
+ id: ev.id,
35
+ result: ev.result,
36
+ isError: ev.isError ?? false,
37
+ });
38
+ }
39
+ }
40
+ catch {
41
+ // Skip malformed JSON lines — they won't be meaningful for
42
+ // rehydration and we'd rather keep the session alive than fail.
43
+ }
44
+ }
45
+ return { toolCalls, toolResults };
46
+ }
47
+ /**
48
+ * Safety clean-up for content blocks rehydrated from persisted wire events.
49
+ *
50
+ * Rehydrated tool-result content is bounded for the model-facing context,
51
+ * while the original wire event remains available to the UI for rendering
52
+ * images and other rich results.
53
+ */
54
+ const MAX_REHYDRATED_TEXT = 100_000;
55
+ /**
56
+ * Ceiling for blocks that already carry an offload footer (`Full output:`).
57
+ * These blocks are a bounded preview + a re-readable path, so cutting them
58
+ * with the generic limit would destroy the footer. Keep them whole up to
59
+ * this size; larger ones lose their middle but keep head + footer.
60
+ */
61
+ const MAX_REHYDRATED_OFFLOAD_TEXT = 200_000;
62
+ export function sanitizeRehydratedBlock(block) {
63
+ // Tool-result image blocks are not accepted by every provider; the raw
64
+ // result remains in events_jsonl for the UI, while the model gets a compact reference.
65
+ if (block.type === "image") {
66
+ // Hosted images carry a url instead of inline bytes — print that rather
67
+ // than a meaningless "0 bytes base64".
68
+ const hostedUrl = typeof block.url === "string" && block.url ? block.url : undefined;
69
+ const descriptor = hostedUrl
70
+ ? `[Image: ${block.mimeType ?? "image/png"}, url: ${hostedUrl}]`
71
+ : `[Image: ${block.mimeType ?? "image/png"}, ${(typeof block.data === "string" ? block.data.length : 0).toLocaleString()} bytes base64]`;
72
+ return {
73
+ type: "text",
74
+ text: descriptor +
75
+ `\n(Image data is available in the raw MCP result.)`,
76
+ };
77
+ }
78
+ // Truncate oversized text blocks to prevent context overflow.
79
+ if (block.type !== "text")
80
+ return block;
81
+ if (block.text.includes("Full output:")) {
82
+ if (block.text.length <= MAX_REHYDRATED_OFFLOAD_TEXT)
83
+ return block;
84
+ // Keep the head (preview) and the tail (offload footer), drop the middle.
85
+ const keepHead = Math.floor(MAX_REHYDRATED_OFFLOAD_TEXT * 0.7);
86
+ const keepTail = MAX_REHYDRATED_OFFLOAD_TEXT - keepHead;
87
+ const omitted = block.text.length - keepHead - keepTail;
88
+ return {
89
+ ...block,
90
+ text: block.text.slice(0, keepHead) +
91
+ `\n\n[... ${omitted.toLocaleString()} bytes omitted from rehydrated offload preview ...]\n\n` +
92
+ block.text.slice(block.text.length - keepTail),
93
+ };
94
+ }
95
+ if (block.text.length <= MAX_REHYDRATED_TEXT)
96
+ return block;
97
+ const omitted = block.text.length - MAX_REHYDRATED_TEXT;
98
+ return {
99
+ ...block,
100
+ text: block.text.slice(0, MAX_REHYDRATED_TEXT) +
101
+ `\n\n[truncated — ${omitted.toLocaleString()} bytes omitted]`,
102
+ };
103
+ }
104
+ function normalizeSnapshotDetails(details) {
105
+ if (details.version === 4)
106
+ return details;
107
+ return {
108
+ type: "observational-memory",
109
+ version: 4,
110
+ observations: details.observations,
111
+ reflections: details.reflections,
112
+ };
113
+ }
114
+ export function toRestoredMemorySnapshot(snapshot) {
115
+ return {
116
+ type: "observational-memory-snapshot",
117
+ version: 1,
118
+ coveredSourceCount: snapshot.coveredSourceCount,
119
+ summary: snapshot.summary,
120
+ details: normalizeSnapshotDetails(snapshot.details),
121
+ };
122
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Synthetic (backend-proxied) model capability inference for the agent bridge.
3
+ *
4
+ * Extracted verbatim from `agent-bridge.ts`: given a backend `AllowedModel`,
5
+ * infer the spectral `Model` fields (context window, thinking-level map,
6
+ * OpenAI-completions compat flags) used when registering the synthetic proxy
7
+ * providers with spectral's `ModelRegistry`. Pure functions — no bridge state.
8
+ */
9
+ import type { Api, Model } from "../../sdk/ai/types.js";
10
+ import type { AllowedModel } from "../../relay/models-fetch.js";
11
+ /**
12
+ * Synthetic provider names registered with spectral's `ModelRegistry`. They route
13
+ * 100% of inference traffic through the backend (`${backendUrl}/v1`), which
14
+ * authenticates with the machine JWT and forwards to the upstream provider
15
+ * with centrally-managed API keys.
16
+ *
17
+ * Two providers (rather than one): `spectral-proxy-openai` for upstream catalog
18
+ * models (OpenRouter, DeepSeek, OpenAI, etc.), and
19
+ * `spectral-proxy-user-model` for team-registered built-in UserModel entries.
20
+ * Both use the OpenAI-compatible `/chat/completions` request shape.
21
+ */
22
+ export declare const SPECTRAL_PROXY_OPENAI = "spectral-proxy-openai";
23
+ export declare const SPECTRAL_PROXY_USER_MODEL = "spectral-proxy-user-model";
24
+ export declare function inferSyntheticContextWindow(model: AllowedModel): number;
25
+ export declare function inferSyntheticThinkingLevelMap(model: AllowedModel): Model<Api>["thinkingLevelMap"] | undefined;
26
+ export declare function inferSyntheticOpenAICompat(model: AllowedModel): Model<"openai-completions">["compat"] | undefined;
27
+ //# sourceMappingURL=synthetic-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synthetic-model.d.ts","sourceRoot":"","sources":["../../../src/server/agent-bridge/synthetic-model.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAC7D,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AAGrE,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAmBvE;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,YAAY,GAClB,KAAK,CAAC,GAAG,CAAC,CAAC,kBAAkB,CAAC,GAAG,SAAS,CAkB5C;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,YAAY,GAClB,KAAK,CAAC,oBAAoB,CAAC,CAAC,QAAQ,CAAC,GAAG,SAAS,CA0CnD"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Synthetic (backend-proxied) model capability inference for the agent bridge.
3
+ *
4
+ * Extracted verbatim from `agent-bridge.ts`: given a backend `AllowedModel`,
5
+ * infer the spectral `Model` fields (context window, thinking-level map,
6
+ * OpenAI-completions compat flags) used when registering the synthetic proxy
7
+ * providers with spectral's `ModelRegistry`. Pure functions — no bridge state.
8
+ */
9
+ import { bareModelId } from "./pricing.js";
10
+ /**
11
+ * Synthetic provider names registered with spectral's `ModelRegistry`. They route
12
+ * 100% of inference traffic through the backend (`${backendUrl}/v1`), which
13
+ * authenticates with the machine JWT and forwards to the upstream provider
14
+ * with centrally-managed API keys.
15
+ *
16
+ * Two providers (rather than one): `spectral-proxy-openai` for upstream catalog
17
+ * models (OpenRouter, DeepSeek, OpenAI, etc.), and
18
+ * `spectral-proxy-user-model` for team-registered built-in UserModel entries.
19
+ * Both use the OpenAI-compatible `/chat/completions` request shape.
20
+ */
21
+ export const SPECTRAL_PROXY_OPENAI = "spectral-proxy-openai";
22
+ export const SPECTRAL_PROXY_USER_MODEL = "spectral-proxy-user-model";
23
+ const KIMI_K2_7_CODE_CONTEXT_WINDOW = 262_144;
24
+ export function inferSyntheticContextWindow(model) {
25
+ if (typeof model.contextWindow === "number" &&
26
+ Number.isFinite(model.contextWindow)) {
27
+ return model.contextWindow;
28
+ }
29
+ const modelId = model.modelId.toLowerCase();
30
+ const bare = bareModelId(modelId);
31
+ if (modelId === "moonshotai/kimi-k2.7-code" ||
32
+ bare === "kimi-k2.7-code" ||
33
+ bare === "local-openrouter-kimi") {
34
+ return KIMI_K2_7_CODE_CONTEXT_WINDOW;
35
+ }
36
+ return 0;
37
+ }
38
+ export function inferSyntheticThinkingLevelMap(model) {
39
+ if (!model.supportsReasoning)
40
+ return undefined;
41
+ const modelId = model.modelId.toLowerCase();
42
+ const bare = bareModelId(modelId);
43
+ if (model.provider === "built-in" &&
44
+ (modelId.includes("gpt-5") || bare.includes("gpt-5"))) {
45
+ return {
46
+ off: "none",
47
+ minimal: "minimal",
48
+ low: "low",
49
+ medium: "medium",
50
+ high: "high",
51
+ xhigh: "xhigh",
52
+ };
53
+ }
54
+ return undefined;
55
+ }
56
+ export function inferSyntheticOpenAICompat(model) {
57
+ const bare = bareModelId(model.modelId);
58
+ const isDeepSeek = model.provider === "deepseek" ||
59
+ model.modelId.startsWith("deepseek/") ||
60
+ bare.startsWith("deepseek");
61
+ if (isDeepSeek) {
62
+ return {
63
+ thinkingFormat: "deepseek",
64
+ requiresReasoningContentOnAssistantMessages: true,
65
+ supportsDeveloperRole: false,
66
+ };
67
+ }
68
+ if (model.provider === "built-in") {
69
+ const compat = {
70
+ // Built-in models are proxied through the backend, which forwards a
71
+ // session-affinity key to OpenRouter for prompt-cache sticky routing.
72
+ // Sending the real per-session id from the CLI lets the backend use it
73
+ // instead of a coarser machine/team fallback, maximizing cache hits on
74
+ // large multi-turn agentic prompts.
75
+ sendSessionAffinityHeaders: true,
76
+ };
77
+ if (model.modelId.toLowerCase().includes("gpt-5") ||
78
+ bare.toLowerCase().includes("gpt-5")) {
79
+ compat.thinkingFormat = "openai";
80
+ compat.supportsReasoningEffort = true;
81
+ compat.supportsStore = false;
82
+ }
83
+ return compat;
84
+ }
85
+ if (model.provider === "openrouter") {
86
+ const compat = {
87
+ thinkingFormat: "openrouter",
88
+ };
89
+ return compat;
90
+ }
91
+ return undefined;
92
+ }
@@ -54,6 +54,10 @@ import { fetchAllowedModels as defaultFetchAllowedModels } from "../relay/models
54
54
  import type { SessionMemorySnapshot } from "./storage.js";
55
55
  import type { ImageAttachment, ServerEvent, WireMessage } from "./wire.js";
56
56
  import type { DevProcessRegistry } from "./dev-process-registry.js";
57
+ export { bareModelId, calculateCredits, getModelCost, lookupPricing, } from "./agent-bridge/pricing.js";
58
+ export { SPECTRAL_PROXY_OPENAI, SPECTRAL_PROXY_USER_MODEL, inferSyntheticContextWindow, inferSyntheticOpenAICompat, inferSyntheticThinkingLevelMap, } from "./agent-bridge/synthetic-model.js";
59
+ export { parseWireToolEvents, sanitizeRehydratedBlock, toRestoredMemorySnapshot, } from "./agent-bridge/rehydrate.js";
60
+ export { createHeadlessUIContext } from "./agent-bridge/headless-ui.js";
57
61
  interface AgentBridgeCompactionOptions {
58
62
  customInstructions?: string;
59
63
  phaseBoundary?: boolean;
@@ -218,6 +222,13 @@ export declare class AgentBridge {
218
222
  * timer flushes it once the minimum interval has elapsed.
219
223
  */
220
224
  private subagentProgressThrottle;
225
+ /**
226
+ * toolCallId → startedAt captured at `tool_execution_start` so the
227
+ * terminal `tool_result` can carry the real start time (durations
228
+ * end-to-end). Entries with no end event are bounded: more than 2000
229
+ * orphans means ids are never resolving, so the oldest are cleared.
230
+ */
231
+ private toolStartedAt;
221
232
  constructor(opts: AgentBridgeOptions);
222
233
  /**
223
234
  * Create the spectral session, wire up subscription, and return.
@@ -501,5 +512,4 @@ export declare class AgentBridge {
501
512
  */
502
513
  handleEvent(ev: AgentSessionEvent): void;
503
514
  }
504
- export {};
505
515
  //# sourceMappingURL=agent-bridge.d.ts.map