@agi-cli/server 0.1.134 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agi-cli/server",
3
- "version": "0.1.134",
3
+ "version": "0.1.135",
4
4
  "description": "HTTP API server for AGI CLI",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -29,8 +29,8 @@
29
29
  "typecheck": "tsc --noEmit"
30
30
  },
31
31
  "dependencies": {
32
- "@agi-cli/sdk": "0.1.134",
33
- "@agi-cli/database": "0.1.134",
32
+ "@agi-cli/sdk": "0.1.135",
33
+ "@agi-cli/database": "0.1.135",
34
34
  "drizzle-orm": "^0.44.5",
35
35
  "hono": "^4.9.9",
36
36
  "zod": "^4.1.8"
package/src/events/bus.ts CHANGED
@@ -4,12 +4,27 @@ type Subscriber = (evt: AGIEvent) => void;
4
4
 
5
5
  const subscribers = new Map<string, Set<Subscriber>>(); // sessionId -> subs
6
6
 
7
+ function sanitizeBigInt<T>(obj: T): T {
8
+ if (obj === null || obj === undefined) return obj;
9
+ if (typeof obj === 'bigint') return Number(obj) as T;
10
+ if (Array.isArray(obj)) return obj.map(sanitizeBigInt) as T;
11
+ if (typeof obj === 'object') {
12
+ const result: Record<string, unknown> = {};
13
+ for (const [key, value] of Object.entries(obj)) {
14
+ result[key] = sanitizeBigInt(value);
15
+ }
16
+ return result as T;
17
+ }
18
+ return obj;
19
+ }
20
+
7
21
  export function publish(event: AGIEvent) {
22
+ const sanitizedEvent = sanitizeBigInt(event);
8
23
  const subs = subscribers.get(event.sessionId);
9
24
  if (!subs) return;
10
25
  for (const sub of subs) {
11
26
  try {
12
- sub(event);
27
+ sub(sanitizedEvent);
13
28
  } catch {}
14
29
  }
15
30
  }
@@ -2,6 +2,12 @@ import type { Hono } from 'hono';
2
2
  import { subscribe } from '../events/bus.ts';
3
3
  import type { AGIEvent } from '../events/types.ts';
4
4
 
5
+ function safeStringify(obj: unknown): string {
6
+ return JSON.stringify(obj, (_key, value) =>
7
+ typeof value === 'bigint' ? Number(value) : value,
8
+ );
9
+ }
10
+
5
11
  export function registerSessionStreamRoute(app: Hono) {
6
12
  app.get('/v1/sessions/:id/stream', async (c) => {
7
13
  const sessionId = c.req.param('id');
@@ -16,9 +22,14 @@ export function registerSessionStreamRoute(app: Hono) {
16
22
  const stream = new ReadableStream<Uint8Array>({
17
23
  start(controller) {
18
24
  const write = (evt: AGIEvent) => {
19
- const line =
20
- `event: ${evt.type}\n` +
21
- `data: ${JSON.stringify(evt.payload ?? {})}\n\n`;
25
+ let line: string;
26
+ try {
27
+ line =
28
+ `event: ${evt.type}\n` +
29
+ `data: ${safeStringify(evt.payload ?? {})}\n\n`;
30
+ } catch {
31
+ line = `event: ${evt.type}\ndata: {}\n\n`;
32
+ }
22
33
  controller.enqueue(encoder.encode(line));
23
34
  };
24
35
  const unsubscribe = subscribe(sessionId, write);
@@ -23,6 +23,7 @@ import {
23
23
  createFinishHandler,
24
24
  } from '../stream/handlers.ts';
25
25
  import { pruneSession } from '../message/compaction.ts';
26
+ import { triggerDeferredTitleGeneration } from '../message/service.ts';
26
27
  import { setupRunner } from './runner-setup.ts';
27
28
  import {
28
29
  type ReasoningState,
@@ -67,6 +68,7 @@ async function runAssistant(opts: RunOpts) {
67
68
 
68
69
  const setup = await setupRunner(opts);
69
70
  const {
71
+ cfg,
70
72
  db,
71
73
  history,
72
74
  system,
@@ -188,6 +190,13 @@ async function runAssistant(opts: RunOpts) {
188
190
  if (!firstDeltaSeen) {
189
191
  firstDeltaSeen = true;
190
192
  streamStartTimer.end();
193
+ if (isFirstMessage) {
194
+ void triggerDeferredTitleGeneration({
195
+ cfg,
196
+ db,
197
+ sessionId: opts.sessionId,
198
+ });
199
+ }
191
200
  }
192
201
 
193
202
  if (!currentPartId) {
@@ -1,5 +1,5 @@
1
1
  import { generateText, streamText } from 'ai';
2
- import { eq } from 'drizzle-orm';
2
+ import { eq, asc } from 'drizzle-orm';
3
3
  import type { AGIConfig } from '@agi-cli/sdk';
4
4
  import type { DB } from '@agi-cli/database';
5
5
  import { messages, messageParts, sessions } from '@agi-cli/database/schema';
@@ -127,8 +127,6 @@ export async function dispatchAssistantMessage(
127
127
  payload: { id: userMessageId, role: 'user' },
128
128
  });
129
129
 
130
- enqueueSessionTitle({ cfg, db, sessionId, content });
131
-
132
130
  const assistantMessageId = crypto.randomUUID();
133
131
  await db.insert(messages).values({
134
132
  id: assistantMessageId,
@@ -304,12 +302,20 @@ async function generateSessionTitle(args: {
304
302
 
305
303
  const promptText = String(content ?? '').slice(0, 2000);
306
304
 
307
- const titlePrompt = [
308
- 'Generate a brief title (6-8 words) summarizing what the user wants to do.',
309
- 'Rules: Plain text only. No markdown, no quotes, no punctuation, no emojis.',
310
- 'Focus on the core task or topic. Be specific but concise.',
311
- 'Examples: "Fix TypeScript build errors", "Add dark mode toggle", "Refactor auth middleware"',
312
- ].join(' ');
305
+ const titleInstructions = `Generate a brief title (6-8 words) summarizing what the user wants to do.
306
+ Rules: Plain text only. No markdown, no quotes, no punctuation, no emojis.
307
+ Focus on the core task or topic. Be specific but concise.
308
+ Examples: "Fix TypeScript build errors", "Add dark mode toggle", "Refactor auth middleware"
309
+
310
+ Output ONLY the title, nothing else.`;
311
+
312
+ const userMessageWithTags = `<task>
313
+ ${titleInstructions}
314
+ </task>
315
+
316
+ <user-message>
317
+ ${promptText}
318
+ </user-message>`;
313
319
 
314
320
  // Build system prompt and messages
315
321
  // For OAuth: Keep spoof pure, add instructions to user message
@@ -323,7 +329,7 @@ async function generateSessionTitle(args: {
323
329
  messagesArray = [
324
330
  {
325
331
  role: 'user',
326
- content: `${titlePrompt}\n\n${promptText}`,
332
+ content: userMessageWithTags,
327
333
  },
328
334
  ];
329
335
 
@@ -335,8 +341,13 @@ async function generateSessionTitle(args: {
335
341
  );
336
342
  } else {
337
343
  // API key mode: normal flow
338
- system = titlePrompt;
339
- messagesArray = [{ role: 'user', content: promptText }];
344
+ system = titleInstructions;
345
+ messagesArray = [
346
+ {
347
+ role: 'user',
348
+ content: `<user-message>\n${promptText}\n</user-message>`,
349
+ },
350
+ ];
340
351
 
341
352
  debugLog(
342
353
  `[TITLE_GEN] Using API key mode (prompts: title-generator, user-request)`,
@@ -441,3 +452,61 @@ async function touchSessionLastActive(args: {
441
452
  debugLog('[touchSessionLastActive] Error:', err);
442
453
  }
443
454
  }
455
+
456
+ export async function triggerDeferredTitleGeneration(args: {
457
+ cfg: AGIConfig;
458
+ db: DB;
459
+ sessionId: string;
460
+ }): Promise<void> {
461
+ const { cfg, db, sessionId } = args;
462
+
463
+ try {
464
+ const userMessages = await db
465
+ .select()
466
+ .from(messages)
467
+ .where(eq(messages.sessionId, sessionId))
468
+ .orderBy(asc(messages.createdAt))
469
+ .limit(1);
470
+
471
+ if (!userMessages.length || userMessages[0].role !== 'user') {
472
+ debugLog(
473
+ '[TITLE_GEN] No user message found for deferred title generation',
474
+ );
475
+ return;
476
+ }
477
+
478
+ const parts = await db
479
+ .select()
480
+ .from(messageParts)
481
+ .where(eq(messageParts.messageId, userMessages[0].id))
482
+ .orderBy(asc(messageParts.index))
483
+ .limit(1);
484
+
485
+ if (!parts.length) {
486
+ debugLog(
487
+ '[TITLE_GEN] No message parts found for deferred title generation',
488
+ );
489
+ return;
490
+ }
491
+
492
+ let content = '';
493
+ try {
494
+ const parsed = JSON.parse(parts[0].content ?? '{}');
495
+ content = String(parsed.text ?? '');
496
+ } catch {
497
+ debugLog('[TITLE_GEN] Failed to parse message part content');
498
+ return;
499
+ }
500
+
501
+ if (!content) {
502
+ debugLog('[TITLE_GEN] Empty content for deferred title generation');
503
+ return;
504
+ }
505
+
506
+ debugLog('[TITLE_GEN] Triggering deferred title generation');
507
+ enqueueSessionTitle({ cfg, db, sessionId, content });
508
+ } catch (err) {
509
+ debugLog('[TITLE_GEN] Error in triggerDeferredTitleGeneration:');
510
+ debugLog(err);
511
+ }
512
+ }
@@ -1,10 +1,14 @@
1
1
  import type { AGIConfig } from '@agi-cli/sdk';
2
- import { getAuth, refreshToken, setAuth } from '@agi-cli/sdk';
2
+ import {
3
+ getAuth,
4
+ refreshToken,
5
+ setAuth,
6
+ createAnthropicOAuthModel,
7
+ createAnthropicCachingFetch,
8
+ } from '@agi-cli/sdk';
3
9
  import { createAnthropic } from '@ai-sdk/anthropic';
4
10
  import { toClaudeCodeName } from '../tools/mapping.ts';
5
11
 
6
- const CLAUDE_CLI_VERSION = '1.0.61';
7
-
8
12
  export async function getAnthropicInstance(cfg: AGIConfig) {
9
13
  const auth = await getAuth('anthropic', cfg.projectRoot);
10
14
 
@@ -32,312 +36,15 @@ export async function getAnthropicInstance(cfg: AGIConfig) {
32
36
  };
33
37
  }
34
38
 
35
- const customFetch = async (
36
- input: string | URL | Request,
37
- init?: RequestInit,
38
- ) => {
39
- const initHeaders = init?.headers;
40
- const headers: Record<string, string> = {};
41
-
42
- if (initHeaders) {
43
- if (initHeaders instanceof Headers) {
44
- initHeaders.forEach((value, key) => {
45
- if (key.toLowerCase() !== 'x-api-key') {
46
- headers[key] = value;
47
- }
48
- });
49
- } else if (Array.isArray(initHeaders)) {
50
- for (const [key, value] of initHeaders) {
51
- if (
52
- key &&
53
- key.toLowerCase() !== 'x-api-key' &&
54
- typeof value === 'string'
55
- ) {
56
- headers[key] = value;
57
- }
58
- }
59
- } else {
60
- for (const [key, value] of Object.entries(initHeaders)) {
61
- if (
62
- key.toLowerCase() !== 'x-api-key' &&
63
- typeof value === 'string'
64
- ) {
65
- headers[key] = value;
66
- }
67
- }
68
- }
69
- }
70
-
71
- headers.authorization = `Bearer ${currentAuth.access}`;
72
- headers['anthropic-beta'] =
73
- 'claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14';
74
- headers['anthropic-dangerous-direct-browser-access'] = 'true';
75
- headers['anthropic-version'] = '2023-06-01';
76
- headers['user-agent'] =
77
- `claude-cli/${CLAUDE_CLI_VERSION} (external, cli)`;
78
- headers['x-app'] = 'cli';
79
- headers['content-type'] = 'application/json';
80
- headers.accept = 'application/json';
81
-
82
- headers['x-stainless-arch'] = process.arch === 'arm64' ? 'arm64' : 'x64';
83
- headers['x-stainless-helper-method'] = 'stream';
84
- headers['x-stainless-lang'] = 'js';
85
- headers['x-stainless-os'] =
86
- process.platform === 'darwin'
87
- ? 'MacOS'
88
- : process.platform === 'win32'
89
- ? 'Windows'
90
- : 'Linux';
91
- headers['x-stainless-package-version'] = '0.70.0';
92
- headers['x-stainless-retry-count'] = '0';
93
- headers['x-stainless-runtime'] = 'node';
94
- headers['x-stainless-runtime-version'] = process.version;
95
- headers['x-stainless-timeout'] = '600';
96
-
97
- let url = typeof input === 'string' ? input : input.toString();
98
- if (url.includes('/v1/messages') && !url.includes('beta=true')) {
99
- url += url.includes('?') ? '&beta=true' : '?beta=true';
100
- }
101
-
102
- let body = init?.body;
103
- if (body && typeof body === 'string') {
104
- try {
105
- const parsed = JSON.parse(body);
106
-
107
- if (parsed.tools && Array.isArray(parsed.tools)) {
108
- parsed.tools = parsed.tools.map(
109
- (tool: { name: string; [key: string]: unknown }) => ({
110
- ...tool,
111
- name: toClaudeCodeName(tool.name),
112
- }),
113
- );
114
- }
115
-
116
- const MAX_SYSTEM_CACHE = 1;
117
- const MAX_MESSAGE_CACHE = 1;
118
- let systemCacheUsed = 0;
119
- let messageCacheUsed = 0;
120
-
121
- if (parsed.system && Array.isArray(parsed.system)) {
122
- parsed.system = parsed.system.map(
123
- (
124
- block: { type: string; cache_control?: unknown },
125
- index: number,
126
- ) => {
127
- if (block.cache_control) return block;
128
- if (
129
- systemCacheUsed < MAX_SYSTEM_CACHE &&
130
- index === 0 &&
131
- block.type === 'text'
132
- ) {
133
- systemCacheUsed++;
134
- return { ...block, cache_control: { type: 'ephemeral' } };
135
- }
136
- return block;
137
- },
138
- );
139
- }
140
-
141
- if (parsed.messages && Array.isArray(parsed.messages)) {
142
- const messageCount = parsed.messages.length;
143
-
144
- parsed.messages = parsed.messages.map(
145
- (
146
- msg: {
147
- role: string;
148
- content: unknown;
149
- [key: string]: unknown;
150
- },
151
- msgIndex: number,
152
- ) => {
153
- const isLast = msgIndex === messageCount - 1;
154
-
155
- if (Array.isArray(msg.content)) {
156
- const content = msg.content.map(
157
- (
158
- block: {
159
- type: string;
160
- name?: string;
161
- cache_control?: unknown;
162
- },
163
- blockIndex: number,
164
- ) => {
165
- let transformedBlock = block;
166
-
167
- if (block.type === 'tool_use' && block.name) {
168
- transformedBlock = {
169
- ...block,
170
- name: toClaudeCodeName(block.name),
171
- };
172
- }
173
- if (block.type === 'tool_result' && block.name) {
174
- transformedBlock = {
175
- ...block,
176
- name: toClaudeCodeName(block.name),
177
- };
178
- }
179
-
180
- if (
181
- isLast &&
182
- !transformedBlock.cache_control &&
183
- messageCacheUsed < MAX_MESSAGE_CACHE &&
184
- blockIndex === (msg.content as unknown[]).length - 1
185
- ) {
186
- messageCacheUsed++;
187
- return {
188
- ...transformedBlock,
189
- cache_control: { type: 'ephemeral' },
190
- };
191
- }
192
-
193
- return transformedBlock;
194
- },
195
- );
196
- return { ...msg, content };
197
- }
198
-
199
- if (
200
- isLast &&
201
- messageCacheUsed < MAX_MESSAGE_CACHE &&
202
- typeof msg.content === 'string'
203
- ) {
204
- messageCacheUsed++;
205
- return {
206
- ...msg,
207
- content: [
208
- {
209
- type: 'text',
210
- text: msg.content,
211
- cache_control: { type: 'ephemeral' },
212
- },
213
- ],
214
- };
215
- }
216
-
217
- return msg;
218
- },
219
- );
220
- }
221
-
222
- body = JSON.stringify(parsed);
223
- } catch {
224
- // If parsing fails, send as-is
225
- }
226
- }
227
-
228
- return fetch(url, {
229
- ...init,
230
- body,
231
- headers,
39
+ return (model: string) =>
40
+ createAnthropicOAuthModel(model, {
41
+ oauth: currentAuth,
42
+ toolNameTransformer: toClaudeCodeName,
232
43
  });
233
- };
234
- return createAnthropic({
235
- apiKey: '',
236
- fetch: customFetch as typeof fetch,
237
- });
238
44
  }
239
45
 
240
- const customFetch = async (
241
- input: string | URL | Request,
242
- init?: RequestInit,
243
- ) => {
244
- let body = init?.body;
245
- if (body && typeof body === 'string') {
246
- try {
247
- const parsed = JSON.parse(body);
248
-
249
- const MAX_SYSTEM_CACHE = 1;
250
- const MAX_MESSAGE_CACHE = 1;
251
- let systemCacheUsed = 0;
252
- let messageCacheUsed = 0;
253
-
254
- if (parsed.system && Array.isArray(parsed.system)) {
255
- parsed.system = parsed.system.map(
256
- (
257
- block: { type: string; cache_control?: unknown },
258
- index: number,
259
- ) => {
260
- if (block.cache_control) return block;
261
- if (
262
- systemCacheUsed < MAX_SYSTEM_CACHE &&
263
- index === 0 &&
264
- block.type === 'text'
265
- ) {
266
- systemCacheUsed++;
267
- return { ...block, cache_control: { type: 'ephemeral' } };
268
- }
269
- return block;
270
- },
271
- );
272
- }
273
-
274
- if (parsed.messages && Array.isArray(parsed.messages)) {
275
- const messageCount = parsed.messages.length;
276
- parsed.messages = parsed.messages.map(
277
- (
278
- msg: {
279
- role: string;
280
- content: unknown;
281
- [key: string]: unknown;
282
- },
283
- msgIndex: number,
284
- ) => {
285
- const isLast = msgIndex === messageCount - 1;
286
-
287
- if (Array.isArray(msg.content)) {
288
- const blocks = msg.content as {
289
- type: string;
290
- cache_control?: unknown;
291
- }[];
292
- const content = blocks.map((block, blockIndex) => {
293
- if (block.cache_control) return block;
294
- if (
295
- isLast &&
296
- messageCacheUsed < MAX_MESSAGE_CACHE &&
297
- blockIndex === blocks.length - 1
298
- ) {
299
- messageCacheUsed++;
300
- return { ...block, cache_control: { type: 'ephemeral' } };
301
- }
302
- return block;
303
- });
304
- return { ...msg, content };
305
- }
306
-
307
- if (
308
- isLast &&
309
- messageCacheUsed < MAX_MESSAGE_CACHE &&
310
- typeof msg.content === 'string'
311
- ) {
312
- messageCacheUsed++;
313
- return {
314
- ...msg,
315
- content: [
316
- {
317
- type: 'text',
318
- text: msg.content,
319
- cache_control: { type: 'ephemeral' },
320
- },
321
- ],
322
- };
323
- }
324
-
325
- return msg;
326
- },
327
- );
328
- }
329
-
330
- body = JSON.stringify(parsed);
331
- } catch {
332
- // If parsing fails, send as-is
333
- }
334
- }
335
-
336
- const url = typeof input === 'string' ? input : input.toString();
337
- return fetch(url, { ...init, body });
338
- };
339
-
46
+ const cachingFetch = createAnthropicCachingFetch();
340
47
  return createAnthropic({
341
- fetch: customFetch as typeof fetch,
48
+ fetch: cachingFetch as typeof fetch,
342
49
  });
343
50
  }
@@ -1,12 +1,8 @@
1
1
  import type { AGIConfig } from '@agi-cli/sdk';
2
- import { getAuth } from '@agi-cli/sdk';
3
- import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
2
+ import { getAuth, createGoogleModel } from '@agi-cli/sdk';
4
3
 
5
4
  export async function resolveGoogleModel(model: string, cfg: AGIConfig) {
6
5
  const auth = await getAuth('google', cfg.projectRoot);
7
- if (auth?.type === 'api' && auth.key) {
8
- const instance = createGoogleGenerativeAI({ apiKey: auth.key });
9
- return instance(model);
10
- }
11
- return google(model);
6
+ const apiKey = auth?.type === 'api' ? auth.key : undefined;
7
+ return createGoogleModel(model, { apiKey });
12
8
  }
@@ -16,7 +16,9 @@ export async function resolveModel(
16
16
  options?: { systemPrompt?: string; sessionId?: string },
17
17
  ) {
18
18
  if (provider === 'openai') {
19
- return resolveOpenAIModel(model, cfg, options);
19
+ return resolveOpenAIModel(model, cfg, {
20
+ systemPrompt: options?.systemPrompt,
21
+ });
20
22
  }
21
23
  if (provider === 'anthropic') {
22
24
  const instance = await getAnthropicInstance(cfg);
@@ -7,8 +7,6 @@ export async function resolveOpenAIModel(
7
7
  cfg: AGIConfig,
8
8
  options?: {
9
9
  systemPrompt?: string;
10
- promptCacheKey?: string;
11
- promptCacheRetention?: 'in_memory' | '24h';
12
10
  },
13
11
  ) {
14
12
  const auth = await getAuth('openai', cfg.projectRoot);
@@ -20,8 +18,6 @@ export async function resolveOpenAIModel(
20
18
  reasoningEffort: isCodexModel ? 'high' : 'medium',
21
19
  reasoningSummary: 'auto',
22
20
  instructions: options?.systemPrompt,
23
- promptCacheKey: options?.promptCacheKey,
24
- promptCacheRetention: options?.promptCacheRetention,
25
21
  });
26
22
  }
27
23
  if (auth?.type === 'api' && auth.key) {
@@ -1,61 +1,7 @@
1
- import type { AGIConfig, ProviderId } from '@agi-cli/sdk';
2
- import { catalog } from '@agi-cli/sdk';
3
- import { createOpenAI } from '@ai-sdk/openai';
4
- import { createAnthropic } from '@ai-sdk/anthropic';
5
- import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
6
-
7
- function normalizeModelIdentifier(provider: ProviderId, model: string): string {
8
- const prefix = `${provider}/`;
9
- return model.startsWith(prefix) ? model.slice(prefix.length) : model;
10
- }
1
+ import type { AGIConfig } from '@agi-cli/sdk';
2
+ import { createOpencodeModel } from '@agi-cli/sdk';
11
3
 
12
4
  export function resolveOpencodeModel(model: string, _cfg: AGIConfig) {
13
- const entry = catalog.opencode;
14
- const normalizedModel = normalizeModelIdentifier('opencode', model);
15
- const modelInfo =
16
- entry?.models.find((m) => m.id === normalizedModel) ??
17
- entry?.models.find((m) => m.id === model);
18
- const resolvedModelId = modelInfo?.id ?? normalizedModel ?? model;
19
- const binding = modelInfo?.provider?.npm ?? entry?.npm;
20
- const apiKey = process.env.OPENCODE_API_KEY ?? '';
21
- const baseURL =
22
- modelInfo?.provider?.baseURL ||
23
- modelInfo?.provider?.api ||
24
- entry?.api ||
25
- 'https://opencode.ai/zen/v1';
26
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
27
- if (binding === '@ai-sdk/openai') {
28
- const instance = createOpenAI({ apiKey, baseURL });
29
- return instance(resolvedModelId);
30
- }
31
- if (binding === '@ai-sdk/anthropic') {
32
- const instance = createAnthropic({ apiKey, baseURL });
33
- return instance(resolvedModelId);
34
- }
35
- if (binding === '@ai-sdk/openai-compatible') {
36
- const instance = createOpenAICompatible({
37
- name: entry?.label ?? 'opencode',
38
- baseURL,
39
- headers,
40
- });
41
- return instance(resolvedModelId);
42
- }
43
-
44
- const ocOpenAI = createOpenAI({ apiKey, baseURL });
45
- const ocAnthropic = createAnthropic({ apiKey, baseURL });
46
- const ocCompat = createOpenAICompatible({
47
- name: entry?.label ?? 'opencode',
48
- baseURL,
49
- headers,
50
- });
51
-
52
- const id = resolvedModelId.toLowerCase();
53
- if (id.includes('claude')) return ocAnthropic(resolvedModelId);
54
- if (
55
- id.includes('qwen3-coder') ||
56
- id.includes('grok-code') ||
57
- id.includes('kimi-k2')
58
- )
59
- return ocCompat(resolvedModelId);
60
- return ocOpenAI(resolvedModelId);
5
+ const apiKey = process.env.OPENCODE_API_KEY;
6
+ return createOpencodeModel(model, { apiKey });
61
7
  }
@@ -1,11 +1,7 @@
1
- import { createOpenRouter } from '@openrouter/ai-sdk-provider';
1
+ import { getOpenRouterInstance, createOpenRouterModel } from '@agi-cli/sdk';
2
2
 
3
- export function getOpenRouterInstance() {
4
- const apiKey = process.env.OPENROUTER_API_KEY ?? '';
5
- return createOpenRouter({ apiKey });
6
- }
3
+ export { getOpenRouterInstance };
7
4
 
8
5
  export function resolveOpenRouterModel(model: string) {
9
- const openrouter = getOpenRouterInstance();
10
- return openrouter.chat(model);
6
+ return createOpenRouterModel(model);
11
7
  }
@@ -1,53 +1,16 @@
1
1
  import type { AGIConfig } from '@agi-cli/sdk';
2
- import { catalog, getAuth } from '@agi-cli/sdk';
3
- import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
2
+ import { getAuth, createZaiModel, createZaiCodingModel } from '@agi-cli/sdk';
4
3
 
5
4
  export async function getZaiInstance(cfg: AGIConfig, model: string) {
6
5
  const auth = await getAuth('zai', cfg.projectRoot);
7
- const entry = catalog.zai;
8
-
9
- let apiKey = '';
10
- const baseURL = entry?.api || 'https://api.z.ai/api/paas/v4';
11
-
12
- if (auth?.type === 'api' && auth.key) {
13
- apiKey = auth.key;
14
- } else {
15
- apiKey = process.env.ZAI_API_KEY || process.env.ZHIPU_API_KEY || '';
16
- }
17
-
18
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
19
-
20
- const instance = createOpenAICompatible({
21
- name: entry?.label ?? 'Z.AI',
22
- baseURL,
23
- headers,
24
- });
25
-
26
- return instance(model);
6
+ const apiKey = auth?.type === 'api' ? auth.key : undefined;
7
+ return createZaiModel(model, { apiKey });
27
8
  }
28
9
 
29
10
  export async function getZaiCodingInstance(cfg: AGIConfig, model: string) {
30
11
  const auth =
31
12
  (await getAuth('zai', cfg.projectRoot)) ||
32
13
  (await getAuth('zai-coding', cfg.projectRoot));
33
- const entry = catalog['zai-coding'];
34
-
35
- let apiKey = '';
36
- const baseURL = entry?.api || 'https://api.z.ai/api/coding/paas/v4';
37
-
38
- if (auth?.type === 'api' && auth.key) {
39
- apiKey = auth.key;
40
- } else {
41
- apiKey = process.env.ZAI_API_KEY || process.env.ZHIPU_API_KEY || '';
42
- }
43
-
44
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
45
-
46
- const instance = createOpenAICompatible({
47
- name: entry?.label ?? 'Z.AI Coding',
48
- baseURL,
49
- headers,
50
- });
51
-
52
- return instance(model);
14
+ const apiKey = auth?.type === 'api' ? auth.key : undefined;
15
+ return createZaiCodingModel(model, { apiKey });
53
16
  }