@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agi-cli/server",
3
- "version": "0.1.66",
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.66",
33
- "@agi-cli/database": "0.1.66",
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 { AskServiceError, handleAskRequest } from '../runtime/ask-service.ts';
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
- if (err instanceof AskServiceError) {
108
- return c.json({ error: err.message }, err.status as never);
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
  }
@@ -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
- const cwd = process.cwd();
69
- const dirName = basename(cwd);
70
- return c.json({
71
- cwd,
72
- dirName,
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 customAgentsPath = join(cfg.projectRoot, '.agi', 'agents');
92
- const files = await readdir(customAgentsPath).catch(() => []);
93
- customAgents = files
94
- .filter((f) => f.endsWith('.txt'))
95
- .map((f) => f.replace('.txt', ''));
96
- } catch {}
97
-
98
- // Agents: Embedded custom agents + file-based agents
99
- const fileAgents = [...builtInAgents, ...customAgents];
100
- const embeddedAgents = embeddedConfig?.agents
101
- ? Object.keys(embeddedConfig.agents)
102
- : [];
103
- const allAgents = Array.from(new Set([...embeddedAgents, ...fileAgents]));
104
-
105
- // Providers: Check both embedded and file-based auth
106
- const authorizedProviders = await getAuthorizedProviders(
107
- embeddedConfig,
108
- cfg,
109
- );
110
-
111
- // Defaults: Embedded overrides file config
112
- const defaults = {
113
- agent: getDefault(
114
- embeddedConfig?.agent,
115
- embeddedConfig?.defaults?.agent,
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
- : ['general', 'build', 'plan'];
147
- return c.json({
148
- agents,
149
- default: getDefault(
150
- embeddedConfig.agent,
151
- embeddedConfig.defaults?.agent,
152
- 'general',
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
- const projectRoot = c.req.query('project') || process.cwd();
158
- const cfg = await loadConfig(projectRoot);
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 customAgentsPath = join(cfg.projectRoot, '.agi', 'agents');
164
- const files = await readdir(customAgentsPath).catch(() => []);
165
- const customAgents = files
166
- .filter((f) => f.endsWith('.txt'))
167
- .map((f) => f.replace('.txt', ''));
168
-
169
- return c.json({
170
- agents: [...builtInAgents, ...customAgents],
171
- default: cfg.defaults.agent,
172
- });
173
- } catch {
174
- return c.json({
175
- agents: builtInAgents,
176
- default: cfg.defaults.agent,
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
- const embeddedConfig = c.get('embeddedConfig') as
184
- | EmbeddedAppConfig
185
- | undefined;
186
-
187
- if (embeddedConfig) {
188
- const providers = embeddedConfig.auth
189
- ? (Object.keys(embeddedConfig.auth) as ProviderId[])
190
- : [embeddedConfig.provider];
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: getDefault(
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
- const embeddedConfig = c.get('embeddedConfig') as
216
- | EmbeddedAppConfig
217
- | undefined;
218
- const provider = c.req.param('provider') as ProviderId;
219
-
220
- // Always load file config for fallback auth check
221
- const projectRoot = c.req.query('project') || process.cwd();
222
- const cfg = await loadConfig(projectRoot);
223
-
224
- // Check if provider is authorized (hybrid: embedded OR file-based)
225
- const authorized = await isProviderAuthorizedHybrid(
226
- embeddedConfig,
227
- cfg,
228
- provider,
229
- );
230
-
231
- if (!authorized) {
232
- return c.json({ error: 'Provider not authorized' }, 403);
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
- const providerCatalog = catalog[provider];
236
- if (!providerCatalog) {
237
- return c.json({ error: 'Provider not found' }, 404);
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
  }