AI Connect/Integration/Error handling

Error handling

Ask AI about Vinkius

Treat capability failure as returned data, narrow thrown VinkiusError subclasses, understand which requests the SDK retries, and record diagnostics without secrets.

Capability execution has two failure channels, and handling both is the difference between an agent that recovers and one that crashes.

Check capability failure as returned data

A resolved execution with isError: true means the request completed and the connector reported a failed action. It is a result, not a throw, so your agent loop can feed the error text back to the model and let it recover:

typescript
const result = await capability.execute(args, { idempotencyKey });

if (result.isError) {
  const text = result.content.map((part) => part.text).join('\n');
  // return the text to the model as the tool result
}

Narrow thrown errors from most specific to general

Everything outside capability results (authentication, HTTP, validation, rate, quota, timeout, network, configuration) throws:

typescript
import {
  VinkiusError,
  AuthError,
  RateLimitError,
  QuotaError,
  ConnectorNotConnectedError,
} from '@vinkius/connect';

try {
  const result = await capability.execute(args, { idempotencyKey });
  if (result.isError) {
    // connector-level failure: feed result.content back to the model
  }
} catch (error) {
  if (error instanceof ConnectorNotConnectedError) {
    // send the user through the connector setup flow
  } else if (error instanceof RateLimitError) {
    // back off using the response's retry information
  } else if (error instanceof QuotaError || error instanceof OverageError) {
    // plan limit reached: surface an upgrade path
  } else if (error instanceof AuthError) {
    // application key rejected: check rotation and environment
  } else if (error instanceof VinkiusError) {
    // any other API error
  } else {
    // hooks can throw their own errors; keep an unknown branch
  }
}

Every VinkiusError has code, status, requestId, and details. Local and transport errors use status 0. requestId is present only when the server supplied one.

The error taxonomy

ErrorMeaning
ConfigErrorLocal configuration problem, thrown before any request
AuthErrorHTTP 401 or 403: the application key was rejected
ValidationErrorThe request payload failed server-side validation
NotFoundErrorThe addressed user, connection, or resource does not exist
RateLimitErrorHTTP 429: too many requests
QuotaErrorThe plan's included quota is exhausted
OverageErrorOverage protection rejected the request
ConnectorNotConnectedErrorThe operation requires a connection that does not exist
ConnectionErrorThe connection is not in a state that allows the operation
NotImplementedErrorThe endpoint exists but is not available in this environment
ProtocolErrorThe response violated the expected protocol
VinkiusErrorAny other non-success response (carries details)

Know which requests the SDK repeats

The default maximum is the initial attempt plus two retries. Network errors, timeouts, and HTTP 429, 502, 503, or 504 qualify as transient, but only repeatable requests are retried: GET, PUT, and DELETE by default; user creation and connection creation because their contracts are upsert/get-or-create; and capability execution only when idempotencyKey is supplied.

Generate and validate execution keys in your application

The transport treats an undefined idempotency key as "do not enable retry" and only sends the Idempotency-Key header for a truthy string. An empty string therefore enables retry without sending the header. Derive keys from the logical operation, validate that they are non-empty, and reuse a key only when retrying that same operation:

typescript
const key = `create-issue:${operationId}`;
if (!key.trim()) throw new Error('idempotency key required');

Do not rely on adapter dispatch for execution options

Adapter dispatch helpers and factory-bound functions call capability.execute(args) without ExecuteOptions: no key, no caller signal, one transport attempt. Unknown names passed to adapter dispatchers throw plain Error, not VinkiusError. Resolve the Capability and call execute() directly when you need cancellation or idempotency.

Record diagnostics without recording secrets

typescript
try {
  // ...
} catch (error) {
  if (error instanceof VinkiusError) {
    logger.error({ code: error.code, status: error.status, requestId: error.requestId });
  }
  throw error;
}

Observability hooks receive redacted views (authorization headers and known secret field names stripped), but your own logging must still treat submitted credential values as secrets. Hook callbacks can throw their original errors, and aborting during retry backoff can propagate the signal reason directly. Keep a final unknown branch instead of assuming every thrown value is a VinkiusError.

Next steps