# @wavespec/kit

Shared utilities and helpers for building test adapters. Simplifies adapter implementation by providing reusable response normalization, error handling, discovery, and VCR (cassette-based replay) helpers.

**Current Status:** Phase 1 Complete - Type definitions and architecture ready. Phase 2 coming soon (implementations).

## Overview

`@wavespec/kit` provides specialized subpath exports for different aspects of adapter development:

- **`@wavespec/kit/response`** - Response normalization and content building
- **`@wavespec/kit/error`** - Error mapping and pattern-based error handling
- **`@wavespec/kit/discovery`** - Fuzzy matching and tool/resource discovery
- **`@wavespec/kit/vcr`** - Request extraction for cassette-based replay
- **`@wavespec/kit/fixtures`** - Test fixtures for creating test data
- **`@wavespec/kit/testing`** - Testing utilities for adapter development

This modular approach lets you use only the helpers you need while keeping related functionality together.

## Installation

```bash
npm install @wavespec/kit @wavespec/types
```

Or with your favorite package manager:

```bash
# pnpm
pnpm add @wavespec/kit @wavespec/types

# yarn
yarn add @wavespec/kit @wavespec/types
```

### Peer Dependencies

`@wavespec/kit` depends on `@wavespec/types@^0.1.0` as a peer dependency. When you install the kit, you must also have adapter-types installed.

```json
{
  "peerDependencies": {
    "@wavespec/types": "^0.1.0"
  },
  "dependencies": {
    "@wavespec/kit": "^0.1.0"
  }
}
```

### Environment Support

- **Runtime:** Node.js 18+ (ES2022+)
- **Module System:** ESM (no CommonJS support)
- **TypeScript:** 5.0+ (with `strict` mode enabled)

## Quick Start

### Minimal Adapter (Types Only)

If you only need types from `@wavespec/types`, you don't need the kit:

```typescript
import type {
  Adapter,
  TestContext,
  TestResult,
  ContentItem,
} from "@wavespec/types";

export class MinimalAdapter implements Adapter {
  readonly name = "minimal";
  readonly version = "0.1.0";

  async run(spec: TestSpec, ctx: TestContext): Promise<TestResult> {
    const content: ContentItem[] = [
      { type: "text", text: "Hello, world!" },
    ];

    return {
      response: {
        content,
        output_text: "Hello, world!",
        isError: false,
        raw: {},
      },
      durationMs: 0,
    };
  }
}
```

### Standard Adapter (With Response + Error Helpers)

Use the kit to reduce boilerplate:

```typescript
import type {
  Adapter,
  TestContext,
  TestResult,
} from "@wavespec/types";
import { textContent, computeOutputText } from "@wavespec/kit/response";
import {
  ErrorMapper,
  type ErrorPattern,
} from "@wavespec/kit/error";

export class StandardAdapter implements Adapter {
  readonly name = "standard";
  readonly version = "0.1.0";

  private errorMapper = new ErrorMapper({
    defaultCode: "EXECUTION_ERROR",
    defaultHint: "An unexpected error occurred",
  });

  constructor() {
    this.errorMapper.addPattern({
      match: /timeout|timed out/i,
      code: "OPERATION_TIMEOUT",
      hint: "The operation took too long to complete",
      fix: "Increase the timeout or check for performance issues",
    } as ErrorPattern);
  }

  async run(spec: TestSpec, ctx: TestContext): Promise<TestResult> {
    try {
      const result = await this.executeOperation(spec);
      const content = [textContent(JSON.stringify(result))];

      return {
        response: {
          content,
          output_text: computeOutputText(content),
          isError: false,
          raw: result,
        },
        durationMs: 0,
      };
    } catch (error) {
      throw this.errorMapper.map(error, { operation: spec.operation });
    }
  }

  private async executeOperation(spec: TestSpec): Promise<unknown> {
    // Implementation
    return {};
  }
}
```

## Documentation

### Comprehensive Utility Documentation

