WakaTime Plugin Integration

Open Source Engineering Contribution to Corsair

An engineering-focused contribution implementing a WakaTime provider plugin for Corsair. The plugin enables secure retrieval of authenticated user details using HTTP Basic Authentication over API keys, featuring strict Zod runtime verification and rate-limiting retry safety.

Contributor Shebha M
Project Corsair
Contribution WakaTime Provider Plugin Integration
Status Merged

Contribution Overview

This contribution integrates WakaTime code statistics tracking context into Corsair's modular provider-plugin architecture. By implementing a lightweight HTTP client and strict response constraints, the plugin establishes safe data-transfer boundaries between Corsair clients (such as AI agents) and the WakaTime REST backend.

Plugin Registration

Registers provider details in core constants, exposing WakaTime identifiers natively to Corsair's registry.

API Basic Auth

Secures API requests using Base64 HTTP Basic Authentication with client API keys, avoiding heavy OAuth configurations.

Robust Retry Handling

Detects 429 rate limit statuses and preserves transport retry delays, driving robust recovery inside Corsair.

Verification coverage

Implements explicit Jest suites asserting HTTP request boundaries, schemas parsing, and error definitions.

Problem & Issue Solved

This contribution directly resolves Corsair Issue #1088, which requested the addition of WakaTime support.

Prior to this integration, Corsair users and agent frameworks could not query developers' coding logs or profile metadata stored in WakaTime. By creating a dedicated provider plugin, this change adds WakaTime to the platform's extensible list of integrations, enabling seamless profile metadata retrieval through a standardized framework contract.

Integration Architecture

The WakaTime integration fits into Corsair's provider structure, isolating all API requests, validation logic, and authorization mappings inside a modular packages layer.

1

AI Agent / Corsair

Dispatches tool calls to retrieve the authenticated user profile.

2

Corsair Plugin Layer

Resolves runtime context, credentials, and error configs.

3

WakaTime Plugin

Maps endpoint parameters and handles route execution.

6

Validated Response

Validates response structures at runtime using Zod.

5

WakaTime API

GET /users/current verifies auth and returns payload.

4

WakaTime HTTP Client

Constructs requests with Base64 API Basic Auth.

7

Validated WakaTime User Data

Returns type-safe profile details structure to the calling Corsair agent application.

Authentication & API Operations

API Key Basic Authentication

The WakaTime integration uses API-Key Authentication only via HTTP Basic Authentication. There is no OAuth flow, no webhooks, and no subscription structures required by this plugin, ensuring a lightweight and secure integration.

The API key is base64-encoded and transmitted inside the HTTP Authorization header of every outbound request, as implemented in the client wrapper:

const config: OpenAPIConfig = {
    BASE: 'https://api.wakatime.com/api/v1',
    VERSION: '1.0.0',
    WITH_CREDENTIALS: false,
    CREDENTIALS: 'omit',
    HEADERS: {
        'Content-Type': 'application/json',
        'Authorization': `Basic ${Buffer.from(apiKey).toString('base64')}`,
    },
};

Supported Endpoint

The plugin registers one main profile retrieval action, mapping Corsair framework tools to the downstream WakaTime endpoint.

Action Name HTTP Method Endpoint Path Purpose
users.current GET /api/v1/users/current Retrieves details of the currently authenticated WakaTime user profile.
/** Fetches and validates the authenticated WakaTime user response. */
export const getCurrentUser: WakaTimeEndpoints['getCurrentUser'] = async (
    ctx,
) => {
    const response = await makeWakaTimeRequest<
        WakaTimeEndpointOutputs['getCurrentUser']
    >('users/current', ctx.key);

    const validatedResponse =
        WakaTimeEndpointOutputSchemas.getCurrentUser.parse(response);

    await logEventFromContext(ctx, 'wakatime.users.current', {}, 'completed');

    return validatedResponse;
};

Type Safety & Validation

To shield client systems from malformed data payloads or undocumented upstream shifts, all request parameters and response outputs are validated using Zod at the plugin boundary.

import { z } from 'zod';

const GetCurrentUserInputSchema = z.object({});

const GetCurrentUserDataSchema = z.object({
    id: z.string(),
    username: z.string().optional(),
    display_name: z.string().optional(),
    email: z.string().optional(),
});

const GetCurrentUserResponseSchema = z.object({
    data: GetCurrentUserDataSchema,
});

Validation Constraints & Characteristics:

Outbound Request
WakaTime REST API
GetCurrentUserResponseSchema.parse()
Type-Safe Result

Error Handling & Retry Protocol

The WakaTime client maps network exceptions to a custom WakaTimeAPIError type. Crucially, the error handler preserves the original status codes and HTTP metadata from Corsair's core ApiError.

By retaining fields like status === 429 and the retryAfter parameter, the plugin integrates seamlessly with Corsair's framework-level retry loops, enabling automated recovery when rate limits are hit.

