AI Connect/Reference/TypeScript
TypeScript
Type client configuration, keep capability lookup explicit, represent both execution outcomes, and narrow thrown values from unknown.
The SDK ships bundled TypeScript types. These patterns keep the type system working for you instead of against you.
Type client configuration before construction
import { Vinkius } from '@vinkius/connect';
import type { VinkiusOptions } from '@vinkius/connect';
const options = {
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!,
timeoutMs: 20_000,
maxRetries: 2,
hooks: {
onResponse: ({ status, requestId }) => {
console.debug({ status, requestId });
},
},
} satisfies VinkiusOptions;
export const vinkius = new Vinkius(options);satisfies checks property names and callback arguments without widening the object to VinkiusOptions before it reaches the constructor.
Import public contracts from the root
import {
Capability,
CapabilitySet,
ValidationError,
Vinkius,
VinkiusError,
} from '@vinkius/connect';
import type {
CapabilityQuery,
CapabilityResult,
ConnectorStatus,
CredentialSchema,
CredentialStatus,
ExecuteOptions,
JSONSchema,
RequestOptions,
} from '@vinkius/connect';Classes and errors are runtime values, so import them normally when using new or instanceof. Interfaces and aliases should use import type when your TypeScript configuration preserves imports.
Keep runtime capability lookup explicit
import type { Capability, CapabilityResult } from '@vinkius/connect';
async function executeIssue(
externalId: string,
operationId: string,
): Promise<CapabilityResult> {
const query = {
include: ['github'],
} satisfies CapabilityQuery;
const capabilities = await vinkius.user(externalId).capabilities(query);
const capability: Capability | undefined =
capabilities.findCapability('github__create_issue');
if (!capability) {
throw new Error('GitHub create_issue is not available for this user');
}
return capability.execute(
{ owner: 'acme', repo: 'product', title: 'Document the SDK' },
{ idempotencyKey: `create-issue:${operationId}` },
);
}Capability availability depends on the user and service response, so lookup returns Capability | undefined. Do not hide that branch with a non-null assertion.
Represent the two execution outcomes
interface CompletedAction {
kind: 'completed';
text: string;
}
interface FailedAction {
kind: 'capability-error';
text: string;
}
type ActionOutcome = CompletedAction | FailedAction;
function mapResult(result: CapabilityResult): ActionOutcome {
const text = result.content.map((part) => part.text).join('\n');
return result.isError
? { kind: 'capability-error', text }
: { kind: 'completed', text };
}isError is a boolean rather than a discriminant literal in the SDK type, so map it to your own union when downstream code benefits from exhaustive switching.
Narrow thrown values from unknown
import {
RateLimitError,
ValidationError,
VinkiusError,
} from '@vinkius/connect';
type RequestFailure =
| { kind: 'validation'; fields: unknown }
| { kind: 'rate-limit'; retryAfterMs?: number }
| { kind: 'sdk'; code: string; status: number; requestId?: string };
function classifyFailure(error: unknown): RequestFailure | undefined {
if (error instanceof ValidationError) {
return { kind: 'validation', fields: error.errors };
}
if (error instanceof RateLimitError) {
return {
kind: 'rate-limit',
...(error.retryAfterMs !== undefined
? { retryAfterMs: error.retryAfterMs }
: {}),
};
}
if (error instanceof VinkiusError) {
return {
kind: 'sdk',
code: error.code,
status: error.status,
...(error.requestId ? { requestId: error.requestId } : {}),
};
}
return undefined;
}Keep an unknown branch. Adapter dispatchers, application hooks, and some abort timing paths can throw values outside the VinkiusError hierarchy.
Treat schemas as runtime contracts
function requiredCredentialKeys(schema: CredentialSchema): string[] {
return Object.entries(schema)
.filter(([, field]) => field.required === true)
.map(([key]) => key);
}
function capabilitySchema(capability: Capability): JSONSchema {
return capability.inputSchema;
}CredentialSchema has typed field descriptors. JSONSchema is intentionally Record<string, unknown> because connector schemas vary. Validate it with the JSON Schema library chosen by your application before treating tool arguments as a domain-specific type. Avoid casting arbitrary model output directly:
// Avoid: the cast performs no runtime validation.
const issue = modelArgs as { owner: string; repo: string; title: string };Instead, validate modelArgs against capability.inputSchema, then map the validated value into your application type.
Type request controls separately
const requestOptions: RequestOptions = {
signal: request.signal,
};
const executeOptions: ExecuteOptions = {
signal: request.signal,
idempotencyKey: `operation:${operationId}`,
};Every request method accepts signal. Only capability execution adds idempotencyKey. Adapter dispatch helpers do not accept ExecuteOptions, even though their returned or bound execution functions are typed.
Preserve factory return types in adapters
Factory adapters are generic and return whatever your injected factory creates:
import { toLangChainTools } from '@vinkius/connect/langchain';
import type { JSONSchema } from '@vinkius/connect';
interface AppTool {
name: string;
run(input: Record<string, unknown>): Promise<string>;
}
const toolFactory = (
fn: (input: Record<string, unknown>) => Promise<string>,
config: { name: string; description: string; schema: JSONSchema },
): AppTool => ({
name: config.name,
run: fn,
});
const tools: AppTool[] = toLangChainTools(capabilities, {
tool: toolFactory,
});This example uses only the adapter's structural contract. Test an actual framework factory against the version installed by your application.
