orbitx 0.2.6 → 0.2.7

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 (263) hide show
  1. package/dist/core/agent-providers.d.ts +15 -10
  2. package/dist/core/agent-providers.d.ts.map +1 -1
  3. package/dist/core/agent-providers.js +65 -15
  4. package/dist/core/agent-providers.js.map +1 -1
  5. package/dist/core/ai-provider.d.ts +52 -2
  6. package/dist/core/ai-provider.d.ts.map +1 -1
  7. package/dist/core/ai-provider.js.map +1 -1
  8. package/dist/core/anthropic-provider.d.ts +5 -0
  9. package/dist/core/anthropic-provider.d.ts.map +1 -1
  10. package/dist/core/anthropic-provider.js +100 -17
  11. package/dist/core/anthropic-provider.js.map +1 -1
  12. package/dist/core/base-agent.d.ts +19 -41
  13. package/dist/core/base-agent.d.ts.map +1 -1
  14. package/dist/core/base-agent.js +112 -136
  15. package/dist/core/base-agent.js.map +1 -1
  16. package/dist/core/deepseek-provider.d.ts +3 -0
  17. package/dist/core/deepseek-provider.d.ts.map +1 -1
  18. package/dist/core/deepseek-provider.js +59 -12
  19. package/dist/core/deepseek-provider.js.map +1 -1
  20. package/dist/core/mcp-client.d.ts +10 -2
  21. package/dist/core/mcp-client.d.ts.map +1 -1
  22. package/dist/core/mcp-client.js +28 -4
  23. package/dist/core/mcp-client.js.map +1 -1
  24. package/dist/core/mcp-server.d.ts +5 -1
  25. package/dist/core/mcp-server.d.ts.map +1 -1
  26. package/dist/core/mcp-server.js +31 -1
  27. package/dist/core/mcp-server.js.map +1 -1
  28. package/dist/core/mcp.d.ts +9 -23
  29. package/dist/core/mcp.d.ts.map +1 -1
  30. package/dist/core/mcp.js +1 -24
  31. package/dist/core/mcp.js.map +1 -1
  32. package/dist/core/ollama-provider.d.ts +3 -0
  33. package/dist/core/ollama-provider.d.ts.map +1 -1
  34. package/dist/core/ollama-provider.js +31 -7
  35. package/dist/core/ollama-provider.js.map +1 -1
  36. package/dist/core/openai-provider.d.ts +3 -0
  37. package/dist/core/openai-provider.d.ts.map +1 -1
  38. package/dist/core/openai-provider.js +51 -8
  39. package/dist/core/openai-provider.js.map +1 -1
  40. package/dist/core/skill.d.ts +2 -2
  41. package/dist/core/skill.d.ts.map +1 -1
  42. package/dist/index.d.ts +13 -20
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +9 -10
  45. package/dist/index.js.map +1 -1
  46. package/dist/templates/simple.d.ts +8 -12
  47. package/dist/templates/simple.d.ts.map +1 -1
  48. package/dist/templates/simple.js +6 -10
  49. package/dist/templates/simple.js.map +1 -1
  50. package/dist/tests/tools/agent/agent-tools.test.js +20 -17
  51. package/dist/tests/tools/agent/agent-tools.test.js.map +1 -1
  52. package/dist/tests/tools/question-answer/question-answer.test.js +1 -11
  53. package/dist/tests/tools/question-answer/question-answer.test.js.map +1 -1
  54. package/dist/tests/tools/utils/read-image.test.js +29 -17
  55. package/dist/tests/tools/utils/read-image.test.js.map +1 -1
  56. package/dist/tools/agent/hire.d.ts +1 -2
  57. package/dist/tools/agent/hire.d.ts.map +1 -1
  58. package/dist/tools/agent/hire.js +1 -4
  59. package/dist/tools/agent/hire.js.map +1 -1
  60. package/dist/tools/agent/index.d.ts +1 -3
  61. package/dist/tools/agent/index.d.ts.map +1 -1
  62. package/dist/tools/agent/index.js +0 -1
  63. package/dist/tools/agent/index.js.map +1 -1
  64. package/dist/tools/agent/list.d.ts +1 -1
  65. package/dist/tools/agent/list.d.ts.map +1 -1
  66. package/dist/tools/agent/prompt.d.ts +1 -2
  67. package/dist/tools/agent/prompt.d.ts.map +1 -1
  68. package/dist/tools/agent/prompt.js +11 -7
  69. package/dist/tools/agent/prompt.js.map +1 -1
  70. package/dist/tools/agent/report.d.ts +1 -1
  71. package/dist/tools/agent/report.d.ts.map +1 -1
  72. package/dist/tools/bash/index.d.ts +1 -3
  73. package/dist/tools/bash/index.d.ts.map +1 -1
  74. package/dist/tools/bash/index.js +0 -1
  75. package/dist/tools/bash/index.js.map +1 -1
  76. package/dist/tools/bash/list.d.ts +1 -1
  77. package/dist/tools/bash/list.d.ts.map +1 -1
  78. package/dist/tools/bash/list.js +1 -1
  79. package/dist/tools/bash/list.js.map +1 -1
  80. package/dist/tools/bash/logs.d.ts +1 -1
  81. package/dist/tools/bash/logs.d.ts.map +1 -1
  82. package/dist/tools/bash/logs.js +1 -1
  83. package/dist/tools/bash/logs.js.map +1 -1
  84. package/dist/tools/bash/run.d.ts +1 -2
  85. package/dist/tools/bash/run.d.ts.map +1 -1
  86. package/dist/tools/bash/run.js +1 -7
  87. package/dist/tools/bash/run.js.map +1 -1
  88. package/dist/tools/bash/terminate.d.ts +1 -2
  89. package/dist/tools/bash/terminate.d.ts.map +1 -1
  90. package/dist/tools/bash/terminate.js +1 -4
  91. package/dist/tools/bash/terminate.js.map +1 -1
  92. package/dist/tools/bash/wait.d.ts +1 -2
  93. package/dist/tools/bash/wait.d.ts.map +1 -1
  94. package/dist/tools/bash/wait.js +1 -7
  95. package/dist/tools/bash/wait.js.map +1 -1
  96. package/dist/tools/bash/write-input.d.ts +1 -2
  97. package/dist/tools/bash/write-input.d.ts.map +1 -1
  98. package/dist/tools/bash/write-input.js +1 -4
  99. package/dist/tools/bash/write-input.js.map +1 -1
  100. package/dist/tools/browser/click.d.ts +1 -2
  101. package/dist/tools/browser/click.d.ts.map +1 -1
  102. package/dist/tools/browser/click.js +1 -4
  103. package/dist/tools/browser/click.js.map +1 -1
  104. package/dist/tools/browser/console.d.ts +1 -1
  105. package/dist/tools/browser/console.d.ts.map +1 -1
  106. package/dist/tools/browser/create-session.d.ts +1 -2
  107. package/dist/tools/browser/create-session.d.ts.map +1 -1
  108. package/dist/tools/browser/create-session.js +1 -4
  109. package/dist/tools/browser/create-session.js.map +1 -1
  110. package/dist/tools/browser/fill.d.ts +1 -2
  111. package/dist/tools/browser/fill.d.ts.map +1 -1
  112. package/dist/tools/browser/fill.js +1 -4
  113. package/dist/tools/browser/fill.js.map +1 -1
  114. package/dist/tools/browser/get-sessions.d.ts +1 -1
  115. package/dist/tools/browser/get-sessions.d.ts.map +1 -1
  116. package/dist/tools/browser/index.d.ts +1 -3
  117. package/dist/tools/browser/index.d.ts.map +1 -1
  118. package/dist/tools/browser/index.js +0 -1
  119. package/dist/tools/browser/index.js.map +1 -1
  120. package/dist/tools/browser/inject.d.ts +1 -2
  121. package/dist/tools/browser/inject.d.ts.map +1 -1
  122. package/dist/tools/browser/inject.js +1 -4
  123. package/dist/tools/browser/inject.js.map +1 -1
  124. package/dist/tools/browser/navigate.d.ts +1 -2
  125. package/dist/tools/browser/navigate.d.ts.map +1 -1
  126. package/dist/tools/browser/navigate.js +1 -4
  127. package/dist/tools/browser/navigate.js.map +1 -1
  128. package/dist/tools/browser/network-status.d.ts +1 -1
  129. package/dist/tools/browser/network-status.d.ts.map +1 -1
  130. package/dist/tools/browser/network.d.ts +1 -1
  131. package/dist/tools/browser/network.d.ts.map +1 -1
  132. package/dist/tools/browser/read.d.ts +1 -2
  133. package/dist/tools/browser/read.d.ts.map +1 -1
  134. package/dist/tools/browser/read.js +1 -4
  135. package/dist/tools/browser/read.js.map +1 -1
  136. package/dist/tools/browser/remove-session.d.ts +1 -2
  137. package/dist/tools/browser/remove-session.d.ts.map +1 -1
  138. package/dist/tools/browser/remove-session.js +1 -4
  139. package/dist/tools/browser/remove-session.js.map +1 -1
  140. package/dist/tools/browser/screenshot.d.ts +1 -2
  141. package/dist/tools/browser/screenshot.d.ts.map +1 -1
  142. package/dist/tools/browser/screenshot.js +16 -15
  143. package/dist/tools/browser/screenshot.js.map +1 -1
  144. package/dist/tools/browser/scroll-info.d.ts +1 -1
  145. package/dist/tools/browser/scroll-info.d.ts.map +1 -1
  146. package/dist/tools/browser/scroll.d.ts +1 -2
  147. package/dist/tools/browser/scroll.d.ts.map +1 -1
  148. package/dist/tools/browser/scroll.js +1 -4
  149. package/dist/tools/browser/scroll.js.map +1 -1
  150. package/dist/tools/browser/submit-form.d.ts +1 -2
  151. package/dist/tools/browser/submit-form.d.ts.map +1 -1
  152. package/dist/tools/browser/submit-form.js +1 -4
  153. package/dist/tools/browser/submit-form.js.map +1 -1
  154. package/dist/tools/fs/create-dir.d.ts +1 -2
  155. package/dist/tools/fs/create-dir.d.ts.map +1 -1
  156. package/dist/tools/fs/create-dir.js +1 -4
  157. package/dist/tools/fs/create-dir.js.map +1 -1
  158. package/dist/tools/fs/delete.d.ts +1 -2
  159. package/dist/tools/fs/delete.d.ts.map +1 -1
  160. package/dist/tools/fs/delete.js +1 -4
  161. package/dist/tools/fs/delete.js.map +1 -1
  162. package/dist/tools/fs/edit-file.d.ts +1 -2
  163. package/dist/tools/fs/edit-file.d.ts.map +1 -1
  164. package/dist/tools/fs/edit-file.js +1 -4
  165. package/dist/tools/fs/edit-file.js.map +1 -1
  166. package/dist/tools/fs/index.d.ts +1 -3
  167. package/dist/tools/fs/index.d.ts.map +1 -1
  168. package/dist/tools/fs/index.js +0 -1
  169. package/dist/tools/fs/index.js.map +1 -1
  170. package/dist/tools/fs/list-dir.d.ts +1 -2
  171. package/dist/tools/fs/list-dir.d.ts.map +1 -1
  172. package/dist/tools/fs/list-dir.js +1 -4
  173. package/dist/tools/fs/list-dir.js.map +1 -1
  174. package/dist/tools/fs/move.d.ts +1 -2
  175. package/dist/tools/fs/move.d.ts.map +1 -1
  176. package/dist/tools/fs/move.js +1 -4
  177. package/dist/tools/fs/move.js.map +1 -1
  178. package/dist/tools/fs/read-file.d.ts +1 -2
  179. package/dist/tools/fs/read-file.d.ts.map +1 -1
  180. package/dist/tools/fs/read-file.js +1 -4
  181. package/dist/tools/fs/read-file.js.map +1 -1
  182. package/dist/tools/fs/stat.d.ts +1 -2
  183. package/dist/tools/fs/stat.d.ts.map +1 -1
  184. package/dist/tools/fs/stat.js +1 -4
  185. package/dist/tools/fs/stat.js.map +1 -1
  186. package/dist/tools/fs/write-file.d.ts +1 -2
  187. package/dist/tools/fs/write-file.d.ts.map +1 -1
  188. package/dist/tools/fs/write-file.js +1 -4
  189. package/dist/tools/fs/write-file.js.map +1 -1
  190. package/dist/tools/present/add.d.ts +1 -2
  191. package/dist/tools/present/add.d.ts.map +1 -1
  192. package/dist/tools/present/add.js +1 -4
  193. package/dist/tools/present/add.js.map +1 -1
  194. package/dist/tools/present/clear.d.ts +1 -2
  195. package/dist/tools/present/clear.d.ts.map +1 -1
  196. package/dist/tools/present/clear.js +1 -4
  197. package/dist/tools/present/clear.js.map +1 -1
  198. package/dist/tools/present/get-list.d.ts +1 -2
  199. package/dist/tools/present/get-list.d.ts.map +1 -1
  200. package/dist/tools/present/get-list.js +0 -2
  201. package/dist/tools/present/get-list.js.map +1 -1
  202. package/dist/tools/present/index.d.ts +1 -3
  203. package/dist/tools/present/index.d.ts.map +1 -1
  204. package/dist/tools/present/index.js +0 -1
  205. package/dist/tools/present/index.js.map +1 -1
  206. package/dist/tools/question-answer/index.d.ts +1 -3
  207. package/dist/tools/question-answer/index.d.ts.map +1 -1
  208. package/dist/tools/question-answer/index.js +0 -1
  209. package/dist/tools/question-answer/index.js.map +1 -1
  210. package/dist/tools/question-answer/question-answer.d.ts +1 -2
  211. package/dist/tools/question-answer/question-answer.d.ts.map +1 -1
  212. package/dist/tools/question-answer/question-answer.js +1 -4
  213. package/dist/tools/question-answer/question-answer.js.map +1 -1
  214. package/dist/tools/todo/check-task.d.ts +1 -2
  215. package/dist/tools/todo/check-task.d.ts.map +1 -1
  216. package/dist/tools/todo/check-task.js +1 -4
  217. package/dist/tools/todo/check-task.js.map +1 -1
  218. package/dist/tools/todo/create-list.d.ts +1 -2
  219. package/dist/tools/todo/create-list.d.ts.map +1 -1
  220. package/dist/tools/todo/create-list.js +1 -4
  221. package/dist/tools/todo/create-list.js.map +1 -1
  222. package/dist/tools/todo/create-task.d.ts +1 -2
  223. package/dist/tools/todo/create-task.d.ts.map +1 -1
  224. package/dist/tools/todo/create-task.js +1 -4
  225. package/dist/tools/todo/create-task.js.map +1 -1
  226. package/dist/tools/todo/get-list.d.ts +1 -1
  227. package/dist/tools/todo/get-list.d.ts.map +1 -1
  228. package/dist/tools/todo/get-list.js +1 -1
  229. package/dist/tools/todo/get-list.js.map +1 -1
  230. package/dist/tools/todo/get-lists.d.ts +1 -1
  231. package/dist/tools/todo/get-lists.d.ts.map +1 -1
  232. package/dist/tools/todo/get-lists.js +1 -1
  233. package/dist/tools/todo/get-lists.js.map +1 -1
  234. package/dist/tools/todo/index.d.ts +1 -3
  235. package/dist/tools/todo/index.d.ts.map +1 -1
  236. package/dist/tools/todo/index.js +0 -1
  237. package/dist/tools/todo/index.js.map +1 -1
  238. package/dist/tools/todo/remove-list.d.ts +1 -2
  239. package/dist/tools/todo/remove-list.d.ts.map +1 -1
  240. package/dist/tools/todo/remove-list.js +1 -4
  241. package/dist/tools/todo/remove-list.js.map +1 -1
  242. package/dist/tools/todo/remove-task.d.ts +1 -2
  243. package/dist/tools/todo/remove-task.d.ts.map +1 -1
  244. package/dist/tools/todo/remove-task.js +1 -4
  245. package/dist/tools/todo/remove-task.js.map +1 -1
  246. package/dist/tools/utils/delay.d.ts +1 -1
  247. package/dist/tools/utils/delay.d.ts.map +1 -1
  248. package/dist/tools/utils/get-current-time.d.ts +1 -1
  249. package/dist/tools/utils/get-current-time.d.ts.map +1 -1
  250. package/dist/tools/utils/index.d.ts +1 -1
  251. package/dist/tools/utils/index.d.ts.map +1 -1
  252. package/dist/tools/utils/read-image.d.ts +12 -7
  253. package/dist/tools/utils/read-image.d.ts.map +1 -1
  254. package/dist/tools/utils/read-image.js +27 -17
  255. package/dist/tools/utils/read-image.js.map +1 -1
  256. package/docs/agents.md +50 -25
  257. package/docs/api-reference.md +12 -13
  258. package/docs/getting-started.md +2 -2
  259. package/docs/mcp-architecture.md +14 -2
  260. package/docs/providers.md +114 -15
  261. package/docs/streaming.md +19 -2
  262. package/docs/tools.md +41 -8
  263. package/package.json +1 -1
