@agi-cli/server 0.1.140 → 0.1.142
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/types.ts +6 -4
- package/src/index.ts +8 -4
- package/src/openapi/paths/{solforge.ts → setu.ts} +13 -13
- package/src/openapi/spec.ts +3 -3
- package/src/routes/config/defaults.ts +3 -0
- package/src/routes/config/main.ts +5 -0
- package/src/routes/research.ts +2 -2
- package/src/routes/session-approval.ts +53 -0
- package/src/routes/{solforge.ts → setu.ts} +19 -19
- package/src/runtime/ask/service.ts +1 -1
- package/src/runtime/message/compaction-auto.ts +1 -1
- package/src/runtime/message/service.ts +4 -0
- package/src/runtime/provider/index.ts +7 -3
- package/src/runtime/provider/moonshot.ts +8 -0
- package/src/runtime/provider/selection.ts +1 -1
- package/src/runtime/provider/{solforge.ts → setu.ts} +14 -14
- package/src/runtime/session/db-operations.ts +1 -1
- package/src/runtime/session/queue.ts +2 -0
- package/src/runtime/stream/finish-handler.ts +0 -1
- package/src/runtime/stream/step-finish.ts +2 -2
- package/src/runtime/stream/types.ts +1 -1
- package/src/runtime/tools/approval.ts +179 -0
- package/src/runtime/tools/context.ts +2 -0
- package/src/runtime/tools/setup.ts +1 -0
- package/src/tools/adapter.ts +28 -0
- package/src/tools/database/get-parent-session.ts +3 -3
- package/src/tools/database/query-messages.ts +2 -2
- package/src/tools/database/query-sessions.ts +1 -1
- package/src/tools/database/search-history.ts +1 -1
- package/sst-env.d.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agi-cli/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.142",
|
|
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.142",
|
|
33
|
+
"@agi-cli/database": "0.1.142",
|
|
34
34
|
"drizzle-orm": "^0.44.5",
|
|
35
35
|
"hono": "^4.9.9",
|
|
36
36
|
"zod": "^4.1.8"
|
package/src/events/types.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export type AGIEventType =
|
|
2
|
-
| '
|
|
3
|
-
| '
|
|
4
|
-
| '
|
|
5
|
-
| '
|
|
2
|
+
| 'tool.approval.required'
|
|
3
|
+
| 'tool.approval.resolved'
|
|
4
|
+
| 'setu.payment.required'
|
|
5
|
+
| 'setu.payment.signing'
|
|
6
|
+
| 'setu.payment.complete'
|
|
7
|
+
| 'setu.payment.error'
|
|
6
8
|
| 'session.created'
|
|
7
9
|
| 'session.updated'
|
|
8
10
|
| 'message.created'
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,8 @@ import { registerTerminalsRoutes } from './routes/terminals.ts';
|
|
|
16
16
|
import { registerSessionFilesRoutes } from './routes/session-files.ts';
|
|
17
17
|
import { registerBranchRoutes } from './routes/branch.ts';
|
|
18
18
|
import { registerResearchRoutes } from './routes/research.ts';
|
|
19
|
-
import {
|
|
19
|
+
import { registerSessionApprovalRoute } from './routes/session-approval.ts';
|
|
20
|
+
import { registerSetuRoutes } from './routes/setu.ts';
|
|
20
21
|
import type { AgentConfigEntry } from './runtime/agent/registry.ts';
|
|
21
22
|
|
|
22
23
|
const globalTerminalManager = new TerminalManager();
|
|
@@ -59,6 +60,7 @@ function initApp() {
|
|
|
59
60
|
registerRootRoutes(app);
|
|
60
61
|
registerOpenApiRoute(app);
|
|
61
62
|
registerSessionsRoutes(app);
|
|
63
|
+
registerSessionApprovalRoute(app);
|
|
62
64
|
registerSessionMessagesRoutes(app);
|
|
63
65
|
registerSessionStreamRoute(app);
|
|
64
66
|
registerAskRoutes(app);
|
|
@@ -69,7 +71,7 @@ function initApp() {
|
|
|
69
71
|
registerSessionFilesRoutes(app);
|
|
70
72
|
registerBranchRoutes(app);
|
|
71
73
|
registerResearchRoutes(app);
|
|
72
|
-
|
|
74
|
+
registerSetuRoutes(app);
|
|
73
75
|
|
|
74
76
|
return app;
|
|
75
77
|
}
|
|
@@ -128,6 +130,7 @@ export function createStandaloneApp(_config?: StandaloneAppConfig) {
|
|
|
128
130
|
registerRootRoutes(honoApp);
|
|
129
131
|
registerOpenApiRoute(honoApp);
|
|
130
132
|
registerSessionsRoutes(honoApp);
|
|
133
|
+
registerSessionApprovalRoute(honoApp);
|
|
131
134
|
registerSessionMessagesRoutes(honoApp);
|
|
132
135
|
registerSessionStreamRoute(honoApp);
|
|
133
136
|
registerAskRoutes(honoApp);
|
|
@@ -138,7 +141,7 @@ export function createStandaloneApp(_config?: StandaloneAppConfig) {
|
|
|
138
141
|
registerSessionFilesRoutes(honoApp);
|
|
139
142
|
registerBranchRoutes(honoApp);
|
|
140
143
|
registerResearchRoutes(honoApp);
|
|
141
|
-
|
|
144
|
+
registerSetuRoutes(honoApp);
|
|
142
145
|
|
|
143
146
|
return honoApp;
|
|
144
147
|
}
|
|
@@ -225,6 +228,7 @@ export function createEmbeddedApp(config: EmbeddedAppConfig = {}) {
|
|
|
225
228
|
registerRootRoutes(honoApp);
|
|
226
229
|
registerOpenApiRoute(honoApp);
|
|
227
230
|
registerSessionsRoutes(honoApp);
|
|
231
|
+
registerSessionApprovalRoute(honoApp);
|
|
228
232
|
registerSessionMessagesRoutes(honoApp);
|
|
229
233
|
registerSessionStreamRoute(honoApp);
|
|
230
234
|
registerAskRoutes(honoApp);
|
|
@@ -235,7 +239,7 @@ export function createEmbeddedApp(config: EmbeddedAppConfig = {}) {
|
|
|
235
239
|
registerSessionFilesRoutes(honoApp);
|
|
236
240
|
registerBranchRoutes(honoApp);
|
|
237
241
|
registerResearchRoutes(honoApp);
|
|
238
|
-
|
|
242
|
+
registerSetuRoutes(honoApp);
|
|
239
243
|
|
|
240
244
|
return honoApp;
|
|
241
245
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export const
|
|
2
|
-
'/v1/
|
|
1
|
+
export const setuPaths = {
|
|
2
|
+
'/v1/setu/balance': {
|
|
3
3
|
get: {
|
|
4
|
-
tags: ['
|
|
5
|
-
operationId: '
|
|
6
|
-
summary: 'Get
|
|
4
|
+
tags: ['setu'],
|
|
5
|
+
operationId: 'getSetuBalance',
|
|
6
|
+
summary: 'Get Setu account balance',
|
|
7
7
|
description:
|
|
8
8
|
'Returns wallet balance, total spent, total topups, and request count',
|
|
9
9
|
responses: {
|
|
@@ -44,7 +44,7 @@ export const solforgePaths = {
|
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
46
|
502: {
|
|
47
|
-
description: 'Failed to fetch balance from
|
|
47
|
+
description: 'Failed to fetch balance from Setu',
|
|
48
48
|
content: {
|
|
49
49
|
'application/json': {
|
|
50
50
|
schema: {
|
|
@@ -58,11 +58,11 @@ export const solforgePaths = {
|
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
|
-
'/v1/
|
|
61
|
+
'/v1/setu/wallet': {
|
|
62
62
|
get: {
|
|
63
|
-
tags: ['
|
|
64
|
-
operationId: '
|
|
65
|
-
summary: 'Get
|
|
63
|
+
tags: ['setu'],
|
|
64
|
+
operationId: 'getSetuWallet',
|
|
65
|
+
summary: 'Get Setu wallet info',
|
|
66
66
|
description:
|
|
67
67
|
'Returns whether the wallet is configured and its public key',
|
|
68
68
|
responses: {
|
|
@@ -85,10 +85,10 @@ export const solforgePaths = {
|
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
87
|
},
|
|
88
|
-
'/v1/
|
|
88
|
+
'/v1/setu/usdc-balance': {
|
|
89
89
|
get: {
|
|
90
|
-
tags: ['
|
|
91
|
-
operationId: '
|
|
90
|
+
tags: ['setu'],
|
|
91
|
+
operationId: 'getSetuUsdcBalance',
|
|
92
92
|
summary: 'Get USDC token balance',
|
|
93
93
|
description:
|
|
94
94
|
'Fetches USDC balance from Solana blockchain for the configured wallet',
|
package/src/openapi/spec.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { streamPaths } from './paths/stream';
|
|
|
8
8
|
import { schemas } from './schemas';
|
|
9
9
|
|
|
10
10
|
import { terminalsPath } from './paths/terminals';
|
|
11
|
-
import {
|
|
11
|
+
import { setuPaths } from './paths/setu';
|
|
12
12
|
|
|
13
13
|
export function getOpenAPISpec() {
|
|
14
14
|
const spec = {
|
|
@@ -28,7 +28,7 @@ export function getOpenAPISpec() {
|
|
|
28
28
|
{ name: 'files' },
|
|
29
29
|
{ name: 'git' },
|
|
30
30
|
{ name: 'terminals' },
|
|
31
|
-
{ name: '
|
|
31
|
+
{ name: 'setu' },
|
|
32
32
|
],
|
|
33
33
|
paths: {
|
|
34
34
|
...askPaths,
|
|
@@ -39,7 +39,7 @@ export function getOpenAPISpec() {
|
|
|
39
39
|
...filesPaths,
|
|
40
40
|
...gitPaths,
|
|
41
41
|
...terminalsPath,
|
|
42
|
-
...
|
|
42
|
+
...setuPaths,
|
|
43
43
|
},
|
|
44
44
|
components: {
|
|
45
45
|
schemas,
|
|
@@ -11,6 +11,7 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
11
11
|
agent?: string;
|
|
12
12
|
provider?: string;
|
|
13
13
|
model?: string;
|
|
14
|
+
toolApproval?: 'auto' | 'dangerous' | 'all';
|
|
14
15
|
scope?: 'global' | 'local';
|
|
15
16
|
}>();
|
|
16
17
|
|
|
@@ -19,11 +20,13 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
19
20
|
agent: string;
|
|
20
21
|
provider: string;
|
|
21
22
|
model: string;
|
|
23
|
+
toolApproval: 'auto' | 'dangerous' | 'all';
|
|
22
24
|
}> = {};
|
|
23
25
|
|
|
24
26
|
if (body.agent) updates.agent = body.agent;
|
|
25
27
|
if (body.provider) updates.provider = body.provider;
|
|
26
28
|
if (body.model) updates.model = body.model;
|
|
29
|
+
if (body.toolApproval) updates.toolApproval = body.toolApproval;
|
|
27
30
|
|
|
28
31
|
await setConfig(scope, updates, projectRoot);
|
|
29
32
|
|
|
@@ -52,6 +52,11 @@ export function registerMainConfigRoute(app: Hono) {
|
|
|
52
52
|
embeddedConfig?.defaults?.model,
|
|
53
53
|
cfg.defaults.model,
|
|
54
54
|
),
|
|
55
|
+
toolApproval: getDefault(
|
|
56
|
+
undefined,
|
|
57
|
+
embeddedConfig?.defaults?.toolApproval,
|
|
58
|
+
cfg.defaults.toolApproval,
|
|
59
|
+
) as 'auto' | 'dangerous' | 'all',
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
return c.json({
|
package/src/routes/research.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { getDb } from '@agi-cli/database';
|
|
|
4
4
|
import { sessions, messages, messageParts } from '@agi-cli/database/schema';
|
|
5
5
|
import { desc, eq, and, asc, count } from 'drizzle-orm';
|
|
6
6
|
import type { ProviderId } from '@agi-cli/sdk';
|
|
7
|
-
import { isProviderId
|
|
7
|
+
import { isProviderId } from '@agi-cli/sdk';
|
|
8
8
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
9
9
|
import { logger } from '@agi-cli/sdk';
|
|
10
10
|
import { publish } from '../events/bus.ts';
|
|
@@ -207,7 +207,7 @@ export function registerResearchRoutes(app: Hono) {
|
|
|
207
207
|
return c.json({ error: 'Research session not found' }, 404);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
const
|
|
210
|
+
const _researchSession = researchRows[0];
|
|
211
211
|
|
|
212
212
|
const researchMessages = await db
|
|
213
213
|
.select({
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import {
|
|
3
|
+
resolveApproval,
|
|
4
|
+
getPendingApprovalsForSession,
|
|
5
|
+
} from '../runtime/tools/approval.ts';
|
|
6
|
+
|
|
7
|
+
export function registerSessionApprovalRoute(app: Hono) {
|
|
8
|
+
app.post('/v1/sessions/:id/approval', async (c) => {
|
|
9
|
+
const sessionId = c.req.param('id');
|
|
10
|
+
const body = await c.req.json<{
|
|
11
|
+
callId: string;
|
|
12
|
+
approved: boolean;
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
if (!body.callId) {
|
|
16
|
+
return c.json({ ok: false, error: 'callId is required' }, 400);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof body.approved !== 'boolean') {
|
|
20
|
+
return c.json({ ok: false, error: 'approved must be a boolean' }, 400);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log('[approval-route] Received approval request', {
|
|
24
|
+
sessionId,
|
|
25
|
+
callId: body.callId,
|
|
26
|
+
approved: body.approved,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const result = resolveApproval(body.callId, body.approved);
|
|
30
|
+
|
|
31
|
+
if (!result.ok) {
|
|
32
|
+
return c.json(result, 404);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return c.json({ ok: true, callId: body.callId, approved: body.approved });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.get('/v1/sessions/:id/approval/pending', async (c) => {
|
|
39
|
+
const sessionId = c.req.param('id');
|
|
40
|
+
const pending = getPendingApprovalsForSession(sessionId);
|
|
41
|
+
|
|
42
|
+
return c.json({
|
|
43
|
+
ok: true,
|
|
44
|
+
pending: pending.map((p) => ({
|
|
45
|
+
callId: p.callId,
|
|
46
|
+
toolName: p.toolName,
|
|
47
|
+
args: p.args,
|
|
48
|
+
messageId: p.messageId,
|
|
49
|
+
createdAt: p.createdAt,
|
|
50
|
+
})),
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
fetchSetuBalance,
|
|
4
4
|
getPublicKeyFromPrivate,
|
|
5
5
|
getAuth,
|
|
6
6
|
loadConfig,
|
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
import { logger } from '@agi-cli/sdk';
|
|
10
10
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
11
11
|
|
|
12
|
-
async function
|
|
13
|
-
if (process.env.
|
|
14
|
-
return process.env.
|
|
12
|
+
async function getSetuPrivateKey(): Promise<string | null> {
|
|
13
|
+
if (process.env.SETU_PRIVATE_KEY) {
|
|
14
|
+
return process.env.SETU_PRIVATE_KEY;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
18
|
const cfg = await loadConfig(process.cwd());
|
|
19
|
-
const auth = await getAuth('
|
|
19
|
+
const auth = await getAuth('setu', cfg.projectRoot);
|
|
20
20
|
if (auth?.type === 'wallet' && auth.secret) {
|
|
21
21
|
return auth.secret;
|
|
22
22
|
}
|
|
@@ -25,33 +25,33 @@ async function getSolforgePrivateKey(): Promise<string | null> {
|
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function
|
|
29
|
-
app.get('/v1/
|
|
28
|
+
export function registerSetuRoutes(app: Hono) {
|
|
29
|
+
app.get('/v1/setu/balance', async (c) => {
|
|
30
30
|
try {
|
|
31
|
-
const privateKey = await
|
|
31
|
+
const privateKey = await getSetuPrivateKey();
|
|
32
32
|
if (!privateKey) {
|
|
33
|
-
return c.json({ error: '
|
|
33
|
+
return c.json({ error: 'Setu wallet not configured' }, 401);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const balance = await
|
|
36
|
+
const balance = await fetchSetuBalance({ privateKey });
|
|
37
37
|
if (!balance) {
|
|
38
|
-
return c.json({ error: 'Failed to fetch balance from
|
|
38
|
+
return c.json({ error: 'Failed to fetch balance from Setu' }, 502);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return c.json(balance);
|
|
42
42
|
} catch (error) {
|
|
43
|
-
logger.error('Failed to fetch
|
|
43
|
+
logger.error('Failed to fetch Setu balance', error);
|
|
44
44
|
const errorResponse = serializeError(error);
|
|
45
45
|
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
app.get('/v1/
|
|
49
|
+
app.get('/v1/setu/wallet', async (c) => {
|
|
50
50
|
try {
|
|
51
|
-
const privateKey = await
|
|
51
|
+
const privateKey = await getSetuPrivateKey();
|
|
52
52
|
if (!privateKey) {
|
|
53
53
|
return c.json(
|
|
54
|
-
{ error: '
|
|
54
|
+
{ error: 'Setu wallet not configured', configured: false },
|
|
55
55
|
200,
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -66,17 +66,17 @@ export function registerSolforgeRoutes(app: Hono) {
|
|
|
66
66
|
publicKey,
|
|
67
67
|
});
|
|
68
68
|
} catch (error) {
|
|
69
|
-
logger.error('Failed to get
|
|
69
|
+
logger.error('Failed to get Setu wallet info', error);
|
|
70
70
|
const errorResponse = serializeError(error);
|
|
71
71
|
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
app.get('/v1/
|
|
75
|
+
app.get('/v1/setu/usdc-balance', async (c) => {
|
|
76
76
|
try {
|
|
77
|
-
const privateKey = await
|
|
77
|
+
const privateKey = await getSetuPrivateKey();
|
|
78
78
|
if (!privateKey) {
|
|
79
|
-
return c.json({ error: '
|
|
79
|
+
return c.json({ error: 'Setu wallet not configured' }, 401);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const network =
|
|
@@ -171,6 +171,9 @@ export async function dispatchAssistantMessage(
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// Read tool approval mode from config
|
|
175
|
+
const toolApprovalMode = cfg.defaults.toolApproval ?? 'auto';
|
|
176
|
+
|
|
174
177
|
enqueueAssistantRun(
|
|
175
178
|
{
|
|
176
179
|
sessionId,
|
|
@@ -184,6 +187,7 @@ export async function dispatchAssistantMessage(
|
|
|
184
187
|
reasoningText,
|
|
185
188
|
isCompactCommand: isCompact,
|
|
186
189
|
compactionContext,
|
|
190
|
+
toolApprovalMode,
|
|
187
191
|
},
|
|
188
192
|
runSessionLoop,
|
|
189
193
|
);
|
|
@@ -3,9 +3,10 @@ import { getAnthropicInstance } from './anthropic.ts';
|
|
|
3
3
|
import { resolveOpenAIModel } from './openai.ts';
|
|
4
4
|
import { resolveGoogleModel } from './google.ts';
|
|
5
5
|
import { resolveOpenRouterModel } from './openrouter.ts';
|
|
6
|
-
import {
|
|
6
|
+
import { resolveSetuModel } from './setu.ts';
|
|
7
7
|
import { getZaiInstance, getZaiCodingInstance } from './zai.ts';
|
|
8
8
|
import { resolveOpencodeModel } from './opencode.ts';
|
|
9
|
+
import { getMoonshotInstance } from './moonshot.ts';
|
|
9
10
|
|
|
10
11
|
export type ProviderName = ProviderId;
|
|
11
12
|
|
|
@@ -33,8 +34,8 @@ export async function resolveModel(
|
|
|
33
34
|
if (provider === 'opencode') {
|
|
34
35
|
return resolveOpencodeModel(model, cfg);
|
|
35
36
|
}
|
|
36
|
-
if (provider === '
|
|
37
|
-
return
|
|
37
|
+
if (provider === 'setu') {
|
|
38
|
+
return resolveSetuModel(model, options?.sessionId);
|
|
38
39
|
}
|
|
39
40
|
if (provider === 'zai') {
|
|
40
41
|
return getZaiInstance(cfg, model);
|
|
@@ -42,5 +43,8 @@ export async function resolveModel(
|
|
|
42
43
|
if (provider === 'zai-coding') {
|
|
43
44
|
return getZaiCodingInstance(cfg, model);
|
|
44
45
|
}
|
|
46
|
+
if (provider === 'moonshot') {
|
|
47
|
+
return getMoonshotInstance(cfg, model);
|
|
48
|
+
}
|
|
45
49
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
46
50
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AGIConfig } from '@agi-cli/sdk';
|
|
2
|
+
import { getAuth, createMoonshotModel } from '@agi-cli/sdk';
|
|
3
|
+
|
|
4
|
+
export async function getMoonshotInstance(cfg: AGIConfig, model: string) {
|
|
5
|
+
const auth = await getAuth('moonshot', cfg.projectRoot);
|
|
6
|
+
const apiKey = auth?.type === 'api' ? auth.key : undefined;
|
|
7
|
+
return createMoonshotModel(model, { apiKey });
|
|
8
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
createSetuModel,
|
|
3
3
|
catalog,
|
|
4
|
-
type
|
|
4
|
+
type SetuPaymentCallbacks,
|
|
5
5
|
} from '@agi-cli/sdk';
|
|
6
6
|
import { publish } from '../../events/bus.ts';
|
|
7
7
|
|
|
8
8
|
function getProviderNpm(model: string): string | undefined {
|
|
9
|
-
const entry = catalog.
|
|
9
|
+
const entry = catalog.setu?.models?.find((m) => m.id === model);
|
|
10
10
|
return entry?.provider?.npm;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function
|
|
14
|
-
const privateKey = process.env.
|
|
13
|
+
export function resolveSetuModel(model: string, sessionId?: string) {
|
|
14
|
+
const privateKey = process.env.SETU_PRIVATE_KEY ?? '';
|
|
15
15
|
if (!privateKey) {
|
|
16
16
|
throw new Error(
|
|
17
|
-
'
|
|
17
|
+
'Setu provider requires SETU_PRIVATE_KEY (base58 Solana secret).',
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
-
const baseURL = process.env.
|
|
21
|
-
const rpcURL = process.env.
|
|
20
|
+
const baseURL = process.env.SETU_BASE_URL;
|
|
21
|
+
const rpcURL = process.env.SETU_SOLANA_RPC_URL;
|
|
22
22
|
|
|
23
|
-
const callbacks:
|
|
23
|
+
const callbacks: SetuPaymentCallbacks = sessionId
|
|
24
24
|
? {
|
|
25
25
|
onPaymentRequired: (amountUsd) => {
|
|
26
26
|
publish({
|
|
27
|
-
type: '
|
|
27
|
+
type: 'setu.payment.required',
|
|
28
28
|
sessionId,
|
|
29
29
|
payload: { amountUsd },
|
|
30
30
|
});
|
|
31
31
|
},
|
|
32
32
|
onPaymentSigning: () => {
|
|
33
33
|
publish({
|
|
34
|
-
type: '
|
|
34
|
+
type: 'setu.payment.signing',
|
|
35
35
|
sessionId,
|
|
36
36
|
payload: {},
|
|
37
37
|
});
|
|
38
38
|
},
|
|
39
39
|
onPaymentComplete: (data) => {
|
|
40
40
|
publish({
|
|
41
|
-
type: '
|
|
41
|
+
type: 'setu.payment.complete',
|
|
42
42
|
sessionId,
|
|
43
43
|
payload: data,
|
|
44
44
|
});
|
|
45
45
|
},
|
|
46
46
|
onPaymentError: (error) => {
|
|
47
47
|
publish({
|
|
48
|
-
type: '
|
|
48
|
+
type: 'setu.payment.error',
|
|
49
49
|
sessionId,
|
|
50
50
|
payload: { error },
|
|
51
51
|
});
|
|
@@ -55,7 +55,7 @@ export function resolveSolforgeModel(model: string, sessionId?: string) {
|
|
|
55
55
|
|
|
56
56
|
const providerNpm = getProviderNpm(model);
|
|
57
57
|
|
|
58
|
-
return
|
|
58
|
+
return createSetuModel(
|
|
59
59
|
model,
|
|
60
60
|
{ privateKey },
|
|
61
61
|
{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ProviderName } from '../provider/index.ts';
|
|
2
2
|
import { publish } from '../../events/bus.ts';
|
|
3
|
+
import type { ToolApprovalMode } from '../tools/approval.ts';
|
|
3
4
|
|
|
4
5
|
export type RunOpts = {
|
|
5
6
|
sessionId: string;
|
|
@@ -14,6 +15,7 @@ export type RunOpts = {
|
|
|
14
15
|
abortSignal?: AbortSignal;
|
|
15
16
|
isCompactCommand?: boolean;
|
|
16
17
|
compactionContext?: string;
|
|
18
|
+
toolApprovalMode?: ToolApprovalMode;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export type QueuedMessage = {
|
|
@@ -47,7 +47,7 @@ export function createStepFinishHandler(
|
|
|
47
47
|
try {
|
|
48
48
|
await updateSessionTokensIncrementalFn(
|
|
49
49
|
step.usage,
|
|
50
|
-
step.
|
|
50
|
+
step.providerMetadata,
|
|
51
51
|
opts,
|
|
52
52
|
db,
|
|
53
53
|
);
|
|
@@ -56,7 +56,7 @@ export function createStepFinishHandler(
|
|
|
56
56
|
try {
|
|
57
57
|
await updateMessageTokensIncrementalFn(
|
|
58
58
|
step.usage,
|
|
59
|
-
step.
|
|
59
|
+
step.providerMetadata,
|
|
60
60
|
opts,
|
|
61
61
|
db,
|
|
62
62
|
);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { publish } from '../../events/bus.ts';
|
|
2
|
+
|
|
3
|
+
export type ToolApprovalMode = 'auto' | 'dangerous' | 'all';
|
|
4
|
+
|
|
5
|
+
export const DANGEROUS_TOOLS = new Set([
|
|
6
|
+
'bash',
|
|
7
|
+
'write',
|
|
8
|
+
'apply_patch',
|
|
9
|
+
'terminal',
|
|
10
|
+
'edit',
|
|
11
|
+
'git_commit',
|
|
12
|
+
'git_push',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export const SAFE_TOOLS = new Set([
|
|
16
|
+
'finish',
|
|
17
|
+
'progress_update',
|
|
18
|
+
'update_todos',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export interface PendingApproval {
|
|
22
|
+
callId: string;
|
|
23
|
+
toolName: string;
|
|
24
|
+
args: unknown;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
messageId: string;
|
|
27
|
+
resolve: (approved: boolean) => void;
|
|
28
|
+
createdAt: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const pendingApprovals = new Map<string, PendingApproval>();
|
|
32
|
+
|
|
33
|
+
export function requiresApproval(
|
|
34
|
+
toolName: string,
|
|
35
|
+
mode: ToolApprovalMode,
|
|
36
|
+
): boolean {
|
|
37
|
+
if (SAFE_TOOLS.has(toolName)) return false;
|
|
38
|
+
if (mode === 'auto') return false;
|
|
39
|
+
if (mode === 'all') return true;
|
|
40
|
+
if (mode === 'dangerous') return DANGEROUS_TOOLS.has(toolName);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function requestApproval(
|
|
45
|
+
sessionId: string,
|
|
46
|
+
messageId: string,
|
|
47
|
+
callId: string,
|
|
48
|
+
toolName: string,
|
|
49
|
+
args: unknown,
|
|
50
|
+
timeoutMs = 120000,
|
|
51
|
+
): Promise<boolean> {
|
|
52
|
+
console.log('[approval] requestApproval called', {
|
|
53
|
+
sessionId,
|
|
54
|
+
messageId,
|
|
55
|
+
callId,
|
|
56
|
+
toolName,
|
|
57
|
+
});
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const approval: PendingApproval = {
|
|
60
|
+
callId,
|
|
61
|
+
toolName,
|
|
62
|
+
args,
|
|
63
|
+
sessionId,
|
|
64
|
+
messageId,
|
|
65
|
+
resolve,
|
|
66
|
+
createdAt: Date.now(),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
pendingApprovals.set(callId, approval);
|
|
70
|
+
console.log(
|
|
71
|
+
'[approval] Added to pendingApprovals, count:',
|
|
72
|
+
pendingApprovals.size,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
publish({
|
|
76
|
+
type: 'tool.approval.required',
|
|
77
|
+
sessionId,
|
|
78
|
+
payload: {
|
|
79
|
+
callId,
|
|
80
|
+
toolName,
|
|
81
|
+
args,
|
|
82
|
+
messageId,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
if (pendingApprovals.has(callId)) {
|
|
88
|
+
pendingApprovals.delete(callId);
|
|
89
|
+
resolve(false);
|
|
90
|
+
publish({
|
|
91
|
+
type: 'tool.approval.resolved',
|
|
92
|
+
sessionId,
|
|
93
|
+
payload: {
|
|
94
|
+
callId,
|
|
95
|
+
toolName,
|
|
96
|
+
approved: false,
|
|
97
|
+
reason: 'timeout',
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}, timeoutMs);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function resolveApproval(
|
|
106
|
+
callId: string,
|
|
107
|
+
approved: boolean,
|
|
108
|
+
): { ok: boolean; error?: string } {
|
|
109
|
+
console.log('[approval] resolveApproval called', {
|
|
110
|
+
callId,
|
|
111
|
+
approved,
|
|
112
|
+
pendingCount: pendingApprovals.size,
|
|
113
|
+
pendingIds: [...pendingApprovals.keys()],
|
|
114
|
+
});
|
|
115
|
+
const approval = pendingApprovals.get(callId);
|
|
116
|
+
if (!approval) {
|
|
117
|
+
console.log('[approval] No pending approval found for callId:', callId);
|
|
118
|
+
return { ok: false, error: 'No pending approval found for this callId' };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pendingApprovals.delete(callId);
|
|
122
|
+
approval.resolve(approved);
|
|
123
|
+
|
|
124
|
+
publish({
|
|
125
|
+
type: 'tool.approval.resolved',
|
|
126
|
+
sessionId: approval.sessionId,
|
|
127
|
+
payload: {
|
|
128
|
+
callId,
|
|
129
|
+
toolName: approval.toolName,
|
|
130
|
+
approved,
|
|
131
|
+
reason: approved ? 'user_approved' : 'user_rejected',
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return { ok: true };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getPendingApproval(
|
|
139
|
+
callId: string,
|
|
140
|
+
): PendingApproval | undefined {
|
|
141
|
+
return pendingApprovals.get(callId);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function updateApprovalArgs(callId: string, args: unknown): boolean {
|
|
145
|
+
const approval = pendingApprovals.get(callId);
|
|
146
|
+
if (!approval) return false;
|
|
147
|
+
|
|
148
|
+
approval.args = args;
|
|
149
|
+
|
|
150
|
+
publish({
|
|
151
|
+
type: 'tool.approval.updated',
|
|
152
|
+
sessionId: approval.sessionId,
|
|
153
|
+
payload: {
|
|
154
|
+
callId,
|
|
155
|
+
toolName: approval.toolName,
|
|
156
|
+
args,
|
|
157
|
+
messageId: approval.messageId,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function getPendingApprovalsForSession(
|
|
165
|
+
sessionId: string,
|
|
166
|
+
): PendingApproval[] {
|
|
167
|
+
return Array.from(pendingApprovals.values()).filter(
|
|
168
|
+
(a) => a.sessionId === sessionId,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function clearPendingApprovalsForSession(sessionId: string): void {
|
|
173
|
+
for (const [callId, approval] of pendingApprovals) {
|
|
174
|
+
if (approval.sessionId === sessionId) {
|
|
175
|
+
approval.resolve(false);
|
|
176
|
+
pendingApprovals.delete(callId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eq } from 'drizzle-orm';
|
|
2
2
|
import type { DB } from '@agi-cli/database';
|
|
3
3
|
import { messageParts } from '@agi-cli/database/schema';
|
|
4
|
+
import type { ToolApprovalMode } from './approval.ts';
|
|
4
5
|
import { publish } from '../../events/bus.ts';
|
|
5
6
|
|
|
6
7
|
export type StepExecutionState = {
|
|
@@ -24,6 +25,7 @@ export type ToolAdapterContext = {
|
|
|
24
25
|
stepExecution?: {
|
|
25
26
|
states: Map<number, StepExecutionState>;
|
|
26
27
|
};
|
|
28
|
+
toolApprovalMode?: ToolApprovalMode;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export function extractFinishText(input: unknown): string | undefined {
|
|
@@ -32,6 +32,7 @@ export async function setupToolContext(
|
|
|
32
32
|
model: opts.model,
|
|
33
33
|
projectRoot: opts.projectRoot,
|
|
34
34
|
stepExecution: { states: new Map() },
|
|
35
|
+
toolApprovalMode: opts.toolApprovalMode,
|
|
35
36
|
onFirstToolCall: () => {
|
|
36
37
|
if (firstToolSeen) return;
|
|
37
38
|
firstToolSeen = true;
|
package/src/tools/adapter.ts
CHANGED
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
toClaudeCodeName,
|
|
14
14
|
requiresClaudeCodeNaming,
|
|
15
15
|
} from '../runtime/tools/mapping.ts';
|
|
16
|
+
import {
|
|
17
|
+
requiresApproval,
|
|
18
|
+
requestApproval,
|
|
19
|
+
} from '../runtime/tools/approval.ts';
|
|
16
20
|
|
|
17
21
|
export type { ToolAdapterContext } from '../runtime/tools/context.ts';
|
|
18
22
|
|
|
@@ -33,6 +37,7 @@ type PendingCallMeta = {
|
|
|
33
37
|
startTs: number;
|
|
34
38
|
stepIndex?: number;
|
|
35
39
|
args?: unknown;
|
|
40
|
+
approvalPromise?: Promise<boolean>;
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
function getPendingQueue(
|
|
@@ -294,6 +299,19 @@ export function adaptTools(
|
|
|
294
299
|
toolCallId: callId,
|
|
295
300
|
});
|
|
296
301
|
} catch {}
|
|
302
|
+
// Start approval request with full args
|
|
303
|
+
if (
|
|
304
|
+
ctx.toolApprovalMode &&
|
|
305
|
+
requiresApproval(name, ctx.toolApprovalMode)
|
|
306
|
+
) {
|
|
307
|
+
meta.approvalPromise = requestApproval(
|
|
308
|
+
ctx.sessionId,
|
|
309
|
+
ctx.messageId,
|
|
310
|
+
callId,
|
|
311
|
+
name,
|
|
312
|
+
args,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
297
315
|
if (typeof base.onInputAvailable === 'function') {
|
|
298
316
|
// biome-ignore lint/suspicious/noExplicitAny: AI SDK types are complex
|
|
299
317
|
await base.onInputAvailable(options as any);
|
|
@@ -324,6 +342,16 @@ export function adaptTools(
|
|
|
324
342
|
|
|
325
343
|
const executeWithGuards = async (): Promise<ToolExecuteReturn> => {
|
|
326
344
|
try {
|
|
345
|
+
// Await approval if it was requested in onInputAvailable
|
|
346
|
+
if (meta?.approvalPromise) {
|
|
347
|
+
const approved = await meta.approvalPromise;
|
|
348
|
+
if (!approved) {
|
|
349
|
+
return {
|
|
350
|
+
ok: false,
|
|
351
|
+
error: 'Tool execution rejected by user',
|
|
352
|
+
} as ToolExecuteReturn;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
327
355
|
// Handle session-relative paths and cwd tools
|
|
328
356
|
let res: ToolExecuteReturn | { cwd: string } | null | undefined;
|
|
329
357
|
const cwd = getCwd(ctx.sessionId);
|
|
@@ -136,12 +136,12 @@ export function buildGetParentSessionTool(
|
|
|
136
136
|
try {
|
|
137
137
|
const parsed = JSON.parse(part.content);
|
|
138
138
|
if (parsed?.text) {
|
|
139
|
-
textContent += parsed.text
|
|
139
|
+
textContent += `${parsed.text}\n`;
|
|
140
140
|
} else {
|
|
141
|
-
textContent += part.content
|
|
141
|
+
textContent += `${part.content}\n`;
|
|
142
142
|
}
|
|
143
143
|
} catch {
|
|
144
|
-
textContent += part.content
|
|
144
|
+
textContent += `${part.content}\n`;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
if (part.type === 'tool_call' && part.toolName) {
|
|
@@ -2,7 +2,7 @@ import { tool } from 'ai';
|
|
|
2
2
|
import { z } from 'zod/v3';
|
|
3
3
|
import { getDb } from '@agi-cli/database';
|
|
4
4
|
import { sessions, messages, messageParts } from '@agi-cli/database/schema';
|
|
5
|
-
import { eq, desc, asc, gte, lte, and,
|
|
5
|
+
import { eq, desc, asc, gte, lte, and, count, sql } from 'drizzle-orm';
|
|
6
6
|
|
|
7
7
|
const inputSchema = z.object({
|
|
8
8
|
sessionId: z.string().optional().describe('Filter by specific session ID'),
|
|
@@ -47,7 +47,7 @@ export function buildQueryMessagesTool(projectRoot: string) {
|
|
|
47
47
|
if (input.sessionId) {
|
|
48
48
|
conditions.push(eq(messages.sessionId, input.sessionId));
|
|
49
49
|
} else {
|
|
50
|
-
const
|
|
50
|
+
const _projectSessions = db
|
|
51
51
|
.select({ id: sessions.id })
|
|
52
52
|
.from(sessions)
|
|
53
53
|
.where(eq(sessions.projectPath, projectRoot));
|
|
@@ -2,7 +2,7 @@ import { tool } from 'ai';
|
|
|
2
2
|
import { z } from 'zod/v3';
|
|
3
3
|
import { getDb } from '@agi-cli/database';
|
|
4
4
|
import { sessions, messages } from '@agi-cli/database/schema';
|
|
5
|
-
import { eq, desc, asc, gte, lte, and,
|
|
5
|
+
import { eq, desc, asc, gte, lte, and, count } from 'drizzle-orm';
|
|
6
6
|
|
|
7
7
|
const inputSchema = z.object({
|
|
8
8
|
limit: z
|
|
@@ -2,7 +2,7 @@ import { tool } from 'ai';
|
|
|
2
2
|
import { z } from 'zod/v3';
|
|
3
3
|
import { getDb } from '@agi-cli/database';
|
|
4
4
|
import { sessions, messages, messageParts } from '@agi-cli/database/schema';
|
|
5
|
-
import { eq,
|
|
5
|
+
import { eq, like, and } from 'drizzle-orm';
|
|
6
6
|
|
|
7
7
|
const inputSchema = z.object({
|
|
8
8
|
query: z.string().min(1).describe('Search term to find in message content'),
|