@aexol/spectral 0.7.1 → 0.7.5

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 (219) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/agent/agents.js +1 -1
  3. package/dist/agent/index.js +199 -184
  4. package/dist/commands/serve.js +0 -3
  5. package/dist/designer/data/systems/renault/DESIGN.md +1 -1
  6. package/dist/designer/philosophies.js +668 -0
  7. package/dist/mcp/sampling-handler.js +1 -1
  8. package/dist/memory/commands/status.js +1 -1
  9. package/dist/memory/compaction.js +2 -2
  10. package/dist/memory/config.js +1 -1
  11. package/dist/memory/debug-log.js +1 -1
  12. package/dist/memory/hooks/compaction-hook.js +29 -0
  13. package/dist/memory/index.js +2 -0
  14. package/dist/memory/observer.js +2 -2
  15. package/dist/memory/project-observations-store.js +14 -0
  16. package/dist/memory/tokens.js +1 -1
  17. package/dist/memory/tools/read-project-observations.js +82 -0
  18. package/dist/memory/tools/recall-observation.js +2 -2
  19. package/dist/pi/agent-core/agent-loop.js +501 -0
  20. package/dist/pi/agent-core/agent.js +401 -0
  21. package/dist/pi/agent-core/harness/agent-harness.js +899 -0
  22. package/dist/pi/agent-core/harness/compaction/branch-summarization.js +173 -0
  23. package/dist/pi/agent-core/harness/compaction/compaction.js +532 -0
  24. package/dist/pi/agent-core/harness/compaction/utils.js +130 -0
  25. package/dist/pi/agent-core/harness/env/nodejs.js +485 -0
  26. package/dist/pi/agent-core/harness/messages.js +101 -0
  27. package/dist/pi/agent-core/harness/prompt-templates.js +229 -0
  28. package/dist/pi/agent-core/harness/session/jsonl-repo.js +100 -0
  29. package/dist/pi/agent-core/harness/session/jsonl-storage.js +230 -0
  30. package/dist/pi/agent-core/harness/session/memory-repo.js +41 -0
  31. package/dist/pi/agent-core/harness/session/memory-storage.js +113 -0
  32. package/dist/pi/agent-core/harness/session/repo-utils.js +38 -0
  33. package/dist/pi/agent-core/harness/session/session.js +196 -0
  34. package/dist/pi/agent-core/harness/session/uuid.js +49 -0
  35. package/dist/pi/agent-core/harness/skills.js +310 -0
  36. package/dist/pi/agent-core/harness/system-prompt.js +29 -0
  37. package/dist/pi/agent-core/harness/types.js +93 -0
  38. package/dist/pi/agent-core/harness/utils/shell-output.js +125 -0
  39. package/dist/pi/agent-core/harness/utils/truncate.js +289 -0
  40. package/dist/pi/agent-core/index.js +24 -0
  41. package/dist/pi/agent-core/node.js +2 -0
  42. package/dist/pi/agent-core/proxy.js +277 -0
  43. package/dist/pi/agent-core/types.js +1 -0
  44. package/dist/pi/ai/api-registry.js +43 -0
  45. package/dist/pi/ai/cli.js +120 -0
  46. package/dist/pi/ai/env-api-keys.js +169 -0
  47. package/dist/pi/ai/image-models.generated.js +441 -0
  48. package/dist/pi/ai/image-models.js +22 -0
  49. package/dist/pi/ai/images-api-registry.js +21 -0
  50. package/dist/pi/ai/images.js +13 -0
  51. package/dist/pi/ai/index.js +18 -0
  52. package/dist/pi/ai/models.generated.js +16220 -0
  53. package/dist/pi/ai/models.js +70 -0
  54. package/dist/pi/ai/oauth.js +1 -0
  55. package/dist/pi/ai/providers/anthropic.js +945 -0
  56. package/dist/pi/ai/providers/faux.js +367 -0
  57. package/dist/pi/ai/providers/github-copilot-headers.js +28 -0
  58. package/dist/pi/ai/providers/openai-completions.js +945 -0
  59. package/dist/pi/ai/providers/openai-prompt-cache.js +9 -0
  60. package/dist/pi/ai/providers/register-builtins.js +97 -0
  61. package/dist/pi/ai/providers/simple-options.js +40 -0
  62. package/dist/pi/ai/providers/transform-messages.js +183 -0
  63. package/dist/pi/ai/session-resources.js +21 -0
  64. package/dist/pi/ai/stream.js +26 -0
  65. package/dist/pi/ai/types.js +1 -0
  66. package/dist/pi/ai/utils/diagnostics.js +24 -0
  67. package/dist/pi/ai/utils/event-stream.js +80 -0
  68. package/dist/pi/ai/utils/hash.js +13 -0
  69. package/dist/pi/ai/utils/headers.js +7 -0
  70. package/dist/pi/ai/utils/json-parse.js +112 -0
  71. package/dist/pi/ai/utils/node-http-proxy.js +96 -0
  72. package/dist/pi/ai/utils/oauth/anthropic.js +334 -0
  73. package/dist/pi/ai/utils/oauth/device-code.js +54 -0
  74. package/dist/pi/ai/utils/oauth/github-copilot.js +270 -0
  75. package/dist/pi/ai/utils/oauth/index.js +121 -0
  76. package/dist/pi/ai/utils/oauth/oauth-page.js +104 -0
  77. package/dist/pi/ai/utils/oauth/openai-codex.js +384 -0
  78. package/dist/pi/ai/utils/oauth/pkce.js +30 -0
  79. package/dist/pi/ai/utils/oauth/types.js +1 -0
  80. package/dist/pi/ai/utils/overflow.js +150 -0
  81. package/dist/pi/ai/utils/sanitize-unicode.js +25 -0
  82. package/dist/pi/ai/utils/typebox-helpers.js +20 -0
  83. package/dist/pi/ai/utils/validation.js +280 -0
  84. package/dist/pi/coding-agent/bun/cli.js +7 -0
  85. package/dist/pi/coding-agent/bun/restore-sandbox-env.js +31 -0
  86. package/dist/pi/coding-agent/cli/args.js +340 -0
  87. package/dist/pi/coding-agent/cli/file-processor.js +82 -0
  88. package/dist/pi/coding-agent/cli/initial-message.js +21 -0
  89. package/dist/pi/coding-agent/cli.js +17 -0
  90. package/dist/pi/coding-agent/config.js +414 -0
  91. package/dist/pi/coding-agent/core/agent-session-runtime.js +299 -0
  92. package/dist/pi/coding-agent/core/agent-session-services.js +117 -0
  93. package/dist/pi/coding-agent/core/agent-session.js +2498 -0
  94. package/dist/pi/coding-agent/core/auth-guidance.js +20 -0
  95. package/dist/pi/coding-agent/core/auth-storage.js +441 -0
  96. package/dist/pi/coding-agent/core/bash-executor.js +110 -0
  97. package/dist/pi/coding-agent/core/compaction/branch-summarization.js +242 -0
  98. package/dist/pi/coding-agent/core/compaction/compaction.js +624 -0
  99. package/dist/pi/coding-agent/core/compaction/index.js +6 -0
  100. package/dist/pi/coding-agent/core/compaction/utils.js +152 -0
  101. package/dist/pi/coding-agent/core/defaults.js +1 -0
  102. package/dist/pi/coding-agent/core/diagnostics.js +1 -0
  103. package/dist/pi/coding-agent/core/event-bus.js +24 -0
  104. package/dist/pi/coding-agent/core/exec.js +74 -0
  105. package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +248 -0
  106. package/dist/pi/coding-agent/core/export-html/index.js +225 -0
  107. package/dist/pi/coding-agent/core/export-html/tool-renderer.js +107 -0
  108. package/dist/pi/coding-agent/core/extensions/index.js +8 -0
  109. package/dist/pi/coding-agent/core/extensions/loader.js +485 -0
  110. package/dist/pi/coding-agent/core/extensions/runner.js +824 -0
  111. package/dist/pi/coding-agent/core/extensions/types.js +44 -0
  112. package/dist/pi/coding-agent/core/extensions/wrapper.js +21 -0
  113. package/dist/pi/coding-agent/core/footer-data-provider.js +309 -0
  114. package/dist/pi/coding-agent/core/http-dispatcher.js +47 -0
  115. package/dist/pi/coding-agent/core/index.js +11 -0
  116. package/dist/pi/coding-agent/core/keybindings.js +294 -0
  117. package/dist/pi/coding-agent/core/messages.js +122 -0
  118. package/dist/pi/coding-agent/core/model-registry.js +728 -0
  119. package/dist/pi/coding-agent/core/model-resolver.js +494 -0
  120. package/dist/pi/coding-agent/core/output-guard.js +58 -0
  121. package/dist/pi/coding-agent/core/package-manager.js +2020 -0
  122. package/dist/pi/coding-agent/core/prompt-templates.js +237 -0
  123. package/dist/pi/coding-agent/core/provider-display-names.js +32 -0
  124. package/dist/pi/coding-agent/core/resolve-config-value.js +125 -0
  125. package/dist/pi/coding-agent/core/resource-loader.js +733 -0
  126. package/dist/pi/coding-agent/core/sdk.js +282 -0
  127. package/dist/pi/coding-agent/core/session-cwd.js +37 -0
  128. package/dist/pi/coding-agent/core/session-manager.js +1146 -0
  129. package/dist/pi/coding-agent/core/settings-manager.js +794 -0
  130. package/dist/pi/coding-agent/core/skills.js +386 -0
  131. package/dist/pi/coding-agent/core/slash-commands.js +24 -0
  132. package/dist/pi/coding-agent/core/source-info.js +18 -0
  133. package/dist/pi/coding-agent/core/system-prompt.js +122 -0
  134. package/dist/pi/coding-agent/core/telemetry.js +8 -0
  135. package/dist/pi/coding-agent/core/timings.js +30 -0
  136. package/dist/pi/coding-agent/core/tools/bash.js +341 -0
  137. package/dist/pi/coding-agent/core/tools/edit-diff.js +344 -0
  138. package/dist/pi/coding-agent/core/tools/edit.js +324 -0
  139. package/dist/pi/coding-agent/core/tools/file-mutation-queue.js +36 -0
  140. package/dist/pi/coding-agent/core/tools/find.js +297 -0
  141. package/dist/pi/coding-agent/core/tools/grep.js +303 -0
  142. package/dist/pi/coding-agent/core/tools/index.js +111 -0
  143. package/dist/pi/coding-agent/core/tools/ls.js +168 -0
  144. package/dist/pi/coding-agent/core/tools/output-accumulator.js +183 -0
  145. package/dist/pi/coding-agent/core/tools/path-utils.js +61 -0
  146. package/dist/pi/coding-agent/core/tools/read.js +288 -0
  147. package/dist/pi/coding-agent/core/tools/render-utils.js +48 -0
  148. package/dist/pi/coding-agent/core/tools/tool-definition-wrapper.js +33 -0
  149. package/dist/pi/coding-agent/core/tools/truncate.js +214 -0
  150. package/dist/pi/coding-agent/core/tools/write.js +212 -0
  151. package/dist/pi/coding-agent/index.js +41 -0
  152. package/dist/pi/coding-agent/main.js +5 -0
  153. package/dist/pi/coding-agent/migrations.js +280 -0
  154. package/dist/pi/coding-agent/modes/index.js +7 -0
  155. package/dist/pi/coding-agent/modes/interactive/components/diff.js +132 -0
  156. package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +35 -0
  157. package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +32 -0
  158. package/dist/pi/coding-agent/modes/interactive/interactive-mode.js +3 -0
  159. package/dist/pi/coding-agent/modes/interactive/theme/theme.js +1023 -0
  160. package/dist/pi/coding-agent/modes/print-mode.js +130 -0
  161. package/dist/pi/coding-agent/modes/rpc/jsonl.js +48 -0
  162. package/dist/pi/coding-agent/modes/rpc/rpc-client.js +409 -0
  163. package/dist/pi/coding-agent/modes/rpc/rpc-mode.js +600 -0
  164. package/dist/pi/coding-agent/modes/rpc/rpc-types.js +7 -0
  165. package/dist/pi/coding-agent/utils/ansi.js +51 -0
  166. package/dist/pi/coding-agent/utils/changelog.js +86 -0
  167. package/dist/pi/coding-agent/utils/child-process.js +87 -0
  168. package/dist/pi/coding-agent/utils/clipboard-image.js +244 -0
  169. package/dist/pi/coding-agent/utils/clipboard-native.js +13 -0
  170. package/dist/pi/coding-agent/utils/clipboard.js +116 -0
  171. package/dist/pi/coding-agent/utils/exif-orientation.js +157 -0
  172. package/dist/pi/coding-agent/utils/frontmatter.js +25 -0
  173. package/dist/pi/coding-agent/utils/fs-watch.js +24 -0
  174. package/dist/pi/coding-agent/utils/git.js +162 -0
  175. package/dist/pi/coding-agent/utils/html.js +39 -0
  176. package/dist/pi/coding-agent/utils/image-convert.js +38 -0
  177. package/dist/pi/coding-agent/utils/image-resize.js +136 -0
  178. package/dist/pi/coding-agent/utils/mime.js +68 -0
  179. package/dist/pi/coding-agent/utils/paths.js +91 -0
  180. package/dist/pi/coding-agent/utils/photon.js +120 -0
  181. package/dist/pi/coding-agent/utils/pi-user-agent.js +4 -0
  182. package/dist/pi/coding-agent/utils/shell.js +194 -0
  183. package/dist/pi/coding-agent/utils/sleep.js +16 -0
  184. package/dist/pi/coding-agent/utils/syntax-highlight.js +117 -0
  185. package/dist/pi/coding-agent/utils/tools-manager.js +327 -0
  186. package/dist/pi/coding-agent/utils/version-check.js +81 -0
  187. package/dist/pi/coding-agent/utils/windows-self-update.js +76 -0
  188. package/dist/pi/tui/autocomplete.js +631 -0
  189. package/dist/pi/tui/components/box.js +103 -0
  190. package/dist/pi/tui/components/cancellable-loader.js +34 -0
  191. package/dist/pi/tui/components/editor.js +1915 -0
  192. package/dist/pi/tui/components/image.js +88 -0
  193. package/dist/pi/tui/components/input.js +425 -0
  194. package/dist/pi/tui/components/loader.js +68 -0
  195. package/dist/pi/tui/components/markdown.js +633 -0
  196. package/dist/pi/tui/components/select-list.js +158 -0
  197. package/dist/pi/tui/components/settings-list.js +184 -0
  198. package/dist/pi/tui/components/spacer.js +22 -0
  199. package/dist/pi/tui/components/text.js +88 -0
  200. package/dist/pi/tui/components/truncated-text.js +50 -0
  201. package/dist/pi/tui/editor-component.js +1 -0
  202. package/dist/pi/tui/fuzzy.js +109 -0
  203. package/dist/pi/tui/index.js +31 -0
  204. package/dist/pi/tui/keybindings.js +173 -0
  205. package/dist/pi/tui/keys.js +1172 -0
  206. package/dist/pi/tui/kill-ring.js +43 -0
  207. package/dist/pi/tui/stdin-buffer.js +360 -0
  208. package/dist/pi/tui/terminal-image.js +335 -0
  209. package/dist/pi/tui/terminal.js +324 -0
  210. package/dist/pi/tui/tui.js +1076 -0
  211. package/dist/pi/tui/undo-stack.js +24 -0
  212. package/dist/pi/tui/utils.js +1016 -0
  213. package/dist/relay/dispatcher.js +30 -0
  214. package/dist/server/handlers/queue.js +52 -0
  215. package/dist/server/pi-bridge.js +9 -1
  216. package/dist/server/session-stream.js +76 -111
  217. package/dist/server/storage.js +154 -2
  218. package/dist/server/title-generator.js +14 -153
  219. package/package.json +24 -6