For detailed documentation on all 7 utility categories, see:

- **[Utilities Overview](./docs/utilities/README.md)** - Quick reference and comparison
- **[Discovery Utilities](./docs/utilities/discovery.md)** - Caching and fuzzy matching
- **[Error Utilities](./docs/utilities/error.md)** - Pattern-based error handling
- **[Response Utilities](./docs/utilities/response.md)** - Response normalization
- **[VCR Utilities](./docs/utilities/vcr.md)** - Record/replay for testing
- **[Dispatch Utilities](./docs/utilities/dispatch.md)** - Operation routing
- **[Parser Utilities](./docs/utilities/parsers.md)** - JSON and NDJSON parsing
- **[Content-Type Utilities](./docs/utilities/content-type.md)** - Content detection
- **[Integration Guide](./docs/integration-guide.md)** - Complete examples and patterns

---

## Subpath Exports

### Response Helpers (`@wavespec/kit/response`)

**Purpose:** Normalize responses from different protocols (HTTP, MCP, OpenAI, etc.) into a universal `ContentItem[]` format.

**When to use:** Every adapter needs to normalize responses into the standard format.

**[Full Documentation](./docs/utilities/response.md)**

#### Available Types

From `@wavespec/kit/response`:

- **`NormalizedResponse`** - The normalized output with content, output_text, and error flag
- **`ContentBuilder<T>`** - Function type for converting protocol-specific items to ContentItems
- **`PathExtractor<T>`** - Flexible path specification (string, array, or function)
- **`ResponseNormalizerConfig<T>`** - Schema-driven configuration for response transformation
- **`OutputTextComputer`** - Function type for computing output_text from ContentItems
- **`ResponseTransformer<T>`** - Full response transformation function

#### Example: Normalizing HTTP Response

```typescript
import type {
  ResponseNormalizerConfig,
  NormalizedResponse,
} from "@wavespec/kit/response";

// Schema defines how to extract content from HTTP response
const httpSchema: ResponseNormalizerConfig<HttpResponse> = {
  contentPath: "data", // Extract from response.data
  errorPath: "error", // Check response.error for errors
  errorExtractor: (data) => data.status >= 400,
  itemNormalizer: (item) => ({
    type: "text",
    text: JSON.stringify(item, null, 2),
  }),
};

// Coming in Phase 2: ResponseNormalizer class will apply this schema
```

#### Example: Building Content Items

Coming in Phase 2, these helpers will create properly typed ContentItems:

```typescript
import { textContent, imageContent } from "@wavespec/kit/response";

const items = [
  textContent("Hello, world!"),
  imageContent(
    "/9j/4AAQSkZJRg...", // base64 encoded PNG
    "image/png"
  ),
];
```

---

### Error Helpers (`@wavespec/kit/error`)

**Purpose:** Map protocol-specific errors to structured `CoachingError`s with helpful hints and fixes.

**When to use:** When catching and transforming errors from external services.

**[Full Documentation](./docs/utilities/error.md)**

#### Available Types

From `@wavespec/kit/error`:

- **`ErrorMatcher`** - Pattern matching strategies (string, RegExp, or function)
- **`ErrorPattern`** - Complete error definition with matching, code, hints, and fix suggestions
- **`ErrorMapperConfig`** - Configuration for error mapper defaults
- **`ErrorContext`** - Context passed when mapping errors (operation, tool name, etc.)
- **`MappedError`** - The final structured error after mapping

#### Example: Pattern-Based Error Handling

