@agi-cli/server 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/package.json +3 -3
- package/src/events/bus.ts +16 -1
- package/src/routes/session-stream.ts +14 -3
- package/src/runtime/agent/runner.ts +9 -0
- package/src/runtime/message/service.ts +81 -12
- package/src/runtime/provider/anthropic.ts +13 -306
- package/src/runtime/provider/google.ts +3 -7
- package/src/runtime/provider/index.ts +3 -1
- package/src/runtime/provider/openai.ts +0 -4
- package/src/runtime/provider/opencode.ts +4 -58
- package/src/runtime/provider/openrouter.ts +3 -7
- package/src/runtime/provider/zai.ts +5 -42
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agi-cli/server",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
33
|
-
"@agi-cli/database": "0.1.
|
|
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(
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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:
|
|
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 =
|
|
339
|
-
messagesArray = [
|
|
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 {
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
8
|
-
|
|
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,
|
|
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
|
|
2
|
-
import {
|
|
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
|
|
14
|
-
|
|
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 {
|
|
1
|
+
import { getOpenRouterInstance, createOpenRouterModel } from '@agi-cli/sdk';
|
|
2
2
|
|
|
3
|
-
export
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
}
|