npm package: @kimchi-by-castai/opencode-otel-plugin
Description: OpenCode OTEL plugin for Kimchi - sends usage telemetry
Author: Kimchi Team
Latest version: 1.3.0

# OpenCode OTEL Plugin for Kimchi

Sends usage telemetry and productivity metrics from OpenCode to the Kimchi service.

OpenCode version 1.2.20+

## Installation

### Option 1: NPM package (recommended)

Add to your `~/.config/opencode/opencode.json`:

```json
{
  "plugin": ["@kimchi-by-castai/opencode-otel-plugin@latest"]
}
```

To pin to a specific version:

```json
{
  "plugin": ["@kimchi-by-castai/opencode-otel-plugin@1.0.7"]
}
```

Set environment variables (see [Configuration](#configuration) section below), then restart OpenCode.

### Option 2: Local plugin

1. Copy `plugin.ts` to your OpenCode plugins directory:

```bash
mkdir -p ~/.config/opencode/plugins
cp plugin.ts ~/.config/opencode/plugins/otel.ts
```

2. Set environment variables (see [Configuration](#configuration) section below)

3. Restart OpenCode.

### Option 3: Project-level plugin

1. Create the plugins directory in your project:

```bash
mkdir -p .opencode/plugins
```

2. Copy `plugin.ts` to `.opencode/plugins/otel.ts`

3. Set the environment variables (same as above)

## Configuration

### Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `OPENCODE_ENABLE_TELEMETRY` | Yes | Set to `1` to enable telemetry |
| `OPENCODE_OTLP_ENDPOINT` | Yes | Kimchi logs ingest endpoint URL |
| `OPENCODE_OTLP_METRICS_ENDPOINT` | Yes | Kimchi metrics ingest endpoint URL |
| `OPENCODE_OTLP_HEADERS` | Yes | Authorization header with your Kimchi API key |

#### Example Environment Variables

Add these to your shell config (`~/.zshrc`, `~/.bashrc`, etc.):

```bash
# Enable the plugin
export OPENCODE_ENABLE_TELEMETRY=1

# Kimchi endpoint for log ingestion (api_request events)
export OPENCODE_OTLP_ENDPOINT=https://api.cast.ai/ai-optimizer/v1beta/logs:ingest

# Kimchi endpoint for metrics ingestion (productivity metrics)
export OPENCODE_OTLP_METRICS_ENDPOINT=https://api.cast.ai/ai-optimizer/v1beta/metrics:ingest

# Authorization header with your Kimchi API key
export OPENCODE_OTLP_HEADERS="Authorization=Bearer YOUR_API_KEY_HERE"
```

After adding, restart your shell or run `source ~/.zshrc`.

### Provider Configuration

The plugin reads provider information from your OpenCode config (`~/.config/opencode/opencode.json`). Ensure your provider key matches one of the valid values below.

### Valid Provider Values

| Provider Key | Description |
|--------------|-------------|
| `openai` | OpenAI |
| `anthropic` | Anthropic (Claude) |
| `azure` | Azure OpenAI |
| `azure_ai` | Azure AI |
| `gemini` | Google Gemini |
| `vertex_ai-language-models` | Vertex AI Gemini |
| `vertex_ai-anthropic_models` | Vertex AI Anthropic |
| `groq` | Groq |
| `mistral` | Mistral |
| `codestral` | Codestral |
| `cohere_chat` | Cohere |
| `anyscale` | Anyscale |
| `openrouter` | OpenRouter |
| `databricks` | Databricks |
| `perplexity` | Perplexity |
| `hosted_vllm` | Hosted vLLM |
| `bedrock` | AWS Bedrock |
| `ai-enabler` | Kimchi (serverless models) |

> **Important:** If your provider key does not match one of the valid values listed above, the request will be **rejected** and you will see an error toast notification in OpenCode. Make sure to use a provider key from the list above to ensure your usage data is recorded correctly.

### Example OpenCode Config

```json
{
  "model": "ai-enabler/glm-5-fp8",
  "provider": {
    "ai-enabler": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Kimchi",
      "options": {
        "baseURL": "https://llm.cast.ai/openai/v1",
        "apiKey": "your-api-key"
      },
      "models": {
        "glm-5-fp8": {
          "name": "glm-5-fp8",
          "tool_call": true
        }
      }
    }
  }
}
```

## Data Sent

The plugin sends two types of OTEL data:

### 1. API Request Logs (to logs:ingest)

Sent for each completed assistant message with token usage.

| Attribute | Description |
|-----------|-------------|
| `event.name` | Always `"api_request"` |
| `client` | Always `"opencode"` |
| `model` | Model identifier |
| `provider` | Provider identifier |
| `input_tokens` | Number of input tokens |
| `output_tokens` | Number of output tokens |
| `cost_usd` | Cost in USD |
| `duration_ms` | Request duration in milliseconds (always 0 - OpenCode doesn't expose this data) |

### 2. Productivity Metrics (to metrics:ingest)

Sent as cumulative OTLP Sum metrics with session-level granularity. Metrics are flushed every 30 seconds.

| Metric Name | Dashboard Widget | Description | Attributes |
|-------------|-----------------|-------------|------------|
| `claude_code.token.usage` | Tokens Used | Cumulative token count per model | `session.id`, `type` (input/output), `model` |
| `claude_code.cost.usage` | Total Cost | Cumulative cost per model | `session.id`, `model` |
| `claude_code.commit.count` | Git Commits | Detected from `git commit` bash commands | `session.id` |
| `claude_code.pull_request.count` | Pull Requests | Detected from `gh pr create` bash commands | `session.id` |
| `claude_code.lines_of_code.count` | Lines Modified | Lines added/removed from edit, write, patch tools | `session.id`, `type` (added/removed) |
| `claude_code.code_edit_tool.decision` | Edit Decisions | Edit/write/patch tool invocations by tool and language | `session.id`, `tool_name`, `language`, `decision`, `source` |

### Session Duration

Session duration is derived on the backend from ClickHouse data: for a given `session.id`, the duration is `MAX(created_at) - MIN(created_at)` across all productivity metrics. No dedicated session metric is needed.

### How Metrics Are Collected

| Dashboard Widget | OpenCode Hook | Detection Method |
|-----------------|---------------|-----------------|
| **CLI Sessions** | `session.created` event | Each session gets a unique `session.id` attribute on all metrics |
| **Git Commits** | `tool.execute.after` (bash) | Regex match on `git commit` commands |
| **Pull Requests** | `tool.execute.after` (bash) | Regex match on `gh pr create` commands |
| **Lines Modified** | `tool.execute.after` (edit/write/patch) | Line diff counting from tool args |
| **Edit Decisions** | `tool.execute.after` (edit/write/patch) | Count per tool name + inferred language |
| **Tokens Used** | `message.updated` event | Accumulated from assistant message token counts |
| **Total Cost** | `message.updated` event | Accumulated from assistant message cost |

## Troubleshooting

The plugin shows error notifications via OpenCode toasts when issues occur:

### Error Toast Messages

- **"Invalid provider"** - Your provider key is not recognized. Update your `opencode.json` to use a valid provider key from the list above.
- **"Telemetry error"** - Other errors (network issues, auth failures, etc.)

### Debugging Steps

1. Verify environment variables are set correctly
2. Check your Kimchi API key is valid
3. Ensure the provider key in your OpenCode config matches a valid value
4. Verify the endpoint URLs are correct