```typescript
import {
  ErrorMapper,
  type ErrorPattern,
  type ErrorContext,
} from "@wavespec/kit/error";
import { ERROR_CODES } from "@wavespec/types";

const errorMapper = new ErrorMapper({
  defaultCode: "ADAPTER_ERROR",
  defaultHint: "An unexpected error occurred",
  defaultFix: "Enable debug logging for more details",
});

// String matcher - case-insensitive substring
errorMapper.addPattern({
  match: "ECONNREFUSED",
  code: "ADAPTER_CONNECTION_FAILED",
  hint: "The server refused the connection",
  fix: "Verify the server is running and the hostname is correct",
} as ErrorPattern);

// RegExp matcher - pattern matching
errorMapper.addPattern({
  match: /timeout|timed out/i,
  code: "OPERATION_TIMEOUT",
  messageTemplate: "Operation timed out: {message}",
  hint: "The server took too long to respond",
  fix: "Increase the timeout or check server performance",
} as ErrorPattern);

// Function matcher - custom logic
errorMapper.addPattern({
  match: (error) => (error as any).code === "ERR_MODULE_NOT_FOUND",
  code: "DEPENDENCY_NOT_FOUND",
  hint: "A required dependency is missing",
  fix: "Run npm install to install dependencies",
} as ErrorPattern);

// Usage
try {
  await operation();
} catch (error) {
  const context: ErrorContext = { operation: "list_tools" };
  throw errorMapper.map(error, context);
}
```

#### Common Error Codes

Error codes are imported from `@wavespec/types`:

```typescript
import { ERROR_CODES } from "@wavespec/types";

// Available codes include:
// - ADAPTER_CONNECTION_FAILED
// - OPERATION_TIMEOUT
// - INVALID_PARAMETERS
// - RESOURCE_NOT_FOUND
// - AUTHENTICATION_FAILED
// - UNKNOWN_ERROR
// ... and 43+ more
```

---

### Discovery Helpers (`@wavespec/kit/discovery`)

**Purpose:** Fuzzy-match tool/resource names to provide helpful suggestions when exact matches aren't found.

**When to use:** When validating requested tools/resources against available options.

**[Full Documentation](./docs/utilities/discovery.md)**

#### Available Types

From `@wavespec/kit/discovery`:

- **`FuzzyMatchResult`** - Result containing matched value, distance, and similarity
- **`FuzzyMatchOptions`** - Configuration for match behavior (maxResults, minSimilarity, caseSensitive)

#### Example: Suggesting Correct Tool Names

```typescript
import {
  findClosestMatches,
  type FuzzyMatchResult,
  type FuzzyMatchOptions,
} from "@wavespec/kit/discovery";

// Get available tools from cache
const tools = await ctx.discoveryCache?.getTools(async () => {
  return client.listTools();
});

const toolNames = tools.map((t) => t.name);

// User requested "reead_tool" - find similar names
const input = "reead_tool";
const options: FuzzyMatchOptions = {
  maxResults: 3,
  minSimilarity: 0.4, // 40% similarity threshold
  caseSensitive: false,
};

const suggestions: FuzzyMatchResult[] = findClosestMatches(
  input,
  toolNames,
  options
);

// suggestions[0] might be: { value: "read_tool", distance: 1, similarity: 0.91 }

if (suggestions.length > 0) {
  console.log(`Did you mean: ${suggestions[0].value}?`);
} else {
  console.log("No similar tools found");
}
```

---

### VCR Helpers (`@wavespec/kit/vcr`)

**Purpose:** Extract request data from test specs for cassette-based record/replay functionality.

**When to use:** When implementing adapters that support cassette recording for faster testing.

**[Full Documentation](./docs/utilities/vcr.md)**

#### Available Types

From `@wavespec/kit/vcr`:

- **`OperationData`** - Extracted operation with type and adapter-specific data
- **`RequestExtractor`** - Interface for implementing extraction in your adapter
- **`ExtractorConfig`** - Configuration for extraction behavior
- **`Cassette`** - Full cassette format for stored interactions
- **`Interaction`** - Single recorded request/response pair

#### Example: Implementing Request Extraction