package/docs/providers.md CHANGED
@@ -12,10 +12,12 @@ abstract class AIProvider {
12
12
  ): Promise<ChatResponse>;
13
13
 
14
14
  abstract getCapabilities(): ProviderCapabilities;
15
+
16
+ abstract setOption(key: string, value: unknown): void;
15
17
  }
16
18
  ```
17
19
 
18
- - `chat` sends the full message history (plus a provider-agnostic `tools` schema, when supplied) and returns a `ChatResponse` (`{ content, inputTokens, outputTokens, toolCalls? }`). If `streamCallback` is passed, text is streamed to it incrementally as it arrives — see [Streaming](./streaming.md).
20
+ - `chat` sends the full message history (plus a provider-agnostic `tools` schema, when supplied) and returns a `ChatResponse` (`{ content, inputMissTokens, inputCacheTokens, outputTokens, toolCalls? }`) — `inputMissTokens`/`inputCacheTokens` are real, provider-reported non-cached/cache-hit input token counts (see "User tracking & prompt caching" below), not a client-side estimate. If `streamCallback` is passed, text is streamed to it incrementally as it arrives — see [Streaming](./streaming.md).
19
21
  - `signal`, when passed and later aborted, cancels the in-flight request to the provider's API immediately rather than just abandoning it client-side — `BaseAgent.immediateStop()` uses this so the call stops generating (and billing for) further output right away. It doesn't retroactively waive input tokens or output already generated before the abort. Anthropic, OpenAI, and DeepSeek honor it for both streaming and non-streaming calls (their SDKs forward it straight to `fetch`). Ollama only honors it for streaming calls — its client has no way to abort a non-streaming request — so a `chat()` call made without a `streamCallback` against `OllamaProvider` can't be cancelled mid-flight; see the retry-loop guard in `ollama-provider.ts`.
20
22
  - **Streaming calls resolve normally on abort**, returning a `ChatResponse` built from whatever content/thinking/tool-calls were accumulated before the cutoff, instead of throwing — so text already delivered via `streamCallback` still ends up recorded rather than discarded. A tool call still being generated when the abort lands is dropped (its arguments may be truncated JSON); only tool calls that had already fully finished streaming are included in `toolCalls`.
21
23
  - **Non-streaming calls still throw on abort** — there's no partial response to salvage since the API only returns once, in full — and `BaseAgent` discards that turn.
@@ -23,15 +25,48 @@ abstract class AIProvider {
23
25
 
24
26
  ```ts
