Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"src/deploy/shims/node.worker.ts",
"src/deploy/shims/quickjs.worker.ts",
"src/deploy/shims/ollama.worker.ts",
"src/deploy/shims/google.worker.ts",
"src/deploy/shims/mcp.worker.ts",
"src/deploy/shims/pi.worker.ts",
"src/agentDir/__fixtures__/**/*.{ts,js}",
Expand All @@ -25,6 +26,7 @@
],
"ignoreDependencies": [
"@ai-sdk/anthropic",
"@ai-sdk/google",
"@ai-sdk/openai",
"ollama-ai-provider",
"ollama-ai-provider-v2",
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ opt-out.
- `lousho eval` without vitest installed now says it runs `*.eval.ts` files through vitest, the directory it searched from, and the install command; [Evals](docs/evals.md) states the requirement up front.
- `result.usage.delegated.toolErrors`: the tool calls that failed inside a run's sub-agents (and theirs), which the lead's own `result.toolErrors` does not count. `ToolCallContext.onDelegatedUsage` takes the child's `toolErrors` as an optional second argument.
- The session HTTP API (`createRouteHandler()`, the deployed node server, the Worker, `lousho dev`) writes an SSE comment (`: ping`) after 15 seconds without an event, so proxies and client idle timeouts no longer drop a turn whose model or tool is slow; `heartbeatMs` changes the interval (`0` turns it off). See [HTTP API](docs/deployment.md#http-api).
- `google/<model>` model strings (e.g. `createAgent({ model: 'google/gemini-2.5-flash' })`) and a `GoogleProvider`, backed by the optional peer `@ai-sdk/google` (`^1.2.0` with `ai` 4, `^3.0.0` with `ai` 6, `^4.0.0` with `ai` 7) and reading `GOOGLE_GENERATIVE_AI_API_KEY`. `lousho init --provider google`, `lousho doctor` (package pairing, key, `--ping`) and the missing-peer install hint cover it. See [Providers](docs/providers.md).
- An unknown model prefix (`mistral/...`, `xai/...`) now says how to reach the vendor anyway: `openrouter/<vendor>/<model>`, `pi/<vendor>/<model>` when pi serves it, or `fromAiSdk()` with the vendor's AI SDK package. See [Vendors without a prefix](docs/providers.md#vendors-without-a-prefix).
- `lousho doctor` fails an installed `ai` 5 (AI SDK v5 is not supported) with an upgrade hint to `ai` 6 or 7 and the matching `@ai-sdk/*` majors.

### Fixed
- MCP tool schemas with OpenAPI's `nullable: true` (any type) now accept `null`; the model's valid `null` was rejected with `ToolArgumentsValidationError`.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ npm install @lousho/build-ai-agent ai@^7.0.0 zod
npm install @ai-sdk/openai@^4.0.0 @ai-sdk/anthropic@^4.0.0
```

Supported `ai` majors are 4 (`^4.3.19`), 6 and 7; AI SDK v5 is not supported. For Gemini add `@ai-sdk/google@^4.0.0` (`google/<model>`).
For Ollama, use `ai@^7.0.0` with `ollama-ai-provider-v2@^4.0.0` and zod 4 (or
`ai@^4.3.19` with `ollama-ai-provider@^1.2.0` and zod 3);
[Installation](docs/installation.md#provider-packages) lists every pairing. `npx lousho doctor` checks Node, peers and API keys and
Expand Down
26 changes: 25 additions & 1 deletion api/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1899,7 +1899,7 @@ export const ERROR_CODES: {
readonly LOUSHO_CONFIG_MISSING_CHECKPOINT_STORE: "Pass createAgent({ store }) with `checkpoints` (e.g. memoryStore() or a SqliteStore), or drop `sessionId`.";
readonly LOUSHO_CONFIG_RESOLVER_FAILED: "Fix the createAgent() option function named in the message; its own error is the `cause`.";
readonly LOUSHO_PROVIDER_SPEC_INVALID: "Write the model as '<provider>/<model>', e.g. 'openai/gpt-4.1-mini'.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message; reach any other vendor through openrouter/<vendor>/<model>, pi/<vendor>/<model> or fromAiSdk().";
readonly LOUSHO_PROVIDER_MISSING_API_KEY: "Set the environment variable named in the message, or pass a provider instance.";
readonly LOUSHO_PROVIDER_REQUEST_FAILED: "Check the provider status, your API key and the request; retryable failures are retried by withRetry().";
readonly LOUSHO_PROVIDER_RATE_LIMITED: "Wait and retry (withRetry() honours Retry-After), or lower the request rate.";
Expand Down Expand Up @@ -2834,6 +2834,30 @@ export interface GitHubTarget {
// @public
export type GitHubTriggers = readonly string[] | ((commenter: GitHubCommenter) => boolean | Promise<boolean>);

// @public
export class GoogleProvider extends AiSdkProvider<GoogleProviderConfig> {
// (undocumented)
protected createModel(modelId: string): Promise<LanguageModel>;
// (undocumented)
protected readonly fallbackModel = "gemini-2.5-flash";
protected fileMediaTypes(): readonly string[];
getModels(): Promise<string[]>;
// (undocumented)
readonly name = "google";
// (undocumented)
supportsStreaming(_model: string): boolean;
supportsTools(model: string): boolean;
}

// @public
export interface GoogleProviderConfig extends AiSdkProviderConfig {
apiKey?: string;
// (undocumented)
baseURL?: string;
// (undocumented)
defaultModel?: string;
}

// @public
export interface GroupDecision {
approved: boolean;
Expand Down
2 changes: 1 addition & 1 deletion api/mcp.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ const ERROR_CODES: {
readonly LOUSHO_CONFIG_MISSING_CHECKPOINT_STORE: "Pass createAgent({ store }) with `checkpoints` (e.g. memoryStore() or a SqliteStore), or drop `sessionId`.";
readonly LOUSHO_CONFIG_RESOLVER_FAILED: "Fix the createAgent() option function named in the message; its own error is the `cause`.";
readonly LOUSHO_PROVIDER_SPEC_INVALID: "Write the model as '<provider>/<model>', e.g. 'openai/gpt-4.1-mini'.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message; reach any other vendor through openrouter/<vendor>/<model>, pi/<vendor>/<model> or fromAiSdk().";
readonly LOUSHO_PROVIDER_MISSING_API_KEY: "Set the environment variable named in the message, or pass a provider instance.";
readonly LOUSHO_PROVIDER_REQUEST_FAILED: "Check the provider status, your API key and the request; retryable failures are retried by withRetry().";
readonly LOUSHO_PROVIDER_RATE_LIMITED: "Wait and retry (withRetry() honours Retry-After), or lower the request rate.";
Expand Down
2 changes: 1 addition & 1 deletion api/testing.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const ERROR_CODES: {
readonly LOUSHO_CONFIG_MISSING_CHECKPOINT_STORE: "Pass createAgent({ store }) with `checkpoints` (e.g. memoryStore() or a SqliteStore), or drop `sessionId`.";
readonly LOUSHO_CONFIG_RESOLVER_FAILED: "Fix the createAgent() option function named in the message; its own error is the `cause`.";
readonly LOUSHO_PROVIDER_SPEC_INVALID: "Write the model as '<provider>/<model>', e.g. 'openai/gpt-4.1-mini'.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message; reach any other vendor through openrouter/<vendor>/<model>, pi/<vendor>/<model> or fromAiSdk().";
readonly LOUSHO_PROVIDER_MISSING_API_KEY: "Set the environment variable named in the message, or pass a provider instance.";
readonly LOUSHO_PROVIDER_REQUEST_FAILED: "Check the provider status, your API key and the request; retryable failures are retried by withRetry().";
readonly LOUSHO_PROVIDER_RATE_LIMITED: "Wait and retry (withRetry() honours Retry-After), or lower the request rate.";
Expand Down
2 changes: 1 addition & 1 deletion api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ const ERROR_CODES: {
readonly LOUSHO_CONFIG_MISSING_CHECKPOINT_STORE: "Pass createAgent({ store }) with `checkpoints` (e.g. memoryStore() or a SqliteStore), or drop `sessionId`.";
readonly LOUSHO_CONFIG_RESOLVER_FAILED: "Fix the createAgent() option function named in the message; its own error is the `cause`.";
readonly LOUSHO_PROVIDER_SPEC_INVALID: "Write the model as '<provider>/<model>', e.g. 'openai/gpt-4.1-mini'.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message; reach any other vendor through openrouter/<vendor>/<model>, pi/<vendor>/<model> or fromAiSdk().";
readonly LOUSHO_PROVIDER_MISSING_API_KEY: "Set the environment variable named in the message, or pass a provider instance.";
readonly LOUSHO_PROVIDER_REQUEST_FAILED: "Check the provider status, your API key and the request; retryable failures are retried by withRetry().";
readonly LOUSHO_PROVIDER_RATE_LIMITED: "Wait and retry (withRetry() honours Retry-After), or lower the request rate.";
Expand Down
2 changes: 1 addition & 1 deletion api/worker.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1274,7 +1274,7 @@ const ERROR_CODES: {
readonly LOUSHO_CONFIG_MISSING_CHECKPOINT_STORE: "Pass createAgent({ store }) with `checkpoints` (e.g. memoryStore() or a SqliteStore), or drop `sessionId`.";
readonly LOUSHO_CONFIG_RESOLVER_FAILED: "Fix the createAgent() option function named in the message; its own error is the `cause`.";
readonly LOUSHO_PROVIDER_SPEC_INVALID: "Write the model as '<provider>/<model>', e.g. 'openai/gpt-4.1-mini'.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message.";
readonly LOUSHO_PROVIDER_UNKNOWN: "Use one of the supported provider prefixes listed in the message; reach any other vendor through openrouter/<vendor>/<model>, pi/<vendor>/<model> or fromAiSdk().";
readonly LOUSHO_PROVIDER_MISSING_API_KEY: "Set the environment variable named in the message, or pass a provider instance.";
readonly LOUSHO_PROVIDER_REQUEST_FAILED: "Check the provider status, your API key and the request; retryable failures are retried by withRetry().";
readonly LOUSHO_PROVIDER_RATE_LIMITED: "Wait and retry (withRetry() honours Retry-After), or lower the request rate.";
Expand Down
7 changes: 5 additions & 2 deletions docs/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,11 @@ names the option and `error.cause` is what the function threw.
**Means:** the provider prefix of a model string is not one the SDK knows. The
message lists the supported prefixes and suggests the closest one.

**Fix:** use a supported prefix (`openai`, `anthropic`, `openrouter`, `ollama`),
or pass your own `provider` instance.
**Fix:** use a supported prefix (`openai`, `anthropic`, `google`, `openrouter`,
`ollama`, `pi`, `mock`), or pass your own `provider` instance. For a vendor with no
prefix, the message lists the other routes: `openrouter/<vendor>/<model>`,
`pi/<vendor>/<model>` when pi serves the vendor, or the vendor's AI SDK package
through `fromAiSdk()` (see [Providers](./providers.md#vendors-without-a-prefix)).

**Example:** `createAgent({ model: 'opnai/gpt-4o' })` says "Did you mean 'openai/gpt-4o'?".

Expand Down
10 changes: 5 additions & 5 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ provider packages declare zod 3 as a peer, so pair zod 4 with `ai` 6 or 7.
Each real LLM provider is backed by an optional peer dependency, in the major
that pairs with your `ai` major. Install the pair from one row:

| `ai` | OpenAI, OpenRouter: `@ai-sdk/openai` | Anthropic: `@ai-sdk/anthropic` | Ollama |
| --------- | ------------------------------------ | ------------------------------ | -------------------------------------- |
| `^4.3.19` | `^0.0.42` (or `^1.0.0`) | `^0.0.42` (or `^1.0.0`) | `ollama-ai-provider@^1.2.0` |
| `^6.0.0` | `^3.0.0` | `^3.0.0` | `ollama-ai-provider-v2@^3.0.0` (zod 4) |
| `^7.0.0` | `^4.0.0` | `^4.0.0` | `ollama-ai-provider-v2@^4.0.0` (zod 4) |
| `ai` | OpenAI, OpenRouter: `@ai-sdk/openai` | Anthropic: `@ai-sdk/anthropic` | Google: `@ai-sdk/google` | Ollama |
| --------- | ------------------------------------ | ------------------------------ | ------------------------ | -------------------------------------- |
| `^4.3.19` | `^0.0.42` (or `^1.0.0`) | `^0.0.42` (or `^1.0.0`) | `^1.2.0` | `ollama-ai-provider@^1.2.0` |
| `^6.0.0` | `^3.0.0` | `^3.0.0` | `^3.0.0` | `ollama-ai-provider-v2@^3.0.0` (zod 4) |
| `^7.0.0` | `^4.0.0` | `^4.0.0` | `^4.0.0` | `ollama-ai-provider-v2@^4.0.0` (zod 4) |

For example, on the current `ai` major:

Expand Down
46 changes: 35 additions & 11 deletions docs/providers.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Providers

An `LLMProvider` is what an agent generates with. The SDK ships OpenAI,
Anthropic, OpenRouter, Ollama and `pi` providers (each backed by an optional
Anthropic, Google (Gemini), OpenRouter, Ollama and `pi` providers (each backed by an optional
peer package, see [Installation](./installation.md#provider-packages)) and a
deterministic mock for tests. Name one with a `provider/model` string, or pass
a provider instance. The `vendor/` model prefix chooses the provider; with
Expand All @@ -14,6 +14,7 @@ import { resolveProvider, LLMProviderRegistry } from '@lousho/build-ai-agent';
// The convenient way — reads the credential from the environment
const openai = resolveProvider('openai/gpt-4o-mini'); // OPENAI_API_KEY
const anthropic = resolveProvider('anthropic/claude-sonnet-5-5'); // ANTHROPIC_API_KEY
const google = resolveProvider('google/gemini-2.5-flash'); // GOOGLE_GENERATIVE_AI_API_KEY
const openrouter = resolveProvider('openrouter/openai/gpt-4o-mini'); // OPENROUTER_API_KEY
const ollama = resolveProvider('ollama/llama3.1'); // OLLAMA_BASE_URL
// pi runs one catalog through `pi/<pi-provider>/<model>` (nested slashes ok):
Expand All @@ -26,6 +27,26 @@ const custom = LLMProviderRegistry.create('openai', {
});
```

The `google/` prefix needs the optional `@ai-sdk/google` package (`^1.2.0` with
`ai` 4, `^3.0.0` with `ai` 6, `^4.0.0` with `ai` 7) and reads
`GOOGLE_GENERATIVE_AI_API_KEY`, the variable `@ai-sdk/google` itself uses.

### Vendors without a prefix

A prefix the SDK does not know fails with
[`LOUSHO_PROVIDER_UNKNOWN`](./errors.md#lousho_provider_unknown). A near-miss
(`anthopic/...`) gets a "did you mean"; any other vendor gets the ways to reach
it anyway:

- `openrouter/<vendor>/<model>`: OpenRouter serves most vendors with one
`OPENROUTER_API_KEY` (e.g. `openrouter/mistralai/mistral-large`; OpenRouter's
own vendor names can differ from the AI SDK's).
- `pi/<vendor>/<model>`, when pi serves that vendor (`xai`, `groq`, `mistral`,
`deepseek`, `amazon-bedrock`, `azure`, ...); see [the `pi` provider](#the-pi-provider).
- the vendor's own AI SDK package with
[`fromAiSdk()`](#any-ai-sdk-model-fromaisdk), e.g.
`createAgent({ provider: fromAiSdk(mistral('mistral-large-latest')) })`.

## Choosing the provider in `createAgent()`

`createAgent()` takes exactly one of:
Expand Down Expand Up @@ -148,6 +169,7 @@ console.log(textOf(messages[0]), '->', result.text);
| -------- | ------ | ---------- |
| `openai` | none | `application/pdf` |
| `anthropic` | none | `application/pdf`, `text/plain` |
| `google` | none | `application/pdf`, `text/plain` |
| `openrouter` | none | `application/pdf` |
| `ollama` | none | none |
| `pi` | none (always a text note) | none (always a text note; pi has no file part) |
Expand Down Expand Up @@ -201,7 +223,7 @@ console.log(result.text);
| ------ | ----------- |
| `resolveProvider('p/model')` | Build a real provider from environment credentials. |
| `LLMProviderRegistry` | Registry of provider factories: `create(type, config)`, `register(type, factory)`, `has(type)`. |
| `OpenAIProvider`, `AnthropicProvider`, `OllamaProvider`, `OpenRouterProvider`, `PiProvider` | The provider classes. See [examples/openrouter](../examples/openrouter) for OpenRouter-specific features. `PiProvider` is the `pi/...` adapter below. |
| `OpenAIProvider`, `AnthropicProvider`, `GoogleProvider`, `OllamaProvider`, `OpenRouterProvider`, `PiProvider` | The provider classes. See [examples/openrouter](../examples/openrouter) for OpenRouter-specific features. `PiProvider` is the `pi/...` adapter below. |
| `createMockProvider()`, `MockLLMProvider` | Canned responses for demos; simulates a tool call when the message names a tool. |
| `mockModel(script)` | Scripted, asserting provider for tests (from `/testing`); see [Testing](./testing.md). |
| `withRetry()`, `withFallback()` | Retry transient failures with backoff and fall back to the next provider; see [Provider retries and fallback](./configuration.md#provider-retries-and-fallback). |
Expand All @@ -218,7 +240,7 @@ own credentials. A factory can take its own config type, an interface that
extends `LLMProviderConfig` (for example `BedrockConfig` with a `region`):
`register('bedrock', (config: BedrockConfig) => new BedrockProvider(config))`,
then `LLMProviderRegistry.create('bedrock', { region: 'eu-west-1' })`.
A built-in name (`'openai'`, `'anthropic'`, `'openrouter'`, `'ollama'`, `'pi'`,
A built-in name (`'openai'`, `'anthropic'`, `'google'`, `'openrouter'`, `'ollama'`, `'pi'`,
`'mock'`) takes that provider's own config type (`BuiltinProviderConfigs`), so
`create('openai', { baseUrl: '...' })` is a type error; if you registered your
own factory under a built-in name, pass its config type:
Expand Down Expand Up @@ -491,11 +513,11 @@ on each major today:
The peer ranges accept all three majors. Pair each with its
provider packages:

| `ai` | `@ai-sdk/openai`, `@ai-sdk/anthropic` | Ollama package |
| ---- | ------------------------------------- | -------------- |
| `^4.3.19` | `^0.0.42` or `^1.0.0` | `ollama-ai-provider@^1.2.0` |
| `^6.0.0` | `^3.0.0` | `ollama-ai-provider-v2@^3.0.0` |
| `^7.0.0` | `^4.0.0` | `ollama-ai-provider-v2@^4.0.0` |
| `ai` | `@ai-sdk/openai`, `@ai-sdk/anthropic` | `@ai-sdk/google` | Ollama package |
| ---- | ------------------------------------- | ---------------- | -------------- |
| `^4.3.19` | `^0.0.42` or `^1.0.0` | `^1.0.0` | `ollama-ai-provider@^1.2.0` |
| `^6.0.0` | `^3.0.0` | `^3.0.0` | `ollama-ai-provider-v2@^3.0.0` |
| `^7.0.0` | `^4.0.0` | `^4.0.0` | `ollama-ai-provider-v2@^4.0.0` |

The install hint of a missing provider package and `lousho doctor` name the
version for the `ai` you have installed, and `lousho doctor` flags a mismatched
Expand All @@ -506,7 +528,7 @@ a mismatch before the first call. `OllamaProvider` loads
`ollama-ai-provider` on `ai` 4 and `ollama-ai-provider-v2` on `ai` 6/7; the v2
package peers on zod 4, which the SDK accepts, so install zod 4
with it (zod 3 projects use Ollama with `ai` 4). `lousho init` scaffolds `ai@^7.0.0` with `@ai-sdk/*@^4.0.0` for
OpenAI, Anthropic and OpenRouter, and `ai@^7.0.0` with `ollama-ai-provider-v2@^4.0.0` and zod 4 for Ollama.
OpenAI, Anthropic, Google and OpenRouter, and `ai@^7.0.0` with `ollama-ai-provider-v2@^4.0.0` and zod 4 for Ollama.

CI installs and tests `ai` 4, 6 and 7, each with zod 3 and zod 4 (six pairings;
`ai` 4 with zod 4 is the one pairing the provider packages do not declare), with
Expand Down Expand Up @@ -553,11 +575,13 @@ or `StreamResult`:
On v6/v7 the model must come from a provider package for that major (the
table above); the `0.0.x`/`1.x` packages produce models `ai` v7 rejects. Image
parts are sent as `file` parts with an image media type (the shape `ai` v6/v7
takes for images, so there is no deprecation warning). `ai` v5 is not supported.
takes for images, so there is no deprecation warning). `ai` v5 is not supported:
`lousho doctor` fails an installed `ai` 5 with an upgrade hint (move to `ai` 6
or 7 and the matching `@ai-sdk/*` major).

## Where each provider runs

All providers run on Node. The `cloudflare-worker` deploy target supports
`mock`, `openai`, `anthropic` and `openrouter` (key from the
`OPENROUTER_API_KEY` binding); `ollama` and `pi` need the `node-server` or
`OPENROUTER_API_KEY` binding); `google`, `ollama` and `pi` need the `node-server` or
`docker` target (see [Cloudflare Workers](./cloudflare-workers.md)).
Loading
Loading