Skip to content

TypeScript Standards

Type safety standards and best practices for the Robota SDK v2.0.

Type Safety Policy

The Robota SDK enforces strict type safety rules:

  • any and {} are prohibited in production code (packages/*/src/**, apps/*/src/**).
  • unknown is allowed only at trust boundaries (JSON parsing, external API responses, catch blocks) and must be narrowed with type guards before domain use.
  • // @ts-ignore and // @ts-nocheck are prohibited.
  • In test files (**/*.test.*, **/*.spec.*), any and unknown may be used only for mocks or boundary fixtures.

Policy Enforcement

typescript
// ✅ Good: Specific types
interface AgentConfig {
  name: string;
  model: string;
  provider: 'openai' | 'anthropic' | 'google';
  aiProviders: IAIProvider[];
  systemMessage?: string;
  tools?: IToolInterface[];
  plugins?: AbstractPlugin[];
}

// ❌ Bad: Any types (avoid in shipped source; do not suppress via eslint-disable)
interface BadConfig {
  providers: any; // Never use any
  options: any; // Always type explicitly
  data: unknown; // Prefer specific types
}

// ✅ Good: Type assertions with guards
function processResponse(response: unknown): string {
  if (typeof response === 'string') {
    return response;
  }
  throw new Error('Invalid response type');
}

TypeScript Configuration

Strict Configuration (tsconfig.json)

json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true
  }
}

ESLint Rules for Type Safety (High-level)

json
{
  "rules": {
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/no-unsafe-any": "error",
    "@typescript-eslint/no-unsafe-assignment": "error",
    "@typescript-eslint/no-unsafe-call": "error",
    "@typescript-eslint/no-unsafe-member-access": "error",
    "@typescript-eslint/no-unsafe-return": "error",
    "@typescript-eslint/prefer-unknown-to-any": "error"
  }
}

Note: In shipped source, disabling @typescript-eslint/no-explicit-any and @typescript-eslint/ban-types is prohibited by lint policy. Fix types instead (SSOT types, proper interfaces, and type guards).

Type Definition Standards

Interface Design

typescript
// ✅ Good: Comprehensive interface with readonly properties
interface AgentStats {
  readonly name: string;
  readonly uptime: number;
  readonly historyLength: number;
  readonly providerStats: Readonly<Record<string, ProviderStats>>;
  readonly lastInteraction?: Readonly<Date>;
}

// Note: Object shapes must use `interface` (I* prefix). Type aliases (T* prefix)
// are for unions, intersections, mapped types, tuples, and primitives only.

// ✅ Good: Branded types for type safety
type ModelName = string & { readonly __brand: 'ModelName' };
type ProviderName = string & { readonly __brand: 'ProviderName' };
type AgentName = string & { readonly __brand: 'AgentName' };

// Helper functions for branded types
function createModelName(value: string): ModelName {
  return value as ModelName;
}

// ✅ Good: Discriminated unions
type ToolResult =
  | { success: true; data: unknown; metadata?: Record<string, unknown> }
  | { success: false; error: string; code?: string };

type StreamChunk =
  | { type: 'content'; content: string }
  | { type: 'error'; error: string }
  | { type: 'end' };

Generic Type Patterns

typescript
// ✅ Good: Proper generic constraints
interface AbstractAgent<TStats extends AgentStats = AgentStats> {
  getStats(): TStats;
  run(input: string): Promise<string>;
  stream(input: string): AsyncIterable<StreamChunk>;
  destroy(): Promise<void>;
}

// ✅ Good: Conditional types for flexibility
type PluginConfig<T extends AbstractPlugin> = T extends ExecutionAnalyticsPlugin
  ? ExecutionAnalyticsConfig
  : T extends ConversationHistoryPlugin
    ? ConversationHistoryConfig
    : T extends LoggingPlugin
      ? LoggingConfig
      : never;

// ✅ Good: Mapped types for transformations
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;

// Usage examples
type PartialAgentConfig = Optional<AgentConfig, 'systemMessage' | 'tools'>;
type RequiredProviderConfig = RequiredFields<ProviderConfig, 'apiKey'>;

Error Type Safety

Result Pattern

typescript
// ✅ Good: Type-safe result pattern
type Result<T, E extends Error = Error> = { success: true; data: T } | { success: false; error: E };

// Custom error types
class AgentError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly context?: Record<string, unknown>,
  ) {
    super(message);
    this.name = 'AgentError';
  }
}

class ProviderError extends AgentError {
  constructor(
    message: string,
    public readonly provider: ProviderName,
    context?: Record<string, unknown>,
  ) {
    super(message, 'PROVIDER_ERROR', { ...context, provider });
    this.name = 'ProviderError';
  }
}

// Usage
async function safeAgentRun(agent: Robota, input: string): Promise<Result<string, AgentError>> {
  try {
    const result = await agent.run(input);
    return { success: true, data: result };
  } catch (error) {
    if (error instanceof AgentError) {
      return { success: false, error };
    }
    // Convert unknown errors to AgentError
    return {
      success: false,
      error: new AgentError('Unknown error', 'UNKNOWN_ERROR', { originalError: error }),
    };
  }
}

Option Pattern

typescript
// ✅ Good: Option pattern for nullable values
type Option<T> = { some: T } | { none: true };

function some<T>(value: T): Option<T> {
  return { some: value };
}

function none<T>(): Option<T> {
  return { none: true };
}

function isSome<T>(option: Option<T>): option is { some: T } {
  return 'some' in option;
}

// Usage
function findPlugin(agent: Robota, name: string): Option<AbstractPlugin> {
  const plugin = agent.getPlugin(name);
  return plugin ? some(plugin) : none();
}

Plugin Type System

Plugin Interface

typescript
// ✅ Good: Generic plugin interface
export abstract class AbstractPlugin<TStats extends PluginStats = PluginStats> {
  abstract readonly name: string;
  abstract getStats(): TStats;

  // Optional lifecycle methods
  onAgentStart?(): Promise<void>;
  onAgentStop?(): Promise<void>;
  onAgentRun?(input: string): Promise<void>;
  onAgentResponse?(response: string): Promise<void>;
}

// Specific plugin stats
interface ExecutionAnalyticsStats extends PluginStats {
  readonly totalExecutions: number;
  readonly successRate: number;
  readonly averageDuration: number;
  readonly errorCount: number;
}

interface ConversationHistoryStats extends PluginStats {
  readonly messageCount: number;
  readonly oldestMessage?: Date;
  readonly newestMessage?: Date;
}

// Plugin implementations
export class ExecutionAnalyticsPlugin extends AbstractPlugin<ExecutionAnalyticsStats> {
  readonly name = 'ExecutionAnalyticsPlugin';

  getStats(): ExecutionAnalyticsStats {
    return {
      name: this.name,
      totalExecutions: this.totalExecutions,
      successRate: this.calculateSuccessRate(),
      averageDuration: this.calculateAverageDuration(),
      errorCount: this.errorCount,
    };
  }
}

Tool Type System

Tool Interface

typescript
// ✅ Good: Type-safe tool definition
interface ToolSchema {
  name: string;
  description: string;
  parameters: JSONSchema;
  handler: (params: unknown) => Promise<unknown>;
}

// Type-safe tool creation
function createFunctionTool<TParams, TResult>(
  name: string,
  description: string,
  schema: JSONSchema,
  handler: (params: TParams) => Promise<TResult>,
): Tool<TParams, TResult> {
  return {
    name,
    description,
    schema,
    execute: handler,
  };
}

// Usage with full type safety
const calculatorTool = createFunctionTool(
  'calculate',
  'Performs mathematical calculations',
  {
    type: 'object',
    properties: {
      operation: { type: 'string', enum: ['add', 'subtract', 'multiply', 'divide'] },
      a: { type: 'number' },
      b: { type: 'number' },
    },
    required: ['operation', 'a', 'b'],
  } as const,
  async (params: {
    operation: 'add' | 'subtract' | 'multiply' | 'divide';
    a: number;
    b: number;
  }) => {
    switch (params.operation) {
      case 'add':
        return { result: params.a + params.b };
      case 'subtract':
        return { result: params.a - params.b };
      case 'multiply':
        return { result: params.a * params.b };
      case 'divide':
        return { result: params.a / params.b };
    }
  },
);

Provider Type System

Provider Interface

typescript
// ✅ Good: Provider type system
export abstract class AbstractAIProvider<TOptions extends ProviderOptions = ProviderOptions> {
  constructor(protected readonly options: TOptions) {}

  abstract generateResponse(
    messages: UniversalMessage[],
    options?: GenerationOptions,
  ): Promise<string>;

  abstract generateStream(
    messages: UniversalMessage[],
    options?: GenerationOptions,
  ): AsyncIterable<StreamChunk>;

  abstract getSupportedModels(): readonly ModelName[];
  abstract validateModel(model: string): model is ModelName;
}

// Provider-specific types
interface OpenAIProviderOptions extends ProviderOptions {
  readonly client: OpenAI;
  readonly organization?: string;
}

interface AnthropicProviderOptions extends ProviderOptions {
  readonly client: Anthropic;
  readonly version?: string;
}

// Provider implementations
export class OpenAIProvider extends AbstractAIProvider<OpenAIProviderOptions> {
  getSupportedModels(): readonly ModelName[] {
    return [
      createModelName('gpt-3.5-turbo'),
      createModelName('gpt-4'),
      createModelName('gpt-4o-mini'),
    ] as const;
  }

  validateModel(model: string): model is ModelName {
    return this.getSupportedModels().includes(model as ModelName);
  }
}

Type Documentation

JSDoc Integration

typescript
/**
 * Configuration for creating a Robota agent
 * @template TStats - The agent statistics type
 */
interface AgentConfig<TStats extends AgentStats = AgentStats> {
  /** Unique identifier for the agent */
  readonly name: AgentName;

  /** AI model to use */
  readonly model: ModelName;

  /** Provider identifier */
  readonly provider: ProviderName;

  /**
   * Available AI providers
   * @example
   * ```typescript
   * {
   *   openai: new OpenAIProvider({ client: openaiClient }),
   *   anthropic: new AnthropicProvider({ client: anthropicClient })
   * }
   * ```
   */
  readonly aiProviders: IAIProvider[];

  /** System message for the agent */
  readonly systemMessage?: string;

  /** Available tools for the agent */
  readonly tools?: readonly IToolInterface[];

  /** Plugins to extend agent functionality */
  readonly plugins?: readonly AbstractPlugin[];
}

Type Utility Documentation

typescript
/**
 * Utility types for common patterns in the Robota SDK
 */

/** Make specific properties optional */
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

/** Make specific properties required */
type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;

/** Deep readonly type */
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

/** Extract function parameter types */
type ParametersOf<T> = T extends (...args: infer P) => unknown ? P : never;

/** Extract function return type */
type ReturnTypeOf<T> = T extends (...args: unknown[]) => infer R ? R : never;

Best Practices Summary

✅ Do

  1. Use specific types: Always prefer specific types over any or unknown
  2. Leverage generics: Use type parameters for reusable components
  3. Document types: Add JSDoc comments for complex types
  4. Use branded types: For domain-specific values like IDs
  5. Implement type guards: For runtime type checking
  6. Use readonly: For immutable data structures
  7. Prefer interfaces: Over type aliases for object shapes

❌ Don't

  1. Use any as a shortcut: Avoid in shipped source; use SSOT types and validation
  2. Use unknown without narrowing: Always narrow with type guards/validators
  3. Ignore TypeScript errors: Fix all type errors
  4. Use function overloads: Prefer union types
  5. Mutate readonly data: Respect immutability
  6. Use index signatures: Prefer specific property definitions

Migration from v1.x

Type System Changes

typescript
// v1.x (deprecated)
interface OldConfig {
  providers: any; // ❌ Any types
  options: unknown; // ❌ Unsafe unknown
}

// v2.0 (current)
interface NewConfig {
  providers: Record<string, AbstractAIProvider>; // ✅ Specific types
  options: GenerationOptions; // ✅ Well-defined interface
}

This type system ensures complete type safety and excellent developer experience throughout the Robota SDK.

Released under the MIT License.