25
27
  interface ProviderCapabilities {
26
- supportsTools: boolean; // must be true — BaseAgent requires native tool-calling
27
- supportsImages: boolean; // whether this provider/model accepts image content
28
- contextWindow: number; // total context window, in tokens
29
- safeUsageRatio?: number; // fraction of contextWindow safe to fill before compacting (default 0.5)
28
+ supportsTools: boolean; // must be true — BaseAgent requires native tool-calling
29
+ supportsImages: boolean; // whether this provider/model accepts image content
30
+ contextWindow: number; // total context window, in tokens
31
+ safeUsageRatio?: number; // fraction of contextWindow safe to fill before compacting (default 0.5)
32
+ supportsThinking?: boolean; // whether reasoning/thinking text is surfaced — see "Think effort" below
33
+ maxOutputTokens?: number; // the model's max output tokens per response, where confirmed — not yet enforced anywhere, reserved for a future release
34
+ supportsVideo?: boolean; // whether this provider/model accepts video content
35
+ supportsAudio?: boolean; // whether this provider/model accepts audio content
36
+ supportsImageGeneration?: boolean; // whether this provider/model can generate new images
37
+ supportsImageEditing?: boolean; // whether this provider/model can edit an existing image
38
+ supportsVideoGeneration?: boolean; // whether this provider/model can generate new video
39
+ supportsVideoEditing?: boolean; // whether this provider/model can edit existing video
40
+ supportsAudioGeneration?: boolean; // whether this provider/model can generate new audio (speech, music, sound effects)
41
+ supportsAudioDesign?: boolean; // whether this provider/model can design/produce sound effects or soundscapes
42
+ supportsAudioClone?: boolean; // whether this provider/model can clone a voice from a reference sample
43
+ supports3DModelGeneration?: boolean; // whether this provider/model can generate a new 3D model
30
44
  }