@@ -0,0 +1,9 @@
1
+ export const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
2
+ export function clampOpenAIPromptCacheKey(key) {
3
+ if (key === undefined)
4
+ return undefined;
5
+ const chars = Array.from(key);
6
+ if (chars.length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH)
7
+ return key;
8
+ return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join("");
9
+ }
@@ -0,0 +1,97 @@
1
+ import { clearApiProviders, registerApiProvider } from "../api-registry.js";
2
+ import { AssistantMessageEventStream } from "../utils/event-stream.js";
3
+ function createLazyErrorMessage(model, error) {
4
+ return {
5
+ role: "assistant",
6
+ content: [],
7
+ api: model.api,
8
+ provider: model.provider,
9
+ model: model.id,
10
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
11
+ stopReason: "error",
12
+ errorMessage: error instanceof Error ? error.message : String(error),
13
+ timestamp: Date.now(),
14
+ };
15
+ }
16
+ function forwardStream(target, source) {
17
+ (async () => {
18
+ for await (const event of source)
19
+ target.push(event);
20
+ target.end();
21
+ })();
22
+ }
23
+ function createLazyStream(loadModule) {
24
+ return (model, context, options) => {
25
+ const outer = new AssistantMessageEventStream();
26
+ loadModule()
27
+ .then((mod) => {
28
+ const inner = mod.stream(model, context, options);
29
+ forwardStream(outer, inner);
30
+ })
31
+ .catch((error) => {
32
+ const message = createLazyErrorMessage(model, error);
33
+ outer.push({ type: "error", reason: "error", error: message });
34
+ outer.end(message);
35
+ });
36
+ return outer;
37
+ };
38
+ }
39
+ function createLazySimpleStream(loadModule) {
40
+ return (model, context, options) => {
41
+ const outer = new AssistantMessageEventStream();
42
+ loadModule()
43
+ .then((mod) => {
44
+ const inner = mod.streamSimple(model, context, options);
45
+ forwardStream(outer, inner);
46
+ })
47
+ .catch((error) => {
48
+ const message = createLazyErrorMessage(model, error);
49
+ outer.push({ type: "error", reason: "error", error: message });
50
+ outer.end(message);
51
+ });
52
+ return outer;
53
+ };
54
+ }
55
+ let anthropicProviderPromise;
56
+ function loadAnthropic() {
57
+ anthropicProviderPromise ||= import("./anthropic.js").then((mod) => {
58
+ const m = mod;
59
+ return { stream: m.streamAnthropic, streamSimple: m.streamSimpleAnthropic };
60
+ });
61
+ return anthropicProviderPromise;
62
+ }
63
+ let openaiCompletionsPromise;
64
+ function loadOpenAICompletions() {
65
+ openaiCompletionsPromise ||= import("./openai-completions.js").then((mod) => {
66
+ const m = mod;
67
+ return { stream: m.streamOpenAICompletions, streamSimple: m.streamSimpleOpenAICompletions };
68
+ });
69
+ return openaiCompletionsPromise;
70
+ }
71
+ // ─── Exports ──────────────────────────────────────────────────────────
72
+ export const streamAnthropic = createLazyStream(loadAnthropic);
73
+ export const streamSimpleAnthropic = createLazySimpleStream(loadAnthropic);
74
+ export const streamOpenAICompletions = createLazyStream(loadOpenAICompletions);
75
+ export const streamSimpleOpenAICompletions = createLazySimpleStream(loadOpenAICompletions);
76
+ /**
77
+ * Register only the API providers needed by Aexol AI proxy:
78
+ * - anthropic-messages (Claude models routed through backend)
79
+ * - openai-completions (OpenAI-compatible models routed through backend)
80
+ */
81
+ export function registerBuiltInApiProviders() {
82
+ registerApiProvider({
83
+ api: "anthropic-messages",
84
+ stream: streamAnthropic,
85
+ streamSimple: streamSimpleAnthropic,
86
+ });
87
+ registerApiProvider({
88
+ api: "openai-completions",
89
+ stream: streamOpenAICompletions,
90
+ streamSimple: streamSimpleOpenAICompletions,
91
+ });
92
+ }
93
+ export function resetApiProviders() {
94
+ clearApiProviders();
95
+ registerBuiltInApiProviders();
96
+ }
97
+ registerBuiltInApiProviders();
@@ -0,0 +1,40 @@
1
+ export function buildBaseOptions(_model, options, apiKey) {
2
+ return {
3
+ temperature: options?.temperature,
4
+ maxTokens: options?.maxTokens,
5
+ signal: options?.signal,
6
+ apiKey: apiKey || options?.apiKey,
7
+ transport: options?.transport,
8
+ cacheRetention: options?.cacheRetention,
9
+ sessionId: options?.sessionId,
10
+ headers: options?.headers,
11
+ onPayload: options?.onPayload,
12
+ onResponse: options?.onResponse,
13
+ timeoutMs: options?.timeoutMs,
14
+ maxRetries: options?.maxRetries,
15
+ maxRetryDelayMs: options?.maxRetryDelayMs,
16
+ metadata: options?.metadata,
17
+ };
18
+ }
19
+ export function clampReasoning(effort) {
20
+ return effort === "xhigh" ? "high" : effort;
21
+ }
22
+ export function adjustMaxTokensForThinking(
23
+ // Undefined means no explicit caller cap. Use the model cap and fit thinking inside it.
24
+ baseMaxTokens, modelMaxTokens, reasoningLevel, customBudgets) {
25
+ const defaultBudgets = {
26
+ minimal: 1024,
27
+ low: 2048,
28
+ medium: 8192,
29
+ high: 16384,
30
+ };
31
+ const budgets = { ...defaultBudgets, ...customBudgets };
32
+ const minOutputTokens = 1024;
33
+ const level = clampReasoning(reasoningLevel);
34
+ let thinkingBudget = budgets[level];
35
+ const maxTokens = baseMaxTokens === undefined ? modelMaxTokens : Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);
36
+ if (maxTokens <= thinkingBudget) {
37
+ thinkingBudget = Math.max(0, maxTokens - minOutputTokens);
38
+ }
39
+ return { maxTokens, thinkingBudget };
40
+ }
@@ -0,0 +1,183 @@
1
+ const NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
2
+ const NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
3
+ function replaceImagesWithPlaceholder(content, placeholder) {
4
+ const result = [];
5
+ let previousWasPlaceholder = false;
6
+ for (const block of content) {
7
+ if (block.type === "image") {
8
+ if (!previousWasPlaceholder) {
9
+ result.push({ type: "text", text: placeholder });
10
+ }
11
+ previousWasPlaceholder = true;
12
+ continue;
13
+ }
14
+ result.push(block);
15
+ previousWasPlaceholder = block.text === placeholder;
16
+ }
17
+ return result;
18
+ }
19
+ function downgradeUnsupportedImages(messages, model) {
20
+ if (model.input.includes("image")) {
21
+ return messages;
22
+ }
23
+ return messages.map((msg) => {
24
+ if (msg.role === "user" && Array.isArray(msg.content)) {
25
+ return {
26
+ ...msg,
27
+ content: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER),
28
+ };
29
+ }
30
+ if (msg.role === "toolResult") {
31
+ return {
32
+ ...msg,
33
+ content: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER),
34
+ };
35
+ }
36
+ return msg;
37
+ });
38
+ }
39
+ /**
40
+ * Normalize tool call ID for cross-provider compatibility.
41
+ * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
42
+ * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
43
+ */
44
+ export function transformMessages(messages, model, normalizeToolCallId) {
45
+ // Build a map of original tool call IDs to normalized IDs
46
+ const toolCallIdMap = new Map();
47
+ const imageAwareMessages = downgradeUnsupportedImages(messages, model);
48
+ // First pass: transform messages (unsupported image downgrade, thinking blocks, tool call ID normalization)
49
+ const transformed = imageAwareMessages.map((msg) => {
50
+ // User messages pass through unchanged
51
+ if (msg.role === "user") {
52
+ return msg;
53
+ }
54
+ // Handle toolResult messages - normalize toolCallId if we have a mapping
55
+ if (msg.role === "toolResult") {
56
+ const normalizedId = toolCallIdMap.get(msg.toolCallId);
57
+ if (normalizedId && normalizedId !== msg.toolCallId) {
58
+ return { ...msg, toolCallId: normalizedId };
59
+ }
60
+ return msg;
61
+ }
62
+ // Assistant messages need transformation check
63
+ if (msg.role === "assistant") {
64
+ const assistantMsg = msg;
65
+ const isSameModel = assistantMsg.provider === model.provider &&
66
+ assistantMsg.api === model.api &&
67
+ assistantMsg.model === model.id;
68
+ const transformedContent = assistantMsg.content.flatMap((block) => {
69
+ if (block.type === "thinking") {
70
+ // Redacted thinking is opaque encrypted content, only valid for the same model.
71
+ // Drop it for cross-model to avoid API errors.
72
+ if (block.redacted) {
73
+ return isSameModel ? block : [];
74
+ }
75
+ // For same model: keep thinking blocks with signatures (needed for replay)
76
+ // even if the thinking text is empty (OpenAI encrypted reasoning)
77
+ if (isSameModel && block.thinkingSignature)
78
+ return block;
79
+ // Skip empty thinking blocks, convert others to plain text
80
+ if (!block.thinking || block.thinking.trim() === "")
81
+ return [];
82
+ if (isSameModel)
83
+ return block;
84
+ return {
85
+ type: "text",
86
+ text: block.thinking,
87
+ };
88
+ }
89
+ if (block.type === "text") {
90
+ if (isSameModel)
91
+ return block;
92
+ return {
93
+ type: "text",
94
+ text: block.text,
95
+ };
96
+ }
97
+ if (block.type === "toolCall") {
98
+ const toolCall = block;
99
+ let normalizedToolCall = toolCall;
100
+ if (!isSameModel && toolCall.thoughtSignature) {
101
+ normalizedToolCall = { ...toolCall };
102
+ delete normalizedToolCall.thoughtSignature;
103
+ }
104
+ if (!isSameModel && normalizeToolCallId) {
105
+ const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
106
+ if (normalizedId !== toolCall.id) {
107
+ toolCallIdMap.set(toolCall.id, normalizedId);
108
+ normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
109
+ }
110
+ }
111
+ return normalizedToolCall;
112
+ }
113
+ return block;
114
+ });
115
+ return {
116
+ ...assistantMsg,
117
+ content: transformedContent,
118
+ };
119
+ }
120
+ return msg;
121
+ });
122
+ // Second pass: insert synthetic empty tool results for orphaned tool calls
123
+ // This preserves thinking signatures and satisfies API requirements
124
+ const result = [];
125
+ let pendingToolCalls = [];
126
+ let existingToolResultIds = new Set();
127
+ const insertSyntheticToolResults = () => {
128
+ if (pendingToolCalls.length > 0) {
129
+ for (const tc of pendingToolCalls) {
130
+ if (!existingToolResultIds.has(tc.id)) {
131
+ result.push({
132
+ role: "toolResult",
133
+ toolCallId: tc.id,
134
+ toolName: tc.name,
135
+ content: [{ type: "text", text: "No result provided" }],
136
+ isError: true,
137
+ timestamp: Date.now(),
138
+ });
139
+ }
140
+ }
141
+ pendingToolCalls = [];
142
+ existingToolResultIds = new Set();
143
+ }
144
+ };
145
+ for (let i = 0; i < transformed.length; i++) {
146
+ const msg = transformed[i];
147
+ if (msg.role === "assistant") {
148
+ // If we have pending orphaned tool calls from a previous assistant, insert synthetic results now
149
+ insertSyntheticToolResults();
150
+ // Skip errored/aborted assistant messages entirely.
151
+ // These are incomplete turns that shouldn't be replayed:
152
+ // - May have partial content (reasoning without message, incomplete tool calls)
153
+ // - Replaying them can cause API errors (e.g., OpenAI "reasoning without following item")
154
+ // - The model should retry from the last valid state
155
+ const assistantMsg = msg;
156
+ if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
157
+ continue;
158
+ }
159
+ // Track tool calls from this assistant message
160
+ const toolCalls = assistantMsg.content.filter((b) => b.type === "toolCall");
161
+ if (toolCalls.length > 0) {
162
+ pendingToolCalls = toolCalls;
163
+ existingToolResultIds = new Set();
164
+ }
165
+ result.push(msg);
166
+ }
167
+ else if (msg.role === "toolResult") {
168
+ existingToolResultIds.add(msg.toolCallId);
169
+ result.push(msg);
170
+ }
171
+ else if (msg.role === "user") {
172
+ // User message interrupts tool flow - insert synthetic results for orphaned calls
173
+ insertSyntheticToolResults();
174
+ result.push(msg);
175
+ }
176
+ else {
177
+ result.push(msg);
178
+ }
179
+ }
180
+ // If the conversation ends with unresolved tool calls, synthesize results now.
181
+ insertSyntheticToolResults();
182
+ return result;
183
+ }
@@ -0,0 +1,21 @@
1
+ const sessionResourceCleanups = new Set();
2
+ export function registerSessionResourceCleanup(cleanup) {
3
+ sessionResourceCleanups.add(cleanup);
4
+ return () => {
5
+ sessionResourceCleanups.delete(cleanup);
6
+ };
7
+ }
8
+ export function cleanupSessionResources(sessionId) {
9
+ const errors = [];
10
+ for (const cleanup of sessionResourceCleanups) {
11
+ try {
12
+ cleanup(sessionId);
13
+ }
14
+ catch (error) {
15
+ errors.push(error);
16
+ }
17
+ }
18
+ if (errors.length > 0) {
19
+ throw new AggregateError(errors, "Failed to cleanup session resources");
20
+ }
21
+ }
@@ -0,0 +1,26 @@
1
+ import "./providers/register-builtins.js";
2
+ import { getApiProvider } from "./api-registry.js";
3
+ export { getEnvApiKey } from "./env-api-keys.js";
4
+ function resolveApiProvider(api) {
5
+ const provider = getApiProvider(api);
6
+ if (!provider) {
7
+ throw new Error(`No API provider registered for api: ${api}`);
8
+ }
9
+ return provider;
10
+ }
11
+ export function stream(model, context, options) {
12
+ const provider = resolveApiProvider(model.api);
13
+ return provider.stream(model, context, options);
14
+ }
15
+ export async function complete(model, context, options) {
16
+ const s = stream(model, context, options);
17
+ return s.result();
18
+ }
19
+ export function streamSimple(model, context, options) {
20
+ const provider = resolveApiProvider(model.api);
21
+ return provider.streamSimple(model, context, options);
22
+ }
23
+ export async function completeSimple(model, context, options) {
24
+ const s = streamSimple(model, context, options);
25
+ return s.result();
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ export function formatThrownValue(value) {
2
+ if (value instanceof Error)
3
+ return value.message || value.name;
4
+ if (typeof value === "string")
5
+ return value;
6
+ return String(value);
7
+ }
8
+ export function extractDiagnosticError(error) {
9
+ if (!(error instanceof Error))
10
+ return { name: "ThrownValue", message: formatThrownValue(error) };
11
+ const code = error.code;
12
+ return {
13
+ name: error.name || undefined,
14
+ message: error.message || error.name,
15
+ stack: error.stack,
16
+ code: typeof code === "string" || typeof code === "number" ? code : undefined,
17
+ };
18
+ }
19
+ export function createAssistantMessageDiagnostic(type, error, details) {
20
+ return { type, timestamp: Date.now(), error: extractDiagnosticError(error), details };
21
+ }
22
+ export function appendAssistantMessageDiagnostic(message, diagnostic) {
23
+ message.diagnostics = [...(message.diagnostics ?? []), diagnostic];
24
+ }
@@ -0,0 +1,80 @@
1
+ // Generic event stream class for async iteration
2
+ export class EventStream {
3
+ queue = [];
4
+ waiting = [];
5
+ done = false;
6
+ finalResultPromise;
7
+ resolveFinalResult;
8
+ isComplete;
9
+ extractResult;
10
+ constructor(isComplete, extractResult) {
11
+ this.isComplete = isComplete;
12
+ this.extractResult = extractResult;
13
+ this.finalResultPromise = new Promise((resolve) => {
14
+ this.resolveFinalResult = resolve;
15
+ });
16
+ }
17
+ push(event) {
18
+ if (this.done)
19
+ return;
20
+ if (this.isComplete(event)) {
21
+ this.done = true;
22
+ this.resolveFinalResult(this.extractResult(event));
23
+ }
24
+ // Deliver to waiting consumer or queue it
25
+ const waiter = this.waiting.shift();
26
+ if (waiter) {
27
+ waiter({ value: event, done: false });
28
+ }
29
+ else {
30
+ this.queue.push(event);
31
+ }
32
+ }
33
+ end(result) {
34
+ this.done = true;
35
+ if (result !== undefined) {
36
+ this.resolveFinalResult(result);
37
+ }
38
+ // Notify all waiting consumers that we're done
39
+ while (this.waiting.length > 0) {
40
+ const waiter = this.waiting.shift();
41
+ waiter({ value: undefined, done: true });
42
+ }
43
+ }
44
+ async *[Symbol.asyncIterator]() {
45
+ while (true) {
46
+ if (this.queue.length > 0) {
47
+ yield this.queue.shift();
48
+ }
49
+ else if (this.done) {
50
+ return;
51
+ }
52
+ else {
53
+ const result = await new Promise((resolve) => this.waiting.push(resolve));
54
+ if (result.done)
55
+ return;
56
+ yield result.value;
57
+ }
58
+ }
59
+ }
60
+ result() {
61
+ return this.finalResultPromise;
62
+ }
63
+ }
64
+ export class AssistantMessageEventStream extends EventStream {
65
+ constructor() {
66
+ super((event) => event.type === "done" || event.type === "error", (event) => {
67
+ if (event.type === "done") {
68
+ return event.message;
69
+ }
70
+ else if (event.type === "error") {
71
+ return event.error;
72
+ }
73
+ throw new Error("Unexpected event type for final result");
74
+ });
75
+ }
76
+ }
77
+ /** Factory function for AssistantMessageEventStream (for use in extensions) */
78
+ export function createAssistantMessageEventStream() {
79
+ return new AssistantMessageEventStream();
80
+ }
@@ -0,0 +1,13 @@
1
+ /** Fast deterministic hash to shorten long strings */
2
+ export function shortHash(str) {
3
+ let h1 = 0xdeadbeef;
4
+ let h2 = 0x41c6ce57;
5
+ for (let i = 0; i < str.length; i++) {
6
+ const ch = str.charCodeAt(i);
7
+ h1 = Math.imul(h1 ^ ch, 2654435761);
8
+ h2 = Math.imul(h2 ^ ch, 1597334677);
9
+ }
10
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
11
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
12
+ return (h2 >>> 0).toString(36) + (h1 >>> 0).toString(36);
13
+ }
@@ -0,0 +1,7 @@
1
+ export function headersToRecord(headers) {
2
+ const result = {};
3
+ for (const [key, value] of headers.entries()) {
4
+ result[key] = value;
5
+ }
6
+ return result;
7
+ }
@@ -0,0 +1,112 @@
1
+ import { parse as partialParse } from "partial-json";
2
+ const VALID_JSON_ESCAPES = new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
3
+ function isControlCharacter(char) {
4
+ const codePoint = char.codePointAt(0);
5
+ return codePoint !== undefined && codePoint >= 0x00 && codePoint <= 0x1f;
6
+ }
7
+ function escapeControlCharacter(char) {
8
+ switch (char) {
9
+ case "\b":
10
+ return "\\b";
11
+ case "\f":
12
+ return "\\f";
13
+ case "\n":
14
+ return "\\n";
15
+ case "\r":
16
+ return "\\r";
17
+ case "\t":
18
+ return "\\t";
19
+ default:
20
+ return `\\u${char.codePointAt(0)?.toString(16).padStart(4, "0") ?? "0000"}`;
21
+ }
22
+ }
23
+ /**
24
+ * Repairs malformed JSON string literals by:
25
+ * - escaping raw control characters inside strings
26
+ * - doubling backslashes before invalid escape characters
27
+ */
28
+ export function repairJson(json) {
29
+ let repaired = "";
30
+ let inString = false;
31
+ for (let index = 0; index < json.length; index++) {
32
+ const char = json[index];
33
+ if (!inString) {
34
+ repaired += char;
35
+ if (char === '"') {
36
+ inString = true;
37
+ }
38
+ continue;
39
+ }
40
+ if (char === '"') {
41
+ repaired += char;
42
+ inString = false;
43
+ continue;
44
+ }
45
+ if (char === "\\") {
46
+ const nextChar = json[index + 1];
47
+ if (nextChar === undefined) {
48
+ repaired += "\\\\";
49
+ continue;
50
+ }
51
+ if (nextChar === "u") {
52
+ const unicodeDigits = json.slice(index + 2, index + 6);
53
+ if (/^[0-9a-fA-F]{4}$/.test(unicodeDigits)) {
54
+ repaired += `\\u${unicodeDigits}`;
55
+ index += 5;
56
+ continue;
57
+ }
58
+ }
59
+ if (VALID_JSON_ESCAPES.has(nextChar)) {
60
+ repaired += `\\${nextChar}`;
61
+ index += 1;
62
+ continue;
63
+ }
64
+ repaired += "\\\\";
65
+ continue;
66
+ }
67
+ repaired += isControlCharacter(char) ? escapeControlCharacter(char) : char;
68
+ }
69
+ return repaired;
70
+ }
71
+ export function parseJsonWithRepair(json) {
72
+ try {
73
+ return JSON.parse(json);
74
+ }
75
+ catch (error) {
76
+ const repairedJson = repairJson(json);
77
+ if (repairedJson !== json) {
78
+ return JSON.parse(repairedJson);
79
+ }
80
+ throw error;
81
+ }
82
+ }
83
+ /**
84
+ * Attempts to parse potentially incomplete JSON during streaming.
85
+ * Always returns a valid object, even if the JSON is incomplete.
86
+ *
87
+ * @param partialJson The partial JSON string from streaming
88
+ * @returns Parsed object or empty object if parsing fails
89
+ */
90
+ export function parseStreamingJson(partialJson) {
91
+ if (!partialJson || partialJson.trim() === "") {
92
+ return {};
93
+ }
94
+ try {
95
+ return parseJsonWithRepair(partialJson);
96
+ }
97
+ catch {
98
+ try {
99
+ const result = partialParse(partialJson);
100
+ return (result ?? {});
101
+ }
102
+ catch {
103
+ try {
104
+ const result = partialParse(repairJson(partialJson));
105
+ return (result ?? {});
106
+ }
107
+ catch {
108
+ return {};
109
+ }
110
+ }
111
+ }
112
+ }