@wavespec/kit
v1.0.0
Published
Shared helpers and utilities for Harness adapters
Readme
@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
npm install @wavespec/kit @wavespec/typesOr with your favorite package manager:
# pnpm
pnpm add @wavespec/kit @wavespec/types
# yarn
yarn add @wavespec/kit @wavespec/typesPeer 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.
{
"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
strictmode enabled)
Quick Start
Minimal Adapter (Types Only)
If you only need types from @wavespec/types, you don't need the kit:
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:
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 - Quick reference and comparison
- Discovery Utilities - Caching and fuzzy matching
- Error Utilities - Pattern-based error handling
- Response Utilities - Response normalization
- VCR Utilities - Record/replay for testing
- Dispatch Utilities - Operation routing
- Parser Utilities - JSON and NDJSON parsing
- Content-Type Utilities - Content detection
- Integration Guide - 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.
Available Types
From @wavespec/kit/response:
NormalizedResponse- The normalized output with content, output_text, and error flagContentBuilder<T>- Function type for converting protocol-specific items to ContentItemsPathExtractor<T>- Flexible path specification (string, array, or function)ResponseNormalizerConfig<T>- Schema-driven configuration for response transformationOutputTextComputer- Function type for computing output_text from ContentItemsResponseTransformer<T>- Full response transformation function
Example: Normalizing HTTP Response
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 schemaExample: Building Content Items
Coming in Phase 2, these helpers will create properly typed ContentItems:
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 CoachingErrors with helpful hints and fixes.
When to use: When catching and transforming errors from external services.
Available Types
From @wavespec/kit/error:
ErrorMatcher- Pattern matching strategies (string, RegExp, or function)ErrorPattern- Complete error definition with matching, code, hints, and fix suggestionsErrorMapperConfig- Configuration for error mapper defaultsErrorContext- Context passed when mapping errors (operation, tool name, etc.)MappedError- The final structured error after mapping
Example: Pattern-Based Error Handling
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:
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+ moreDiscovery 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.
Available Types
From @wavespec/kit/discovery:
FuzzyMatchResult- Result containing matched value, distance, and similarityFuzzyMatchOptions- Configuration for match behavior (maxResults, minSimilarity, caseSensitive)
Example: Suggesting Correct Tool Names
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.
Available Types
From @wavespec/kit/vcr:
OperationData- Extracted operation with type and adapter-specific dataRequestExtractor- Interface for implementing extraction in your adapterExtractorConfig- Configuration for extraction behaviorCassette- Full cassette format for stored interactionsInteraction- Single recorded request/response pair
Example: Implementing Request Extraction
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 defaultstext(text, mimeType?)- Create text ContentItemjson(obj)- Create JSON text ContentItem from objectimage(data, mimeType?)- Create image ContentItemresource(data, mimeType?)- Create resource ContentItemcreateMockDiscoveryCache(options?)- Create mock discovery cache
Example: Using Fixtures in Tests
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
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:
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:
# 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# 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-adapterWhy 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
NormalizedResponseinterfaceResponseNormalizerConfigfor schema-based transformationPathExtractor,ContentBuilder,OutputTextComputertypes
Phase 2 (Coming Soon): Implementation
ResponseNormalizerclass - applies config to transform responsescomputeOutputText()- extracts text from ContentItems- Builder functions:
textContent(),imageContent(),resourceContent()
Error Subpath
Phase 1 (Current): Type definitions
ErrorPatternfor pattern-based error matchingErrorMatchersupporting string, RegExp, and function matchersErrorMapperConfigfor defaultsErrorContextfor operation context
Phase 2 (Coming Soon): Implementation
ErrorMapperclass - applies patterns to errors- Common error patterns for network, timeout, validation failures
Discovery Subpath
Phase 1 (Current): Type definitions
FuzzyMatchResultwith distance and similarity scoresFuzzyMatchOptionsfor controlling match behavior
Phase 2 (Coming Soon): Implementation
findClosestMatches()- Levenshtein distance-based fuzzy matching- Built on top of
fastest-levenshteinfor performance
VCR Subpath
Phase 1 (Current): Type definitions
RequestExtractorinterfaceOperationDataformatCassetteformat with interactionsExtractorConfigfor customization
Phase 2 (Coming Soon): Implementation
BaseRequestExtractorabstract 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-devkitpackage- Test context builders
- Assertion helpers
- Scaffolding CLI (
create-adapter)
Example Adapters
Minimal HTTP Adapter
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
// 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:
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:
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Type checking
npm run type-check
# Build
npm run buildTroubleshooting
"Cannot find module '@wavespec/kit'"
Make sure you've installed both packages:
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:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true
}
}Reference Links
- Adapter Types: @wavespec/types
- Agent Harness: Main Repository
- Adapter Implementation Guide: [Coming in Phase 2]
License
MIT - See LICENSE file in root repository
Support
For issues, questions, or suggestions:
- Check the troubleshooting section above
- Open an issue on GitHub
- See existing adapter implementations for examples
Last Updated: November 6, 2025 Version: 0.1.0 Status: Phase 1 Complete - Accepting Phase 2 Implementations