31
45
  ```
32
46
 
47
+ These flags describe what the model itself can natively do — they're unrelated to `ProviderType` (see "Provider roles" below), which is just a role name used to look a provider up in a `ProviderRegistry`. A provider registered under the `"image-generation"` role doesn't need `supportsImageGeneration: true` (or vice versa) — the role name and the capability flag are independent. All four built-in providers are chat/vision LLMs, not generation APIs, so every one of the new flags above is always `false` on them.
48
+
33
49
  `BaseAgent.run()` throws immediately if `getCapabilities().supportsTools` is `false` — the legacy JSON-in-text tool-call convention has been removed from the main loop. All providers default `supportsTools` to `true` except Ollama, which defaults it to `false` (many locally-hosted models don't support native function calling; pass `{ supportsTools: true }` explicitly if your model does).
34
50
 
51
+ ## Changing an option without reconstructing
52
+
53
+ `setOption(key, value)` mutates an already-constructed provider in place — e.g. `provider.setOption("thinkEffort", 0.8)` — instead of building a new one. Every built-in provider only allows keys it already reads fresh on every `chat()` call (never something baked into derived state at construction, like `contextWindow`); an unrecognized key throws.
54
+
55
+ | Provider | Settable keys |
56
+ |---|---|
57
+ | Anthropic | `thinkEffort`, `userId`, `disablePromptCaching`, `maxTokens`, `supportsTools` |
58
+ | OpenAI | `thinkEffort`, `userId`, `supportsTools` |
59
+ | DeepSeek | `thinkEffort`, `userId`, `supportsTools` |
60
+ | Ollama | `thinkEffort`, `supportsTools` |
61
+
62
+ ## Usage on the streaming path
63
+
64
+ OpenAI and DeepSeek's Chat-Completions-style streaming protocol doesn't compute token usage until a turn is fully complete — it arrives once, in a special final chunk (`stream_options: { include_usage: true }`), alongside `done: true`. Every chunk before that has `usage: { inputMissTokens: 0, inputCacheTokens: 0, outputTokens: 0 }` — not because those numbers are unknown-but-estimated, but because the API genuinely hasn't computed them yet. `OpenAIProvider` and `DeepSeekProvider` both trust this real, provider-reported final chunk directly — no local estimation involved on either. DeepSeek's final chunk includes its own `prompt_cache_hit_tokens`/`prompt_cache_miss_tokens` fields (falling back to `prompt_tokens_details.cached_tokens` if those aren't present), parsed the same way real cache data is read from any other provider.
65
+
66
+ If a call is cut off before that final chunk arrives (e.g. `BaseAgent.immediateStop()` aborting the connection to stop paying for further output), the real usage number simply never gets computed or sent for either provider — not partially, not late, just never — so `inputMissTokens`/`inputCacheTokens`/`outputTokens` stay `0` on `ChatResponse` too in that case. Anthropic doesn't have this problem (it streams real usage incrementally, see [Streaming](./streaming.md#usage-chunks)); Ollama has the same final-chunk-only limitation but isn't covered by what's described here.
67
+
68
+ This is the only usage source DeepSeek's streaming path has, so it's treated as the real number, not flagged as a lesser "estimate" — whatever lands on the final `done: true` chunk (and the returned `ChatResponse`) is what a consumer should use for billing/accounting, the same as it would for any other provider's real, API-reported usage. The two sides of that number are revealed at different points, though: `outputTokens` grows on every streamed chunk as content arrives, since it's cheap to recompute from what's been generated so far; `inputMissTokens`/`inputCacheTokens` are only computed once, on that final chunk, since the input side never changes mid-call and there's no reason to resend the same number on every content delta.
69
+
35
70
  ## Think effort
36
71
 
37
72
  Reasoning/thinking models each expose a different native "how hard should the model think" knob — OpenAI and Ollama use `low`/`medium`/`high`, DeepSeek uses `none`/`low`/`high`/`max`, Anthropic uses a numeric thinking-token budget. OrbitX replaces all of that with one universal option on every provider's constructor:
@@ -60,6 +95,19 @@ Per-provider specifics:
60
95
 
61
96
  See [Streaming](./streaming.md#thinking-chunks) for how (and whether) a given provider's reasoning text is observable while it streams.
62
97
 
98
+ ## User tracking & prompt caching
99
+
100
+ Every provider's constructor accepts `userId?: string` — an opaque end-user identifier forwarded to the underlying API for that provider's own abuse-monitoring/analytics, same universal-option pattern as `thinkEffort`: pass it uniformly and providers that have no equivalent field just ignore it.
101
+
102
+ | Provider | Where `userId` goes | Notes |
103
+ |---|---|---|
104
+ | Anthropic | `metadata.user_id` on every request | Anthropic's documented field for this purpose. |
105
+ | OpenAI | `user_id` on every request | Sent as an extra body field alongside the standard params. |
106
+ | DeepSeek | `user_id` on every request | Same mechanism as OpenAI (DeepSeek's API is OpenAI-compatible). |
107
+ | Ollama | Ignored | Accepted in the options type for interface parity only — Ollama's API has no equivalent field. |
108
+
109
+ **Anthropic prompt caching is on by default.** Every request marks the last content block with an ephemeral `cache_control` breakpoint, so a growing conversation gets served from cache instead of being fully reprocessed each turn — pass `disablePromptCaching: true` to send every request uncached. DeepSeek's cache is automatic server-side (no `cache_control` equivalent to toggle) and reports its own explicit `prompt_cache_hit_tokens`/`prompt_cache_miss_tokens`. OpenAI's cache is also automatic, reporting hits via `prompt_tokens_details.cached_tokens`. Ollama has no cache concept at all — `inputCacheTokens` is always `0`.
110
+
63
111
  ## Built-in providers
64
112
 
65
113
  ### Ollama
@@ -71,6 +119,7 @@ new OllamaProvider(model: string, host = "http://localhost:11434", options?: {
71
119
  supportsTools?: boolean; // default false
72
120
  contextWindow?: number; // default 32_000
73
121
  thinkEffort?: number; // 0-1, see "Think effort" above
122
+ userId?: string; // accepted for interface parity only — ignored, Ollama has no equivalent field
74
123
  });
75
124
  ```
