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.
- package/dist/core/agent-providers.d.ts +15 -10
- package/dist/core/agent-providers.d.ts.map +1 -1
- package/dist/core/agent-providers.js +65 -15
- package/dist/core/agent-providers.js.map +1 -1
- package/dist/core/ai-provider.d.ts +52 -2
- package/dist/core/ai-provider.d.ts.map +1 -1
- package/dist/core/ai-provider.js.map +1 -1
- package/dist/core/anthropic-provider.d.ts +5 -0
- package/dist/core/anthropic-provider.d.ts.map +1 -1
- package/dist/core/anthropic-provider.js +100 -17
- package/dist/core/anthropic-provider.js.map +1 -1
- package/dist/core/base-agent.d.ts +19 -41
- package/dist/core/base-agent.d.ts.map +1 -1
- package/dist/core/base-agent.js +112 -136
- package/dist/core/base-agent.js.map +1 -1
- package/dist/core/deepseek-provider.d.ts +3 -0
- package/dist/core/deepseek-provider.d.ts.map +1 -1
- package/dist/core/deepseek-provider.js +59 -12
- package/dist/core/deepseek-provider.js.map +1 -1
- package/dist/core/mcp-client.d.ts +10 -2
- package/dist/core/mcp-client.d.ts.map +1 -1
- package/dist/core/mcp-client.js +28 -4
- package/dist/core/mcp-client.js.map +1 -1
- package/dist/core/mcp-server.d.ts +5 -1
- package/dist/core/mcp-server.d.ts.map +1 -1
- package/dist/core/mcp-server.js +31 -1
- package/dist/core/mcp-server.js.map +1 -1
- package/dist/core/mcp.d.ts +9 -23
- package/dist/core/mcp.d.ts.map +1 -1
- package/dist/core/mcp.js +1 -24
- package/dist/core/mcp.js.map +1 -1
- package/dist/core/ollama-provider.d.ts +3 -0
- package/dist/core/ollama-provider.d.ts.map +1 -1
- package/dist/core/ollama-provider.js +31 -7
- package/dist/core/ollama-provider.js.map +1 -1
- package/dist/core/openai-provider.d.ts +3 -0
- package/dist/core/openai-provider.d.ts.map +1 -1
- package/dist/core/openai-provider.js +51 -8
- package/dist/core/openai-provider.js.map +1 -1
- package/dist/core/skill.d.ts +2 -2
- package/dist/core/skill.d.ts.map +1 -1
- package/dist/index.d.ts +13 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -10
- package/dist/index.js.map +1 -1
- package/dist/templates/simple.d.ts +8 -12
- package/dist/templates/simple.d.ts.map +1 -1
- package/dist/templates/simple.js +6 -10
- package/dist/templates/simple.js.map +1 -1
- package/dist/tests/tools/agent/agent-tools.test.js +20 -17
- package/dist/tests/tools/agent/agent-tools.test.js.map +1 -1
- package/dist/tests/tools/question-answer/question-answer.test.js +1 -11
- package/dist/tests/tools/question-answer/question-answer.test.js.map +1 -1
- package/dist/tests/tools/utils/read-image.test.js +29 -17
- package/dist/tests/tools/utils/read-image.test.js.map +1 -1
- package/dist/tools/agent/hire.d.ts +1 -2
- package/dist/tools/agent/hire.d.ts.map +1 -1
- package/dist/tools/agent/hire.js +1 -4
- package/dist/tools/agent/hire.js.map +1 -1
- package/dist/tools/agent/index.d.ts +1 -3
- package/dist/tools/agent/index.d.ts.map +1 -1
- package/dist/tools/agent/index.js +0 -1
- package/dist/tools/agent/index.js.map +1 -1
- package/dist/tools/agent/list.d.ts +1 -1
- package/dist/tools/agent/list.d.ts.map +1 -1
- package/dist/tools/agent/prompt.d.ts +1 -2
- package/dist/tools/agent/prompt.d.ts.map +1 -1
- package/dist/tools/agent/prompt.js +11 -7
- package/dist/tools/agent/prompt.js.map +1 -1
- package/dist/tools/agent/report.d.ts +1 -1
- package/dist/tools/agent/report.d.ts.map +1 -1
- package/dist/tools/bash/index.d.ts +1 -3
- package/dist/tools/bash/index.d.ts.map +1 -1
- package/dist/tools/bash/index.js +0 -1
- package/dist/tools/bash/index.js.map +1 -1
- package/dist/tools/bash/list.d.ts +1 -1
- package/dist/tools/bash/list.d.ts.map +1 -1
- package/dist/tools/bash/list.js +1 -1
- package/dist/tools/bash/list.js.map +1 -1
- package/dist/tools/bash/logs.d.ts +1 -1
- package/dist/tools/bash/logs.d.ts.map +1 -1
- package/dist/tools/bash/logs.js +1 -1
- package/dist/tools/bash/logs.js.map +1 -1
- package/dist/tools/bash/run.d.ts +1 -2
- package/dist/tools/bash/run.d.ts.map +1 -1
- package/dist/tools/bash/run.js +1 -7
- package/dist/tools/bash/run.js.map +1 -1
- package/dist/tools/bash/terminate.d.ts +1 -2
- package/dist/tools/bash/terminate.d.ts.map +1 -1
- package/dist/tools/bash/terminate.js +1 -4
- package/dist/tools/bash/terminate.js.map +1 -1
- package/dist/tools/bash/wait.d.ts +1 -2
- package/dist/tools/bash/wait.d.ts.map +1 -1
- package/dist/tools/bash/wait.js +1 -7
- package/dist/tools/bash/wait.js.map +1 -1
- package/dist/tools/bash/write-input.d.ts +1 -2
- package/dist/tools/bash/write-input.d.ts.map +1 -1
- package/dist/tools/bash/write-input.js +1 -4
- package/dist/tools/bash/write-input.js.map +1 -1
- package/dist/tools/browser/click.d.ts +1 -2
- package/dist/tools/browser/click.d.ts.map +1 -1
- package/dist/tools/browser/click.js +1 -4
- package/dist/tools/browser/click.js.map +1 -1
- package/dist/tools/browser/console.d.ts +1 -1
- package/dist/tools/browser/console.d.ts.map +1 -1
- package/dist/tools/browser/create-session.d.ts +1 -2
- package/dist/tools/browser/create-session.d.ts.map +1 -1
- package/dist/tools/browser/create-session.js +1 -4
- package/dist/tools/browser/create-session.js.map +1 -1
- package/dist/tools/browser/fill.d.ts +1 -2
- package/dist/tools/browser/fill.d.ts.map +1 -1
- package/dist/tools/browser/fill.js +1 -4
- package/dist/tools/browser/fill.js.map +1 -1
- package/dist/tools/browser/get-sessions.d.ts +1 -1
- package/dist/tools/browser/get-sessions.d.ts.map +1 -1
- package/dist/tools/browser/index.d.ts +1 -3
- package/dist/tools/browser/index.d.ts.map +1 -1
- package/dist/tools/browser/index.js +0 -1
- package/dist/tools/browser/index.js.map +1 -1
- package/dist/tools/browser/inject.d.ts +1 -2
- package/dist/tools/browser/inject.d.ts.map +1 -1
- package/dist/tools/browser/inject.js +1 -4
- package/dist/tools/browser/inject.js.map +1 -1
- package/dist/tools/browser/navigate.d.ts +1 -2
- package/dist/tools/browser/navigate.d.ts.map +1 -1
- package/dist/tools/browser/navigate.js +1 -4
- package/dist/tools/browser/navigate.js.map +1 -1
- package/dist/tools/browser/network-status.d.ts +1 -1
- package/dist/tools/browser/network-status.d.ts.map +1 -1
- package/dist/tools/browser/network.d.ts +1 -1
- package/dist/tools/browser/network.d.ts.map +1 -1
- package/dist/tools/browser/read.d.ts +1 -2
- package/dist/tools/browser/read.d.ts.map +1 -1
- package/dist/tools/browser/read.js +1 -4
- package/dist/tools/browser/read.js.map +1 -1
- package/dist/tools/browser/remove-session.d.ts +1 -2
- package/dist/tools/browser/remove-session.d.ts.map +1 -1
- package/dist/tools/browser/remove-session.js +1 -4
- package/dist/tools/browser/remove-session.js.map +1 -1
- package/dist/tools/browser/screenshot.d.ts +1 -2
- package/dist/tools/browser/screenshot.d.ts.map +1 -1
- package/dist/tools/browser/screenshot.js +16 -15
- package/dist/tools/browser/screenshot.js.map +1 -1
- package/dist/tools/browser/scroll-info.d.ts +1 -1
- package/dist/tools/browser/scroll-info.d.ts.map +1 -1
- package/dist/tools/browser/scroll.d.ts +1 -2
- package/dist/tools/browser/scroll.d.ts.map +1 -1
- package/dist/tools/browser/scroll.js +1 -4
- package/dist/tools/browser/scroll.js.map +1 -1
- package/dist/tools/browser/submit-form.d.ts +1 -2
- package/dist/tools/browser/submit-form.d.ts.map +1 -1
- package/dist/tools/browser/submit-form.js +1 -4
- package/dist/tools/browser/submit-form.js.map +1 -1
- package/dist/tools/fs/create-dir.d.ts +1 -2
- package/dist/tools/fs/create-dir.d.ts.map +1 -1
- package/dist/tools/fs/create-dir.js +1 -4
- package/dist/tools/fs/create-dir.js.map +1 -1
- package/dist/tools/fs/delete.d.ts +1 -2
- package/dist/tools/fs/delete.d.ts.map +1 -1
- package/dist/tools/fs/delete.js +1 -4
- package/dist/tools/fs/delete.js.map +1 -1
- package/dist/tools/fs/edit-file.d.ts +1 -2
- package/dist/tools/fs/edit-file.d.ts.map +1 -1
- package/dist/tools/fs/edit-file.js +1 -4
- package/dist/tools/fs/edit-file.js.map +1 -1
- package/dist/tools/fs/index.d.ts +1 -3
- package/dist/tools/fs/index.d.ts.map +1 -1
- package/dist/tools/fs/index.js +0 -1
- package/dist/tools/fs/index.js.map +1 -1
- package/dist/tools/fs/list-dir.d.ts +1 -2
- package/dist/tools/fs/list-dir.d.ts.map +1 -1
- package/dist/tools/fs/list-dir.js +1 -4
- package/dist/tools/fs/list-dir.js.map +1 -1
- package/dist/tools/fs/move.d.ts +1 -2
- package/dist/tools/fs/move.d.ts.map +1 -1
- package/dist/tools/fs/move.js +1 -4
- package/dist/tools/fs/move.js.map +1 -1
- package/dist/tools/fs/read-file.d.ts +1 -2
- package/dist/tools/fs/read-file.d.ts.map +1 -1
- package/dist/tools/fs/read-file.js +1 -4
- package/dist/tools/fs/read-file.js.map +1 -1
- package/dist/tools/fs/stat.d.ts +1 -2
- package/dist/tools/fs/stat.d.ts.map +1 -1
- package/dist/tools/fs/stat.js +1 -4
- package/dist/tools/fs/stat.js.map +1 -1
- package/dist/tools/fs/write-file.d.ts +1 -2
- package/dist/tools/fs/write-file.d.ts.map +1 -1
- package/dist/tools/fs/write-file.js +1 -4
- package/dist/tools/fs/write-file.js.map +1 -1
- package/dist/tools/present/add.d.ts +1 -2
- package/dist/tools/present/add.d.ts.map +1 -1
- package/dist/tools/present/add.js +1 -4
- package/dist/tools/present/add.js.map +1 -1
- package/dist/tools/present/clear.d.ts +1 -2
- package/dist/tools/present/clear.d.ts.map +1 -1
- package/dist/tools/present/clear.js +1 -4
- package/dist/tools/present/clear.js.map +1 -1
- package/dist/tools/present/get-list.d.ts +1 -2
- package/dist/tools/present/get-list.d.ts.map +1 -1
- package/dist/tools/present/get-list.js +0 -2
- package/dist/tools/present/get-list.js.map +1 -1
- package/dist/tools/present/index.d.ts +1 -3
- package/dist/tools/present/index.d.ts.map +1 -1
- package/dist/tools/present/index.js +0 -1
- package/dist/tools/present/index.js.map +1 -1
- package/dist/tools/question-answer/index.d.ts +1 -3
- package/dist/tools/question-answer/index.d.ts.map +1 -1
- package/dist/tools/question-answer/index.js +0 -1
- package/dist/tools/question-answer/index.js.map +1 -1
- package/dist/tools/question-answer/question-answer.d.ts +1 -2
- package/dist/tools/question-answer/question-answer.d.ts.map +1 -1
- package/dist/tools/question-answer/question-answer.js +1 -4
- package/dist/tools/question-answer/question-answer.js.map +1 -1
- package/dist/tools/todo/check-task.d.ts +1 -2
- package/dist/tools/todo/check-task.d.ts.map +1 -1
- package/dist/tools/todo/check-task.js +1 -4
- package/dist/tools/todo/check-task.js.map +1 -1
- package/dist/tools/todo/create-list.d.ts +1 -2
- package/dist/tools/todo/create-list.d.ts.map +1 -1
- package/dist/tools/todo/create-list.js +1 -4
- package/dist/tools/todo/create-list.js.map +1 -1
- package/dist/tools/todo/create-task.d.ts +1 -2
- package/dist/tools/todo/create-task.d.ts.map +1 -1
- package/dist/tools/todo/create-task.js +1 -4
- package/dist/tools/todo/create-task.js.map +1 -1
- package/dist/tools/todo/get-list.d.ts +1 -1
- package/dist/tools/todo/get-list.d.ts.map +1 -1
- package/dist/tools/todo/get-list.js +1 -1
- package/dist/tools/todo/get-list.js.map +1 -1
- package/dist/tools/todo/get-lists.d.ts +1 -1
- package/dist/tools/todo/get-lists.d.ts.map +1 -1
- package/dist/tools/todo/get-lists.js +1 -1
- package/dist/tools/todo/get-lists.js.map +1 -1
- package/dist/tools/todo/index.d.ts +1 -3
- package/dist/tools/todo/index.d.ts.map +1 -1
- package/dist/tools/todo/index.js +0 -1
- package/dist/tools/todo/index.js.map +1 -1
- package/dist/tools/todo/remove-list.d.ts +1 -2
- package/dist/tools/todo/remove-list.d.ts.map +1 -1
- package/dist/tools/todo/remove-list.js +1 -4
- package/dist/tools/todo/remove-list.js.map +1 -1
- package/dist/tools/todo/remove-task.d.ts +1 -2
- package/dist/tools/todo/remove-task.d.ts.map +1 -1
- package/dist/tools/todo/remove-task.js +1 -4
- package/dist/tools/todo/remove-task.js.map +1 -1
- package/dist/tools/utils/delay.d.ts +1 -1
- package/dist/tools/utils/delay.d.ts.map +1 -1
- package/dist/tools/utils/get-current-time.d.ts +1 -1
- package/dist/tools/utils/get-current-time.d.ts.map +1 -1
- package/dist/tools/utils/index.d.ts +1 -1
- package/dist/tools/utils/index.d.ts.map +1 -1
- package/dist/tools/utils/read-image.d.ts +12 -7
- package/dist/tools/utils/read-image.d.ts.map +1 -1
- package/dist/tools/utils/read-image.js +27 -17
- package/dist/tools/utils/read-image.js.map +1 -1
- package/docs/agents.md +50 -25
- package/docs/api-reference.md +12 -13
- package/docs/getting-started.md +2 -2
- package/docs/mcp-architecture.md +14 -2
- package/docs/providers.md +114 -15
- package/docs/streaming.md +19 -2
- package/docs/tools.md +41 -8
- 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,
|
|
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;
|
|
27
|
-
supportsImages: boolean;
|
|
28
|
-
contextWindow: number;
|
|
29
|
-
safeUsageRatio?: number;
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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",
|
|
143
|
-
{ type: "image",
|
|
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
|
-
|
|
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`.**
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
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
|
-
- `
|
|
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.
|
|
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
|
|
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
|
|
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
|
|