```typescript
import type {
  RequestExtractor,
  OperationData,
  ExtractorConfig,
} from "@wavespec/kit/vcr";
import type { TestSpec } from "@wavespec/types";

// Coming in Phase 2: BaseRequestExtractor base class

export class HttpRequestExtractor implements RequestExtractor {
  constructor(private config?: ExtractorConfig) {}

  extract(spec: TestSpec): OperationData {
    const { operation, params } = spec;

    return {
      operation,
      data: {
        path: params.path,
        query: params.query,
        headers: params.headers,
        body: params.body,
        method: params.method,
      },
    };
  }
}

// Usage in adapter
export class HttpAdapter {
  private extractor = new HttpRequestExtractor();

  // When recording: pass extracted data to cassette recorder
  const operationData = this.extractor.extract(spec);
  cassette.record(operationData, response);

  // When replaying: use data to find matching cassette
  const operationData = this.extractor.extract(spec);
  const recorded = cassette.find(operationData);
}
```

---

### Fixtures (`@wavespec/kit/fixtures`)

**Purpose:** Create test data without boilerplate for unit testing adapters.

**When to use:** When writing unit tests for adapter logic.

#### Available Utilities

From `@wavespec/kit/fixtures`:

- **`createTestSpec(options?)`** - Generate TestSpec objects with smart defaults
- **`text(text, mimeType?)`** - Create text ContentItem
- **`json(obj)`** - Create JSON text ContentItem from object
- **`image(data, mimeType?)`** - Create image ContentItem
- **`resource(data, mimeType?)`** - Create resource ContentItem
- **`createMockDiscoveryCache(options?)`** - Create mock discovery cache

#### Example: Using Fixtures in Tests

```typescript
import { createTestSpec, text, json } from "@wavespec/kit/fixtures";
import { MyAdapter } from "./my-adapter";

describe("MyAdapter", () => {
  it("should parse JSON responses", () => {
    const adapter = new MyAdapter();
    const spec = createTestSpec({
      operation: "fetch",
      params: { url: "https://api.example.com" }
    });

    const mockResponse = {
      content: [json({ status: "ok", data: [1, 2, 3] })]
    };

    const result = adapter.parseResponse(mockResponse);
    expect(result.data).toEqual([1, 2, 3]);
  });

  it("should handle text content", () => {
    const adapter = new MyAdapter();
    const content = [text("Hello world", "text/plain")];

    const output = adapter.computeOutput(content);
    expect(output).toBe("Hello world");
  });
});
```

---

### Testing Utilities (`@wavespec/kit/testing`)

**Purpose:** Create mock test contexts for adapter unit testing.

**When to use:** When testing adapter internals that require a TestContext.

#### Available Utilities

From `@wavespec/kit/testing`:

- **`createMockContext(overrides?)`** - Create TestContext with smart defaults

#### Example: Testing with Mock Context

```typescript
import { createMockContext } from "@wavespec/kit/testing";
import { createTestSpec } from "@wavespec/kit/fixtures";
import { MyAdapter } from "./my-adapter";

describe("MyAdapter with context", () => {
  it("should respect context timeout", async () => {
    const adapter = new MyAdapter();
    const ctx = createMockContext({
      mode: "live",
      timeout: 30000,
      adapterConfig: { apiKey: "test-key" }
    });

    const spec = createTestSpec({
      operation: "fetch",
      params: { url: "https://api.example.com" }
    });

    const result = await adapter.run(spec, ctx);
    expect(result.response.isError).toBe(false);
  });
});
```

---

## Testing Your Adapter

There are two types of testing for adapters:

### 1. Unit Tests (Use adapter-kit utilities)

For testing adapter internals, parsing logic, and transformations:

```typescript
import { describe, it, expect } from "vitest";
import { text, json, createMockContext } from "@wavespec/kit/fixtures";
import { MyAdapter } from "./my-adapter";

describe("MyAdapter unit tests", () => {
  it("should parse responses correctly", () => {
    const adapter = new MyAdapter({ apiKey: "test" });
    const mockResponse = {
      content: [json({ status: "ok", data: [1, 2, 3] })]
    };

    const parsed = adapter.extractData(mockResponse);
    expect(parsed).toEqual([1, 2, 3]);
  });
});
```