export const errorHandlers = {
    RATE_LIMIT_ERROR: {
        match: (error: Error) => {
            if (
                (error instanceof ApiError || error instanceof WakaTimeAPIError) &&
                error.status === 429
            )
                return true;
            const msg = error.message.toLowerCase();
            return msg.includes('rate_limited') || msg.includes('429');
        },
        handler: async (error: Error) => {
            let retryAfterMs: number | undefined;
            if (
                (error instanceof ApiError || error instanceof WakaTimeAPIError) &&
                error.retryAfter !== undefined
            ) {
                retryAfterMs = error.retryAfter;
            }
            return { maxRetries: 5, headersRetryAfterMs: retryAfterMs };
        },
    },
    AUTH_ERROR: {
        match: (error: Error) => {
            if (
                (error instanceof ApiError || error instanceof WakaTimeAPIError) &&
                error.status === 401
            )
                return true;
            const msg = error.message.toLowerCase();
            return msg.includes('unauthorized') || msg.includes('invalid_auth');
        },
        handler: async () => ({ maxRetries: 0 }),
    },
} satisfies CorsairErrorHandler;
Request Failure
Determine Status Code
HTTP 401 (Auth Failure)
Halt execution (maxRetries: 0)
HTTP 429 (Rate Limited)
Has Retry-After?
YES
Extract header delay value
NO
Fall back to standard interval
Retry Request (Max 5 attempts)

Implementation Structure

The directory footprint adheres strictly to Corsair's codebase convention for integrations, locating the plugin source files inside a dedicated packages directory.

packages/wakatime/
├── client.ts                 // HTTP requester, sets Base URL and Basic Auth headers
├── client.test.ts            // Mocks requests, verifies client auth, query mapping, and errors
├── error-handlers.ts         // Defines rate-limiting and auth match/recovery logic
├── error-handlers.test.ts    // Tests RATE_LIMIT_ERROR checks and delay extraction
├── index.ts                  // Registers metadata, endpoints nested mapping, and options schema
├── schema.test.ts            // Asserts semver schema conventions and Zod payload parsers
├── endpoints/
│   ├── index.ts              // Exports users current operation entrypoint
│   ├── types.ts              // Zod models representing inputs/outputs structures
│   ├── users.ts              // Implements getCurrentUser caller logic
│   └── users.test.ts         // Asserts users endpoints validation and completion events
├── schema/
│   └── index.ts              // Exports empty WakaTimeSchema (data is non-persistent)
└── webhooks/                 // Empty directory (plugin has no webhook dependencies)
📁 packages/wakatime/client.ts
Defines WakaTimeAPIError mapping logic and uses the client request executor with WakaTime API credentials.
📁 packages/wakatime/schema/index.ts
Exports WakaTimeSchema with entities: {}, asserting the plugin's non-persistent data design.
📁 packages/corsair/core/constants.ts
Integrates WakaTime into Corsair core's BaseProviders and ProviderDisplayNames mappings.

Verification & Quality Assurance

The integration is fully verified locally via Jest test suites, providing coverage for client requests, schema validation, endpoints, and error handlers.

✓
4 Test Suites Passed
⚗
Jest Test Runner Framework
📁 client.test.ts

Verifies request authorization encoding, query-string mapping, and ApiError translation.

📁 error-handlers.test.ts

Verifies rate-limit identification matches HTTP 429 status and parses header retry intervals.

📁 schema.test.ts

Asserts proper SemVer formatting of the plugin version, empty entities structure, and response parse rules.

📁 users.test.ts

Validates response schema mapping on completion, context events emission, and invalid schema detection.

Key Engineering Decisions

Several intentional engineering choices were made during implementation to guarantee alignment with Corsair's codebase architecture and maintainability guidelines:

1

Consistent Architecture Fit

Leveraged Corsair's existing modular plugin registration framework, avoiding custom transport layers or configuration boilerplate.

2

API-Key Basic Authentication

Chose a straightforward Basic Authentication header scheme instead of adding complex OAuth setups or token refresh states.

3

Runtime Response Validation

Enforced Zod validation at the boundary, ensuring API contract drift is immediately caught before downstream usage.

4

Non-Persistent Design

Kept the integration stateless by defining an empty entities block, as storing code session metrics is outside the plugin's scope.

5

Preservation of Error Context

Decided to forward original HTTP exception properties within WakaTimeAPIError, allowing Corsair to use native retry timing.

Review & Merge

The pull request passed all repository validation checks and CI testing before being reviewed and merged by the repository maintainer.

✓ MERGED Corsair Maintainer

Pull Request: #1097 — Feat/wakatime | Repository: corsairdev/corsair

"Basic Auth looks correct, tests match conventions. Happy to merge this, thanks Shebha M!"
Issue #1088
Implementation
Testing
PR #1097
Approval
Merged to Main

PR #1097 was successfully merged into the main branch of corsairdev/corsair.

WakaTime Current User API Demo

This interactive demo console simulates the WakaTime current-user request lifecycle, showing the Basic Auth setup and response validation against the Zod schema.

wakatime-client-simulation
$ corsair run wakatime:users.current

Contribution Impact & Takeaways

Functional Utility

The merged integration provides substantial functional utility to the framework's capability set:

Engineering Takeaways

Building and merging this plugin highlighted several key lessons in engineering for complex agent platforms:

Summary

Shebha M's contribution successfully integrated WakaTime profile lookup capabilities into the Corsair platform via PR #1097, establishing type safety, rate-limit retry support, and comprehensive test coverage.

MERGED INTO MAIN