@agi-cli/sdk 0.1.133 → 0.1.135

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 CHANGED
@@ -4,14 +4,14 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- `@agi-cli/sdk` is the unified SDK for building AI agents with AGI CLI. It re-exports all functionality from the underlying packages (`@agi-cli/core`, `@agi-cli/providers`, `@agi-cli/auth`, etc.) in a tree-shakable way.
7
+ `@agi-cli/sdk` is the unified SDK for building AI agents with AGI CLI. All authentication, configuration, providers, prompts, tools, and core AI functionality are included in this single package.
8
8
 
9
9
  **Why use the SDK?**
10
10
  - ✅ **Single import**: All functionality from one package
11
11
  - ✅ **Tree-shakable**: Bundlers only include what you use
12
12
  - ✅ **Type-safe**: Full TypeScript support with comprehensive types
13
13
  - ✅ **Zero circular dependencies**: Clean architecture
14
- - ✅ **Consistent API**: No need to remember which package exports what
14
+ - ✅ **Consistent API**: No need to remember which module exports what
15
15
 
16
16
  ## Installation
17
17
 
@@ -25,7 +25,7 @@ bun add @agi-cli/sdk
25
25
  import { generateText, resolveModel } from '@agi-cli/sdk';
26
26
  import type { ProviderId } from '@agi-cli/sdk';
27
27
 
28
- const model = resolveModel('anthropic', 'claude-3-5-sonnet-20241022');
28
+ const model = resolveModel('anthropic', 'claude-sonnet-4-20250514');
29
29
 
