@agi-cli/server 0.1.67 → 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 +28 -16
- 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
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
ensureProviderEnv,
|
|
10
10
|
} from '@agi-cli/sdk';
|
|
11
11
|
import { dispatchAssistantMessage } from '../runtime/message-service.ts';
|
|
12
|
+
import { logger } from '../runtime/logger.ts';
|
|
13
|
+
import { serializeError } from '../runtime/api-error.ts';
|
|
12
14
|
|
|
13
15
|
type MessagePartRow = typeof messageParts.$inferSelect;
|
|
14
16
|
type SessionRow = typeof sessions.$inferSelect;
|
|
@@ -16,108 +18,125 @@ type SessionRow = typeof sessions.$inferSelect;
|
|
|
16
18
|
export function registerSessionMessagesRoutes(app: Hono) {
|
|
17
19
|
// List messages for a session
|
|
18
20
|
app.get('/v1/sessions/:id/messages', async (c) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
21
|
+
try {
|
|
22
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
23
|
+
const cfg = await loadConfig(projectRoot);
|
|
24
|
+
const db = await getDb(cfg.projectRoot);
|
|
25
|
+
const id = c.req.param('id');
|
|
26
|
+
const rows = await db
|
|
27
|
+
.select()
|
|
28
|
+
.from(messages)
|
|
29
|
+
.where(eq(messages.sessionId, id))
|
|
30
|
+
.orderBy(messages.createdAt);
|
|
31
|
+
const without = c.req.query('without');
|
|
32
|
+
if (without !== 'parts') {
|
|
33
|
+
const ids = rows.map((m) => m.id);
|
|
34
|
+
const parts = ids.length
|
|
35
|
+
? await db
|
|
36
|
+
.select()
|
|
37
|
+
.from(messageParts)
|
|
38
|
+
.where(inArray(messageParts.messageId, ids))
|
|
39
|
+
: [];
|
|
40
|
+
const partsByMsg = new Map<string, MessagePartRow[]>();
|
|
41
|
+
for (const p of parts) {
|
|
42
|
+
const existing = partsByMsg.get(p.messageId);
|
|
43
|
+
if (existing) existing.push(p);
|
|
44
|
+
else partsByMsg.set(p.messageId, [p]);
|
|
45
|
+
}
|
|
46
|
+
const wantParsed = (() => {
|
|
47
|
+
const q = (c.req.query('parsed') || '').toLowerCase();
|
|
48
|
+
return q === '1' || q === 'true' || q === 'yes';
|
|
49
|
+
})();
|
|
50
|
+
function parseContent(raw: string): Record<string, unknown> | string {
|
|
51
|
+
try {
|
|
52
|
+
const v = JSON.parse(String(raw ?? ''));
|
|
53
|
+
if (v && typeof v === 'object' && !Array.isArray(v))
|
|
54
|
+
return v as Record<string, unknown>;
|
|
55
|
+
} catch {}
|
|
56
|
+
return raw;
|
|
57
|
+
}
|
|
58
|
+
const enriched = rows.map((m) => {
|
|
59
|
+
const parts = (partsByMsg.get(m.id) ?? []).sort(
|
|
60
|
+
(a, b) => a.index - b.index,
|
|
61
|
+
);
|
|
62
|
+
const mapped = parts.map((p) => {
|
|
63
|
+
const parsed = parseContent(p.content);
|
|
64
|
+
return wantParsed
|
|
65
|
+
? { ...p, content: parsed }
|
|
66
|
+
: { ...p, contentJson: parsed };
|
|
67
|
+
});
|
|
68
|
+
return { ...m, parts: mapped };
|
|
64
69
|
});
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
return c.json(
|
|
70
|
+
return c.json(enriched);
|
|
71
|
+
}
|
|
72
|
+
return c.json(rows);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger.error('Failed to list session messages', error);
|
|
75
|
+
const errorResponse = serializeError(error);
|
|
76
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
68
77
|
}
|
|
69
|
-
return c.json(rows);
|
|
70
78
|
});
|
|
71
79
|
|
|
72
80
|
// Post a user message and get assistant reply (non-streaming for v0)
|
|
73
81
|
app.post('/v1/sessions/:id/messages', async (c) => {
|
|
74
|
-
const projectRoot = c.req.query('project') || process.cwd();
|
|
75
|
-
const cfg = await loadConfig(projectRoot);
|
|
76
|
-
const db = await getDb(cfg.projectRoot);
|
|
77
|
-
const sessionId = c.req.param('id');
|
|
78
|
-
const body = await c.req.json().catch(() => ({}));
|
|
79
|
-
// Load session to inherit its provider/model/agent by default
|
|
80
|
-
const sessionRows = await db
|
|
81
|
-
.select()
|
|
82
|
-
.from(sessions)
|
|
83
|
-
.where(eq(sessions.id, sessionId));
|
|
84
|
-
if (!sessionRows.length) return c.json({ error: 'Session not found' }, 404);
|
|
85
|
-
const sess: SessionRow = sessionRows[0];
|
|
86
|
-
const provider = body?.provider ?? sess.provider ?? cfg.defaults.provider;
|
|
87
|
-
const modelName = body?.model ?? sess.model ?? cfg.defaults.model;
|
|
88
|
-
const agent = body?.agent ?? sess.agent ?? cfg.defaults.agent;
|
|
89
|
-
const content = body?.content ?? '';
|
|
90
|
-
|
|
91
|
-
// Validate model capabilities if tools are allowed for this agent
|
|
92
|
-
const wantsToolCalls = true; // agent toolset may be non-empty
|
|
93
82
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
83
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
84
|
+
const cfg = await loadConfig(projectRoot);
|
|
85
|
+
const db = await getDb(cfg.projectRoot);
|
|
86
|
+
const sessionId = c.req.param('id');
|
|
87
|
+
const body = await c.req.json().catch(() => ({}));
|
|
88
|
+
// Load session to inherit its provider/model/agent by default
|
|
89
|
+
const sessionRows = await db
|
|
90
|
+
.select()
|
|
91
|
+
.from(sessions)
|
|
92
|
+
.where(eq(sessions.id, sessionId));
|
|
93
|
+
if (!sessionRows.length) {
|
|
94
|
+
logger.warn('Session not found', { sessionId });
|
|
95
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
96
|
+
}
|
|
97
|
+
const sess: SessionRow = sessionRows[0];
|
|
98
|
+
const provider = body?.provider ?? sess.provider ?? cfg.defaults.provider;
|
|
99
|
+
const modelName = body?.model ?? sess.model ?? cfg.defaults.model;
|
|
100
|
+
const agent = body?.agent ?? sess.agent ?? cfg.defaults.agent;
|
|
101
|
+
const content = body?.content ?? '';
|
|
102
|
+
|
|
103
|
+
// Validate model capabilities if tools are allowed for this agent
|
|
104
|
+
const wantsToolCalls = true; // agent toolset may be non-empty
|
|
105
|
+
try {
|
|
106
|
+
validateProviderModel(provider, modelName, { wantsToolCalls });
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logger.error('Model validation failed', err, { provider, modelName });
|
|
109
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
110
|
+
return c.json({ error: message }, 400);
|
|
111
|
+
}
|
|
112
|
+
// Enforce provider auth: only allow providers/models the user authenticated for
|
|
113
|
+
const authorized = await isProviderAuthorized(cfg, provider);
|
|
114
|
+
if (!authorized) {
|
|
115
|
+
logger.warn('Provider not authorized', { provider });
|
|
116
|
+
return c.json(
|
|
117
|
+
{
|
|
118
|
+
error: `Provider ${provider} is not configured. Run \`agi auth login\` to add credentials.`,
|
|
119
|
+
},
|
|
120
|
+
400,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
await ensureProviderEnv(cfg, provider);
|
|
110
124
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
const { assistantMessageId } = await dispatchAssistantMessage({
|
|
126
|
+
cfg,
|
|
127
|
+
db,
|
|
128
|
+
session: sess,
|
|
129
|
+
agent,
|
|
130
|
+
provider,
|
|
131
|
+
model: modelName,
|
|
132
|
+
content,
|
|
133
|
+
oneShot: Boolean(body?.oneShot),
|
|
134
|
+
});
|
|
135
|
+
return c.json({ messageId: assistantMessageId }, 202);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error('Failed to create session message', error);
|
|
138
|
+
const errorResponse = serializeError(error);
|
|
139
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
140
|
+
}
|
|
122
141
|
});
|
|
123
142
|
}
|
package/src/routes/sessions.ts
CHANGED
|
@@ -7,6 +7,8 @@ import type { ProviderId } from '@agi-cli/sdk';
|
|
|
7
7
|
import { isProviderId } from '@agi-cli/sdk';
|
|
8
8
|
import { resolveAgentConfig } from '../runtime/agent-registry.ts';
|
|
9
9
|
import { createSession as createSessionRow } from '../runtime/session-manager.ts';
|
|
10
|
+
import { serializeError } from '../runtime/api-error.ts';
|
|
11
|
+
import { logger } from '../runtime/logger.ts';
|
|
10
12
|
|
|
11
13
|
export function registerSessionsRoutes(app: Hono) {
|
|
12
14
|
// List sessions
|
|
@@ -72,8 +74,9 @@ export function registerSessionsRoutes(app: Hono) {
|
|
|
72
74
|
});
|
|
73
75
|
return c.json(row, 201);
|
|
74
76
|
} catch (err) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
logger.error('Failed to create session', err);
|
|
78
|
+
const errorResponse = serializeError(err);
|
|
79
|
+
return c.json(errorResponse, errorResponse.error.status || 400);
|
|
77
80
|
}
|
|
78
81
|
});
|
|
79
82
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified API error handling
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent error serialization and response formatting
|
|
5
|
+
* across all API endpoints.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { isDebugEnabled } from './debug-state';
|
|
9
|
+
import { toErrorPayload } from './error-handling';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Standard API error response format
|
|
13
|
+
*/
|
|
14
|
+
export type APIErrorResponse = {
|
|
15
|
+
error: {
|
|
16
|
+
message: string;
|
|
17
|
+
type: string;
|
|
18
|
+
code?: string;
|
|
19
|
+
status?: number;
|
|
20
|
+
details?: Record<string, unknown>;
|
|
21
|
+
stack?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Custom API Error class
|
|
27
|
+
*/
|
|
28
|
+
export class APIError extends Error {
|
|
29
|
+
public readonly code?: string;
|
|
30
|
+
public readonly status: number;
|
|
31
|
+
public readonly type: string;
|
|
32
|
+
public readonly details?: Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
message: string,
|
|
36
|
+
options?: {
|
|
37
|
+
code?: string;
|
|
38
|
+
status?: number;
|
|
39
|
+
type?: string;
|
|
40
|
+
details?: Record<string, unknown>;
|
|
41
|
+
cause?: unknown;
|
|
42
|
+
},
|
|
43
|
+
) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'APIError';
|
|
46
|
+
this.code = options?.code;
|
|
47
|
+
this.status = options?.status ?? 500;
|
|
48
|
+
this.type = options?.type ?? 'api_error';
|
|
49
|
+
this.details = options?.details;
|
|
50
|
+
|
|
51
|
+
if (options?.cause) {
|
|
52
|
+
this.cause = options.cause;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Maintain proper stack trace
|
|
56
|
+
if (Error.captureStackTrace) {
|
|
57
|
+
Error.captureStackTrace(this, APIError);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Serialize any error into a consistent API error response
|
|
64
|
+
*
|
|
65
|
+
* @param err - The error to serialize
|
|
66
|
+
* @returns A properly formatted API error response
|
|
67
|
+
*/
|
|
68
|
+
export function serializeError(err: unknown): APIErrorResponse {
|
|
69
|
+
// Use existing error payload logic
|
|
70
|
+
const payload = toErrorPayload(err);
|
|
71
|
+
|
|
72
|
+
// Determine HTTP status code
|
|
73
|
+
let status = 500;
|
|
74
|
+
if (err && typeof err === 'object') {
|
|
75
|
+
const errObj = err as Record<string, unknown>;
|
|
76
|
+
if (typeof errObj.status === 'number') {
|
|
77
|
+
status = errObj.status;
|
|
78
|
+
} else if (typeof errObj.statusCode === 'number') {
|
|
79
|
+
status = errObj.statusCode;
|
|
80
|
+
} else if (
|
|
81
|
+
errObj.details &&
|
|
82
|
+
typeof errObj.details === 'object' &&
|
|
83
|
+
typeof (errObj.details as Record<string, unknown>).statusCode === 'number'
|
|
84
|
+
) {
|
|
85
|
+
status = (errObj.details as Record<string, unknown>).statusCode as number;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle APIError instances
|
|
90
|
+
if (err instanceof APIError) {
|
|
91
|
+
status = err.status;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Extract code if available
|
|
95
|
+
let code: string | undefined;
|
|
96
|
+
if (err && typeof err === 'object') {
|
|
97
|
+
const errObj = err as Record<string, unknown>;
|
|
98
|
+
if (typeof errObj.code === 'string') {
|
|
99
|
+
code = errObj.code;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (err instanceof APIError && err.code) {
|
|
104
|
+
code = err.code;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Build response
|
|
108
|
+
const response: APIErrorResponse = {
|
|
109
|
+
error: {
|
|
110
|
+
message: payload.message || 'An error occurred',
|
|
111
|
+
type: payload.type || 'unknown',
|
|
112
|
+
status,
|
|
113
|
+
...(code ? { code } : {}),
|
|
114
|
+
...(payload.details ? { details: payload.details } : {}),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Include stack trace in debug mode
|
|
119
|
+
if (isDebugEnabled() && err instanceof Error && err.stack) {
|
|
120
|
+
response.error.stack = err.stack;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return response;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create an error response with proper HTTP status code
|
|
128
|
+
*
|
|
129
|
+
* @param err - The error to convert
|
|
130
|
+
* @returns Tuple of [APIErrorResponse, HTTP status code]
|
|
131
|
+
*/
|
|
132
|
+
export function createErrorResponse(err: unknown): [APIErrorResponse, number] {
|
|
133
|
+
const response = serializeError(err);
|
|
134
|
+
return [response, response.error.status ?? 500];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Normalize error to ensure it's an Error instance
|
|
139
|
+
*
|
|
140
|
+
* @param err - The error to normalize
|
|
141
|
+
* @returns An Error instance
|
|
142
|
+
*/
|
|
143
|
+
export function normalizeError(err: unknown): Error {
|
|
144
|
+
if (err instanceof Error) {
|
|
145
|
+
return err;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (typeof err === 'string') {
|
|
149
|
+
return new Error(err);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (err && typeof err === 'object') {
|
|
153
|
+
const errObj = err as Record<string, unknown>;
|
|
154
|
+
if (typeof errObj.message === 'string') {
|
|
155
|
+
return new Error(errObj.message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return new Error('An unknown error occurred');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Extract error message from any error type
|
|
164
|
+
*
|
|
165
|
+
* @param err - The error to extract message from
|
|
166
|
+
* @returns The error message string
|
|
167
|
+
*/
|
|
168
|
+
export function getErrorMessage(err: unknown): string {
|
|
169
|
+
if (typeof err === 'string') {
|
|
170
|
+
return err;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (err instanceof Error) {
|
|
174
|
+
return err.message;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (err && typeof err === 'object') {
|
|
178
|
+
const errObj = err as Record<string, unknown>;
|
|
179
|
+
if (typeof errObj.message === 'string') {
|
|
180
|
+
return errObj.message;
|
|
181
|
+
}
|
|
182
|
+
if (typeof errObj.error === 'string') {
|
|
183
|
+
return errObj.error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return 'An unknown error occurred';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Legacy compatibility - AskServiceError alias
|
|
191
|
+
export { APIError as AskServiceError };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime debug state management
|
|
3
|
+
*
|
|
4
|
+
* Centralizes debug flag state that can be set either via:
|
|
5
|
+
* - Environment variables (AGI_DEBUG, DEBUG_AGI)
|
|
6
|
+
* - Runtime configuration (CLI --debug flag)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
10
|
+
|
|
11
|
+
type DebugState = {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
traceEnabled: boolean;
|
|
14
|
+
runtimeOverride: boolean | null;
|
|
15
|
+
runtimeTraceOverride: boolean | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Global state
|
|
19
|
+
const state: DebugState = {
|
|
20
|
+
enabled: false,
|
|
21
|
+
traceEnabled: false,
|
|
22
|
+
runtimeOverride: null,
|
|
23
|
+
runtimeTraceOverride: null,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if environment variables indicate debug mode
|
|
28
|
+
*/
|
|
29
|
+
function checkEnvDebug(): boolean {
|
|
30
|
+
const sources = [process.env.AGI_DEBUG, process.env.DEBUG_AGI];
|
|
31
|
+
for (const value of sources) {
|
|
32
|
+
if (!value) continue;
|
|
33
|
+
const trimmed = value.trim().toLowerCase();
|
|
34
|
+
if (TRUTHY.has(trimmed) || trimmed === 'all') {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if environment variables indicate trace mode
|
|
43
|
+
*/
|
|
44
|
+
function checkEnvTrace(): boolean {
|
|
45
|
+
const sources = [process.env.AGI_TRACE, process.env.TRACE_AGI];
|
|
46
|
+
for (const value of sources) {
|
|
47
|
+
if (!value) continue;
|
|
48
|
+
const trimmed = value.trim().toLowerCase();
|
|
49
|
+
if (TRUTHY.has(trimmed)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Initialize debug state from environment
|
|
58
|
+
*/
|
|
59
|
+
function initialize() {
|
|
60
|
+
if (state.runtimeOverride === null) {
|
|
61
|
+
state.enabled = checkEnvDebug();
|
|
62
|
+
}
|
|
63
|
+
if (state.runtimeTraceOverride === null) {
|
|
64
|
+
state.traceEnabled = checkEnvTrace();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if debug mode is enabled
|
|
70
|
+
* Considers both runtime override and environment variables
|
|
71
|
+
*/
|
|
72
|
+
export function isDebugEnabled(): boolean {
|
|
73
|
+
initialize();
|
|
74
|
+
return state.enabled;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if trace mode is enabled (shows stack traces)
|
|
79
|
+
* Trace mode requires debug mode to be enabled
|
|
80
|
+
*/
|
|
81
|
+
export function isTraceEnabled(): boolean {
|
|
82
|
+
initialize();
|
|
83
|
+
return state.enabled && state.traceEnabled;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Enable or disable debug mode at runtime
|
|
88
|
+
* Overrides environment variable settings
|
|
89
|
+
*
|
|
90
|
+
* @param enabled - true to enable debug mode, false to disable
|
|
91
|
+
*/
|
|
92
|
+
export function setDebugEnabled(enabled: boolean): void {
|
|
93
|
+
state.enabled = enabled;
|
|
94
|
+
state.runtimeOverride = enabled;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Enable or disable trace mode at runtime
|
|
99
|
+
* Trace mode shows full stack traces in error logs
|
|
100
|
+
*
|
|
101
|
+
* @param enabled - true to enable trace mode, false to disable
|
|
102
|
+
*/
|
|
103
|
+
export function setTraceEnabled(enabled: boolean): void {
|
|
104
|
+
state.traceEnabled = enabled;
|
|
105
|
+
state.runtimeTraceOverride = enabled;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Reset debug state to environment defaults
|
|
110
|
+
*/
|
|
111
|
+
export function resetDebugState(): void {
|
|
112
|
+
state.runtimeOverride = null;
|
|
113
|
+
state.runtimeTraceOverride = null;
|
|
114
|
+
state.enabled = checkEnvDebug();
|
|
115
|
+
state.traceEnabled = checkEnvTrace();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get current debug state (for testing/diagnostics)
|
|
120
|
+
*/
|
|
121
|
+
export function getDebugState(): Readonly<DebugState> {
|
|
122
|
+
initialize();
|
|
123
|
+
return { ...state };
|
|
124
|
+
}
|
package/src/runtime/debug.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy debug utilities - now integrated with new logger
|
|
3
|
+
*
|
|
4
|
+
* This file maintains backward compatibility while using the new
|
|
5
|
+
* centralized debug-state and logger modules.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { isDebugEnabled as isDebugEnabledNew } from './debug-state';
|
|
9
|
+
import { time as timeNew, debug as debugNew } from './logger';
|
|
10
|
+
|
|
1
11
|
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
2
12
|
|
|
3
13
|
const SYNONYMS: Record<string, string> = {
|
|
@@ -60,45 +70,48 @@ function getDebugConfig(): DebugConfig {
|
|
|
60
70
|
return cachedConfig;
|
|
61
71
|
}
|
|
62
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Check if debug mode is enabled for a specific flag
|
|
75
|
+
* Now uses the centralized debug state
|
|
76
|
+
*
|
|
77
|
+
* @deprecated Use isDebugEnabled from debug-state.ts instead
|
|
78
|
+
*/
|
|
63
79
|
export function isDebugEnabled(flag?: string): boolean {
|
|
80
|
+
// Use new centralized debug state for general debug
|
|
81
|
+
if (!flag || flag === 'log') {
|
|
82
|
+
return isDebugEnabledNew();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// For specific flags like 'timing', check both new state and legacy env vars
|
|
86
|
+
if (flag === 'timing') {
|
|
87
|
+
// If new debug state is enabled OR timing flag is set
|
|
88
|
+
if (isDebugEnabledNew()) return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Legacy flag checking
|
|
64
92
|
const config = getDebugConfig();
|
|
65
93
|
if (config.flags.has('all')) return true;
|
|
66
94
|
if (flag) return config.flags.has(flag);
|
|
67
95
|
return config.flags.has('log');
|
|
68
96
|
}
|
|
69
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Log debug message
|
|
100
|
+
* Now uses the centralized logger
|
|
101
|
+
*
|
|
102
|
+
* @deprecated Use logger.debug from logger.ts instead
|
|
103
|
+
*/
|
|
70
104
|
export function debugLog(...args: unknown[]) {
|
|
71
105
|
if (!isDebugEnabled('log')) return;
|
|
72
|
-
|
|
73
|
-
console.log('[debug]', ...args);
|
|
74
|
-
} catch {}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function nowMs(): number {
|
|
78
|
-
const perf = (globalThis as { performance?: { now?: () => number } })
|
|
79
|
-
.performance;
|
|
80
|
-
if (perf && typeof perf.now === 'function') return perf.now();
|
|
81
|
-
return Date.now();
|
|
106
|
+
debugNew(args.map((arg) => String(arg)).join(' '));
|
|
82
107
|
}
|
|
83
108
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
end(meta?: Record<string, unknown>) {
|
|
94
|
-
if (finished) return;
|
|
95
|
-
finished = true;
|
|
96
|
-
const duration = nowMs() - start;
|
|
97
|
-
try {
|
|
98
|
-
const line = `[timing] ${label} ${duration.toFixed(1)}ms`;
|
|
99
|
-
if (meta && Object.keys(meta).length) console.log(line, meta);
|
|
100
|
-
else console.log(line);
|
|
101
|
-
} catch {}
|
|
102
|
-
},
|
|
103
|
-
};
|
|
109
|
+
/**
|
|
110
|
+
* Create a timer for performance measurement
|
|
111
|
+
* Integrated with centralized logger
|
|
112
|
+
*/
|
|
113
|
+
export function time(label: string): {
|
|
114
|
+
end(meta?: Record<string, unknown>): void;
|
|
115
|
+
} {
|
|
116
|
+
return timeNew(label);
|
|
104
117
|
}
|