Lesson 14: Production-Ready Observability & Error Handling

Learn how to build resilient applications by using the SDK's event listeners for observability and its custom APIError class for robust error handling.

Code: lesson_14_observability_and_error_handling.mjs

This script intentionally uses an invalid model name to trigger an API error. We set up listeners for api_request and api_response to log the network activity. The try...catch block is used to inspect the structured data within the caught APIError object.

// lesson_14_observability_and_error_handling.mjs
// Merci SDK Tutorial: Lesson 14 - Production-Ready Observability & Error Handling

// --- IMPORTS ---
import { MerciClient, createUserMessage } from '../lib/merci.2.14.0.mjs';
import { token } from '../secret/token.mjs';

// To reliably trigger a 400-level error, we use a model name that does not exist.
const MODEL = 'invalid-model-name-for-testing';

async function main() {
    console.log(`--- Merci SDK Tutorial: Lesson 14 - Observability & Error Handling (Model: ${MODEL}) ---`);

    // --- STEP 1: INITIALIZE THE CLIENT WITH EVENT LISTENERS ---
    console.log('[STEP 1] Initializing MerciClient with observability listeners...');
    const client = new MerciClient({ token });

    // These listeners provide a detailed log of the SDK's internal operations.
    // This is invaluable for debugging and monitoring in a production environment.
    client.on('api_request', ({ url, method }) => {
        console.log(`\n[EVENT: api_request] ➡️  Sending ${method} request to ${url}`);
    });
    client.on('api_response', ({ url, status, ok }) => {
        const icon = ok ? '✅' : '❌';
        console.log(`[EVENT: api_response] ⬅️  Received response from ${url} | Status: ${status} ${icon}`);
    });
    client.on('error', (error) => {
        console.error(`[EVENT: error] 🚨 An internal SDK error was caught: ${error.message}`);
    });


    // --- STEP 2: ATTEMPT A FAULTY REQUEST TO DEMONSTRATE ERROR HANDLING ---
    console.log(`\n[STEP 2] Attempting a request that is expected to fail...`);
    try {
        const userPrompt = "This request will fail because the model profile is invalid.";
        const messages = [createUserMessage(userPrompt)];

        // We don't need any special parameters. Simply creating a chat session
        // with an invalid model name is enough to guarantee an API error.
        const chatSession = client.chat.session(MODEL);

        console.log('[INFO] Sending request with an invalid model name to cause a 400 Bad Request error...');
        // We don't need to process the stream, just trigger the call.
        for await (const event of chatSession.stream(messages)) { /* consume stream */ }

    } catch (error) {
        // --- STEP 3: CATCH AND INSPECT THE APIError OBJECT ---
        console.log(`\n[STEP 3] Successfully caught the expected error. Now inspecting it...`);
        console.error(`\n\n--- DETAILED ERROR ANALYSIS ---`);
        console.error('  Message:', error.message);

        // The APIError object contains structured data that is crucial for robust error handling.
        if (error.status) {
            console.error('  API Status:', error.status);
            if (error.status === 400) {
                console.error('  [ANALYSIS] This is a 400 Bad Request. As expected, the API rejected our request because the model profile was invalid. Check the API documentation for available models.');
            } else if (error.status === 401) {
                console.error('  [ANALYSIS] This is a 401 Unauthorized error. Your token is invalid or has expired.');
            } else if (error.status === 429) {
                console.error('  [ANALYSIS] This is a 429 Too Many Requests error. You have hit a rate limit. Implement exponential backoff and retry.');
            }
        }
        if (error.details) {
            console.error('  Details:', JSON.stringify(error.details, null, 2));
        }
        console.error('---------------------------------');
    }

    console.log(`\n\n[INFO] Lesson complete. This demonstrates how to use SDK events and typed errors to build a resilient application.`);
}

main().catch(err => {
    // A final catch block for any unexpected errors during the main execution.
    // We only log if it's not our expected APIError, which we've already handled.
    if (!err.status) {
        console.error("\n\n[FATAL ERROR] An unexpected error occurred:", err);
        process.exit(1);
    }
});

Expected Output

The script will log the outgoing request and the failing response. The catch block will then execute, printing a detailed analysis of the error, including the 400 status code and the specific error details returned by the API.

[EVENT: api_request] ➡️  Sending POST request to https://api.jetbrains.ai/user/v5/llm/chat/stream/v8
[EVENT: api_response] ⬅️  Received response from https://api.jetbrains.ai/user/v5/llm/chat/stream/v8 | Status: 400 ❌

--- DETAILED ERROR ANALYSIS ---
  Message: API request failed with status 400
  API Status: 400
  [ANALYSIS] This is a 400 Bad Request. As expected, the API rejected our request because the model profile was invalid...
  Details: {
    "message": "Unknown profile invalid-model-name-for-testing"
  }
---------------------------------