76
125
 
@@ -86,12 +135,13 @@ new DeepSeekProvider(apiKey: string, model = "deepseek-v4-flash", options?: {
86
135
  supportsImages?: boolean; // default: true only for deepseek-v4-flash-vision-exp
87
136
  contextWindow?: number; // default 1_000_000 (deepseek-v4-flash / deepseek-v4-flash-vision-exp / deepseek-v4-pro)
88
137
  thinkEffort?: number; // 0-1, see "Think effort" above
138
+ userId?: string; // see "User tracking & prompt caching" above
89
139
  });
90
140
  ```
91
141
 
92
142
  Uses the `openai` SDK pointed at `https://api.deepseek.com` (DeepSeek's API is OpenAI-compatible). `getCapabilities().supportsImages` is `false` for every model except `deepseek-v4-flash-vision-exp`, which is `deepseek-v4-flash` plus native image input (same context window, same reasoning support) — pass `supportsImages: true` explicitly if a future model adds vision support before this SDK's allowlist is updated.
93
143
 
94
- Token usage (`inputTokens`/`outputTokens`) is only accurate on the non-streaming path (`chat()` called without `streamCallback`), which reads `response.usage` directly. On the streaming path this provider deliberately does not set `stream_options: { include_usage: true }`, so DeepSeek never sends a usage-bearing chunk and both counts report as `0`. This matters most for `ImageDescriber`, which always calls `chat()` without a `streamCallback` (see [Token accounting](./agents.md#token-accounting)) — so image-description usage is captured correctly even though live agent streaming isn't.
144
+ `stream_options: { include_usage: true }` is set on every streaming call, so `inputMissTokens`/`inputCacheTokens`/`outputTokens` are accurate on both the streaming and non-streaming paths — DeepSeek sends a usage-bearing final chunk (including its own `prompt_cache_hit_tokens`/`prompt_cache_miss_tokens` split) the same way OpenAI does.
95
145
 
96
146
  ### Anthropic
97
147
 
@@ -101,9 +151,12 @@ import { AnthropicProvider } from "orbitx";
101
151
  new AnthropicProvider(apiKey: string, model = "claude-sonnet-5", options?: {
102
152
  supportsTools?: boolean; // default true
103
153
  supportsImages?: boolean; // default true
104
- contextWindow?: number; // default from a per-model table, else 200_000
154
+ contextWindow?: number; // default from a per-model table, else 200_000; an explicit value here always wins over extendedContext below
105
155
  maxTokens?: number; // output token cap, default 4096
106
156
  thinkEffort?: number; // 0-1, see "Think effort" above
157
+ userId?: string; // see "User tracking & prompt caching" above
158
+ disablePromptCaching?: boolean; // default false — prompt caching is on by default, see above
159
+ extendedContext?: boolean | string; // default false/undefined — see "Context window" below
107
160
  });
108
161
  ```
109
162
 
@@ -120,27 +173,47 @@ new OpenAIProvider(apiKey: string, model = "gpt-5", options?: {
120
173
  contextWindow?: number; // default from a per-model table, else 128_000
121
174
  baseURL?: string; // point at an OpenAI-compatible endpoint
122
175
  thinkEffort?: number; // 0-1, see "Think effort" above — ignored on non-reasoning models
176
+ userId?: string; // see "User tracking & prompt caching" above
123
177
  });
124
178
  ```
125
179
 
126
180
  Uses the Chat Completions API (not the Responses API). `baseURL` makes this usable against any OpenAI-compatible provider, not just OpenAI itself.
127
181
 
128
- ## Two provider roles: main + image
182
+ ## Provider roles
183
+
184
+ An agent's providers are registered under open-ended role names, not a fixed pair. `"main"` — the provider driving the agent loop itself — is the only one that's required; every other role is optional and looked up on demand by whatever tool needs it.
129
185
 
130
- An agent has exactly two provider "roles":
186
+ ```ts
187
+ export type ProviderType =
188
+ | "main"
189
+ | "image-describer"
190
+ | "image-generation"
191
+ | "image-editing"
192
+ | "video-describer"
193
+ | "video-generation"
194
+ | "video-editing"
195
+ | "audio-describer"
196
+ | "audio-generation"
197
+ | "audio-design"
198
+ | "audio-clone"
199
+ | "3d-model-generator"
200
+ | "llm-low"
201
+ | "llm-medium"
202
+ | "llm-high"
203
+ | (string & {});
204
+ ```
131
205
 
132
- - **`main`** — drives the agent loop itself (required).
133
- - **`image`** — describes image tool output (e.g. a screenshot from `BrowserScreenshotTool`) into text, so the main conversation doesn't have to carry raw image bytes. Optional.
206
+ `ProviderType` (exported from `orbitx`) is the documented starting vocabulary for every role name — not a closed union, and purely for naming/registry lookup. It has no relationship to `ProviderCapabilities` (see above) — a role name doesn't imply anything about what the provider registered under it can actually do. The trailing `(string & {})` (not bare `string`) is what makes the open-endedness work: a plain `| string` union collapses to just `string` and an editor stops suggesting the literals at all, while intersecting with `{}` keeps TS from widening it away, so a custom name still type-checks the moment nothing here fits. `llm-low`/`medium`/`high` are a self-declared overall quality/durability tier (cheap-and-good-enough vs. worth relying on for longer/harder tasks) — not a specific claim about reasoning power.
134
207
 
135
- Pass a single provider to use it for both roles (if it supports images, it's also used as the image describer; otherwise images are dropped with an explanatory error message back to the model). Pass an array to split them:
208
+ Pass a single provider to use it as `main` (same as today); pass an array of `{ type, provider, default? }` entries to register more:
136
209
 
137
210
  ```ts
138
211
  import { BaseAgent, DeepSeekProvider, OllamaProvider } from "orbitx";
139
212
 
140
213
  const agent = new BaseAgent({
141
214
  aiProvider: [
142
- { type: "main", aiProvider: new DeepSeekProvider("api-key", "deepseek-v4-flash") },
143
- { type: "image", aiProvider: new OllamaProvider("llava:latest") },
215
+ { type: "main", provider: new DeepSeekProvider("api-key", "deepseek-v4-flash") },
216
+ { type: "image-describer", provider: new OllamaProvider("llava:latest") },
144
217
  ],
145
218
  instruction: "You are a helpful assistant.",
146
219
  mcpClient,
@@ -148,8 +221,34 @@ const agent = new BaseAgent({
148
221
  });
149
222
  ```
150
223
 
151
- This resolution is done by `resolveAgentProviders()` (`AgentProvidersInput = AIProvider | AgentProviderEntry[]`), exported from `orbitx` if you need it directly. There is no third "utils" role — every tool/skill is included in the system prompt from the start, so there's nothing left for a separate pass to decide.
224
+ `provider` can also be an array — a pool of candidates for that role — with `default` (0-based, defaults to `0`) picking which one is used when code needs exactly one. The rest of the pool is there to be enumerated later; nothing built-in does smarter selection than "the default" yet.
225
+
226
+ ### Resolving a provider by role
227
+
228
+ `ProviderRegistry.getProvider(type)` tries an exact role-name match first (that role's pool default). If nothing is registered under `type`, it falls back to scanning every provider registered under *any* role for the first one whose `getCapabilities()` reports the matching capability flag — not a generic capability bag keyed by `ProviderType` (that's gone; `ProviderType` is purely a registry role name and has no relationship to what a model can do), but a fixed, small mapping from a handful of well-known role names to their one corresponding `ProviderCapabilities` flag:
229
+
230
+ | Role (`ProviderType`) | Capability flag checked in the fallback |
231
+ |---|---|
232
+ | `"image-describer"` | `supportsImages` |
233
+ | `"image-generation"` | `supportsImageGeneration` |
234
+ | `"image-editing"` | `supportsImageEditing` |
235
+ | `"video-describer"` | `supportsVideo` |
236
+ | `"video-generation"` | `supportsVideoGeneration` |
237
+ | `"video-editing"` | `supportsVideoEditing` |
238
+ | `"audio-describer"` | `supportsAudio` |
239
+ | `"audio-generation"` | `supportsAudioGeneration` |
240
+ | `"audio-design"` | `supportsAudioDesign` |
241
+ | `"audio-clone"` | `supportsAudioClone` |
242
+ | `"3d-model-generator"` | `supports3DModelGeneration` |
243
+
244
+ This is why a single vision-capable `main` provider "just works" as an image describer with zero extra config: nothing needs to be registered under `"image-describer"` explicitly — the fallback finds `main` because its `getCapabilities().supportsImages` is `true`. Role names outside this table (`"main"`, `"llm-low"`/`"medium"`/`"high"`, or any custom string) have no capability equivalent, so the fallback never applies to them — an unregistered role like that returns `undefined` from `getProvider` regardless of what any registered provider can do.
245
+
246
+ `BaseAgent` is the only thing that ever calls `getProvider` directly — a tool never touches a `ProviderRegistry`, or a raw provider, itself. It reaches this indirectly via `mcp.executeProvider(toolCallId, type, input)` (see [Tools](./tools.md#mcpexecuteprovider--calling-a-provider-from-inside-a-tool)), which works the same way whether the tool is registered on the local `MCPClient` or a sandboxed/remote `MCPServer` — the request is always resolved on `BaseAgent`'s side, never handed across as a live object.
247
+
248
+ `resolveAgentProviders()` (`AgentProvidersInput = AIProvider | AgentProviderEntry[]`) builds the `ProviderRegistry` this all runs on — both are exported from `orbitx` if you need them directly (`getMain()`, `getProvider(type)`, `getProviders(type)` for the full pool under an exact role). There is no separate "utils" role — every tool/skill is included in the system prompt from the start, so there's nothing left for a separate pass to decide.
152
249
 
153
250
  ## Writing a custom provider
154
251
 
155
252
  Extend `AIProvider` and implement `chat`/`getCapabilities`. Follow an existing provider (e.g. `src/core/ollama-provider.ts`) as a template: translate `Message[]` to your API's wire format, stream text chunks to `streamCallback` as they arrive, accumulate tool-call deltas and emit them on the final chunk, and translate `ToolSchema[]` into your API's function-calling format (see `src/core/tool-schema-translator.ts` for existing OpenAI/Anthropic translators you can reuse or reference).
253
+
254
+ `inputMissTokens`/`inputCacheTokens`/`outputTokens` on the returned `ChatResponse` are optional — every built-in provider is token-billed and always sets them, but a provider registered under a per-request-billed role (e.g. `image-generation`, `audio-clone`) should omit them and set `cost` instead. `BaseAgent#executeProvider` (see [Tools](./tools.md#mcpexecuteprovider--calling-a-provider-from-inside-a-tool)) checks `cost` first and records a `unit: "cost"` usage entry when it's present, falling back to the token fields (defaulting any missing one to `0`) otherwise — see [Token accounting](./agents.md#token-accounting).
package/docs/streaming.md CHANGED
@@ -11,12 +11,14 @@ type StreamCallback = (chunk: {
11
11
  toolCallId?: string; // on "tool"-role chunks
12
12
  toolName?: string; // on "tool"-role chunks
13
13
  thinking?: string; // reasoning-text delta, mirroring `content` — see "Thinking chunks" below
14
+ usage?: { inputMissTokens: number; inputCacheTokens: number; outputTokens: number }; // see "Usage chunks" below
15
+ hidden?: boolean; // true on messages BaseAgent injected itself
14
16
  }) => Promise<void> | void;
15
17
  ```
16
18
 
17
19
  ## What you actually receive, turn by turn
18
20
 
19
- 1. **`role: "user"`** — one chunk, `done: true`, echoing the prompt that was just added to history (either the one you passed to `run()`, or a queued one — see [Agents](./agents.md#the-run-loop)).
21
+ 1. **`role: "user"`** — one chunk, `done: true`, echoing the prompt that was just added to history (either the one you passed to `run()`, or a queued one — see [Agents](./agents.md#the-run-loop)). One exception: the forced-compaction nudge (see [Agents](./agents.md#memory-compaction)) is also a `role: "user"` chunk, but carries `hidden: true` since it's `BaseAgent` talking to the model, not the actual user — check that flag before showing a `"user"` chunk to an end user.
20
22
  2. **`role: "assistant"`** — the model's reply, streamed incrementally: zero or more `done: false` chunks each carrying the next slice of `content` as it arrives from the provider, followed by one final `done: true` chunk. `content` is delta text, not the accumulated total — append it yourself if you need the running message.
21
23
  - If the model requested tool calls this turn, each one is emitted individually, mid-stream, as soon as *that* call finishes generating — `toolCalls: [thatOneCall]` on a `done: false` chunk. See "Incremental tool-call chunks" below.
22
24
  3. **`role: "tool"`** — one chunk per dispatched tool call, `done: true`, with `content` set to the tool's result text and `toolCallId`/`toolName` identifying which call it answers. Emitted after the tool has actually finished executing (there is no streaming/progress signal for tool execution itself through this callback — see below for how to get that separately).
@@ -49,6 +51,21 @@ Detection differs by provider, since not every API has an explicit "this call is
49
51
  | OpenAI / DeepSeek | Inferred: the API streams one call's arguments at a time with no end marker, so a call is treated as finished the instant the *next* call's first delta (carrying a fresh `id`) arrives, or the stream ends (for the last/only call). |
50
52
  | Ollama | Ollama sends each tool call already fully formed (not built up token by token), so it's emitted the moment it's seen — deduplicated in case the same call reappears in a later chunk. |
51
53
 
54
+ ## Usage chunks
55
+
56
+ Every `assistant`-role chunk carries `usage` — not just the ones with a fresh number to report. Where a value isn't known yet, it's an explicit `0`, never an absent/undefined field, so consuming code can always read `chunk.usage.outputTokens` etc. without separately guarding for `usage` or its fields being missing. This is the same `streamCallback` passed into `run()`, forwarded straight through to the provider, so external code (e.g. something watching an external credit/cost budget, deciding when to call `immediateStop()`) sees it too — no separate channel.
57
+
58
+ Every number here is real, straight from whatever the provider reports — nothing in this SDK estimates or guesses a token count. What differs is *when* each field stops being a placeholder `0` and starts reflecting the API's real number:
59
+
60
+ | Provider | Mid-stream `usage` | Final `usage` (on `done: true`) |
61
+ |---|---|---|
62
+ | Anthropic | `inputMissTokens`/`inputCacheTokens` are real from the very first chunk (before any content) — that's simply when its protocol first has them, not something computed early on purpose. `outputTokens` updates on every delta. | Same numbers, just the last update. |
63
+ | OpenAI | All three fields are `0` on every chunk before the last one (`include_usage` is honored correctly, but only delivers on completion — there's nothing to report early). | Real, billed numbers for all three fields. If the call is cut off before this chunk arrives, `inputMissTokens`/`inputCacheTokens`/`outputTokens` come back `0` on `ChatResponse` too — the API never computed them, so there's nothing to report. |
64
+ | DeepSeek | Same as OpenAI — `0` on every chunk before the last one. | Real, billed numbers, including its own `prompt_cache_hit_tokens`/`prompt_cache_miss_tokens` for the cache split. Same `0`-on-abort behavior as OpenAI if the call is cut off first. |
65
+ | Ollama | All three fields are `0` on every chunk before the last one — no live signal exists for this provider at all. | Real. |
66
+
67
+ Practical implication: for Anthropic and DeepSeek you get a live, reactable signal before a turn finishes (immediately for Anthropic's input side, throughout for either provider's output side); for OpenAI there's no live signal at all today — you only find out after the turn finishes, or get a hard `0` if it was cut off first.
68
+
52
69
  ## Minimal consumer
53
70
 
54
71
  ```ts
@@ -110,7 +127,7 @@ Once a turn finishes, its accumulated reasoning is available two ways on both `C
110
127
 
111
128
  ## What's *not* in the stream today
112
129
 
113
- - **No live per-tool progress channel through `StreamCallback`.** For domains that emit their own progress (browser navigation, long-running bash processes), subscribe to that domain's `*Interaction` `EventEmitter` directly instead (e.g. `BrowserInteraction`, `BashInteraction`) — see [Tools](./tools.md).
130
+ - **No live per-tool progress channel through `StreamCallback`.** There's no built-in way to observe a long-running tool call (browser navigation, a bash process) mid-flight — `execute()` runs to completion and its return value is the only thing surfaced.
114
131
  - **Tool calls still only appear once, fully assembled** — see above. `thinkEffort`/thinking chunks don't change that.
115
132
 
116
133
  If your use case depends on either of these, check the project's issue tracker / recent changes before assuming they're unavailable — this area of the SDK is actively evolving.
package/docs/tools.md CHANGED
@@ -16,21 +16,54 @@ new MCPTool({
16
16
  default?: any;
17
17
  }[];
18
18
  stopIterationAfterUsingThisTool?: boolean; // default false
19
- customClass?: MCPCustomClass; // optional stateful helper, see below
20
19
  execute: (
21
20
  envID: string,
22
21
  inputs: Record<string, any>,
23
- mcp?: MCP,
24
- customClass?: typeof customClass
22
+ toolCallId?: string,
23
+ mcp?: MCP
25
24
  ) => Promise<any>;
26
25
  });
27
26
  ```
28
27
 
29
28
  - `inputs` doubles as both the JSON-schema-like description sent to the model (translated per-provider by `src/core/tool-schema-translator.ts`) and the shape `execute` should expect on `inputs`.
30
29
  - `stopIterationAfterUsingThisTool: true` ends the agent's run loop immediately after this tool fires, even if the model didn't naturally stop calling tools — useful for a "final answer" or "hand off" style tool.
31
- - `execute`'s return value is normalized via `normalizeToolOutput()` into either `{ type: "text", output: {...} }` or `{ type: "image", output: { image, mimeType, ... } }`. Return `{ type: "image", output: {...} }` explicitly to hand back an image (it gets routed through the agent's image provider — see [Providers](./providers.md#two-provider-roles-main--image)); anything else is treated as a plain JSON text result.
30
+ - `execute`'s return value is normalized via `normalizeToolOutput()` into `{ output: {...} }` — return a plain object (or anything else) and it becomes `output` as-is, same convention as always.
32
31
  - `envID` identifies which "environment" (in-process client id, IPC/WS session) the call came from — most tools ignore it unless they need per-environment isolated state (see `MCPStorage`/`MCPFSStorage` in [MCP Architecture](./mcp-architecture.md)).
33
- - `customClass` (a subclass of `MCPCustomClass`) is a way to give a tool its own persistent helper object with access to the owning `MCP` instance (`getMCP()`) and an `EventEmitter` (`getEvents()`) for progress/status events — see how the built-in `*Interaction` classes are used (e.g. `FsInteraction`, `BrowserInteraction`) if you want to expose live progress to a UI. **Experimental:** `customClass` is still settling — its name and shape may change (or it may be removed) in a future release, so avoid depending on it for anything beyond the built-in `*Interaction` pattern until it stabilizes.
32
+ - `toolCallId` is this specific call's id, needed only by a tool that calls `mcp.executeProvider(...)` (see below) — everything else can ignore it.
33
+
34
+ ### `mcp.executeProvider(...)` — calling a provider from inside a tool
35
+
36
+ `MCP` (what `execute`'s 4th argument gives you, same as it always has) has one method beyond `getStorage()`/`getRNG()`:
37
+
38
+ ```ts
39
+ abstract class MCP {
40
+ getStorage(): MCPStorage;
41
+ getRNG(): MCPRNG;
42
+ executeProvider(toolCallId: string, type: ProviderType, input: Record<string, any>): Promise<{ output: Record<string, any> }>;
43
+ }
44
+ ```
45
+
46
+ `type` picks a role (see [Provider roles](./providers.md#provider-roles)); `input` must be `Message`-shaped — `{ content: string }` for plain text, or `{ parts: MessageContentPart[] }` when real multimodal content (an image, say) needs to reach the provider. `BaseAgent#executeProvider` spreads `input` straight onto the outgoing `role: "user"` message (no `safetyPolicies` system message here, unlike the main run loop — see [Guardrail system prompt](./agents.md#guardrail-system-prompt)) and stays completely type-agnostic itself — it never branches on `type`, so a role that needs `parts` (`image-describer`) and one that only needs `content` (a plain text-generation role) go through the exact same code path. Shaping `input` correctly is the calling tool's job: **use `parts`, not a `data:` URI folded into `content`, for images/video** — every built-in provider (Anthropic/OpenAI/DeepSeek/Ollama) only builds a real multimodal content block from `Message.parts`; a base64 string inside `content` is sent to the model as literal text, not an image. This works identically no matter where the tool is registered — directly on an `MCPClient`, or on a sandboxed/remote `MCPServer` (see [MCP Architecture](./mcp-architecture.md#custom-mcp-subclasses)) — because the actual provider call always happens on the trusted side (wherever `BaseAgent` lives): a client-registered tool reaches it in-process, a server-registered one has the request proxied there over the existing connection and back. No provider instance, or the credentials it holds, is ever visible to the tool — only this request/response.
47
+
48
+ **Off by default.** `executeProvider` throws immediately unless the owning `BaseAgent` was constructed with `features: { executeProviderFromMCPTool: true }` (see [`executeProvider` feature flag](./agents.md#executeprovider-feature-flag)) — a tool reaching a provider directly, with no guardrail prompt ahead of it, is a bigger trust surface than one that only returns data to the model, so it needs an explicit opt-in from whoever builds the agent.
49
+
50
+ `read-image`/`browser-screenshot` (below) are the reference example — the instruction as a `text` part, the base64 image as an `image` part:
51
+
52
+ ```ts
53
+ const { output } = await mcp.executeProvider(toolCallId, "image-describer", {
54
+ parts: [
55
+ { type: "text", text: instruction },
56
+ { type: "image", image, mimeType },
57
+ ],
58
+ });
59
+ return { description: output.content }; // output.content: string
60
+ ```
61
+
62
+ If `executeProvider` rejects (no provider configured for that role, etc.), just let it throw — `BaseAgent`'s existing `execute()` error handling turns it into a normal `Error: ...` tool result, no special handling needed.
63
+
64
+ ### Usage is never something a tool reports itself
65
+
66
+ There's no `usage` field on what `execute` returns. Every `executeProvider` call is recorded by `BaseAgent` the instant the real provider call resolves — keyed by `toolCallId`, moved onto the tool's result `Message.usage` only after `execute()` returns (see [Token accounting](./agents.md#token-accounting)) — specifically so a compromised or rewritten tool can't just under-report what it spent; it never holds that number to begin with.
34
67
 
35
68
  Tools are registered on an `MCPServer` and invoked through an `MCPClient`; `SimpleAgent` does this wiring for you from a flat `tools` array.
36
69
 
@@ -38,7 +71,7 @@ One tool schema the model sees isn't an `MCPTool` at all: `BaseAgent` always app
38
71
 
39
72
  ## Built-in tool catalog
40
73
 
41
- Import a whole domain's tools at once (e.g. `FsTools()`), or import individual tools by name. Every domain also exports a `*Interaction` `EventEmitter` for subscribing to that domain's live progress events (e.g. `FsInteraction`, `BashInteraction`).
74
+ Import a whole domain's tools at once (e.g. `FsTools()`), or import individual tools by name.
42
75
 
43
76
  ### Filesystem — `FsTools()`
44
77
 
@@ -90,7 +123,7 @@ Drives a real headless browser session.
90
123
  | `BrowserNetworkTool` | Read captured network request/response activity. |
91
124
  | `BrowserNetworkStatusTool` | Check whether the page currently has in-flight network activity. |
92
125
  | `BrowserInjectTool` | Evaluate arbitrary JS in the page and return the result. |
93
- | `BrowserScreenshotTool` | Capture a screenshot; can be described by the `image` provider instead of passed raw to the main model. |
126
+ | `BrowserScreenshotTool` | Capture a screenshot and return a text description of it (via an `image-describer` provider — see [Provider roles](./providers.md#provider-roles)). |
94
127
 
95
128
  ### Todo lists — `TodoTools()`
96
129
 
@@ -126,7 +159,7 @@ Backs the `PlannerSkill` (see [Skills](./skills.md)), but usable standalone.
126
159
  |---|---|
127
160
  | `GetCurrentTimeTool` | Get the current date/time (ISO string, unix timestamp, timezone). |
128
161
  | `DelayTool` | Wait a given number of milliseconds (max 60000ms) before continuing. |
129
- | `ReadImageTool` | Read an image file off disk and hand it to the agent as an image tool-output — same handling as `BrowserScreenshotTool` (raw bytes to the model, or described by the image provider first, depending on config). |
162
+ | `ReadImageTool` | Read an image file off disk and return a text description of it — same handling as `BrowserScreenshotTool`. |
130
163
 
131
164
  ### Multi-agent — `AgentTools(availableAgents, options?)` *(experimental)*
132
165
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orbitx",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "An AI SDK providing agents, swarms, planners, skills, MCP tools, workflows, and multi-provider AI integrations.",
5
5
  "repository": {
6
6
  "type": "git",