@skillstate/opencode 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +162 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,162 @@
1
+ <div align="center">
2
+
3
+ # @skillstate/opencode
4
+
5
+ **OpenCode platform adapter for the @skillstate/core runtime — the only case with real O(1) prompt economy via history trimming.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@skillstate/opencode)](https://www.npmjs.com/package/@skillstate/opencode)
8
+ [![node](https://img.shields.io/node/v/@skillstate/opencode)](https://www.npmjs.com/package/@skillstate/opencode)
9
+ [![Tests](https://img.shields.io/badge/tests-755%20passing-brightgreen)](https://github.com/vitkuz573/skillstate)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/vitkuz573/skillstate/blob/main/LICENSE)
11
+
12
+ </div>
13
+
14
+ ---
15
+
16
+ `@skillstate/opencode` integrates the paper-exact runtime
17
+ ([`@skillstate/core`](../core)) into **OpenCode**. It emits a `SKILL.md` so the
18
+ host discovers the skill, and a plugin that hooks
19
+ `experimental.chat.messages.transform` to **trim history before every LLM
20
+ call** — dropping old messages and injecting only the state, which is genuine
21
+ **O(1)** prompt footprint.
22
+
23
+ > **@non-paper** — no adapters exist in arXiv 2608.26263v3. This adapter is an
24
+ > additive integration, not part of the paper.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm i @skillstate/core @skillstate/opencode
30
+ ```
31
+
32
+ Requires Node.js >= 20. TypeScript types are bundled.
33
+
34
+ ## Quick start
35
+
36
+ ```ts
37
+ import { OpenCodeAdapter } from '@skillstate/opencode';
38
+ import { INTERCODE_CTF_SPEC } from '@skillstate/core/schemas';
39
+
40
+ const adapter = new OpenCodeAdapter();
41
+
42
+ // SKILL.md with frontmatter (name/description/version) + an
43
+ // execution_context block pointing at the persisted state file:
44
+ const skillMd = adapter.generateSkillMd(INTERCODE_CTF_SPEC, './.skillstate.json');
45
+
46
+ // Plugin with real O(1) history trimming via experimental.chat.messages.transform:
47
+ const plugin = adapter.generatePluginCode('./.skillstate.json');
48
+
49
+ // Default keeps the last 3 non-system messages + state injection.
50
+ // Configure history depth:
51
+ const plugin2 = adapter.generatePluginCode('./.skillstate.json', {
52
+ maxHistoryMessages: 5, // keep last 5 non-system messages
53
+ });
54
+
55
+ // Persist the plugin to disk atomically:
56
+ const saved = await adapter.savePluginCode(
57
+ './skillstate.plugin.ts',
58
+ './.skillstate.json',
59
+ { maxHistoryMessages: 5 },
60
+ );
61
+ ```
62
+
63
+ ## Install into OpenCode (host)
64
+
65
+ Tested end-to-end against OpenCode ≥ 1.17 (`@opencode-ai/plugin` 1.15.x
66
+ hook contracts). Four steps:
67
+
68
+ **1. Generate the plugin file** (one-off, or wire into a build script):
69
+
70
+ ```ts
71
+ // scripts/gen-plugin.mjs (run with: node scripts/gen-plugin.mjs)
72
+ import { OpenCodeAdapter } from '@skillstate/opencode';
73
+ import { writeFileSync } from 'node:fs';
74
+
75
+ const adapter = new OpenCodeAdapter();
76
+ const statePath = '/abs/path/to/.skillstate.json';
77
+ const code = adapter.generatePluginCode(statePath, { maxHistoryMessages: 3 });
78
+ writeFileSync('/abs/path/to/skillstate.plugin.ts', code);
79
+ ```
80
+
81
+ Put the generated `skillstate.plugin.ts` anywhere stable (absolute path is
82
+ safest), e.g. `<project>/.opencode-runtime/skillstate.plugin.ts`.
83
+
84
+ **2. Create the initial state file** (`statePath` from step 1), matching your
85
+ spec schema, e.g. for `INTERCODE_CTF_SPEC`:
86
+
87
+ ```json
88
+ {
89
+ "discovered_flags": [],
90
+ "tested_hypotheses": [],
91
+ "active_files": [],
92
+ "working_dir": "/abs/work/dir",
93
+ "cmd_summary": "initialized"
94
+ }
95
+ ```
96
+
97
+ **3. Register the plugin in `opencode.jsonc`** — add a `file://` entry to the
98
+ `plugin` array (local paths are resolved by OpenCode and imported directly;
99
+ TypeScript is supported because plugins load under Bun):
100
+
101
+ ```jsonc
102
+ {
103
+ "plugin": [
104
+ "@ai-sdk/anthropic",
105
+ "file:///abs/path/to/skillstate.plugin.ts"
106
+ ]
107
+ }
108
+ ```
109
+
110
+ **4. Install the SKILL.md** — write `adapter.generateSkillMd(spec, statePath)`
111
+ to `~/.config/opencode/skills/skillstate/SKILL.md` (global) or
112
+ `.opencode/skills/skillstate/SKILL.md` (project). Keep the frontmatter fields
113
+ `name` (must match the folder name) and a one-line `description`; the
114
+ generated frontmatter may need a manual trim to those two fields.
115
+
116
+ Verify with `opencode debug config` (plugin entry shows under `plugin`) and
117
+ `opencode debug skill` (your skill is listed). Hook notes for OpenCode ≥ 1.17:
118
+ `messages.transform` receives `{ info: Message, parts: Part[] }` entries and
119
+ must mutate `output.messages` **in place**; the plugin injects state as a
120
+ synthetic `{ info, parts }` message.
121
+
122
+ ## API / Exports
123
+
124
+ Root path `@skillstate/opencode` exports one thing: `OpenCodeAdapter`.
125
+
126
+ - `new OpenCodeAdapter()` — implements `PlatformAdapter` (`name = 'opencode'`).
127
+ - `generateSkillMd(spec, statePath?): string` — a `SKILL.md` body with
128
+ frontmatter and a state-based process description.
129
+ - `generatePluginCode(statePath, options?): string` — an OpenCode plugin
130
+ (`options.maxHistoryMessages`, default 3). Hooks:
131
+ `experimental.chat.messages.transform` (real history trimming),
132
+ `experimental.session.compacting` (inject state into compaction context),
133
+ `tool.execute.after` (persist `state_patch` to disk).
134
+ - `savePluginCode(target, statePath, options?): Promise<string>` — writes the
135
+ plugin atomically and returns the destination.
136
+ - `injectState(state, spec): string` / `formatPrompt(state, observation, spec): string`.
137
+ - `extractPatch(response): StatePatch | null` / `extractAction(response): string | null`.
138
+
139
+ Both `generatePluginCode`/`savePluginCode` accept a raw path (legacy) or a
140
+ `{ root, name }` ref confined by `resolveStatePath` (`..` escapes throw).
141
+
142
+ ## Notes
143
+
144
+ - **Real O(1).** Unlike Claude Code and Codex, OpenCode exposes
145
+ `experimental.chat.messages.transform`, so the plugin drops old messages
146
+ instead of just hiding them — only the last N non-system messages plus an
147
+ injected state message reach the LLM.
148
+ - The generated plugin is a self-contained ESM/TS module; it reads and writes
149
+ the state file directly and applies the paper ⊕ null-deletion merge.
150
+ - Depends on [`@skillstate/core`](../core) for `PromptTransformer`,
151
+ `atomicWriteFile`, and `resolveStatePath`.
152
+
153
+ ## Related
154
+
155
+ - Paper: [arXiv:2608.26263](https://arxiv.org/abs/2608.26263).
156
+ - Core runtime: [`@skillstate/core`](../core).
157
+ - [`state.md`](../../state.md) — design notes.
158
+ - Other adapters: `@skillstate/claude`, `@skillstate/codex`, `@skillstate/mcp`.
159
+
160
+ ## License
161
+
162
+ [MIT](LICENSE) © 2026 Vitaly Kuzyaev
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillstate/opencode",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "OpenCode platform adapter for the skillstate runtime.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",