### 2. Integration Tests (Use harness CLI)

For end-to-end testing with real or recorded API interactions, **use the harness CLI**:

```yaml
# tests/my-adapter.yaml
suite_name: "My Adapter Integration Tests"
adapter: "my-adapter"
mode: "record"  # Record once, then replay
isolation: "per_test"

tests:
  - name: "should fetch data"
    operation: "fetch"
    params: { url: "https://api.example.com" }
    assert:
      not_error: true
      has_content: true
```

```bash
# Register your adapter
$ harness register my-adapter ./dist/my-adapter.js

# Run integration tests (first run records, subsequent runs replay)
$ harness run tests/my-adapter.yaml --adapter my-adapter
```

**Why use the harness CLI for integration tests?**

- Full VCR record/replay support (test against real APIs, replay offline)
- Isolation modes (per_test, per_suite, none)
- Variable interpolation
- Matches production behavior exactly
- No test framework boilerplate needed
- Cassettes ensure consistent, fast CI/CD runs

---

## Architecture & API Design

### Response Subpath

**Phase 1 (Current):** Type definitions
- `NormalizedResponse` interface
- `ResponseNormalizerConfig` for schema-based transformation
- `PathExtractor`, `ContentBuilder`, `OutputTextComputer` types

**Phase 2 (Coming Soon):** Implementation
- `ResponseNormalizer` class - applies config to transform responses
- `computeOutputText()` - extracts text from ContentItems
- Builder functions: `textContent()`, `imageContent()`, `resourceContent()`

### Error Subpath

**Phase 1 (Current):** Type definitions
- `ErrorPattern` for pattern-based error matching
- `ErrorMatcher` supporting string, RegExp, and function matchers
- `ErrorMapperConfig` for defaults
- `ErrorContext` for operation context

**Phase 2 (Coming Soon):** Implementation
- `ErrorMapper` class - applies patterns to errors
- Common error patterns for network, timeout, validation failures

### Discovery Subpath

**Phase 1 (Current):** Type definitions
- `FuzzyMatchResult` with distance and similarity scores
- `FuzzyMatchOptions` for controlling match behavior

**Phase 2 (Coming Soon):** Implementation
- `findClosestMatches()` - Levenshtein distance-based fuzzy matching
- Built on top of `fastest-levenshtein` for performance

### VCR Subpath

**Phase 1 (Current):** Type definitions
- `RequestExtractor` interface
- `OperationData` format
- `Cassette` format with interactions
- `ExtractorConfig` for customization

**Phase 2 (Coming Soon):** Implementation
- `BaseRequestExtractor` abstract class
- Helper utilities for common extraction patterns

---

## Development Roadmap

### Phase 1: Foundations (Complete)

- Package infrastructure
- Subpath exports configured
- Type definitions for all 4 helpers
- Build/test infrastructure

### Phase 2: Core Helpers (In Progress)

**Week 3:**
- Response normalization implementation
- Error mapping implementation
- Tests for response and error helpers
- 100% coverage

**Week 4:**
- Fuzzy matching implementation
- VCR helper base classes
- Tests for discovery and VCR helpers
- HTTP adapter migration (proof-of-concept)

### Phase 3: Developer Tools (Planned)

- `@harness/adapter-devkit` package
- Test context builders
- Assertion helpers
- Scaffolding CLI (`create-adapter`)

---

## Example Adapters

### Minimal HTTP Adapter

```typescript
import type { Adapter, TestContext, TestResult } from "@wavespec/types";

export class MinimalHttpAdapter implements Adapter {
  readonly name = "http";
  readonly version = "0.1.0";

  async run(spec: TestSpec, ctx: TestContext): Promise<TestResult> {
    const response = await fetch(spec.params.url);
    const data = await response.text();

    return {
      response: {
        content: [{ type: "text", text: data }],
        output_text: data,
        isError: !response.ok,
        raw: { status: response.status },
      },
      durationMs: 0,
    };
  }
}
```

