> ## Documentation Index
> Fetch the complete documentation index at: https://actelos.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# AdapterModule

> The contract every adapter module implements

```ts theme={null}
interface AdapterModule extends Module {
  generateDefinition(input: string): Promise<ServiceDefinition>;
  hydrateService(state: ServiceState): Promise<void>;
  dehydrateService(id: string): Promise<void>;
  invoke(input: InvokeInput): Promise<unknown>;
}
```

Adapter modules translate service definitions and tool invocations into
calls to the underlying end service. See
[Adapter Modules](/cyrnel/docs/adapters-modules) for the user-facing
description.

## `generateDefinition(input)`

Convert a raw definition string (an OpenAPI document, a custom schema, a
URL to fetch and parse, whatever this adapter understands) into a
[`ServiceDefinition`](/cyrnel/specs/types#servicedefinition).

* Called from `POST /services` (direct install), `POST /services/install`
  (registry install), `PATCH /services/:serviceId` (direct update),
  `POST /services/:serviceId/update` (registry update), and
  during `PATCH /modules/:moduleId` / `POST /modules/:moduleId/update`
  to regenerate every service that targets the updated adapter.
* Expected to be **pure**, given the same input string, return the same
  definition. Adapters should not rely on instance state here.
* Throw to reject the install. The host returns the thrown message in
  the error response.
* Populate `adapterDomain` (on the service and on each tool) with
  whatever the adapter needs at invoke time. Treat it as your private
  scratch space. The host never inspects it.

The host validates that the service `id` and every tool `id` is a valid
TypeScript identifier before persisting.

## `hydrateService(state)`

Receive a snapshot of one service. Called when:

* A service that targets this adapter is enabled.
* Configuration or secrets on an already-enabled service are patched.
* This adapter is activated while the service is already enabled.

```ts theme={null}
interface ServiceState {
  id: string;
  adapterDomain: Record<string, unknown>;
  tools: Record<string, ToolState>;
  config: Record<string, unknown>;
  secrets: Record<string, unknown>;
}
```

Implementations should **replace** any internal state they kept for
`state.id`, not merge into it. `secrets` is decrypted plaintext; do not
log it.

Throw to reject the operation, the host rolls back `enabled` on the
service before surfacing the error.

## `dehydrateService(id)`

Drop any internal state for service `id`. Called when:

* A service is disabled.
* A service is deleted.
* A service is updated (immediately before the new tools are inserted).

Should be idempotent. If the adapter never saw the service, return
without error.

## `invoke(input)`

```ts theme={null}
interface InvokeInput {
  serviceId: string;
  toolId: string;
  parameters: Record<string, unknown>;
}
```

Perform one tool call. The host has already verified:

* The service exists and is enabled.
* The tool exists and is enabled.
* The service is owned by this adapter.

The adapter is free to validate `parameters` against the stored
`inputSchema` itself (recommended) before issuing the outbound call. The
returned value is whatever the environment receives back from tool invokes.

Throw on any failure. Throwing an `Error` with a useful message is fine;
the host surfaces it to the calling process.

## Security

Adapters run with full host privileges and receive decrypted secrets.
See [Security → Adapter modules](/cyrnel/docs/security#adapter-modules) for
operator-facing guidance and **what a well-behaved adapter must not do**.

## Minimal Skeleton

```ts theme={null}
import type { AdapterModule, InvokeInput, ModuleSetupContext, ServiceDefinition, ServiceState } from "@cyrnel/sdk";

class MyAdapter implements AdapterModule {
  private state = new Map<string, ServiceState>();

  async setup(_context: ModuleSetupContext) {}
  async teardown() { this.state.clear(); }

  async generateDefinition(input: string): Promise<ServiceDefinition> {
    return {
      name: "...",
      description: "...",
      configSchema: { type: "object", properties: {} },
      secretsSchema: { type: "object", properties: {} },
      adapterDomain: { /* parsed metadata */ },
      tools: [
        {
          id: "ping",
          name: "ping",
          description: "Health check",
          inputSchema: { type: "object" },
          outputSchema: { type: "object" },
          adapterDomain: { /* per-tool routing info */ },
        },
      ],
    };
  }

  async hydrateService(state: ServiceState): Promise<void> {
    this.state.set(state.id, state);
  }

  async dehydrateService(id: string): Promise<void> {
    this.state.delete(id);
  }

  async invoke(input: InvokeInput): Promise<unknown> {
    const svc = this.state.get(input.serviceId);
    if (!svc) throw new Error(`unknown service ${input.serviceId}`);
    // ...use svc.adapterDomain / svc.config / svc.secrets to issue the call
    return { ok: true };
  }
}

export function instantiate(): AdapterModule {
  return new MyAdapter();
}
```

> **Note:** The manifest metadata (name, version, type, etc.) goes in
> `module.json` for custom modules. Only built-in modules ship `export
> const manifest` from code. The skeleton above omits it, see
> [Writing a custom adapter module](/cyrnel/specs/writing-custom-adapter-module)
> for a step-by-step walkthrough and a full working example.
