@providerprotocol/agents 0.0.1 → 0.0.3
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/README.md +333 -6
- package/dist/checkpoint/index.d.ts +43 -0
- package/dist/checkpoint/index.js +64 -0
- package/dist/checkpoint/index.js.map +1 -0
- package/{src/execution/loop.ts → dist/chunk-4ESYN66B.js} +54 -162
- package/dist/chunk-4ESYN66B.js.map +1 -0
- package/dist/chunk-EKRXMSDX.js +8 -0
- package/dist/chunk-EKRXMSDX.js.map +1 -0
- package/dist/chunk-PHI5ULBV.js +427 -0
- package/dist/chunk-PHI5ULBV.js.map +1 -0
- package/dist/execution/index.d.ts +105 -0
- package/dist/execution/index.js +679 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index-qsPwbY86.d.ts +65 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +23 -0
- package/dist/middleware/index.js +82 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/thread-tree/index.d.ts +115 -0
- package/dist/thread-tree/index.js +4 -0
- package/dist/thread-tree/index.js.map +1 -0
- package/dist/types-2Vsthzyu.d.ts +163 -0
- package/dist/types-BhX9uD_d.d.ts +91 -0
- package/dist/types-DR02gtFv.d.ts +270 -0
- package/dist/types-NGQMdnaD.d.ts +65 -0
- package/package.json +40 -8
- package/.claude/settings.local.json +0 -27
- package/AGENTS.md +0 -681
- package/CLAUDE.md +0 -681
- package/bun.lock +0 -472
- package/eslint.config.js +0 -75
- package/index.ts +0 -1
- package/llms.md +0 -796
- package/specs/UAP-1.0.md +0 -2355
- package/src/agent/index.ts +0 -384
- package/src/agent/types.ts +0 -91
- package/src/checkpoint/file.ts +0 -126
- package/src/checkpoint/index.ts +0 -40
- package/src/checkpoint/types.ts +0 -95
- package/src/execution/index.ts +0 -37
- package/src/execution/plan.ts +0 -497
- package/src/execution/react.ts +0 -340
- package/src/execution/tool-ordering.ts +0 -186
- package/src/execution/types.ts +0 -315
- package/src/index.ts +0 -80
- package/src/middleware/index.ts +0 -7
- package/src/middleware/logging.ts +0 -123
- package/src/middleware/types.ts +0 -69
- package/src/state/index.ts +0 -301
- package/src/state/types.ts +0 -173
- package/src/thread-tree/index.ts +0 -249
- package/src/thread-tree/types.ts +0 -29
- package/src/utils/uuid.ts +0 -7
- package/tests/live/agent-anthropic.test.ts +0 -288
- package/tests/live/agent-strategy-hooks.test.ts +0 -268
- package/tests/live/checkpoint.test.ts +0 -243
- package/tests/live/execution-strategies.test.ts +0 -255
- package/tests/live/plan-strategy.test.ts +0 -160
- package/tests/live/subagent-events.live.test.ts +0 -249
- package/tests/live/thread-tree.test.ts +0 -186
- package/tests/unit/agent.test.ts +0 -703
- package/tests/unit/checkpoint.test.ts +0 -232
- package/tests/unit/execution/equivalence.test.ts +0 -402
- package/tests/unit/execution/loop.test.ts +0 -437
- package/tests/unit/execution/plan.test.ts +0 -590
- package/tests/unit/execution/react.test.ts +0 -604
- package/tests/unit/execution/subagent-events.test.ts +0 -235
- package/tests/unit/execution/tool-ordering.test.ts +0 -310
- package/tests/unit/middleware/logging.test.ts +0 -276
- package/tests/unit/state.test.ts +0 -573
- package/tests/unit/thread-tree.test.ts +0 -249
- package/tsconfig.json +0 -29
package/AGENTS.md
DELETED
|
@@ -1,681 +0,0 @@
|
|
|
1
|
-
1. **100% TypeScript compliant** – All code must be fully TypeScript compliant with no use of `any`, implicit types, or unchecked assumptions.
|
|
2
|
-
2. **No complex abstractions around library data types** – Use library APIs as first-class application code; never truncate, shrink, or morph library data. Maintain full data stacks to accurately and transparently represent upstream libraries.
|
|
3
|
-
3. **High coding standards** – Follow modern best practices, prioritize readability, maintainability, and correctness, and write code that is clear in intent and behavior.
|
|
4
|
-
4. **AirBnB style compliance** – All code must strictly follow AirBnB’s TypeScript/JavaScript style guidelines and conventions.
|
|
5
|
-
5. **Lint cleanliness required** – Always ensure `bun lint` reports **zero errors** before code is considered complete or acceptable.
|
|
6
|
-
6. **Type-check cleanliness required** – Always ensure `bun typecheck` reports **zero errors** to guarantee full type safety and correctness.
|
|
7
|
-
7. **Electron-first environment** – This is a desktop **Electron** application. Web technologies are used for UI and runtime only; standard web application or hosted API security assumptions do not apply unless explicitly requested.
|
|
8
|
-
8. **No re-exports of library types or APIs** – Do **not** create re-exports, wrappers, or barrel files for third-party library types or functions (e.g., `export type { SDKMessage } from '@anthropic-ai/claude-agent-sdk'`). Always import directly from the source library in every file that uses them. However, **do import and use library types** for proper typing—avoid `unknown` or `any` when a concrete type exists. The rule prohibits re-exporting, not importing for local use.
|
|
9
|
-
9. **Never disable tooling rules** – Do **not** suppress, disable, or bypass linting, TypeScript, or language rules (e.g. `eslint-disable`, `@ts-ignore`, `@ts-expect-error`, rule overrides, or inline suppressions). Code must be written to comply with the active ruleset rather than weakening or working around it.
|
|
10
|
-
10. **Shared UI components for reuse** – Create reusable components for shared styling and shared views (e.g., consistently styled dropdowns, standardized buttons, or complex UI elements used in multiple places like an AI input bar). Prefer composition and clear props so shared UI stays consistent, maintainable, and avoids duplicated styling/logic across the app.
|
|
11
|
-
11. **Small, maintainable files** – Aim to keep files small and easy to reason about. When a file grows overly large (≈300 lines of code, excluding whitespace), consider refactoring it into cohesive, maintainable modules **when it is realistic and beneficial**. Refactoring should improve clarity and structure—not split code arbitrarily or create artificial indirection.
|
|
12
|
-
12. **Use icon libraries only** – Use established icon libraries for all icons. Do **not** write or embed custom inline SVGs. Rely on library-provided icons to ensure consistency, readability, and easier maintenance across the codebase.
|
|
13
|
-
13. **Comprehensive, intentional testing discipline** – All features and changes must include both **static unit tests** and **live API/integration tests** where applicable. Every code change must explicitly consider its testing impact and ensure **all unit and live tests pass fully**. Tests should be written deliberately: validate real behavior, edge cases, and regressions while avoiding unnecessary duplication, over-mocking, or low-value assertions that create test bloat without increasing confidence.
|
|
14
|
-
|
|
15
|
-
`.env` contains all AI provider keys.
|
|
16
|
-
|
|
17
|
-
>Persist only atomic values; do not persist derived attributes. Calculated fields such as duration or netCost should be computed at runtime to prevent data inconsistency
|
|
18
|
-
|
|
19
|
-
Default to using Bun instead of Node.js.
|
|
20
|
-
|
|
21
|
-
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
|
22
|
-
- Use `bun test` instead of `jest` or `vitest`
|
|
23
|
-
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
|
24
|
-
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
|
25
|
-
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
|
26
|
-
- Use `bunx <package> <command>` instead of `npx <package> <command>`
|
|
27
|
-
- Bun automatically loads .env, so don't use dotenv.
|
|
28
|
-
|
|
29
|
-
## APIs
|
|
30
|
-
|
|
31
|
-
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
|
32
|
-
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
|
33
|
-
- `Bun.redis` for Redis. Don't use `ioredis`.
|
|
34
|
-
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
|
35
|
-
- `WebSocket` is built-in. Don't use `ws`.
|
|
36
|
-
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
|
|
37
|
-
- Bun.$`ls` instead of execa.
|
|
38
|
-
|
|
39
|
-
## Testing
|
|
40
|
-
|
|
41
|
-
Use `bun test` to run tests.
|
|
42
|
-
|
|
43
|
-
```ts#index.test.ts
|
|
44
|
-
import { test, expect } from "bun:test";
|
|
45
|
-
|
|
46
|
-
test("hello world", () => {
|
|
47
|
-
expect(1).toBe(1);
|
|
48
|
-
});
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Frontend
|
|
52
|
-
|
|
53
|
-
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
|
|
54
|
-
|
|
55
|
-
Server:
|
|
56
|
-
|
|
57
|
-
```ts#index.ts
|
|
58
|
-
import index from "./index.html"
|
|
59
|
-
|
|
60
|
-
Bun.serve({
|
|
61
|
-
routes: {
|
|
62
|
-
"/": index,
|
|
63
|
-
"/api/users/:id": {
|
|
64
|
-
GET: (req) => {
|
|
65
|
-
return new Response(JSON.stringify({ id: req.params.id }));
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
// optional websocket support
|
|
70
|
-
websocket: {
|
|
71
|
-
open: (ws) => {
|
|
72
|
-
ws.send("Hello, world!");
|
|
73
|
-
},
|
|
74
|
-
message: (ws, message) => {
|
|
75
|
-
ws.send(message);
|
|
76
|
-
},
|
|
77
|
-
close: (ws) => {
|
|
78
|
-
// handle close
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
development: {
|
|
82
|
-
hmr: true,
|
|
83
|
-
console: true,
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
|
|
89
|
-
|
|
90
|
-
```html#index.html
|
|
91
|
-
<html>
|
|
92
|
-
<body>
|
|
93
|
-
<h1>Hello, world!</h1>
|
|
94
|
-
<script type="module" src="./frontend.tsx"></script>
|
|
95
|
-
</body>
|
|
96
|
-
</html>
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
With the following `frontend.tsx`:
|
|
100
|
-
|
|
101
|
-
```tsx#frontend.tsx
|
|
102
|
-
import React from "react";
|
|
103
|
-
import { createRoot } from "react-dom/client";
|
|
104
|
-
|
|
105
|
-
// import .css files directly and it works
|
|
106
|
-
import './index.css';
|
|
107
|
-
|
|
108
|
-
const root = createRoot(document.body);
|
|
109
|
-
|
|
110
|
-
export default function Frontend() {
|
|
111
|
-
return <h1>Hello, world!</h1>;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
root.render(<Frontend />);
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Then, run index.ts
|
|
118
|
-
|
|
119
|
-
```sh
|
|
120
|
-
bun --hot ./index.ts
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
|
|
124
|
-
|
|
125
|
-
# How to Use @providerprotocol/ai
|
|
126
|
-
|
|
127
|
-
This library provides a unified SDK for AI inference across multiple LLM providers (Anthropic, OpenAI, Google, Ollama, OpenRouter, xAI). It implements the Unified Provider Protocol (UPP-1.2).
|
|
128
|
-
|
|
129
|
-
## Quick Start
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
import { llm } from '@providerprotocol/ai';
|
|
133
|
-
import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
134
|
-
|
|
135
|
-
const claude = llm({
|
|
136
|
-
model: anthropic('claude-sonnet-4-20250514'),
|
|
137
|
-
params: { max_tokens: 1000 },
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
const turn = await claude.generate('Hello!');
|
|
141
|
-
console.log(turn.response.text);
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Provider Setup
|
|
145
|
-
|
|
146
|
-
Each provider has a factory function. Import from the provider's path:
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
// Anthropic
|
|
150
|
-
import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
151
|
-
import type { AnthropicLLMParams } from '@providerprotocol/ai/anthropic';
|
|
152
|
-
|
|
153
|
-
// OpenAI
|
|
154
|
-
import { openai } from '@providerprotocol/ai/openai';
|
|
155
|
-
import type { OpenAIResponsesParams, OpenAICompletionsParams } from '@providerprotocol/ai/openai';
|
|
156
|
-
|
|
157
|
-
// Google Gemini
|
|
158
|
-
import { google } from '@providerprotocol/ai/google';
|
|
159
|
-
import type { GoogleLLMParams } from '@providerprotocol/ai/google';
|
|
160
|
-
|
|
161
|
-
// Ollama (local models)
|
|
162
|
-
import { ollama } from '@providerprotocol/ai/ollama';
|
|
163
|
-
import type { OllamaLLMParams } from '@providerprotocol/ai/ollama';
|
|
164
|
-
|
|
165
|
-
// OpenRouter
|
|
166
|
-
import { openrouter } from '@providerprotocol/ai/openrouter';
|
|
167
|
-
|
|
168
|
-
// xAI (Grok)
|
|
169
|
-
import { xai } from '@providerprotocol/ai/xai';
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
## Creating LLM Instances
|
|
173
|
-
|
|
174
|
-
Use `llm()` with provider-specific params type for type safety:
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
// Anthropic
|
|
178
|
-
const claude = llm<AnthropicLLMParams>({
|
|
179
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
180
|
-
params: { max_tokens: 100 },
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// OpenAI (Responses API - default)
|
|
184
|
-
const gpt = llm<OpenAIResponsesParams>({
|
|
185
|
-
model: openai('gpt-5.2'),
|
|
186
|
-
params: { max_output_tokens: 100 },
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// OpenAI (Completions API)
|
|
190
|
-
const gptCompletions = llm<OpenAICompletionsParams>({
|
|
191
|
-
model: openai('gpt-5.2', { api: 'completions' }),
|
|
192
|
-
params: { max_completion_tokens: 100 },
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Google Gemini
|
|
196
|
-
const gemini = llm<GoogleLLMParams>({
|
|
197
|
-
model: google('gemini-3-flash-preview'),
|
|
198
|
-
params: { maxOutputTokens: 500 },
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Ollama (local)
|
|
202
|
-
const local = llm<OllamaLLMParams>({
|
|
203
|
-
model: ollama('gemma3:4b'),
|
|
204
|
-
params: { num_predict: 100 },
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## Basic Generation
|
|
209
|
-
|
|
210
|
-
```ts
|
|
211
|
-
const turn = await model.generate('What is 2+2?');
|
|
212
|
-
|
|
213
|
-
// Access response
|
|
214
|
-
console.log(turn.response.text); // The text response
|
|
215
|
-
console.log(turn.cycles); // Number of inference cycles (>1 if tools used)
|
|
216
|
-
console.log(turn.usage.totalTokens); // Total tokens used
|
|
217
|
-
console.log(turn.usage.inputTokens); // Input tokens
|
|
218
|
-
console.log(turn.usage.outputTokens); // Output tokens
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## Streaming
|
|
222
|
-
|
|
223
|
-
```ts
|
|
224
|
-
const stream = model.stream('Count from 1 to 5.');
|
|
225
|
-
|
|
226
|
-
// Iterate over events
|
|
227
|
-
for await (const event of stream) {
|
|
228
|
-
if (event.type === 'text_delta' && event.delta.text) {
|
|
229
|
-
process.stdout.write(event.delta.text);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Get final turn after stream completes
|
|
234
|
-
const turn = await stream.turn;
|
|
235
|
-
console.log(turn.response.text);
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Stream Event Types
|
|
239
|
-
|
|
240
|
-
- `text_delta` - Text chunk: `event.delta.text`
|
|
241
|
-
- `tool_call_delta` - Tool call info: `event.delta.toolCallId`, `event.delta.toolName`, `event.delta.argumentsJson`
|
|
242
|
-
- `message_start` - Message started
|
|
243
|
-
- `message_stop` - Message complete
|
|
244
|
-
- `content_block_start` - Content block started
|
|
245
|
-
- `content_block_stop` - Content block complete
|
|
246
|
-
|
|
247
|
-
## System Prompts
|
|
248
|
-
|
|
249
|
-
```ts
|
|
250
|
-
const model = llm({
|
|
251
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
252
|
-
params: { max_tokens: 100 },
|
|
253
|
-
system: 'You are a helpful assistant who speaks like a pirate.',
|
|
254
|
-
});
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
## Multi-turn Conversations
|
|
258
|
-
|
|
259
|
-
### Manual History Management
|
|
260
|
-
|
|
261
|
-
```ts
|
|
262
|
-
const model = llm<AnthropicLLMParams>({
|
|
263
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
264
|
-
params: { max_tokens: 100 },
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const history: any[] = [];
|
|
268
|
-
|
|
269
|
-
// First turn
|
|
270
|
-
const turn1 = await model.generate(history, 'My name is Alice.');
|
|
271
|
-
history.push(...turn1.messages);
|
|
272
|
-
|
|
273
|
-
// Second turn (model remembers context)
|
|
274
|
-
const turn2 = await model.generate(history, 'What is my name?');
|
|
275
|
-
// turn2.response.text will contain "Alice"
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### Using Thread Class
|
|
279
|
-
|
|
280
|
-
```ts
|
|
281
|
-
import { Thread, UserMessage, AssistantMessage } from '@providerprotocol/ai';
|
|
282
|
-
|
|
283
|
-
const thread = new Thread();
|
|
284
|
-
|
|
285
|
-
// Add messages
|
|
286
|
-
thread.user('Hello');
|
|
287
|
-
thread.assistant('Hi there!');
|
|
288
|
-
thread.push(new UserMessage('How are you?'));
|
|
289
|
-
|
|
290
|
-
// Use with generate
|
|
291
|
-
const turn = await model.generate(thread.messages, 'Tell me a joke.');
|
|
292
|
-
thread.append(turn); // Appends turn.messages to thread
|
|
293
|
-
|
|
294
|
-
// Thread utilities
|
|
295
|
-
thread.filter('user'); // Get only user messages
|
|
296
|
-
thread.tail(5); // Get last 5 messages
|
|
297
|
-
thread.slice(0, 10); // Get messages 0-9
|
|
298
|
-
thread.clear(); // Clear all messages
|
|
299
|
-
|
|
300
|
-
// Serialization
|
|
301
|
-
const json = thread.toJSON();
|
|
302
|
-
const restored = Thread.fromJSON(json);
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
## Tool Calling
|
|
306
|
-
|
|
307
|
-
Define tools with JSON Schema parameters and a `run` function:
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
const getWeather = {
|
|
311
|
-
name: 'getWeather',
|
|
312
|
-
description: 'Get the weather for a location',
|
|
313
|
-
parameters: {
|
|
314
|
-
type: 'object' as const,
|
|
315
|
-
properties: {
|
|
316
|
-
location: { type: 'string' as const, description: 'The city name' },
|
|
317
|
-
},
|
|
318
|
-
required: ['location'],
|
|
319
|
-
},
|
|
320
|
-
run: async (params: { location: string }) => {
|
|
321
|
-
// Your implementation here
|
|
322
|
-
return `The weather in ${params.location} is 72°F and sunny.`;
|
|
323
|
-
},
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const model = llm<AnthropicLLMParams>({
|
|
327
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
328
|
-
params: { max_tokens: 200 },
|
|
329
|
-
tools: [getWeather],
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
const turn = await model.generate('What is the weather in Tokyo?');
|
|
333
|
-
|
|
334
|
-
// Tool executions are tracked
|
|
335
|
-
console.log(turn.toolExecutions);
|
|
336
|
-
// [{ toolName: 'getWeather', arguments: { location: 'Tokyo' }, result: '...', duration: 123 }]
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### Tool with Approval
|
|
340
|
-
|
|
341
|
-
```ts
|
|
342
|
-
const deleteTool = {
|
|
343
|
-
name: 'deleteFile',
|
|
344
|
-
description: 'Delete a file',
|
|
345
|
-
parameters: {
|
|
346
|
-
type: 'object' as const,
|
|
347
|
-
properties: { path: { type: 'string' as const } },
|
|
348
|
-
required: ['path'],
|
|
349
|
-
},
|
|
350
|
-
run: async (params: { path: string }) => {
|
|
351
|
-
// Delete logic
|
|
352
|
-
},
|
|
353
|
-
approval: async (params: { path: string }) => {
|
|
354
|
-
// Return true to allow, false to deny
|
|
355
|
-
return confirm(`Delete ${params.path}?`);
|
|
356
|
-
},
|
|
357
|
-
};
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### Tool Use Strategy (Hooks)
|
|
361
|
-
|
|
362
|
-
```ts
|
|
363
|
-
const model = llm({
|
|
364
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
365
|
-
tools: [myTool],
|
|
366
|
-
toolStrategy: {
|
|
367
|
-
maxIterations: 5, // Max tool execution rounds (default: 10)
|
|
368
|
-
onToolCall: (tool, params) => console.log(`Calling ${tool.name}`),
|
|
369
|
-
onBeforeCall: (tool, params) => true, // Return false to skip
|
|
370
|
-
onAfterCall: (tool, params, result) => console.log('Result:', result),
|
|
371
|
-
onError: (tool, params, error) => console.error(error),
|
|
372
|
-
onMaxIterations: (n) => console.warn(`Reached ${n} iterations`),
|
|
373
|
-
},
|
|
374
|
-
});
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
### Streaming with Tools
|
|
378
|
-
|
|
379
|
-
```ts
|
|
380
|
-
const stream = model.stream('What is 7 + 15?');
|
|
381
|
-
|
|
382
|
-
for await (const event of stream) {
|
|
383
|
-
if (event.type === 'tool_call_delta') {
|
|
384
|
-
console.log('Tool:', event.delta.toolName, event.delta.argumentsJson);
|
|
385
|
-
}
|
|
386
|
-
if (event.type === 'text_delta') {
|
|
387
|
-
process.stdout.write(event.delta.text ?? '');
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const turn = await stream.turn;
|
|
392
|
-
console.log('Tool executions:', turn.toolExecutions);
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
## Structured Output
|
|
396
|
-
|
|
397
|
-
Use JSON Schema to enforce response structure:
|
|
398
|
-
|
|
399
|
-
```ts
|
|
400
|
-
const model = llm<AnthropicLLMParams>({
|
|
401
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
402
|
-
params: { max_tokens: 200 },
|
|
403
|
-
structure: {
|
|
404
|
-
type: 'object',
|
|
405
|
-
properties: {
|
|
406
|
-
city: { type: 'string' },
|
|
407
|
-
population: { type: 'number' },
|
|
408
|
-
isCapital: { type: 'boolean' },
|
|
409
|
-
},
|
|
410
|
-
required: ['city', 'population', 'isCapital'],
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
const turn = await model.generate('Tell me about Paris, France.');
|
|
415
|
-
|
|
416
|
-
// Parsed data is available on turn.data
|
|
417
|
-
console.log(turn.data);
|
|
418
|
-
// { city: 'Paris', population: 2161000, isCapital: true }
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### Streaming Structured Output
|
|
422
|
-
|
|
423
|
-
Different providers stream structured output differently:
|
|
424
|
-
|
|
425
|
-
```ts
|
|
426
|
-
// OpenAI/Google: Accumulate text_delta events
|
|
427
|
-
const stream = gpt.stream('Tell me about Tokyo.');
|
|
428
|
-
let json = '';
|
|
429
|
-
for await (const event of stream) {
|
|
430
|
-
if (event.type === 'text_delta' && event.delta.text) {
|
|
431
|
-
json += event.delta.text;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const data = JSON.parse(json);
|
|
435
|
-
|
|
436
|
-
// Anthropic: Accumulate tool_call_delta events (tool-based approach)
|
|
437
|
-
const stream = claude.stream('Tell me about Tokyo.');
|
|
438
|
-
let json = '';
|
|
439
|
-
for await (const event of stream) {
|
|
440
|
-
if (event.type === 'tool_call_delta' && event.delta.argumentsJson) {
|
|
441
|
-
json += event.delta.argumentsJson;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
const data = JSON.parse(json);
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
## Vision / Multimodal
|
|
448
|
-
|
|
449
|
-
### Using Images
|
|
450
|
-
|
|
451
|
-
```ts
|
|
452
|
-
import { UserMessage } from '@providerprotocol/ai';
|
|
453
|
-
import { readFileSync } from 'fs';
|
|
454
|
-
|
|
455
|
-
// Base64 image
|
|
456
|
-
const imageBase64 = readFileSync('./image.png').toString('base64');
|
|
457
|
-
|
|
458
|
-
const message = new UserMessage([
|
|
459
|
-
{ type: 'text', text: 'What is in this image?' },
|
|
460
|
-
{
|
|
461
|
-
type: 'image',
|
|
462
|
-
mimeType: 'image/png',
|
|
463
|
-
source: { type: 'base64', data: imageBase64 },
|
|
464
|
-
},
|
|
465
|
-
]);
|
|
466
|
-
|
|
467
|
-
const turn = await model.generate([message]);
|
|
468
|
-
console.log(turn.response.text);
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
### Image from URL
|
|
472
|
-
|
|
473
|
-
```ts
|
|
474
|
-
const message = new UserMessage([
|
|
475
|
-
{ type: 'text', text: 'Describe this image.' },
|
|
476
|
-
{
|
|
477
|
-
type: 'image',
|
|
478
|
-
mimeType: 'image/jpeg',
|
|
479
|
-
source: { type: 'url', url: 'https://example.com/image.jpg' },
|
|
480
|
-
},
|
|
481
|
-
]);
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
## OpenAI Built-in Tools
|
|
485
|
-
|
|
486
|
-
OpenAI Responses API provides native tools:
|
|
487
|
-
|
|
488
|
-
```ts
|
|
489
|
-
import { openai, tools } from '@providerprotocol/ai/openai';
|
|
490
|
-
|
|
491
|
-
// Web Search
|
|
492
|
-
const model = llm<OpenAIResponsesParams>({
|
|
493
|
-
model: openai('gpt-4o'),
|
|
494
|
-
params: {
|
|
495
|
-
max_output_tokens: 500,
|
|
496
|
-
tools: [tools.webSearch()],
|
|
497
|
-
},
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
// Web Search with user location
|
|
501
|
-
const modelWithLocation = llm<OpenAIResponsesParams>({
|
|
502
|
-
model: openai('gpt-4o'),
|
|
503
|
-
params: {
|
|
504
|
-
max_output_tokens: 500,
|
|
505
|
-
tools: [
|
|
506
|
-
tools.webSearch({
|
|
507
|
-
search_context_size: 'medium',
|
|
508
|
-
user_location: {
|
|
509
|
-
type: 'approximate',
|
|
510
|
-
city: 'Tokyo',
|
|
511
|
-
country: 'JP',
|
|
512
|
-
},
|
|
513
|
-
}),
|
|
514
|
-
],
|
|
515
|
-
},
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
// Image Generation
|
|
519
|
-
const imageModel = llm<OpenAIResponsesParams>({
|
|
520
|
-
model: openai('gpt-4o'),
|
|
521
|
-
params: {
|
|
522
|
-
max_output_tokens: 1000,
|
|
523
|
-
tools: [tools.imageGeneration({ quality: 'low', size: '1024x1024' })],
|
|
524
|
-
},
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
const turn = await imageModel.generate('Generate an image of a red apple.');
|
|
528
|
-
// Generated images are in turn.response.images
|
|
529
|
-
const image = turn.response.images[0];
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
## Error Handling
|
|
533
|
-
|
|
534
|
-
All errors are normalized to `UPPError`:
|
|
535
|
-
|
|
536
|
-
```ts
|
|
537
|
-
import { UPPError } from '@providerprotocol/ai';
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
await model.generate('Hello');
|
|
541
|
-
} catch (error) {
|
|
542
|
-
if (error instanceof UPPError) {
|
|
543
|
-
console.log(error.code); // 'AUTHENTICATION_FAILED', 'RATE_LIMITED', etc.
|
|
544
|
-
console.log(error.provider); // 'anthropic', 'openai', etc.
|
|
545
|
-
console.log(error.modality); // 'llm', 'embedding', 'image'
|
|
546
|
-
console.log(error.message); // Human-readable message
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### Error Codes
|
|
552
|
-
|
|
553
|
-
- `AUTHENTICATION_FAILED` - Invalid API key
|
|
554
|
-
- `RATE_LIMITED` - Too many requests
|
|
555
|
-
- `CONTEXT_LENGTH_EXCEEDED` - Input too long
|
|
556
|
-
- `MODEL_NOT_FOUND` - Invalid model ID
|
|
557
|
-
- `INVALID_REQUEST` - Malformed request
|
|
558
|
-
- `SERVER_ERROR` - Provider error
|
|
559
|
-
- `NETWORK_ERROR` - Connection failed
|
|
560
|
-
- `TIMEOUT` - Request timeout
|
|
561
|
-
|
|
562
|
-
## Custom API Key / Config
|
|
563
|
-
|
|
564
|
-
```ts
|
|
565
|
-
const model = llm({
|
|
566
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
567
|
-
params: { max_tokens: 100 },
|
|
568
|
-
config: {
|
|
569
|
-
apiKey: 'sk-...', // Override env var
|
|
570
|
-
baseUrl: 'https://custom-endpoint.com', // Custom endpoint
|
|
571
|
-
},
|
|
572
|
-
});
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
## Key Management Strategies
|
|
576
|
-
|
|
577
|
-
For load balancing across multiple API keys:
|
|
578
|
-
|
|
579
|
-
```ts
|
|
580
|
-
import { RoundRobinKeys, WeightedKeys, DynamicKey } from '@providerprotocol/ai';
|
|
581
|
-
|
|
582
|
-
// Round robin
|
|
583
|
-
const model = llm({
|
|
584
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
585
|
-
config: {
|
|
586
|
-
apiKey: new RoundRobinKeys(['key1', 'key2', 'key3']),
|
|
587
|
-
},
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
// Weighted distribution
|
|
591
|
-
const weighted = llm({
|
|
592
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
593
|
-
config: {
|
|
594
|
-
apiKey: new WeightedKeys([
|
|
595
|
-
{ key: 'primary-key', weight: 80 },
|
|
596
|
-
{ key: 'backup-key', weight: 20 },
|
|
597
|
-
]),
|
|
598
|
-
},
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
// Dynamic key fetching
|
|
602
|
-
const dynamic = llm({
|
|
603
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
604
|
-
config: {
|
|
605
|
-
apiKey: new DynamicKey(async () => {
|
|
606
|
-
// Fetch from secrets manager, etc.
|
|
607
|
-
return await getKeyFromVault();
|
|
608
|
-
}),
|
|
609
|
-
},
|
|
610
|
-
});
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
## Retry Strategies
|
|
614
|
-
|
|
615
|
-
```ts
|
|
616
|
-
import { ExponentialBackoff, LinearBackoff, NoRetry } from '@providerprotocol/ai';
|
|
617
|
-
|
|
618
|
-
const model = llm({
|
|
619
|
-
model: anthropic('claude-3-5-haiku-latest'),
|
|
620
|
-
config: {
|
|
621
|
-
retry: new ExponentialBackoff({
|
|
622
|
-
maxRetries: 3,
|
|
623
|
-
baseDelay: 1000,
|
|
624
|
-
maxDelay: 30000,
|
|
625
|
-
}),
|
|
626
|
-
},
|
|
627
|
-
});
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
## Provider-Specific Parameters
|
|
631
|
-
|
|
632
|
-
Each provider has unique parameter names:
|
|
633
|
-
|
|
634
|
-
| Feature | Anthropic | OpenAI Responses | OpenAI Completions | Google |
|
|
635
|
-
|---------|-----------|------------------|-------------------|--------|
|
|
636
|
-
| Max tokens | `max_tokens` | `max_output_tokens` | `max_completion_tokens` | `maxOutputTokens` |
|
|
637
|
-
| Temperature | `temperature` | `temperature` | `temperature` | `temperature` |
|
|
638
|
-
|
|
639
|
-
## Message Types
|
|
640
|
-
|
|
641
|
-
```ts
|
|
642
|
-
import { UserMessage, AssistantMessage, ToolResultMessage } from '@providerprotocol/ai';
|
|
643
|
-
|
|
644
|
-
// User message with text
|
|
645
|
-
const user = new UserMessage('Hello');
|
|
646
|
-
|
|
647
|
-
// User message with content blocks
|
|
648
|
-
const userMulti = new UserMessage([
|
|
649
|
-
{ type: 'text', text: 'Describe this:' },
|
|
650
|
-
{ type: 'image', mimeType: 'image/png', source: { type: 'base64', data: '...' } },
|
|
651
|
-
]);
|
|
652
|
-
|
|
653
|
-
// Assistant message
|
|
654
|
-
const assistant = new AssistantMessage('Hi there!');
|
|
655
|
-
|
|
656
|
-
// Assistant message with tool calls
|
|
657
|
-
const assistantWithTools = new AssistantMessage('Let me check...', [
|
|
658
|
-
{ toolCallId: 'call_123', toolName: 'getWeather', arguments: { city: 'Tokyo' } },
|
|
659
|
-
]);
|
|
660
|
-
|
|
661
|
-
// Tool result
|
|
662
|
-
const toolResult = new ToolResultMessage([
|
|
663
|
-
{ toolCallId: 'call_123', result: '72°F and sunny' },
|
|
664
|
-
]);
|
|
665
|
-
|
|
666
|
-
// Type guards
|
|
667
|
-
import { isUserMessage, isAssistantMessage, isToolResultMessage } from '@providerprotocol/ai';
|
|
668
|
-
if (isUserMessage(msg)) { /* ... */ }
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
## Environment Variables
|
|
672
|
-
|
|
673
|
-
Set API keys as environment variables (Bun auto-loads `.env`):
|
|
674
|
-
|
|
675
|
-
```
|
|
676
|
-
ANTHROPIC_API_KEY=sk-ant-...
|
|
677
|
-
OPENAI_API_KEY=sk-...
|
|
678
|
-
GOOGLE_API_KEY=AI...
|
|
679
|
-
XAI_API_KEY=xai-...
|
|
680
|
-
OPENROUTER_API_KEY=sk-or-...
|
|
681
|
-
```
|