### Full-Featured HTTP Adapter (With Helpers)

Coming in Phase 2 - will show:
- Response normalization with schema
- Error mapping with patterns
- Fuzzy matching for resource discovery
- VCR cassette recording

---

## Types and Imports

### All Available Types

```typescript
// Response types
import type {
  NormalizedResponse,
  ContentBuilder,
  PathExtractor,
  ResponseNormalizerConfig,
  OutputTextComputer,
  ResponseTransformer,
} from "@wavespec/kit/response";

// Error types
import type {
  ErrorMatcher,
  ErrorPattern,
  ErrorMapperConfig,
  ErrorContext,
  MappedError,
} from "@wavespec/kit/error";

// Discovery types
import type {
  FuzzyMatchResult,
  FuzzyMatchOptions,
} from "@wavespec/kit/discovery";

// VCR types
import type {
  OperationData,
  RequestExtractor,
  ExtractorConfig,
  Cassette,
  Interaction,
  ResponseData,
} from "@wavespec/kit/vcr";
```

### Importing from adapter-types

Many types come from the base `@wavespec/types` package:

```typescript
import type {
  Adapter,
  TestContext,
  TestResult,
  TestSpec,
  ValidationResult,
  ContentItem,
  CoachingError,
  ErrorSeverity,
  RecoveryStrategy,
} from "@wavespec/types";

import { ERROR_CODES } from "@wavespec/types";
```

---

## Dependencies

### Production Dependencies

- **`fastest-levenshtein@^1.0.16`** - High-performance Levenshtein distance for fuzzy matching

### Peer Dependencies

- **`@wavespec/types@^0.1.0`** - Base types (required)

### No Runtime Dependencies

The kit has minimal dependencies:
- No heavy frameworks
- No polyfills
- Pure TypeScript/JavaScript
- Fast and lightweight

---

## API Stability

This package follows semantic versioning:

- **Major (0.x):** Breaking changes to public API
- **Minor (.x):** New features, backward compatible
- **Patch (.x.x):** Bug fixes only

**Current Status:** 0.1.0 - Experimental

While in 0.x, minor version bumps may include breaking changes. After 1.0.0, we commit to semantic versioning.

---

## Contributing

Contributions are welcome! See the main repository's CONTRIBUTING.md for details.

**For adapter-kit specifically:**
- Add types and implementations to appropriate subpaths
- Keep related helpers together (don't split response helpers)
- Update JSDoc comments with examples
- Add tests for all public APIs (100% coverage target)
- Update this README if adding new helpers

---

## Testing

The adapter-kit package includes comprehensive tests for all helpers:

```bash
# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Type checking
npm run type-check

# Build
npm run build
```

---

## Troubleshooting

### "Cannot find module '@wavespec/kit'"

Make sure you've installed both packages:

```bash
npm install @wavespec/kit @wavespec/types
```

### "Cannot find module '@wavespec/kit/response'"

The subpath exports might not be set up correctly. Verify your import matches the available subpaths:
- `@wavespec/kit/response`
- `@wavespec/kit/error`
- `@wavespec/kit/discovery`
- `@wavespec/kit/vcr`

### TypeScript strict mode errors

The kit is designed for TypeScript strict mode. Make sure your `tsconfig.json` has:

```json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true
  }
}
```

---

## Reference Links

- **Adapter Types:** [@wavespec/types](../adapter-types)
- **Agent Harness:** [Main Repository](https://github.com/mikec-ai/agent-harness)
- **Adapter Implementation Guide:** [Coming in Phase 2]

---

## License

MIT - See LICENSE file in root repository

---

## Support

For issues, questions, or suggestions:
1. Check the [troubleshooting section](#troubleshooting) above
2. Open an issue on GitHub
3. See existing adapter implementations for examples

---

**Last Updated:** November 6, 2025
**Version:** 0.1.0
**Status:** Phase 1 Complete - Accepting Phase 2 Implementations
