AI Connect/Reference/Recipes

Recipes

Ask AI about Vinkius

Focused patterns for connector setup, direct execution, narrow model tools, cancellation, tracing, and catalog-schema caching.

These recipes use only the public SDK surface and make user scope explicit. Combine the ones your application needs rather than placing every concern in one route.

Reuse one server-side client

typescript
// lib/vinkius.ts
import { Vinkius } from '@vinkius/connect';

export const vinkius = new Vinkius({
  appId: process.env.VINKIUS_APP_ID!,
  apiKey: process.env.VINKIUS_APP_KEY!,
  timeoutMs: 20_000,
  maxRetries: 2,
});

The client stores application configuration, not a current user. Derive externalId for each incoming request.

Build a connector form from catalog schema

typescript
import type { CredentialField, CredentialSchema } from '@vinkius/connect';
import { vinkius } from './lib/vinkius';

interface FormField {
  key: string;
  label: string;
  required: boolean;
  type: CredentialField['type'];
  docsUrl?: string;
}

async function connectorForm(slug: string): Promise<FormField[]> {
  const schema: CredentialSchema = await vinkius
    .user('schema-only-placeholder')
    .connector(slug)
    .credentials.schema();

  return Object.entries(schema).map(([key, field]) => ({
    key,
    label: field.label ?? key,
    required: field.required ?? false,
    type: field.type,
    ...(field.docs_url ? { docsUrl: field.docs_url } : {}),
  }));
}

Schema lookup is catalog-scoped and does not require a real connection. The user handle above makes no request and is used only to reach the fluent schema method; vinkius.catalog.get(slug).credential_schema is the direct low-level alternative.

Save connector credentials from an authenticated route

typescript
async function saveConnector(
  externalId: string,
  slug: string,
  values: Record<string, string>,
) {
  const connector = vinkius.user(externalId).connector(slug);

  await connector.connect();
  const state = await connector.credentials.set(values);

  return {
    configured: state.configured,
    status: await connector.status(),
  };
}

Authenticate and authorize externalId before this function. Stored values are not returned; the result contains configured-key flags.

Render connection health

typescript
const connections = await vinkius.user(externalId).connectors();

const view = connections.map((connection) => ({
  slug: connection.slug,
  status: connection.status,
  action:
    connection.status === 'ready'
      ? 'use'
      : connection.status === 'needs_credentials'
        ? 'update-credentials'
        : 'review-connection',
}));

connectors() lists existing connections. It does not add catalog connectors that the user has never connected.

Execute an action without a model

typescript
import type { CapabilityResult } from '@vinkius/connect';

async function createIssue(
  externalId: string,
  input: { owner: string; repo: string; title: string },
  operationId: string,
): Promise<CapabilityResult> {
  const capabilities = await vinkius.user(externalId).capabilities({
    include: ['github'],
  });
  const capability = capabilities.findCapability('github__create_issue');

  if (!capability) {
    throw new Error('GitHub create_issue is not available for this user');
  }

  return capability.execute(input, {
    idempotencyKey: `create-issue:${operationId}`,
  });
}

Direct execution preserves CapabilityResult and accepts ExecuteOptions, unlike adapter dispatch helpers.

Give a model the smallest connector set it needs

typescript
const issueTools = await vinkius.user(externalId).capabilities({
  include: ['github'],
});

if (issueTools.length === 0) {
  return { tools: [], requiresConnection: true };
}

include filters server-side. Use exclude for local removal after aggregation, or connector(slug).capabilities() when you specifically want one existing connection. Convert the set only at the model call:

typescript
import { toJSONSchemaTools } from '@vinkius/connect/json-schema';

const definitions = toJSONSchemaTools(issueTools);

Retain issueTools for execution. Converted definitions do not carry the user-scoped routing properties.

Add caller cancellation

typescript
async function loadCapabilitiesWithBudget(externalId: string) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort('application budget exceeded'), 5_000);

  try {
    return await vinkius.user(externalId).capabilities({
      signal: controller.signal,
    });
  } finally {
    clearTimeout(timer);
  }
}

The caller signal is combined with the client's per-attempt timeout. Pass the incoming request signal when disconnecting work after the client closes matters. Factory-bound and dispatch-helper adapter executions do not accept this signal; call Capability.execute() directly when cancellation must reach execution.

Trace attempts and request IDs

typescript
const vinkius = new Vinkius({
  appId: process.env.VINKIUS_APP_ID!,
  apiKey: process.env.VINKIUS_APP_KEY!,
  hooks: {
    onRequest: ({ method, url }) => {
      console.debug('Vinkius request attempt', { method, url });
    },
    onResponse: ({ status, requestId }) => {
      console.debug('Vinkius response', { status, requestId });
    },
  },
});

Hooks run once per HTTP attempt, so repeated entries can represent automatic retries. onRequest has no body. onResponse receives a fixed-name redacted copy of the response body. Keep callbacks synchronous, non-throwing, and free of unbounded work.

Cache a non-secret catalog schema

typescript
import { ResolverCache } from '@vinkius/connect';
import type { CredentialSchema } from '@vinkius/connect';

const catalogCache = new ResolverCache(10 * 60 * 1000);

async function credentialSchema(slug: string): Promise<CredentialSchema> {
  return catalogCache.resolve(`credential-schema:${slug}`, async () => {
    const detail = await vinkius.catalog.get(slug);
    return detail.credential_schema;
  });
}

ResolverCache is standalone; the client does not use it automatically. Concurrent misses are not coalesced, expired entries are removed when read, and rejected computations are not cached. Never store credential values, bearer tokens, or authorization decisions in this cache.

Next steps