@agi-cli/server 0.1.66 → 0.1.68
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/index.ts +9 -0
- package/src/routes/ask.ts +6 -6
- package/src/routes/config.ts +196 -159
- package/src/routes/git.ts +160 -47
- package/src/routes/session-messages.ts +114 -95
- package/src/routes/sessions.ts +5 -2
- package/src/runtime/api-error.ts +191 -0
- package/src/runtime/debug-state.ts +124 -0
- package/src/runtime/debug.ts +43 -30
- package/src/runtime/logger.ts +204 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agi-cli/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.68",
|
|
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.68",
|
|
33
|
+
"@agi-cli/database": "0.1.68",
|
|
34
34
|
"drizzle-orm": "^0.44.5",
|
|
35
35
|
"hono": "^4.9.9",
|
|
36
36
|
"zod": "^4.1.8"
|
package/src/index.ts
CHANGED
|
@@ -230,3 +230,12 @@ export {
|
|
|
230
230
|
type BuiltinAgent,
|
|
231
231
|
type BuiltinTool,
|
|
232
232
|
} from './presets.ts';
|
|
233
|
+
|
|
234
|
+
// Export debug state management
|
|
235
|
+
export {
|
|
236
|
+
setDebugEnabled,
|
|
237
|
+
isDebugEnabled,
|
|
238
|
+
setTraceEnabled,
|
|
239
|
+
isTraceEnabled,
|
|
240
|
+
} from './runtime/debug-state.ts';
|
|
241
|
+
export { logger } from './runtime/logger.ts';
|
package/src/routes/ask.ts
CHANGED
|
@@ -4,8 +4,10 @@ import type {
|
|
|
4
4
|
InjectableConfig,
|
|
5
5
|
InjectableCredentials,
|
|
6
6
|
} from '../runtime/ask-service.ts';
|
|
7
|
-
import {
|
|
7
|
+
import { handleAskRequest } from '../runtime/ask-service.ts';
|
|
8
8
|
import type { EmbeddedAppConfig } from '../index.ts';
|
|
9
|
+
import { serializeError } from '../runtime/api-error.ts';
|
|
10
|
+
import { logger } from '../runtime/logger.ts';
|
|
9
11
|
|
|
10
12
|
export function registerAskRoutes(app: Hono) {
|
|
11
13
|
app.post('/v1/ask', async (c) => {
|
|
@@ -104,11 +106,9 @@ export function registerAskRoutes(app: Hono) {
|
|
|
104
106
|
const response = await handleAskRequest(request);
|
|
105
107
|
return c.json(response, 202);
|
|
106
108
|
} catch (err) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
111
|
-
return c.json({ error: message }, 400);
|
|
109
|
+
logger.error('Ask request failed', err);
|
|
110
|
+
const errorResponse = serializeError(err);
|
|
111
|
+
return c.json(errorResponse, errorResponse.error.status || 400);
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
}
|
package/src/routes/config.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { readdir } from 'node:fs/promises';
|
|
|
5
5
|
import { join, basename } from 'node:path';
|
|
6
6
|
import type { EmbeddedAppConfig } from '../index.ts';
|
|
7
7
|
import type { AGIConfig } from '@agi-cli/sdk';
|
|
8
|
+
import { logger } from '../runtime/logger.ts';
|
|
9
|
+
import { serializeError } from '../runtime/api-error.ts';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Check if a provider is authorized in either embedded config or file-based config
|
|
@@ -65,190 +67,225 @@ function getDefault<T>(
|
|
|
65
67
|
export function registerConfigRoutes(app: Hono) {
|
|
66
68
|
// Get working directory info
|
|
67
69
|
app.get('/v1/config/cwd', (c) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
try {
|
|
71
|
+
const cwd = process.cwd();
|
|
72
|
+
const dirName = basename(cwd);
|
|
73
|
+
return c.json({
|
|
74
|
+
cwd,
|
|
75
|
+
dirName,
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
logger.error('Failed to get current working directory', error);
|
|
79
|
+
const errorResponse = serializeError(error);
|
|
80
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
81
|
+
}
|
|
74
82
|
});
|
|
75
83
|
|
|
76
84
|
// Get full config (agents, providers, models, defaults)
|
|
77
85
|
app.get('/v1/config', async (c) => {
|
|
78
|
-
const projectRoot = c.req.query('project') || process.cwd();
|
|
79
|
-
const embeddedConfig = c.get('embeddedConfig') as
|
|
80
|
-
| EmbeddedAppConfig
|
|
81
|
-
| undefined;
|
|
82
|
-
|
|
83
|
-
// Always load file config as base/fallback
|
|
84
|
-
const cfg = await loadConfig(projectRoot);
|
|
85
|
-
|
|
86
|
-
// Hybrid mode: Merge embedded config with file config
|
|
87
|
-
const builtInAgents = ['general', 'build', 'plan'];
|
|
88
|
-
let customAgents: string[] = [];
|
|
89
|
-
|
|
90
86
|
try {
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
cfg.defaults.agent,
|
|
117
|
-
),
|
|
118
|
-
provider: getDefault(
|
|
119
|
-
embeddedConfig?.provider,
|
|
120
|
-
embeddedConfig?.defaults?.provider,
|
|
121
|
-
cfg.defaults.provider,
|
|
122
|
-
),
|
|
123
|
-
model: getDefault(
|
|
124
|
-
embeddedConfig?.model,
|
|
125
|
-
embeddedConfig?.defaults?.model,
|
|
126
|
-
cfg.defaults.model,
|
|
127
|
-
),
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
return c.json({
|
|
131
|
-
agents: allAgents,
|
|
132
|
-
providers: authorizedProviders,
|
|
133
|
-
defaults,
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Get available agents
|
|
138
|
-
app.get('/v1/config/agents', async (c) => {
|
|
139
|
-
const embeddedConfig = c.get('embeddedConfig') as
|
|
140
|
-
| EmbeddedAppConfig
|
|
141
|
-
| undefined;
|
|
142
|
-
|
|
143
|
-
if (embeddedConfig) {
|
|
144
|
-
const agents = embeddedConfig.agents
|
|
87
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
88
|
+
const embeddedConfig = c.get('embeddedConfig') as
|
|
89
|
+
| EmbeddedAppConfig
|
|
90
|
+
| undefined;
|
|
91
|
+
|
|
92
|
+
// Always load file config as base/fallback
|
|
93
|
+
const cfg = await loadConfig(projectRoot);
|
|
94
|
+
|
|
95
|
+
// Hybrid mode: Merge embedded config with file config
|
|
96
|
+
const builtInAgents = ['general', 'build', 'plan'];
|
|
97
|
+
let customAgents: string[] = [];
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const customAgentsPath = join(cfg.projectRoot, '.agi', 'agents');
|
|
101
|
+
const files = await readdir(customAgentsPath).catch(() => []);
|
|
102
|
+
customAgents = files
|
|
103
|
+
.filter((f) => f.endsWith('.txt'))
|
|
104
|
+
.map((f) => f.replace('.txt', ''));
|
|
105
|
+
} catch (err) {
|
|
106
|
+
logger.debug('Failed to read custom agents directory', err);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Agents: Embedded custom agents + file-based agents
|
|
110
|
+
const fileAgents = [...builtInAgents, ...customAgents];
|
|
111
|
+
const embeddedAgents = embeddedConfig?.agents
|
|
145
112
|
? Object.keys(embeddedConfig.agents)
|
|
146
|
-
: [
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
113
|
+
: [];
|
|
114
|
+
const allAgents = Array.from(new Set([...embeddedAgents, ...fileAgents]));
|
|
115
|
+
|
|
116
|
+
// Providers: Check both embedded and file-based auth
|
|
117
|
+
const authorizedProviders = await getAuthorizedProviders(
|
|
118
|
+
embeddedConfig,
|
|
119
|
+
cfg,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Defaults: Embedded overrides file config
|
|
123
|
+
const defaults = {
|
|
124
|
+
agent: getDefault(
|
|
125
|
+
embeddedConfig?.agent,
|
|
126
|
+
embeddedConfig?.defaults?.agent,
|
|
127
|
+
cfg.defaults.agent,
|
|
153
128
|
),
|
|
129
|
+
provider: getDefault(
|
|
130
|
+
embeddedConfig?.provider,
|
|
131
|
+
embeddedConfig?.defaults?.provider,
|
|
132
|
+
cfg.defaults.provider,
|
|
133
|
+
),
|
|
134
|
+
model: getDefault(
|
|
135
|
+
embeddedConfig?.model,
|
|
136
|
+
embeddedConfig?.defaults?.model,
|
|
137
|
+
cfg.defaults.model,
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return c.json({
|
|
142
|
+
agents: allAgents,
|
|
143
|
+
providers: authorizedProviders,
|
|
144
|
+
defaults,
|
|
154
145
|
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.error('Failed to load config', error);
|
|
148
|
+
const errorResponse = serializeError(error);
|
|
149
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
155
150
|
}
|
|
151
|
+
});
|
|
156
152
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const builtInAgents = ['general', 'build', 'plan'];
|
|
161
|
-
|
|
153
|
+
// Get available agents
|
|
154
|
+
app.get('/v1/config/agents', async (c) => {
|
|
162
155
|
try {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
156
|
+
const embeddedConfig = c.get('embeddedConfig') as
|
|
157
|
+
| EmbeddedAppConfig
|
|
158
|
+
| undefined;
|
|
159
|
+
|
|
160
|
+
if (embeddedConfig) {
|
|
161
|
+
const agents = embeddedConfig.agents
|
|
162
|
+
? Object.keys(embeddedConfig.agents)
|
|
163
|
+
: ['general', 'build', 'plan'];
|
|
164
|
+
return c.json({
|
|
165
|
+
agents,
|
|
166
|
+
default: getDefault(
|
|
167
|
+
embeddedConfig.agent,
|
|
168
|
+
embeddedConfig.defaults?.agent,
|
|
169
|
+
'general',
|
|
170
|
+
),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
175
|
+
const cfg = await loadConfig(projectRoot);
|
|
176
|
+
|
|
177
|
+
const builtInAgents = ['general', 'build', 'plan'];
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const customAgentsPath = join(cfg.projectRoot, '.agi', 'agents');
|
|
181
|
+
const files = await readdir(customAgentsPath).catch(() => []);
|
|
182
|
+
const customAgents = files
|
|
183
|
+
.filter((f) => f.endsWith('.txt'))
|
|
184
|
+
.map((f) => f.replace('.txt', ''));
|
|
185
|
+
|
|
186
|
+
return c.json({
|
|
187
|
+
agents: [...builtInAgents, ...customAgents],
|
|
188
|
+
default: cfg.defaults.agent,
|
|
189
|
+
});
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logger.debug('Failed to read custom agents directory', err);
|
|
192
|
+
return c.json({
|
|
193
|
+
agents: builtInAgents,
|
|
194
|
+
default: cfg.defaults.agent,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
logger.error('Failed to get agents', error);
|
|
199
|
+
const errorResponse = serializeError(error);
|
|
200
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
178
201
|
}
|
|
179
202
|
});
|
|
180
203
|
|
|
181
204
|
// Get available providers (only authorized ones)
|
|
182
205
|
app.get('/v1/config/providers', async (c) => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
206
|
+
try {
|
|
207
|
+
const embeddedConfig = c.get('embeddedConfig') as
|
|
208
|
+
| EmbeddedAppConfig
|
|
209
|
+
| undefined;
|
|
210
|
+
|
|
211
|
+
if (embeddedConfig) {
|
|
212
|
+
const providers = embeddedConfig.auth
|
|
213
|
+
? (Object.keys(embeddedConfig.auth) as ProviderId[])
|
|
214
|
+
: [embeddedConfig.provider];
|
|
215
|
+
|
|
216
|
+
return c.json({
|
|
217
|
+
providers,
|
|
218
|
+
default: getDefault(
|
|
219
|
+
embeddedConfig.provider,
|
|
220
|
+
embeddedConfig.defaults?.provider,
|
|
221
|
+
undefined,
|
|
222
|
+
),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
227
|
+
const cfg = await loadConfig(projectRoot);
|
|
228
|
+
|
|
229
|
+
const authorizedProviders = await getAuthorizedProviders(undefined, cfg);
|
|
191
230
|
|
|
192
231
|
return c.json({
|
|
193
|
-
providers,
|
|
194
|
-
default:
|
|
195
|
-
embeddedConfig.provider,
|
|
196
|
-
embeddedConfig.defaults?.provider,
|
|
197
|
-
undefined,
|
|
198
|
-
),
|
|
232
|
+
providers: authorizedProviders,
|
|
233
|
+
default: cfg.defaults.provider,
|
|
199
234
|
});
|
|
235
|
+
} catch (error) {
|
|
236
|
+
logger.error('Failed to get providers', error);
|
|
237
|
+
const errorResponse = serializeError(error);
|
|
238
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
200
239
|
}
|
|
201
|
-
|
|
202
|
-
const projectRoot = c.req.query('project') || process.cwd();
|
|
203
|
-
const cfg = await loadConfig(projectRoot);
|
|
204
|
-
|
|
205
|
-
const authorizedProviders = await getAuthorizedProviders(undefined, cfg);
|
|
206
|
-
|
|
207
|
-
return c.json({
|
|
208
|
-
providers: authorizedProviders,
|
|
209
|
-
default: cfg.defaults.provider,
|
|
210
|
-
});
|
|
211
240
|
});
|
|
212
241
|
|
|
213
242
|
// Get available models for a provider
|
|
214
243
|
app.get('/v1/config/providers/:provider/models', async (c) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
244
|
+
try {
|
|
245
|
+
const embeddedConfig = c.get('embeddedConfig') as
|
|
246
|
+
| EmbeddedAppConfig
|
|
247
|
+
| undefined;
|
|
248
|
+
const provider = c.req.param('provider') as ProviderId;
|
|
249
|
+
|
|
250
|
+
// Always load file config for fallback auth check
|
|
251
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
252
|
+
const cfg = await loadConfig(projectRoot);
|
|
253
|
+
|
|
254
|
+
// Check if provider is authorized (hybrid: embedded OR file-based)
|
|
255
|
+
const authorized = await isProviderAuthorizedHybrid(
|
|
256
|
+
embeddedConfig,
|
|
257
|
+
cfg,
|
|
258
|
+
provider,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (!authorized) {
|
|
262
|
+
logger.warn('Provider not authorized', { provider });
|
|
263
|
+
return c.json({ error: 'Provider not authorized' }, 403);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const providerCatalog = catalog[provider];
|
|
267
|
+
if (!providerCatalog) {
|
|
268
|
+
logger.warn('Provider not found in catalog', { provider });
|
|
269
|
+
return c.json({ error: 'Provider not found' }, 404);
|
|
270
|
+
}
|
|
234
271
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
272
|
+
return c.json({
|
|
273
|
+
models: providerCatalog.models.map((m) => ({
|
|
274
|
+
id: m.id,
|
|
275
|
+
label: m.label || m.id,
|
|
276
|
+
toolCall: m.toolCall,
|
|
277
|
+
reasoning: m.reasoning,
|
|
278
|
+
})),
|
|
279
|
+
default: getDefault(
|
|
280
|
+
embeddedConfig?.model,
|
|
281
|
+
embeddedConfig?.defaults?.model,
|
|
282
|
+
cfg.defaults.model,
|
|
283
|
+
),
|
|
284
|
+
});
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger.error('Failed to get provider models', error);
|
|
287
|
+
const errorResponse = serializeError(error);
|
|
288
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
238
289
|
}
|
|
239
|
-
|
|
240
|
-
return c.json({
|
|
241
|
-
models: providerCatalog.models.map((m) => ({
|
|
242
|
-
id: m.id,
|
|
243
|
-
label: m.label || m.id,
|
|
244
|
-
toolCall: m.toolCall,
|
|
245
|
-
reasoning: m.reasoning,
|
|
246
|
-
})),
|
|
247
|
-
default: getDefault(
|
|
248
|
-
embeddedConfig?.model,
|
|
249
|
-
embeddedConfig?.defaults?.model,
|
|
250
|
-
cfg.defaults.model,
|
|
251
|
-
),
|
|
252
|
-
});
|
|
253
290
|
});
|
|
254
291
|
}
|