66import { ApiError , ApiClientError , ApiServerError } from "../api-client" ;
77import { logIssue } from "../telem/logging" ;
88import { APICallError } from "ai" ;
9+ import type { TransportType } from "../types" ;
910
1011/**
1112 * Type guard to identify user input validation errors.
@@ -58,56 +59,101 @@ export function isAPICallError(error: unknown): error is APICallError {
5859 return APICallError . isInstance ( error ) ;
5960}
6061
62+ const GENERIC_CONFIG_ERROR_MESSAGE = [
63+ "**Feature Unavailable**" ,
64+ "This feature is temporarily unavailable due to a server configuration issue." ,
65+ "The service operator has been notified. Please try again later." ,
66+ ] . join ( "\n\n" ) ;
67+
68+ /**
69+ * Format a server-side configuration error for user display.
70+ *
71+ * For HTTP transport, the detailed message is hidden (the user cannot fix
72+ * server-side configuration) — the error is logged to Sentry and a generic
73+ * message is returned instead.
74+ * For stdio/unset transport, the detailed parts are returned as-is.
75+ */
76+ function formatServerConfigError (
77+ error : Error ,
78+ detailedParts : string [ ] ,
79+ options ?: { transport ?: TransportType } ,
80+ ) : string {
81+ if ( options ?. transport === "http" ) {
82+ logIssue ( error ) ;
83+ return GENERIC_CONFIG_ERROR_MESSAGE ;
84+ }
85+ return detailedParts . join ( "\n\n" ) ;
86+ }
87+
6188/**
6289 * Format an error for user display with markdown formatting.
6390 * This is used by tool handlers to format errors for MCP responses.
6491 *
92+ * When transport is "http", config/provider errors are logged to Sentry
93+ * and a generic message is returned (users can't fix server-side config).
94+ * When transport is "stdio" or undefined, detailed messages are returned
95+ * (users can fix their own config).
96+ *
6597 * SECURITY: Only return trusted error messages to prevent prompt injection vulnerabilities.
6698 * We trust: Sentry API errors, our own UserInputError/ConfigurationError messages, and system templates.
6799 */
68- export async function formatErrorForUser ( error : unknown ) : Promise < string > {
100+ export async function formatErrorForUser (
101+ error : unknown ,
102+ options ?: { transport ?: TransportType } ,
103+ ) : Promise < string > {
69104 if ( isUserInputError ( error ) ) {
70105 return [
71106 "**Input Error**" ,
72107 "It looks like there was a problem with the input you provided." ,
73108 error . message ,
74- ` You may be able to resolve the issue by addressing the concern and trying again.` ,
109+ " You may be able to resolve the issue by addressing the concern and trying again." ,
75110 ] . join ( "\n\n" ) ;
76111 }
77112
78113 if ( isConfigurationError ( error ) ) {
79- return [
80- "**Configuration Error**" ,
81- "There appears to be a configuration issue with your setup." ,
82- error . message ,
83- `Please check your environment configuration and try again.` ,
84- ] . join ( "\n\n" ) ;
114+ return formatServerConfigError (
115+ error ,
116+ [
117+ "**Configuration Error**" ,
118+ "There appears to be a configuration issue with your setup." ,
119+ error . message ,
120+ "Please check your environment configuration and try again." ,
121+ ] ,
122+ options ,
123+ ) ;
85124 }
86125
87126 if ( isLLMProviderError ( error ) ) {
88- return [
89- "**AI Provider Error**" ,
90- "The AI provider service is not available for this request." ,
91- error . message ,
92- `This is a service availability issue that cannot be resolved by retrying.` ,
93- ] . join ( "\n\n" ) ;
127+ return formatServerConfigError (
128+ error ,
129+ [
130+ "**AI Provider Error**" ,
131+ "The AI provider service is not available for this request." ,
132+ error . message ,
133+ "This is a service availability issue that cannot be resolved by retrying." ,
134+ ] ,
135+ options ,
136+ ) ;
94137 }
95138
96- // Handle AI SDK APICallError that wasn't converted to LLMProviderError
97- // This is a defensive layer - ideally callEmbeddedAgent converts these
139+ // Handle AI SDK APICallError that wasn't converted to LLMProviderError.
140+ // This is a defensive layer - ideally callEmbeddedAgent converts these.
98141 if ( isAPICallError ( error ) ) {
99142 const statusCode = error . statusCode ;
100143 // 4xx errors are user-facing (account issues, rate limits, invalid keys)
101- // These should NOT be logged to Sentry
102144 if ( statusCode && statusCode >= 400 && statusCode < 500 ) {
103- return [
104- "**AI Provider Error**" ,
105- "The AI provider service returned an error." ,
106- error . message ,
107- "This may be a configuration or account issue. Please check your AI provider settings." ,
108- ] . join ( "\n\n" ) ;
145+ return formatServerConfigError (
146+ error ,
147+ [
148+ "**AI Provider Error**" ,
149+ "The AI provider service returned an error." ,
150+ error . message ,
151+ "This may be a configuration or account issue. Please check your AI provider settings." ,
152+ ] ,
153+ options ,
154+ ) ;
109155 }
110- // 5xx errors - log to Sentry
156+ // 5xx errors - always log to Sentry regardless of transport
111157 const eventId = logIssue ( error ) ;
112158 const parts = [
113159 "**AI Provider Error**" ,
@@ -131,7 +177,7 @@ export async function formatErrorForUser(error: unknown): Promise<string> {
131177 "**Input Error**" ,
132178 statusText ,
133179 error . toUserMessage ( ) ,
134- ` You may be able to resolve the issue by addressing the concern and trying again.` ,
180+ " You may be able to resolve the issue by addressing the concern and trying again." ,
135181 ] . join ( "\n\n" ) ;
136182 }
137183
@@ -142,11 +188,11 @@ export async function formatErrorForUser(error: unknown): Promise<string> {
142188 ? `There was an HTTP ${ error . status } server error with the Sentry API.`
143189 : "There was a server error." ;
144190
145- const parts = [ "**Error**" , statusText , ` ${ error . message } ` ] ;
191+ const parts = [ "**Error**" , statusText , error . message ] ;
146192 if ( eventId ) {
147193 parts . push ( `**Event ID**: ${ eventId } ` ) ;
148194 }
149- parts . push ( ` Please contact support if the problem persists.` ) ;
195+ parts . push ( " Please contact support if the problem persists." ) ;
150196 return parts . join ( "\n\n" ) ;
151197 }
152198
@@ -159,8 +205,8 @@ export async function formatErrorForUser(error: unknown): Promise<string> {
159205 return [
160206 "**Error**" ,
161207 statusText ,
162- ` ${ error . message } ` ,
163- ` You may be able to resolve the issue by addressing the concern and trying again.` ,
208+ error . message ,
209+ " You may be able to resolve the issue by addressing the concern and trying again." ,
164210 ] . join ( "\n\n" ) ;
165211 }
166212
0 commit comments