30
30
  const { text } = await generateText({
31
31
  model,
@@ -37,7 +37,7 @@ console.log(text);
37
37
 
38
38
  ## What's Included?
39
39
 
40
- ### Types (from `@agi-cli/types`)
40
+ ### Types
41
41
 
42
42
  All shared types are available:
43
43
 
@@ -52,7 +52,7 @@ import type {
52
52
  } from '@agi-cli/sdk';
53
53
  ```
54
54
 
55
- ### Providers (from `@agi-cli/providers`)
55
+ ### Providers
56
56
 
57
57
  Provider catalog and utilities:
58
58
 
@@ -72,16 +72,16 @@ import {
72
72
  } from '@agi-cli/sdk';
73
73
 
74
74
  // Check available providers
75
- console.log(providerIds); // ['openai', 'anthropic', 'google', 'openrouter', 'opencode']
75
+ console.log(providerIds); // ['openai', 'anthropic', 'google', 'openrouter', 'opencode', 'solforge']
76
76
 
77
77
  // Get model information
78
78
  const models = catalog.anthropic.models;
79
79
 
80
80
  // Validate provider/model combination
81
- const result = validateProviderModel('anthropic', 'claude-3-5-sonnet-20241022');
81
+ const result = validateProviderModel('anthropic', 'claude-sonnet-4-20250514');
82
82
  ```
83
83
 
84
- ### Authentication (from `@agi-cli/auth`)
84
+ ### Authentication
85
85
 
86
86
  Manage API keys and OAuth:
87
87
 
@@ -106,35 +106,30 @@ const url = await authorize('anthropic');
106
106
  console.log(`Visit: ${url}`);
107
107
  ```
108
108
 
109
- ### Configuration (from `@agi-cli/config`)
109
+ ### Configuration
110
110
 
111
111
  Load and manage configuration:
112
112
 
113
113
  ```typescript
114
- import { loadConfig, readConfig } from '@agi-cli/sdk';
114
+ import { loadConfig } from '@agi-cli/sdk';
115
115
  import type { AGIConfig } from '@agi-cli/sdk';
116
116
 
117
117
  const config = await loadConfig();
118
118
  console.log(config.provider); // 'anthropic'
119
- console.log(config.model); // 'claude-3-5-sonnet-20241022'
119
+ console.log(config.model); // 'claude-sonnet-4-20250514'
120
120
  ```
121
121
 
122
- ### Prompts (from `@agi-cli/prompts`)
122
+ ### Prompts
123
123
 
124
124
  Pre-built system prompts:
125
125
 
126
126
  ```typescript
127
- import { systemPrompt, codeContext } from '@agi-cli/sdk';
127
+ import { providerBasePrompt } from '@agi-cli/sdk';
128
128
 
129
- const prompt = systemPrompt('dev', {
130
- workdir: '/home/user/project',
131
- platform: 'linux',
132
- });
133
-
134
- const context = codeContext({ includeGitInfo: true });
129
+ const prompt = providerBasePrompt('anthropic');
135
130
  ```
136
131
 
137
- ### Core AI Functions (from `@agi-cli/core`)
132
+ ### Core AI Functions
138
133
 
139
134
  AI SDK re-exports and utilities:
140
135
 
@@ -181,28 +176,7 @@ const { object } = await generateObject({
181
176
  const tools = await discoverProjectTools('/path/to/project');
182
177
  ```
183
178
 
184
- ### Database (from `@agi-cli/database`)
185
-
186
- Database access:
187
-
188
- ```typescript
189
- import { getDb, dbSchema } from '@agi-cli/sdk';
190
-
191
- const db = getDb();
192
- const messages = await db.select().from(dbSchema.messages);
193
- ```
194
-
195
- ### Server (from `@agi-cli/server`)
196
-
197
- Create an HTTP server:
198
-
199
- ```typescript
200
- import { createServer } from '@agi-cli/sdk';
201
-
202
- const app = createServer();
203
- ```
204
-
205
- ### Error Handling (from `@agi-cli/core`)
179
+ ### Error Handling
206
180
 
207
181
  Typed error classes:
208
182
 
@@ -237,39 +211,30 @@ The SDK is fully tree-shakable. Modern bundlers (Vite, Rollup, esbuild, webpack)
237
211
  import { generateText, resolveModel } from '@agi-cli/sdk';
238
212
  ```
239
213
 
240
- ## Direct Package Access (Not Recommended)
241
-
242
- While you _can_ import directly from individual packages, we recommend using the SDK for consistency:
243
-
244
- ```typescript
245
- // ❌ Discouraged - fragmented imports
246
- import { catalog } from '@agi-cli/providers';
247
- import { generateText } from '@agi-cli/core';
248
- import type { ProviderId } from '@agi-cli/types';
249
-
250
- // ✅ Recommended - single source of truth
251
- import { catalog, generateText } from '@agi-cli/sdk';
252
- import type { ProviderId } from '@agi-cli/sdk';
253
- ```
254
-
255
214
  ## Architecture
256
215
 
257
- The SDK follows a clean dependency graph with zero circular dependencies:
216
+ The SDK contains all functionality internally:
258
217
 
259
218
  ```
260
- @agi-cli/types (foundation)
261
-
262
- @agi-cli/providers, @agi-cli/auth, @agi-cli/config
263
-
264
- @agi-cli/database, @agi-cli/prompts
265
-
266
- @agi-cli/core
267
-
268
- @agi-cli/server
269
-
270
- @agi-cli/sdkYOU ARE HERE (single source of truth)
219
+ @agi-cli/sdk/src/
220
+ ├── auth/ ← Authentication (OAuth, API keys)
221
+ ├── config/ Configuration (global + project)
222
+ ├── core/ ← Core AI functionality
223
+ │ ├── providers/ (model resolution)
224
+ │ ├── tools/ (builtin tools)
225
+ │ ├── streaming/ (artifacts)
226
+ │ └── errors.ts (error classes)
227
+ ├── prompts/ ← System prompts
228
+ ├── providers/ ← Provider catalog & utilities
229
+ └── index.ts Main exports
271
230
  ```
272
231
 
232
+ Related packages:
233
+ - `@agi-cli/database` - SQLite persistence (depends on sdk)
234
+ - `@agi-cli/server` - HTTP API (depends on sdk, database)
235
+ - `@agi-cli/api` - Type-safe API client (standalone)
236
+ - `@agi-cli/web-sdk` - React hooks & components (depends on api)
237
+
273
238
  ## Examples
274
239
 
275
240
  ### Basic Agent
@@ -356,8 +321,7 @@ import type {
356
321
  CoreMessage,
357
322
  Tool,
358
323
  DiscoveredTool,
359
- Artifact,
360
- ExecutionContext
324
+ Artifact
361
325
  } from '@agi-cli/sdk';
362
326
 
363
327
  // All types are fully documented and type-safe
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agi-cli/sdk",
3
- "version": "0.1.133",
3
+ "version": "0.1.135",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "ntishxyz",
6
6
  "license": "MIT",
@@ -112,7 +112,7 @@ export class TerminalManager {
112
112
  async killAll(): Promise<void> {
113
113
  const killPromises = Array.from(this.terminals.keys()).map((id) =>
114
114
  this.kill(id).catch((err) =>
115
- console.error(`Failed to kill terminal ${id}:`, err),
115
+ logger.error(`Failed to kill terminal ${id}`, err),
116
116
  ),
117
117
  );
118
118
 
package/src/index.ts CHANGED
@@ -42,6 +42,8 @@ export {
42
42
  hasModel,
43
43
  getFastModel,
44
44
  getFastModelForAuth,
45
+ getModelNpmBinding,
46
+ isAnthropicBasedModel,
45
47
  } from './providers/src/index.ts';
46
48
  export {
47
49
  isProviderAuthorized,
@@ -77,6 +79,27 @@ export {
77
79
  filterModelsForAuthType,
78
80
  getOAuthModelPrefixes,
79
81
  } from './providers/src/index.ts';
82
+ export {
83
+ addAnthropicCacheControl,
84
+ createAnthropicCachingFetch,
85
+ createConditionalCachingFetch,
86
+ } from './providers/src/index.ts';
87
+ export {
88
+ createAnthropicOAuthFetch,
89
+ createAnthropicOAuthModel,
90
+ } from './providers/src/index.ts';
91
+ export type { AnthropicOAuthConfig } from './providers/src/index.ts';
92
+ export { createGoogleModel } from './providers/src/index.ts';
93
+ export type { GoogleProviderConfig } from './providers/src/index.ts';
94
+ export { createZaiModel, createZaiCodingModel } from './providers/src/index.ts';
95
+ export type { ZaiProviderConfig } from './providers/src/index.ts';
96
+ export {
97
+ getOpenRouterInstance,
98
+ createOpenRouterModel,
99
+ } from './providers/src/index.ts';
100
+ export type { OpenRouterProviderConfig } from './providers/src/index.ts';
101
+ export { createOpencodeModel } from './providers/src/index.ts';
102
+ export type { OpencodeProviderConfig } from './providers/src/index.ts';
80
103
 
81
104
  // =======================
82
105
  // Authentication (from internal auth module)
@@ -0,0 +1,202 @@
1
+ type MessageBlock = {
2
+ type: string;
3
+ cache_control?: { type: string };
4
+ [key: string]: unknown;
5
+ };
6
+
7
+ type Message = {
8
+ role: string;
9
+ content: unknown;
10
+ [key: string]: unknown;
11
+ };
12
+
13
+ type ParsedBody = {
14
+ system?: MessageBlock[];
15
+ messages?: Message[];
16
+ [key: string]: unknown;
17
+ };
18
+
19
+ export function addAnthropicCacheControl(parsed: ParsedBody): ParsedBody {
20
+ const MAX_SYSTEM_CACHE = 1;
21
+ const MAX_MESSAGE_CACHE = 1;
22
+ let systemCacheUsed = 0;
23
+ let messageCacheUsed = 0;
24
+
25
+ if (parsed.system && Array.isArray(parsed.system)) {
26
+ parsed.system = parsed.system.map((block, index) => {
27
+ if (block.cache_control) return block;
28
+ if (
29
+ systemCacheUsed < MAX_SYSTEM_CACHE &&
30
+ index === 0 &&
31
+ block.type === 'text'
32
+ ) {
33
+ systemCacheUsed++;
34
+ return { ...block, cache_control: { type: 'ephemeral' } };
35
+ }
36
+ return block;
37
+ });
38
+ }
39
+
40
+ if (parsed.messages && Array.isArray(parsed.messages)) {
41
+ const messageCount = parsed.messages.length;
42
+ parsed.messages = parsed.messages.map((msg, msgIndex) => {
43
+ const isLast = msgIndex === messageCount - 1;
44
+
45
+ if (Array.isArray(msg.content)) {
46
+ const blocks = msg.content as MessageBlock[];
47
+ const content = blocks.map((block, blockIndex) => {
48
+ if (block.cache_control) return block;
49
+ if (
50
+ isLast &&
51
+ messageCacheUsed < MAX_MESSAGE_CACHE &&
52
+ blockIndex === blocks.length - 1
53
+ ) {
54
+ messageCacheUsed++;
55
+ return { ...block, cache_control: { type: 'ephemeral' } };
56
+ }
57
+ return block;
58
+ });
59
+ return { ...msg, content };
60
+ }
61
+
62
+ if (
63
+ isLast &&
64
+ messageCacheUsed < MAX_MESSAGE_CACHE &&
65
+ typeof msg.content === 'string'
66
+ ) {
67
+ messageCacheUsed++;
68
+ return {
69
+ ...msg,
70
+ content: [
71
+ {
72
+ type: 'text',
73
+ text: msg.content,
74
+ cache_control: { type: 'ephemeral' },
75
+ },
76
+ ],
77
+ };
78
+ }
79
+
80
+ return msg;
81
+ });
82
+ }
83
+
84
+ return parsed;
85
+ }
86
+
87
+ export function createAnthropicCachingFetch(): typeof fetch {
88
+ return async (input: RequestInfo | URL, init?: RequestInit) => {
89
+ let body = init?.body;
90
+ if (body && typeof body === 'string') {
91
+ try {
92
+ const parsed = JSON.parse(body);
93
+ const modified = addAnthropicCacheControl(parsed);
94
+ body = JSON.stringify(modified);
95
+ } catch {
96
+ // If parsing fails, send as-is
97
+ }
98
+ }
99
+ return fetch(input, { ...init, body });
100
+ };
101
+ }
102
+
103
+ export function createConditionalCachingFetch(
104
+ shouldCache: (model: string) => boolean,
105
+ model: string,
106
+ ): typeof fetch {
107
+ return async (input: RequestInfo | URL, init?: RequestInit) => {
108
+ if (!shouldCache(model)) {
109
+ return fetch(input, init);
110
+ }
111
+
112
+ let body = init?.body;
113
+ if (body && typeof body === 'string') {
114
+ try {
115
+ const parsed = JSON.parse(body);
116
+
117
+ const MAX_SYSTEM_CACHE = 1;
118
+ const MAX_MESSAGE_CACHE = 1;
119
+ let systemCacheUsed = 0;
120
+ let messageCacheUsed = 0;
121
+
122
+ if (parsed.messages && Array.isArray(parsed.messages)) {
123
+ const messageCount = parsed.messages.length;
124
+ parsed.messages = parsed.messages.map(
125
+ (msg: Message, msgIndex: number) => {
126
+ if (msg.role === 'system' && systemCacheUsed < MAX_SYSTEM_CACHE) {
127
+ systemCacheUsed++;
128
+ if (typeof msg.content === 'string') {
129
+ return {
130
+ ...msg,
131
+ content: [
132
+ {
133
+ type: 'text',
134
+ text: msg.content,
135
+ cache_control: { type: 'ephemeral' },
136
+ },
137
+ ],
138
+ };
139
+ }
140
+ if (Array.isArray(msg.content)) {
141
+ const blocks = msg.content as MessageBlock[];
142
+ const content = blocks.map((block, i) => {
143
+ if (i === blocks.length - 1 && !block.cache_control) {
144
+ return { ...block, cache_control: { type: 'ephemeral' } };
145
+ }
146
+ return block;
147
+ });
148
+ return { ...msg, content };
149
+ }
150
+ }
151
+
152
+ const isLast = msgIndex === messageCount - 1;
153
+
154
+ if (Array.isArray(msg.content)) {
155
+ const blocks = msg.content as MessageBlock[];
156
+ const content = blocks.map((block, blockIndex) => {
157
+ if (block.cache_control) return block;
158
+ if (
159
+ isLast &&
160
+ messageCacheUsed < MAX_MESSAGE_CACHE &&
161
+ blockIndex === blocks.length - 1
162
+ ) {
163
+ messageCacheUsed++;
164
+ return { ...block, cache_control: { type: 'ephemeral' } };
165
+ }
166
+ return block;
167
+ });
168
+ return { ...msg, content };
169
+ }
170
+
171
+ if (
172
+ isLast &&
173
+ messageCacheUsed < MAX_MESSAGE_CACHE &&
174
+ typeof msg.content === 'string'
175
+ ) {
176
+ messageCacheUsed++;
177
+ return {
178
+ ...msg,
179
+ content: [
180
+ {
181
+ type: 'text',
182
+ text: msg.content,
183
+ cache_control: { type: 'ephemeral' },
184
+ },
185
+ ],
186
+ };
187
+ }
188
+
189
+ return msg;
190
+ },
191
+ );
192
+ }
193
+
194
+ body = JSON.stringify(parsed);
195
+ } catch {
196
+ // If parsing fails, send as-is
197
+ }
198
+ }
199
+
200
+ return fetch(input, { ...init, body });
201
+ };
202
+ }
@@ -0,0 +1,157 @@
1
+ import { createAnthropic } from '@ai-sdk/anthropic';
2
+ import { addAnthropicCacheControl } from './anthropic-caching.ts';
3
+
4
+ const CLAUDE_CLI_VERSION = '1.0.61';
5
+
6
+ export type AnthropicOAuthConfig = {
7
+ oauth: {
8
+ access: string;
9
+ refresh: string;
10
+ expires: number;
11
+ };
12
+ toolNameTransformer?: (name: string) => string;
13
+ };
14
+
15
+ function buildOAuthHeaders(accessToken: string): Record<string, string> {
16
+ const headers: Record<string, string> = {
17
+ authorization: `Bearer ${accessToken}`,
18
+ 'anthropic-beta':
19
+ 'claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14',
20
+ 'anthropic-dangerous-direct-browser-access': 'true',
21
+ 'anthropic-version': '2023-06-01',
22
+ 'user-agent': `claude-cli/${CLAUDE_CLI_VERSION} (external, cli)`,
23
+ 'x-app': 'cli',
24
+ 'content-type': 'application/json',
25
+ accept: 'application/json',
26
+ 'x-stainless-arch': process.arch === 'arm64' ? 'arm64' : 'x64',
27
+ 'x-stainless-helper-method': 'stream',
28
+ 'x-stainless-lang': 'js',
29
+ 'x-stainless-os':
30
+ process.platform === 'darwin'
31
+ ? 'MacOS'
32
+ : process.platform === 'win32'
33
+ ? 'Windows'
34
+ : 'Linux',
35
+ 'x-stainless-package-version': '0.70.0',
36
+ 'x-stainless-retry-count': '0',
37
+ 'x-stainless-runtime': 'node',
38
+ 'x-stainless-runtime-version': process.version,
39
+ 'x-stainless-timeout': '600',
40
+ };
41
+ return headers;
42
+ }
43
+
44
+ function filterExistingHeaders(
45
+ initHeaders: HeadersInit | undefined,
46
+ ): Record<string, string> {
47
+ const headers: Record<string, string> = {};
48
+ if (!initHeaders) return headers;
49
+
50
+ if (initHeaders instanceof Headers) {
51
+ initHeaders.forEach((value, key) => {
52
+ if (key.toLowerCase() !== 'x-api-key') {
53
+ headers[key] = value;
54
+ }
55
+ });
56
+ } else if (Array.isArray(initHeaders)) {
57
+ for (const [key, value] of initHeaders) {
58
+ if (
59
+ key &&
60
+ key.toLowerCase() !== 'x-api-key' &&
61
+ typeof value === 'string'
62
+ ) {
63
+ headers[key] = value;
64
+ }
65
+ }
66
+ } else {
67
+ for (const [key, value] of Object.entries(initHeaders)) {
68
+ if (key.toLowerCase() !== 'x-api-key' && typeof value === 'string') {
69
+ headers[key] = value;
70
+ }
71
+ }
72
+ }
73
+ return headers;
74
+ }
75
+
76
+ export function createAnthropicOAuthFetch(
77
+ config: AnthropicOAuthConfig,
78
+ ): typeof fetch {
79
+ const { oauth, toolNameTransformer } = config;
80
+
81
+ return async (input: string | URL | Request, init?: RequestInit) => {
82
+ const existingHeaders = filterExistingHeaders(init?.headers);
83
+ const oauthHeaders = buildOAuthHeaders(oauth.access);
84
+ const headers = { ...existingHeaders, ...oauthHeaders };
85
+
86
+ let url = typeof input === 'string' ? input : input.toString();
87
+ if (url.includes('/v1/messages') && !url.includes('beta=true')) {
88
+ url += url.includes('?') ? '&beta=true' : '?beta=true';
89
+ }
90
+
91
+ let body = init?.body;
92
+ if (body && typeof body === 'string') {
93
+ try {
94
+ const parsed = JSON.parse(body);
95
+
96
+ if (toolNameTransformer) {
97
+ if (parsed.tools && Array.isArray(parsed.tools)) {
98
+ parsed.tools = parsed.tools.map(
99
+ (tool: { name: string; [key: string]: unknown }) => ({
100
+ ...tool,
101
+ name: toolNameTransformer(tool.name),
102
+ }),
103
+ );
104
+ }
105
+
106
+ if (parsed.messages && Array.isArray(parsed.messages)) {
107
+ parsed.messages = parsed.messages.map(
108
+ (msg: { content: unknown; [key: string]: unknown }) => {
109
+ if (Array.isArray(msg.content)) {
110
+ const content = msg.content.map(
111
+ (block: {
112
+ type: string;
113
+ name?: string;
114
+ [key: string]: unknown;
115
+ }) => {
116
+ if (
117
+ (block.type === 'tool_use' ||
118
+ block.type === 'tool_result') &&
119
+ block.name
120
+ ) {
121
+ return {
122
+ ...block,
123
+ name: toolNameTransformer(block.name),
124
+ };
125
+ }
126
+ return block;
127
+ },
128
+ );
129
+ return { ...msg, content };
130
+ }
131
+ return msg;
132
+ },
133
+ );
134
+ }
135
+ }
136
+
137
+ const withCache = addAnthropicCacheControl(parsed);
138
+ body = JSON.stringify(withCache);
139
+ } catch {
140
+ // If parsing fails, send as-is
141
+ }
142
+ }
143
+
144
+ return fetch(url, { ...init, body, headers });
145
+ };
146
+ }
147
+
148
+ export function createAnthropicOAuthModel(
149
+ model: string,
150
+ config: AnthropicOAuthConfig,
151
+ ) {
152
+ const customFetch = createAnthropicOAuthFetch(config);
153
+ return createAnthropic({
154
+ apiKey: '',
155
+ fetch: customFetch as typeof fetch,
156
+ })(model);
157
+ }
@@ -0,0 +1,16 @@
1
+ import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
2
+
3
+ export type GoogleProviderConfig = {
4
+ apiKey?: string;
5
+ };
6
+
7
+ export function createGoogleModel(
8
+ model: string,
9
+ config?: GoogleProviderConfig,
10
+ ) {
11
+ if (config?.apiKey) {
12
+ const instance = createGoogleGenerativeAI({ apiKey: config.apiKey });
13
+ return instance(model);
14
+ }
15
+ return google(model);
16
+ }
@@ -13,6 +13,8 @@ export {
13
13
  hasModel,
14
14
  getFastModel,
15
15
  getFastModelForAuth,
16
+ getModelNpmBinding,
17
+ isAnthropicBasedModel,
16
18
  } from './utils.ts';
17
19
  export { validateProviderModel } from './validate.ts';
18
20
  export { estimateModelCostUsd } from './pricing.ts';
@@ -41,3 +43,24 @@ export {
41
43
  filterModelsForAuthType,
42
44
  getOAuthModelPrefixes,
43
45
  } from './oauth-models.ts';
46
+ export {
47
+ addAnthropicCacheControl,
48
+ createAnthropicCachingFetch,
49
+ createConditionalCachingFetch,
50
+ } from './anthropic-caching.ts';
51
+ export {
52
+ createAnthropicOAuthFetch,
53
+ createAnthropicOAuthModel,
54
+ } from './anthropic-oauth-client.ts';
55
+ export type { AnthropicOAuthConfig } from './anthropic-oauth-client.ts';
56
+ export { createGoogleModel } from './google-client.ts';
57
+ export type { GoogleProviderConfig } from './google-client.ts';
58
+ export { createZaiModel, createZaiCodingModel } from './zai-client.ts';
59
+ export type { ZaiProviderConfig } from './zai-client.ts';
60
+ export {
61
+ getOpenRouterInstance,
62
+ createOpenRouterModel,
63
+ } from './openrouter-client.ts';
64
+ export type { OpenRouterProviderConfig } from './openrouter-client.ts';
65
+ export { createOpencodeModel } from './opencode-client.ts';
66
+ export type { OpencodeProviderConfig } from './opencode-client.ts';
@@ -14,8 +14,6 @@ export type OpenAIOAuthConfig = {
14
14
  instructions?: string;
15
15
  reasoningEffort?: 'none' | 'low' | 'medium' | 'high' | 'xhigh';
16
16
  reasoningSummary?: 'auto' | 'detailed';
17
- promptCacheKey?: string;
18
- promptCacheRetention?: 'in_memory' | '24h';
19
17
  };
20
18
 
21
19
  async function ensureValidToken(
@@ -185,19 +183,6 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
185
183
  }
186
184
  }
187
185
 
188
- const cacheKey =
189
- parsed.providerOptions?.openai?.promptCacheKey ||
190
- config.promptCacheKey;
191
- const cacheRetention =
192
- parsed.providerOptions?.openai?.promptCacheRetention ||
193
- config.promptCacheRetention;
194
- if (cacheKey) {
195
- parsed.prompt_cache_key = cacheKey;
196
- }
197
- if (cacheRetention) {
198
- parsed.prompt_cache_retention = cacheRetention;
199
- }
200
-
201
186
  delete parsed.max_output_tokens;
202
187
  delete parsed.max_completion_tokens;
203
188
 
@@ -0,0 +1,80 @@
1
+ import { createOpenAI } from '@ai-sdk/openai';
2
+ import { createAnthropic } from '@ai-sdk/anthropic';
3
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
4
+ import { catalog } from './catalog-merged.ts';
5
+ import { createAnthropicCachingFetch } from './anthropic-caching.ts';
6
+ import type { ProviderId } from '../../types/src/index.ts';
7
+
8
+ export type OpencodeProviderConfig = {
9
+ apiKey?: string;
10
+ };
11
+
12
+ function normalizeModelIdentifier(provider: ProviderId, model: string): string {
13
+ const prefix = `${provider}/`;
14
+ return model.startsWith(prefix) ? model.slice(prefix.length) : model;
15
+ }
16
+
17
+ export function createOpencodeModel(
18
+ model: string,
19
+ config?: OpencodeProviderConfig,
20
+ ) {
21
+ const entry = catalog.opencode;
22
+ const normalizedModel = normalizeModelIdentifier('opencode', model);
23
+ const modelInfo =
24
+ entry?.models.find((m) => m.id === normalizedModel) ??
25
+ entry?.models.find((m) => m.id === model);
26
+ const resolvedModelId = modelInfo?.id ?? normalizedModel ?? model;
27
+ const binding = modelInfo?.provider?.npm ?? entry?.npm;
28
+ const apiKey = config?.apiKey ?? process.env.OPENCODE_API_KEY ?? '';
29
+ const baseURL =
30
+ modelInfo?.provider?.baseURL ||
31
+ modelInfo?.provider?.api ||
32
+ entry?.api ||
33
+ 'https://opencode.ai/zen/v1';
34
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
35
+
36
+ if (binding === '@ai-sdk/openai') {
37
+ const instance = createOpenAI({ apiKey, baseURL });
38
+ return instance(resolvedModelId);
39
+ }
40
+ if (binding === '@ai-sdk/anthropic') {
41
+ const cachingFetch = createAnthropicCachingFetch();
42
+ const instance = createAnthropic({
43
+ apiKey,
44
+ baseURL,
45
+ fetch: cachingFetch as typeof fetch,
46
+ });
47
+ return instance(resolvedModelId);
48
+ }
49
+ if (binding === '@ai-sdk/openai-compatible') {
50
+ const instance = createOpenAICompatible({
51
+ name: entry?.label ?? 'opencode',
52
+ baseURL,
53
+ headers,
54
+ });
55
+ return instance(resolvedModelId);
56
+ }
57
+
58
+ const ocOpenAI = createOpenAI({ apiKey, baseURL });
59
+ const cachingFetch = createAnthropicCachingFetch();
60
+ const ocAnthropic = createAnthropic({
61
+ apiKey,
62
+ baseURL,
63
+ fetch: cachingFetch as typeof fetch,
64
+ });
65
+ const ocCompat = createOpenAICompatible({
66
+ name: entry?.label ?? 'opencode',
67
+ baseURL,
68
+ headers,
69
+ });
70
+
71
+ const id = resolvedModelId.toLowerCase();
72
+ if (id.includes('claude')) return ocAnthropic(resolvedModelId);
73
+ if (
74
+ id.includes('qwen3-coder') ||
75
+ id.includes('grok-code') ||
76
+ id.includes('kimi-k2')
77
+ )
78
+ return ocCompat(resolvedModelId);
79
+ return ocOpenAI(resolvedModelId);
80
+ }
@@ -0,0 +1,30 @@
1
+ import { createOpenRouter } from '@openrouter/ai-sdk-provider';
2
+ import { createConditionalCachingFetch } from './anthropic-caching.ts';
3
+
4
+ export type OpenRouterProviderConfig = {
5
+ apiKey?: string;
6
+ };
7
+
8
+ function isAnthropicModel(model: string): boolean {
9
+ const lower = model.toLowerCase();
10
+ return lower.includes('anthropic') || lower.includes('claude');
11
+ }
12
+
13
+ export function getOpenRouterInstance(
14
+ model?: string,
15
+ config?: OpenRouterProviderConfig,
16
+ ) {
17
+ const apiKey = config?.apiKey ?? process.env.OPENROUTER_API_KEY ?? '';
18
+ const customFetch = model
19
+ ? createConditionalCachingFetch(isAnthropicModel, model)
20
+ : undefined;
21
+ return createOpenRouter({ apiKey, fetch: customFetch });
22
+ }
23
+
24
+ export function createOpenRouterModel(
25
+ model: string,
26
+ config?: OpenRouterProviderConfig,
27
+ ) {
28
+ const openrouter = getOpenRouterInstance(model, config);
29
+ return openrouter.chat(model);
30
+ }
@@ -7,6 +7,7 @@ import { svm } from 'x402/shared';
7
7
  import nacl from 'tweetnacl';
8
8
  import { createOpenAI } from '@ai-sdk/openai';
9
9
  import { createAnthropic } from '@ai-sdk/anthropic';
10
+ import { addAnthropicCacheControl } from './anthropic-caching.ts';
10
11
 
11
12
  const DEFAULT_BASE_URL = 'https://router.solforge.sh';
12
13
  const DEFAULT_RPC_URL = 'https://api.mainnet-beta.solana.com';
@@ -148,87 +149,7 @@ export function createSolforgeFetch(
148
149
  if (promptCacheRetention)
149
150
  parsed.prompt_cache_retention = promptCacheRetention;
150
151
 
151
- const MAX_SYSTEM_CACHE = 1;
152
- const MAX_MESSAGE_CACHE = 1;
153
- let systemCacheUsed = 0;
154
- let messageCacheUsed = 0;
155
-
156
- if (parsed.system && Array.isArray(parsed.system)) {
157
- parsed.system = parsed.system.map(
158
- (
159
- block: { type: string; cache_control?: unknown },
160
- index: number,
161
- ) => {
162
- if (block.cache_control) return block;
163
- if (
164
- systemCacheUsed < MAX_SYSTEM_CACHE &&
165
- index === 0 &&
166
- block.type === 'text'
167
- ) {
168
- systemCacheUsed++;
169
- return { ...block, cache_control: { type: 'ephemeral' } };
170
- }
171
- return block;
172
- },
173
- );
174
- }
175
-
176
- if (parsed.messages && Array.isArray(parsed.messages)) {
177
- const messageCount = parsed.messages.length;
178
- parsed.messages = parsed.messages.map(
179
- (
180
- msg: {
181
- role: string;
182
- content: unknown;
183
- [key: string]: unknown;
184
- },
185
- msgIndex: number,
186
- ) => {
187
- const isLast = msgIndex === messageCount - 1;
188
-
189
- if (Array.isArray(msg.content)) {
190
- const blocks = msg.content as {
191
- type: string;
192
- cache_control?: unknown;
193
- }[];
194
- const content = blocks.map((block, blockIndex) => {
195
- if (block.cache_control) return block;
196
- if (
197
- isLast &&
198
- messageCacheUsed < MAX_MESSAGE_CACHE &&
199
- blockIndex === blocks.length - 1
200
- ) {
201
- messageCacheUsed++;
202
- return { ...block, cache_control: { type: 'ephemeral' } };
203
- }
204
- return block;
205
- });
206
- return { ...msg, content };
207
- }
208
-
209
- if (
210
- isLast &&
211
- messageCacheUsed < MAX_MESSAGE_CACHE &&
212
- typeof msg.content === 'string'
213
- ) {
214
- messageCacheUsed++;
215
- return {
216
- ...msg,
217
- content: [
218
- {
219
- type: 'text',
220
- text: msg.content,
221
- cache_control: { type: 'ephemeral' },
222
- },
223
- ],
224
- };
225
- }
226
-
227
- return msg;
228
- },
229
- );
230
- }
231
-
152
+ addAnthropicCacheControl(parsed);
232
153
  body = JSON.stringify(parsed);
233
154
  } catch {}
234
155
  }
@@ -404,7 +325,21 @@ async function processSinglePayment(args: {
404
325
  }): Promise<{ attempts: number; balance?: number | string }> {
405
326
  args.callbacks.onPaymentSigning?.();
406
327
 
407
- const paymentPayload = await createPaymentPayload(args);
328
+ let paymentPayload: PaymentPayload;
329
+ try {
330
+ paymentPayload = await createPaymentPayload(args);
331
+ } catch (err) {
332
+ const errMsg = err instanceof Error ? err.message : String(err);
333
+ const isInsufficientFunds =
334
+ errMsg.toLowerCase().includes('insufficient') ||
335
+ errMsg.toLowerCase().includes('not enough') ||
336
+ errMsg.toLowerCase().includes('balance');
337
+ const userMsg = isInsufficientFunds
338
+ ? 'Insufficient USDC balance in wallet for payment'
339
+ : `Payment failed: ${errMsg}`;
340
+ args.callbacks.onPaymentError?.(userMsg);
341
+ throw new Error(`Solforge: ${userMsg}`);
342
+ }
408
343
  const walletHeaders = args.buildWalletHeaders();
409
344
  const headers = {
410
345
  'Content-Type': 'application/json',
@@ -98,3 +98,24 @@ export function getFastModelForAuth(
98
98
 
99
99
  return sorted[0]?.id ?? filteredModels[0]?.id;
100
100
  }
101
+
102
+ export function getModelNpmBinding(
103
+ provider: ProviderId,
104
+ model: string,
105
+ ): string | undefined {
106
+ const entry = catalog[provider];
107
+ if (!entry) return undefined;
108
+ const modelInfo = entry.models?.find((m) => m.id === model);
109
+ return modelInfo?.provider?.npm ?? entry.npm;
110
+ }
111
+
112
+ export function isAnthropicBasedModel(
113
+ provider: ProviderId,
114
+ model: string,
115
+ ): boolean {
116
+ if (provider === 'anthropic') return true;
117
+ const npm = getModelNpmBinding(provider, model);
118
+ if (npm === '@ai-sdk/anthropic') return true;
119
+ const lower = model.toLowerCase();
120
+ return lower.includes('claude') || lower.includes('anthropic');
121
+ }
@@ -0,0 +1,47 @@
1
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
2
+ import { catalog } from './catalog-merged.ts';
3
+
4
+ export type ZaiProviderConfig = {
5
+ apiKey?: string;
6
+ };
7
+
8
+ export function createZaiModel(model: string, config?: ZaiProviderConfig) {
9
+ const entry = catalog.zai;
10
+ const baseURL = entry?.api || 'https://api.z.ai/api/paas/v4';
11
+ const apiKey =
12
+ config?.apiKey ||
13
+ process.env.ZAI_API_KEY ||
14
+ process.env.ZHIPU_API_KEY ||
15
+ '';
16
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
17
+
18
+ const instance = createOpenAICompatible({
19
+ name: entry?.label ?? 'Z.AI',
20
+ baseURL,
21
+ headers,
22
+ });
23
+
24
+ return instance(model);
25
+ }
26
+
27
+ export function createZaiCodingModel(
28
+ model: string,
29
+ config?: ZaiProviderConfig,
30
+ ) {
31
+ const entry = catalog['zai-coding'];
32
+ const baseURL = entry?.api || 'https://api.z.ai/api/coding/paas/v4';
33
+ const apiKey =
34
+ config?.apiKey ||
35
+ process.env.ZAI_API_KEY ||
36
+ process.env.ZHIPU_API_KEY ||
37
+ '';
38
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
39
+
40
+ const instance = createOpenAICompatible({
41
+ name: entry?.label ?? 'Z.AI Coding',
42
+ baseURL,
43
+ headers,
44
+ });
45
+
46
+ return instance(model);
47
+ }