Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e05b656
scaffold project with pnpm, posthog-node, and opencode plugin types
Quantumlyy Apr 5, 2026
e66f727
add types, utilities, and version module
Quantumlyy Apr 5, 2026
1fca5a9
add event builders for generation, span, and trace
Quantumlyy Apr 5, 2026
b191247
add plugin entry point with event router and state management
Quantumlyy Apr 5, 2026
5cf9ca8
add README with configuration, events, and privacy docs
Quantumlyy Apr 5, 2026
feca479
remove dead loop in session idle cleanup
Quantumlyy Apr 5, 2026
edfc0be
fix span parenting and string-level secret redaction
Quantumlyy Apr 5, 2026
a9003b5
add vitest tests for event builders and utilities
Quantumlyy Apr 5, 2026
60ab456
fix bearer token redaction, error field leaks, and per-step input con…
Quantumlyy Apr 5, 2026
5003b3f
use static import for posthog-node instead of dynamic import()
Quantumlyy Apr 5, 2026
3105794
redact tool output in step input, clean up per-message state, fix README
Quantumlyy Apr 5, 2026
c0348b5
set version to 0.0.1, add author, CI workflow, and reorder package.json
Quantumlyy Apr 5, 2026
700fc7d
add oxlint/oxfmt, license headers, derive version from package.json
Quantumlyy Apr 5, 2026
d63a62f
update README install instructions to match OpenCode plugin docs
Quantumlyy Apr 5, 2026
9c2358f
switch from pnpm to bun
Quantumlyy Apr 5, 2026
997063b
fix CI: replace pnpm with bun run in workflow steps
Quantumlyy Apr 5, 2026
6aafb71
fix step-input ordering and cache token property names
Quantumlyy Apr 5, 2026
df16bf9
configure package.json and tsconfig for npm publishing
Quantumlyy Apr 5, 2026
63b58df
only ship dist in npm package
Quantumlyy Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# MIT License
#
# Copyright (c) 2026 PostHog Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

name: Setup
description: Setup Node.js and install dependencies

inputs:
install:
description: 'Whether to install dependencies'
required: false
default: 'true'

runs:
using: composite
steps:
- name: Setup Bun
uses: oven-sh/setup-bun@v2

- name: Cache dependencies
uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }}
restore-keys: |
${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
${{ runner.os }}-bun-

- name: Install dependencies
if: inputs.install == 'true'
run: bun install --frozen-lockfile
shell: bash
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# MIT License
#
# Copyright (c) 2026 PostHog Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

name: CI

on:
pull_request:
push:
branches: [main]

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Setup environment
uses: ./.github/actions/setup

- name: Typecheck
run: bun run typecheck

- name: Lint
run: bun run lint

- name: Test
run: bun run test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
dist/
*.tsbuildinfo
.context/
9 changes: 9 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"sortPackageJson": false,
"ignorePatterns": ["node_modules", "dist", "pnpm-lock.yaml"]
}
11 changes: 11 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "promise", "vitest"],
"categories": {
"correctness": "error",
"suspicious": "warn",
"perf": "warn"
},
"rules": {},
"ignorePatterns": ["node_modules", "dist"]
}
91 changes: 90 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,90 @@
# opencode-posthog
# opencode-posthog

PostHog LLM Analytics plugin for [OpenCode](https://opencode.ai). Captures LLM generations, tool executions, and conversation traces, sending them to PostHog as structured `$ai_*` events for the LLM Analytics dashboard.

## Installation

Add `opencode-posthog` to your `opencode.json`:

```json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-posthog"]
}
```

The package is installed automatically at startup and cached in `~/.cache/opencode/node_modules/`.

### Local development

Place the plugin source in your project's `.opencode/plugins/` directory (or `~/.config/opencode/plugins/` for global use). Add `posthog-node` to `.opencode/package.json` so OpenCode installs it at startup:

```json
{
"dependencies": {
"posthog-node": "^5.0.0"
}
}
```

## Configuration

All configuration is via environment variables:

| Variable | Default | Description |
| ------------------------------ | -------------------------- | ----------------------------------------------- |
| `POSTHOG_API_KEY` | _(required)_ | PostHog project API key |
| `POSTHOG_HOST` | `https://us.i.posthog.com` | PostHog instance URL |
| `POSTHOG_PRIVACY_MODE` | `false` | Redact all LLM input/output content when `true` |
| `POSTHOG_ENABLED` | `true` | Set `false` to disable |
| `POSTHOG_DISTINCT_ID` | machine hostname | The `distinct_id` for all events |
| `POSTHOG_PROJECT_NAME` | cwd basename | Project name in all events |
| `POSTHOG_TAGS` | _(none)_ | Custom tags: `key1:val1,key2:val2` |
| `POSTHOG_MAX_ATTRIBUTE_LENGTH` | `12000` | Max length for serialized tool input/output |

If `POSTHOG_API_KEY` is not set, the plugin is a no-op.

## Events

### `$ai_generation` — per LLM call

Emitted for each LLM roundtrip (step-finish part). Properties include:

- `$ai_model`, `$ai_provider` — model and provider identifiers
- `$ai_input_tokens`, `$ai_output_tokens`, `$ai_reasoning_tokens` — token counts
- `$ai_cache_read_input_tokens`, `$ai_cache_creation_input_tokens` — cache token counts
- `$ai_total_cost_usd` — cost in USD
- `$ai_latency` — not available per-step (use trace-level latency)
- `$ai_stop_reason` — `stop`, `tool_calls`, `error`, etc.
- `$ai_input`, `$ai_output_choices` — message content (null in privacy mode)
- `$ai_trace_id`, `$ai_span_id`, `$ai_session_id` — correlation IDs

### `$ai_span` — per tool execution

Emitted when a tool call completes or errors. Properties include:

- `$ai_span_name` — tool name (`read`, `write`, `bash`, `edit`, etc.)
- `$ai_latency` — execution time in seconds
- `$ai_input_state`, `$ai_output_state` — tool input/output (null in privacy mode)
- `$ai_parent_id` — span ID of the generation that triggered this tool
- `$ai_is_error`, `$ai_error` — error status

### `$ai_trace` — per user prompt

Emitted on `session.idle` (agent finished responding). Properties include:

- `$ai_trace_id`, `$ai_session_id` — correlation IDs
- `$ai_latency` — total trace time in seconds
- `$ai_total_input_tokens`, `$ai_total_output_tokens` — accumulated token counts
- `$ai_input_state`, `$ai_output_state` — user prompt and final response
- `$ai_is_error` — whether any step/tool errored

## Privacy

When `POSTHOG_PRIVACY_MODE=true`, all content fields (`$ai_input`, `$ai_output_choices`, `$ai_input_state`, `$ai_output_state`) are set to `null`. Token counts, costs, latency, and model metadata still flow.

Sensitive keys (matching `api_key`, `token`, `secret`, `password`, `authorization`, `credential`, `private_key`) are always redacted in tool inputs/outputs regardless of privacy mode.

## License

MIT
Loading
Loading