|
|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
# @mariozechner/pi-ai
|
|
|
|
|
# @earendil-works/pi-ai
|
|
|
|
|
|
|
|
|
|
Unified LLM API with automatic model discovery, provider configuration, token and cost tracking, and simple context persistence and hand-off to other models mid-session.
|
|
|
|
|
|
|
|
|
|
@@ -75,15 +75,15 @@ Unified LLM API with automatic model discovery, provider configuration, token an
|
|
|
|
|
## Installation
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npm install @mariozechner/pi-ai
|
|
|
|
|
npm install @earendil-works/pi-ai
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
TypeBox exports are re-exported from `@mariozechner/pi-ai`: `Type`, `Static`, and `TSchema`.
|
|
|
|
|
TypeBox exports are re-exported from `@earendil-works/pi-ai`: `Type`, `Static`, and `TSchema`.
|
|
|
|
|
|
|
|
|
|
## Quick Start
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { Type, getModel, stream, complete, Context, Tool, StringEnum } from '@mariozechner/pi-ai';
|
|
|
|
|
import { Type, getModel, stream, complete, Context, Tool, StringEnum } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// Fully typed with auto-complete support for both providers and models
|
|
|
|
|
const model = getModel('openai', 'gpt-4o-mini');
|
|
|
|
|
@@ -209,7 +209,7 @@ Tools enable LLMs to interact with external systems. This library uses TypeBox s
|
|
|
|
|
### Defining Tools
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { Type, Tool, StringEnum } from '@mariozechner/pi-ai';
|
|
|
|
|
import { Type, Tool, StringEnum } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// Define tool parameters with TypeBox
|
|
|
|
|
const weatherTool: Tool = {
|
|
|
|
|
@@ -335,7 +335,7 @@ When using `agentLoop`, tool arguments are automatically validated against your
|
|
|
|
|
When implementing your own tool execution loop with `stream()` or `complete()`, use `validateToolCall` to validate arguments before passing them to your tools:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { stream, validateToolCall, Tool } from '@mariozechner/pi-ai';
|
|
|
|
|
import { stream, validateToolCall, Tool } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
const tools: Tool[] = [weatherTool, calculatorTool];
|
|
|
|
|
const s = stream(model, { messages, tools });
|
|
|
|
|
@@ -391,7 +391,7 @@ Models with vision capabilities can process images. You can check if a model sup
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { readFileSync } from 'fs';
|
|
|
|
|
import { getModel, complete } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
const model = getModel('openai', 'gpt-4o-mini');
|
|
|
|
|
|
|
|
|
|
@@ -428,7 +428,7 @@ Many models support thinking/reasoning capabilities where they can show their in
|
|
|
|
|
### Unified Interface (streamSimple/completeSimple)
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getModel, streamSimple, completeSimple } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getModel, streamSimple, completeSimple } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// Many models across providers support thinking/reasoning
|
|
|
|
|
const model = getModel('anthropic', 'claude-sonnet-4-20250514');
|
|
|
|
|
@@ -466,7 +466,7 @@ for (const block of response.content) {
|
|
|
|
|
For fine-grained control, use the provider-specific options:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getModel, complete } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// OpenAI Reasoning (o1, o3, gpt-5)
|
|
|
|
|
const openaiModel = getModel('openai', 'gpt-5-mini');
|
|
|
|
|
@@ -555,7 +555,7 @@ if (message.stopReason === 'error' || message.stopReason === 'aborted') {
|
|
|
|
|
The abort signal allows you to cancel in-progress requests. Aborted requests have `stopReason === 'aborted'`:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getModel, stream } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getModel, stream } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
const model = getModel('openai', 'gpt-4o-mini');
|
|
|
|
|
const controller = new AbortController();
|
|
|
|
|
@@ -653,7 +653,7 @@ import {
|
|
|
|
|
fauxToolCall,
|
|
|
|
|
registerFauxProvider,
|
|
|
|
|
stream,
|
|
|
|
|
} from '@mariozechner/pi-ai';
|
|
|
|
|
} from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
const registration = registerFauxProvider({
|
|
|
|
|
tokensPerSecond: 50 // optional
|
|
|
|
|
@@ -738,7 +738,7 @@ A **provider** offers models through a specific API. For example:
|
|
|
|
|
### Querying Providers and Models
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getProviders, getModels, getModel } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getProviders, getModels, getModel } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// Get all available providers
|
|
|
|
|
const providers = getProviders();
|
|
|
|
|
@@ -764,7 +764,7 @@ console.log(`Using ${model.name} via ${model.api} API`);
|
|
|
|
|
You can create custom models for local inference servers or custom endpoints:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { Model, stream } from '@mariozechner/pi-ai';
|
|
|
|
|
import { Model, stream } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// Example: Ollama using OpenAI-compatible API
|
|
|
|
|
const ollamaModel: Model<'openai-completions'> = {
|
|
|
|
|
@@ -892,7 +892,7 @@ If `compat` is not set, the library falls back to URL-based detection. If `compa
|
|
|
|
|
Models are typed by their API, which keeps the model metadata accurate. Provider-specific option types are enforced when you call the provider functions directly. The generic `stream` and `complete` functions accept `StreamOptions` with additional provider fields.
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { streamAnthropic, type AnthropicOptions } from '@mariozechner/pi-ai';
|
|
|
|
|
import { streamAnthropic, type AnthropicOptions } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// TypeScript knows this is an Anthropic model
|
|
|
|
|
const claude = getModel('anthropic', 'claude-sonnet-4-20250514');
|
|
|
|
|
@@ -921,7 +921,7 @@ When messages from one provider are sent to a different provider, the library au
|
|
|
|
|
### Example: Multi-Provider Conversation
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getModel, complete, Context } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getModel, complete, Context } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// Start with Claude
|
|
|
|
|
const claude = getModel('anthropic', 'claude-sonnet-4-20250514');
|
|
|
|
|
@@ -966,7 +966,7 @@ This enables flexible workflows where you can:
|
|
|
|
|
The `Context` object can be easily serialized and deserialized using standard JSON methods, making it simple to persist conversations, implement chat history, or transfer contexts between services:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { Context, getModel, complete } from '@mariozechner/pi-ai';
|
|
|
|
|
import { Context, getModel, complete } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// Create and use a context
|
|
|
|
|
const context: Context = {
|
|
|
|
|
@@ -1003,7 +1003,7 @@ const continuation = await complete(newModel, restored);
|
|
|
|
|
The library supports browser environments. You must pass the API key explicitly since environment variables are not available in browsers:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getModel, complete } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// API key must be passed explicitly in browser
|
|
|
|
|
const model = getModel('anthropic', 'claude-3-5-haiku-20241022');
|
|
|
|
|
@@ -1020,7 +1020,7 @@ const response = await complete(model, {
|
|
|
|
|
### Browser Compatibility Notes
|
|
|
|
|
|
|
|
|
|
- Amazon Bedrock (`bedrock-converse-stream`) is not supported in browser environments.
|
|
|
|
|
- OAuth login flows are not supported in browser environments. Use the `@mariozechner/pi-ai/oauth` entry point in Node.js.
|
|
|
|
|
- OAuth login flows are not supported in browser environments. Use the `@earendil-works/pi-ai/oauth` entry point in Node.js.
|
|
|
|
|
- In browser builds, Bedrock can still appear in model lists. Calls to Bedrock models fail at runtime.
|
|
|
|
|
- Use a server-side proxy or backend service if you need Bedrock or OAuth-based auth from a web app.
|
|
|
|
|
|
|
|
|
|
@@ -1071,7 +1071,7 @@ const response = await complete(model, context, {
|
|
|
|
|
### Checking Environment Variables
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getEnvApiKey } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getEnvApiKey } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
// Check if an API key is set in environment variables
|
|
|
|
|
const key = getEnvApiKey('openai'); // checks OPENAI_API_KEY
|
|
|
|
|
@@ -1110,7 +1110,7 @@ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getModel, complete } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
|
|
|
|
|
|
|
|
(async () => {
|
|
|
|
|
const model = getModel('google-vertex', 'gemini-2.5-flash');
|
|
|
|
|
@@ -1133,16 +1133,16 @@ Official docs: [Application Default Credentials](https://cloud.google.com/docs/a
|
|
|
|
|
The quickest way to authenticate:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npx @mariozechner/pi-ai login # interactive provider selection
|
|
|
|
|
npx @mariozechner/pi-ai login anthropic # login to specific provider
|
|
|
|
|
npx @mariozechner/pi-ai list # list available providers
|
|
|
|
|
npx @earendil-works/pi-ai login # interactive provider selection
|
|
|
|
|
npx @earendil-works/pi-ai login anthropic # login to specific provider
|
|
|
|
|
npx @earendil-works/pi-ai list # list available providers
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Credentials are saved to `auth.json` in the current directory.
|
|
|
|
|
|
|
|
|
|
### Programmatic OAuth
|
|
|
|
|
|
|
|
|
|
The library provides login and token refresh functions via the `@mariozechner/pi-ai/oauth` entry point. Credential storage is the caller's responsibility.
|
|
|
|
|
The library provides login and token refresh functions via the `@earendil-works/pi-ai/oauth` entry point. Credential storage is the caller's responsibility.
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import {
|
|
|
|
|
@@ -1159,13 +1159,13 @@ import {
|
|
|
|
|
// Types
|
|
|
|
|
type OAuthProvider,
|
|
|
|
|
type OAuthCredentials,
|
|
|
|
|
} from '@mariozechner/pi-ai/oauth';
|
|
|
|
|
} from '@earendil-works/pi-ai/oauth';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Login Flow Example
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { loginGitHubCopilot } from '@mariozechner/pi-ai/oauth';
|
|
|
|
|
import { loginGitHubCopilot } from '@earendil-works/pi-ai/oauth';
|
|
|
|
|
import { writeFileSync } from 'fs';
|
|
|
|
|
|
|
|
|
|
const credentials = await loginGitHubCopilot({
|
|
|
|
|
@@ -1189,8 +1189,8 @@ writeFileSync('auth.json', JSON.stringify(auth, null, 2));
|
|
|
|
|
Use `getOAuthApiKey()` to get an API key, automatically refreshing if expired:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { getModel, complete } from '@mariozechner/pi-ai';
|
|
|
|
|
import { getOAuthApiKey } from '@mariozechner/pi-ai/oauth';
|
|
|
|
|
import { getModel, complete } from '@earendil-works/pi-ai';
|
|
|
|
|
import { getOAuthApiKey } from '@earendil-works/pi-ai/oauth';
|
|
|
|
|
import { readFileSync, writeFileSync } from 'fs';
|
|
|
|
|
|
|
|
|
|
// Load your stored credentials
|
|
|
|
|
@@ -1247,7 +1247,7 @@ Create a new provider file (for example `amazon-bedrock.ts`) that exports:
|
|
|
|
|
- Register the API with `registerApiProvider()`
|
|
|
|
|
- Add a package subpath export in `package.json` for the provider module (`./dist/providers/<provider>.js`)
|
|
|
|
|
- Add lazy loader wrappers in `src/providers/register-builtins.ts`, do not statically import provider implementation modules there
|
|
|
|
|
- Add any root-level `export type` re-exports in `src/index.ts` that should remain available from `@mariozechner/pi-ai`
|
|
|
|
|
- Add any root-level `export type` re-exports in `src/index.ts` that should remain available from `@earendil-works/pi-ai`
|
|
|
|
|
- Add credential detection in `env-api-keys.ts` for the new provider
|
|
|
|
|
- Ensure `streamSimple` handles auth lookup via `getEnvApiKey()` or provider-specific auth
|
|
|
|
|
|
|
|
|
|
|