Skip to content

Commit 76fb059

Browse files
committed
fix: handle SDK error responses and improve session error handling
- Add isSDKErrorResponse() to detect SDK errors returned as JSON strings (e.g., {"error": "Not connected or no session..."}) instead of thrown exceptions - Add isSessionError() to identify session/connection errors from SDK - Make session errors retryable with automatic retry (up to 3 attempts) - Add discardAuggieClient() to remove faulty clients from pool on session errors - Provide specific OpenAI-compatible error messages with helpful suggestions - Ensures proper error format for OpenAI SDK compatibility (error as object, not string)
1 parent 90aeceb commit 76fb059

File tree

1 file changed

+112
-4
lines changed

1 file changed

+112
-4
lines changed

src/server.ts

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,19 @@ function isContextLengthError(error: Error): boolean {
298298
);
299299
}
300300

301+
function isSessionError(error: Error): boolean {
302+
const message = error.message.toLowerCase();
303+
return (
304+
message.includes('not connected') ||
305+
message.includes('no session') ||
306+
message.includes('initialization failed') ||
307+
message.includes('session expired') ||
308+
message.includes('session invalid') ||
309+
message.includes('websocket') ||
310+
message.includes('disconnected')
311+
);
312+
}
313+
301314
function isTransientError(error: Error): boolean {
302315
const message = error.message.toLowerCase();
303316
const augmentError = error as AugmentAPIError;
@@ -306,6 +319,9 @@ function isTransientError(error: Error): boolean {
306319
// 5xx server errors are transient
307320
if (statusCode >= 500 && statusCode < 600) return true;
308321

322+
// Session/connection errors from SDK are transient (can retry with new client)
323+
if (isSessionError(error)) return true;
324+
309325
// Network-related errors
310326
if (
311327
message.includes('network') ||
@@ -362,6 +378,22 @@ function createOpenAIError(error: Error): OpenAIError {
362378
};
363379
}
364380

381+
// Session/connection errors from SDK - provide specific guidance
382+
if (isSessionError(error)) {
383+
return {
384+
error: {
385+
message: `SDK session error: ${error.message}`,
386+
type: 'server_error',
387+
code: 'connection_error',
388+
param: null,
389+
suggestion:
390+
'The Augment SDK connection was lost. This is usually temporary. ' +
391+
'If the issue persists: 1) Run "auggie login" to refresh authentication, ' +
392+
'2) Restart the server, 3) Check your network connection.',
393+
},
394+
};
395+
}
396+
365397
if (isTransientError(error)) {
366398
return {
367399
error: {
@@ -710,6 +742,22 @@ function releaseAuggieClient(modelId: string, client: AuggieClient): void {
710742
}
711743
}
712744

745+
// Discard a client without returning it to the pool (used when client has errors)
746+
function discardAuggieClient(modelId: string, client: AuggieClient): void {
747+
const modelConfig = MODEL_MAP[modelId] ?? MODEL_MAP[DEFAULT_MODEL];
748+
if (!modelConfig) return;
749+
const auggieModel = modelConfig.auggie;
750+
const pool = clientPools[auggieModel];
751+
if (!pool) return;
752+
753+
if (pool.inUse.has(client)) {
754+
pool.inUse.delete(client);
755+
}
756+
// Close the client without returning to pool
757+
void client.close();
758+
console.log(`Discarded faulty client for ${auggieModel} (session/connection error)`);
759+
}
760+
713761
function getModels() {
714762
const now = Math.floor(Date.now() / 1000);
715763
return Object.entries(MODEL_MAP).map(([id, config]) => ({
@@ -1284,6 +1332,24 @@ function createStreamCallback(res: ServerResponse, model: string, requestId: str
12841332
};
12851333
}
12861334
1335+
// Check if response is an SDK error (JSON with error field)
1336+
function isSDKErrorResponse(response: string): { isError: boolean; message?: string } {
1337+
try {
1338+
const parsed = JSON.parse(response) as Record<string, unknown>;
1339+
const errorField = parsed['error'];
1340+
if (typeof errorField === 'string') {
1341+
return { isError: true, message: errorField };
1342+
}
1343+
if (errorField && typeof errorField === 'object') {
1344+
const errObj = errorField as Record<string, unknown>;
1345+
return { isError: true, message: (errObj['message'] as string) ?? String(errObj) };
1346+
}
1347+
} catch {
1348+
// Not JSON, so not an error response
1349+
}
1350+
return { isError: false };
1351+
}
1352+
12871353
async function callAugmentAPIStreamingInternal(
12881354
prompt: string,
12891355
modelId: string,
@@ -1293,11 +1359,30 @@ async function callAugmentAPIStreamingInternal(
12931359
): Promise<void> {
12941360
const client = await getAuggieClient(modelId);
12951361
client.onSessionUpdate(createStreamCallback(res, model, requestId));
1362+
let hasError = false;
1363+
let caughtError: Error | null = null;
12961364
try {
1297-
await client.prompt(prompt);
1365+
const response = await client.prompt(prompt);
1366+
// Check if SDK returned an error as a response string (can happen even in streaming mode)
1367+
const errorCheck = isSDKErrorResponse(response);
1368+
if (errorCheck.isError) {
1369+
hasError = true;
1370+
caughtError = new Error(errorCheck.message ?? 'Unknown SDK error');
1371+
}
1372+
} catch (err) {
1373+
hasError = true;
1374+
caughtError = err as Error;
12981375
} finally {
12991376
client.onSessionUpdate(null);
1300-
releaseAuggieClient(modelId, client);
1377+
// Discard client on session errors, otherwise return to pool
1378+
if (hasError && caughtError && isSessionError(caughtError)) {
1379+
discardAuggieClient(modelId, client);
1380+
} else {
1381+
releaseAuggieClient(modelId, client);
1382+
}
1383+
}
1384+
if (caughtError) {
1385+
throw caughtError;
13011386
}
13021387
}
13031388
@@ -1317,11 +1402,34 @@ async function callAugmentAPIStreaming(
13171402
13181403
async function callAugmentAPIInternal(prompt: string, modelId: string): Promise<string> {
13191404
const client = await getAuggieClient(modelId);
1405+
let hasError = false;
1406+
let caughtError: Error | null = null;
1407+
let result = '';
13201408
try {
1321-
return await client.prompt(prompt);
1409+
const response = await client.prompt(prompt);
1410+
// Check if SDK returned an error as a response string
1411+
const errorCheck = isSDKErrorResponse(response);
1412+
if (errorCheck.isError) {
1413+
hasError = true;
1414+
caughtError = new Error(errorCheck.message ?? 'Unknown SDK error');
1415+
} else {
1416+
result = response;
1417+
}
1418+
} catch (err) {
1419+
hasError = true;
1420+
caughtError = err as Error;
13221421
} finally {
1323-
releaseAuggieClient(modelId, client);
1422+
// Discard client on session errors, otherwise return to pool
1423+
if (hasError && caughtError && isSessionError(caughtError)) {
1424+
discardAuggieClient(modelId, client);
1425+
} else {
1426+
releaseAuggieClient(modelId, client);
1427+
}
1428+
}
1429+
if (caughtError) {
1430+
throw caughtError;
13241431
}
1432+
return result;
13251433
}
13261434
13271435
async function callAugmentAPI(prompt: string, modelId: string, requestId: string): Promise<string> {

0 commit comments

Comments
 (0)