@op1/fast-mode 0.5.2

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.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # @op1/fast-mode
2
+
3
+ Fast-mode plugin for OpenCode that toggles request priority on demand and applies
4
+ it only when your runtime conditions match (provider, model, and enabled agent).
5
+
6
+ ## What the plugin does
7
+
8
+ - Exposes `fast_mode` tool actions: `status`, `on`, and `off`.
9
+ - Writes/reads runtime toggles from `.opencode/fast-mode-state.json`.
10
+ - Reads guard configuration from:
11
+ - `~/.config/opencode/fast-mode.json` (global)
12
+ - `.opencode/fast-mode.json` (project, overrides global)
13
+ - Applies `options.serviceTier = "priority"` only when a request passes provider,
14
+ model, and agent allowlists.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ bun add @op1/fast-mode
20
+ ```
21
+
22
+ If you install from source workspace, run the local build first:
23
+
24
+ ```bash
25
+ bun run build --cwd packages/fast-mode
26
+ ```
27
+
28
+ ## Configure
29
+
30
+ Add plugin access in `~/.config/opencode/opencode.json`:
31
+
32
+ ```json
33
+ {
34
+ "plugin": ["@op1/fast-mode"]
35
+ }
36
+ ```
37
+
38
+ If your `opencode.json` already has a plugin list, merge `@op1/fast-mode` into it.
39
+
40
+ ### Fast-mode config file shape
41
+
42
+ Create or edit `~/.config/opencode/fast-mode.json`:
43
+
44
+ ```json
45
+ {
46
+ "enabled": true,
47
+ "providers": {
48
+ "openai": {
49
+ "enabled": true,
50
+ "agents": ["build", "coder"],
51
+ "models": ["gpt-5.3-codex", "gpt-5.3-codex-mini"]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ Supported fields:
58
+
59
+ - `enabled` (boolean): global feature gate for fast-mode.
60
+ - `providers` (record): provider-level config, keyed by provider ID.
61
+ - `providers.<provider>.enabled` (optional boolean): per-provider gate.
62
+ - `providers.<provider>.agents` (optional string[]): agent allowlist.
63
+ - `providers.<provider>.models` (optional string[]): model allowlist.
64
+
65
+ Project-level `/.opencode/fast-mode.json` config is merged over the global file.
66
+
67
+ You can also add a `.opencode/fast-mode.json` in a repo to tune behavior per project.
68
+
69
+ ## `/fast` command shim
70
+
71
+ This plugin is tool-first; `/fast` is typically provided via a local command shim.
72
+
73
+ Example local command shim file:
74
+
75
+ `~/.config/opencode/commands/fast.md`
76
+
77
+ ```md
78
+ ---
79
+ description: Toggle fast mode state
80
+ agent: build
81
+ ---
82
+
83
+ Use the `fast_mode` tool from this plugin.
84
+
85
+ - `/fast status [agent|all]`
86
+ - `/fast on [agent]`
87
+ - `/fast off [agent|all]`
88
+ ```
89
+
90
+ The shim keeps behavior consistent with agent commands in OpenCode and lets you
91
+ type concise `/fast ...` actions during a chat session.
92
+
93
+ ## Notes on behavior
94
+
95
+ - This plugin is **provider-first**: it matches provider ID first, then model and
96
+ agent allowlists.
97
+ - It is currently **OpenAI-focused** because it writes `serviceTier` in the
98
+ request options contract used by OpenAI-style providers.
99
+ - It is **model-guarded**: if model/agent/provider checks fail, no request
100
+ mutation occurs.
101
+
102
+ ## Publishing readiness
103
+
104
+ Build now emits declarations for npm publishing:
105
+
106
+ ```bash
107
+ bun run build --cwd packages/fast-mode
108
+ ```
109
+
110
+ That script writes:
111
+
112
+ - `dist/fast-mode.js`
113
+ - declaration files under `dist/` (for example `dist/index.d.ts`)
114
+
115
+ Keep `dist/` committed for package publishing.
116
+
117
+ ## License
118
+
119
+ MIT
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ declare const fastModeConfigSchema: z.ZodObject<{
3
+ enabled: z.ZodOptional<z.ZodBoolean>;
4
+ providers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
5
+ enabled: z.ZodOptional<z.ZodBoolean>;
6
+ agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
+ models: z.ZodOptional<z.ZodArray<z.ZodString>>;
8
+ }, z.core.$strip>>>;
9
+ }, z.core.$strip>;
10
+ export type FastModeConfigInput = z.input<typeof fastModeConfigSchema>;
11
+ export interface FastModeProviderConfig {
12
+ enabled: boolean;
13
+ agents: string[];
14
+ models: string[];
15
+ }
16
+ export interface FastModeConfig {
17
+ enabled: boolean;
18
+ providers: Record<string, FastModeProviderConfig>;
19
+ }
20
+ export declare function parseFastModeConfig(input: unknown): FastModeConfig;
21
+ export declare function mergeFastModeConfigInput(base: FastModeConfigInput, override: FastModeConfigInput | null): FastModeConfigInput;
22
+ export declare function getFastModeConfigPaths(directory: string): {
23
+ globalPath: string;
24
+ projectPath: string;
25
+ };
26
+ export declare function loadFastModeConfig(directory: string): Promise<FastModeConfig>;
27
+ export {};
@@ -0,0 +1,335 @@
1
+ // @bun
2
+ // src/plugin.ts
3
+ import { tool } from "@opencode-ai/plugin";
4
+
5
+ // src/config.ts
6
+ import { homedir } from "os";
7
+ import { join } from "path";
8
+ import { z } from "zod";
9
+
10
+ // src/normalize.ts
11
+ function normalizeProviderID(value) {
12
+ return value.trim().toLowerCase();
13
+ }
14
+ function normalizeAgentName(value) {
15
+ return value.trim().toLowerCase();
16
+ }
17
+ function normalizeModelID(value) {
18
+ return value.trim();
19
+ }
20
+ function normalizeAllowlist(values, normalize) {
21
+ if (!values || values.length === 0)
22
+ return [];
23
+ const deduped = new Set;
24
+ for (const value of values) {
25
+ const normalized = normalize(value);
26
+ if (normalized.length > 0)
27
+ deduped.add(normalized);
28
+ }
29
+ return [...deduped];
30
+ }
31
+
32
+ // src/config.ts
33
+ var providerConfigSchema = z.object({
34
+ enabled: z.boolean().optional(),
35
+ agents: z.array(z.string()).optional(),
36
+ models: z.array(z.string()).optional()
37
+ });
38
+ var fastModeConfigSchema = z.object({
39
+ enabled: z.boolean().optional(),
40
+ providers: z.record(z.string(), providerConfigSchema).optional()
41
+ });
42
+ var fastModeConfigDefaults = {
43
+ enabled: false,
44
+ providers: {}
45
+ };
46
+ function parseFastModeConfig(input) {
47
+ const parsed = fastModeConfigSchema.parse(input);
48
+ const providers = {};
49
+ for (const [providerID, providerConfig] of Object.entries(parsed.providers ?? {})) {
50
+ const normalizedProviderID = normalizeProviderID(providerID);
51
+ if (normalizedProviderID.length === 0)
52
+ continue;
53
+ providers[normalizedProviderID] = {
54
+ enabled: providerConfig.enabled ?? true,
55
+ agents: normalizeAllowlist(providerConfig.agents, normalizeAgentName),
56
+ models: normalizeAllowlist(providerConfig.models, normalizeModelID)
57
+ };
58
+ }
59
+ return {
60
+ enabled: parsed.enabled ?? fastModeConfigDefaults.enabled,
61
+ providers
62
+ };
63
+ }
64
+ async function readConfigFile(path) {
65
+ const file = Bun.file(path);
66
+ if (!await file.exists())
67
+ return null;
68
+ try {
69
+ return await file.json();
70
+ } catch {
71
+ return "invalid";
72
+ }
73
+ }
74
+ function mergeProviderRecords(base, override) {
75
+ const merged = {
76
+ ...base
77
+ };
78
+ for (const [providerID, providerConfig] of Object.entries(override)) {
79
+ merged[providerID] = {
80
+ ...merged[providerID] ?? {},
81
+ ...providerConfig
82
+ };
83
+ }
84
+ return merged;
85
+ }
86
+ function mergeFastModeConfigInput(base, override) {
87
+ if (!override)
88
+ return base;
89
+ return {
90
+ ...base,
91
+ ...override,
92
+ providers: mergeProviderRecords(base.providers ?? {}, override.providers ?? {})
93
+ };
94
+ }
95
+ function getFastModeConfigPaths(directory) {
96
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME;
97
+ const configRoot = typeof xdgConfigHome === "string" && xdgConfigHome.length > 0 ? xdgConfigHome : join(homedir(), ".config");
98
+ return {
99
+ globalPath: join(configRoot, "opencode", "fast-mode.json"),
100
+ projectPath: join(directory, ".opencode", "fast-mode.json")
101
+ };
102
+ }
103
+ async function loadFastModeConfig(directory) {
104
+ const { globalPath, projectPath } = getFastModeConfigPaths(directory);
105
+ const [globalConfig, projectConfig] = await Promise.all([
106
+ readConfigFile(globalPath),
107
+ readConfigFile(projectPath)
108
+ ]);
109
+ if (globalConfig === "invalid" || projectConfig === "invalid") {
110
+ return {
111
+ enabled: false,
112
+ providers: {}
113
+ };
114
+ }
115
+ try {
116
+ return parseFastModeConfig(mergeFastModeConfigInput(mergeFastModeConfigInput(fastModeConfigDefaults, globalConfig), projectConfig));
117
+ } catch {
118
+ return {
119
+ enabled: false,
120
+ providers: {}
121
+ };
122
+ }
123
+ }
124
+
125
+ // src/state.ts
126
+ import { join as join2 } from "path";
127
+ import { z as z2 } from "zod";
128
+ var fastModeStateSchema = z2.object({
129
+ agents: z2.record(z2.string(), z2.boolean()).optional()
130
+ });
131
+ var fastModeStateDefaults = {
132
+ agents: {}
133
+ };
134
+ function parseFastModeState(input) {
135
+ const parsed = fastModeStateSchema.parse(input);
136
+ const agents = {};
137
+ for (const [agentName, enabled] of Object.entries(parsed.agents ?? {})) {
138
+ if (!enabled)
139
+ continue;
140
+ const normalized = normalizeAgentName(agentName);
141
+ if (normalized.length === 0)
142
+ continue;
143
+ agents[normalized] = true;
144
+ }
145
+ return { agents };
146
+ }
147
+ function getFastModeStatePath(directory) {
148
+ return join2(directory, ".opencode", "fast-mode-state.json");
149
+ }
150
+ async function loadFastModeState(directory) {
151
+ const file = Bun.file(getFastModeStatePath(directory));
152
+ if (!await file.exists()) {
153
+ return { agents: { ...fastModeStateDefaults.agents } };
154
+ }
155
+ try {
156
+ return parseFastModeState(await file.json());
157
+ } catch {
158
+ return { agents: { ...fastModeStateDefaults.agents } };
159
+ }
160
+ }
161
+ async function saveFastModeState(directory, state) {
162
+ const normalized = parseFastModeState(state);
163
+ await Bun.write(getFastModeStatePath(directory), `${JSON.stringify(normalized, null, 2)}
164
+ `);
165
+ }
166
+ function isAgentFastModeEnabled(state, agentName) {
167
+ const normalized = normalizeAgentName(agentName);
168
+ if (normalized.length === 0)
169
+ return false;
170
+ return state.agents[normalized] === true;
171
+ }
172
+ function setAgentFastModeEnabled(state, agentName, enabled) {
173
+ const normalized = normalizeAgentName(agentName);
174
+ if (normalized.length === 0) {
175
+ throw new Error("Agent name is required for fast mode state updates.");
176
+ }
177
+ if (enabled) {
178
+ return {
179
+ agents: {
180
+ ...state.agents,
181
+ [normalized]: true
182
+ }
183
+ };
184
+ }
185
+ const nextAgents = { ...state.agents };
186
+ delete nextAgents[normalized];
187
+ return { agents: nextAgents };
188
+ }
189
+ function disableAllAgentFastMode() {
190
+ return { agents: {} };
191
+ }
192
+ function getEnabledAgents(state) {
193
+ return Object.keys(state.agents).filter((agentName) => state.agents[agentName]).sort();
194
+ }
195
+
196
+ // src/runtime.ts
197
+ function shouldApplyFastMode(input) {
198
+ if (!input.config.enabled)
199
+ return false;
200
+ const providerID = normalizeProviderID(input.request.providerID);
201
+ if (providerID.length === 0)
202
+ return false;
203
+ const providerConfig = input.config.providers[providerID];
204
+ if (!providerConfig || !providerConfig.enabled)
205
+ return false;
206
+ const modelID = normalizeModelID(input.request.modelID);
207
+ if (modelID.length === 0 || !providerConfig.models.includes(modelID)) {
208
+ return false;
209
+ }
210
+ const agentName = normalizeAgentName(input.request.agentName);
211
+ if (agentName.length === 0 || !providerConfig.agents.includes(agentName)) {
212
+ return false;
213
+ }
214
+ return isAgentFastModeEnabled(input.state, agentName);
215
+ }
216
+ function applyFastModeServiceTier(output) {
217
+ output.options.serviceTier = "priority";
218
+ }
219
+
220
+ // src/plugin.ts
221
+ function resolveToolTarget(input) {
222
+ const target = input.target ?? input.fallbackAgent;
223
+ const normalized = normalizeAgentName(target);
224
+ if (normalized.length === 0) {
225
+ throw new Error("Target agent is required.");
226
+ }
227
+ return normalized;
228
+ }
229
+ function formatToolStatus(input) {
230
+ const enabledAgents = getEnabledAgents(input.state);
231
+ if (input.target && input.target !== "all") {
232
+ return JSON.stringify({
233
+ action: "status",
234
+ configEnabled: input.configEnabled,
235
+ target: input.target,
236
+ enabled: isAgentFastModeEnabled(input.state, input.target)
237
+ }, null, 2);
238
+ }
239
+ return JSON.stringify({
240
+ action: "status",
241
+ configEnabled: input.configEnabled,
242
+ currentAgent: input.currentAgent,
243
+ currentAgentEnabled: isAgentFastModeEnabled(input.state, input.currentAgent),
244
+ enabledAgents
245
+ }, null, 2);
246
+ }
247
+ var FastModePlugin = async (ctx) => {
248
+ const workspaceRoot = ctx.directory;
249
+ let memoizedState = null;
250
+ async function readState() {
251
+ if (memoizedState)
252
+ return memoizedState;
253
+ memoizedState = await loadFastModeState(workspaceRoot);
254
+ return memoizedState;
255
+ }
256
+ async function writeState(nextState) {
257
+ memoizedState = nextState;
258
+ await saveFastModeState(workspaceRoot, nextState);
259
+ }
260
+ const fast_mode = tool({
261
+ description: "Toggle or inspect fast mode state by agent for provider/model guarded request mutation.",
262
+ args: {
263
+ action: tool.schema.enum(["status", "on", "off"]).describe("Choose status, on, or off."),
264
+ target: tool.schema.string().optional().describe("Optional agent name. Defaults to current agent for on/off. Supports 'all' for status/off.")
265
+ },
266
+ async execute(args, toolCtx) {
267
+ const config = await loadFastModeConfig(workspaceRoot);
268
+ const state = await readState();
269
+ const target = args.target ? normalizeAgentName(args.target) : undefined;
270
+ if (args.action === "status") {
271
+ return formatToolStatus({
272
+ configEnabled: config.enabled,
273
+ target,
274
+ currentAgent: toolCtx.agent,
275
+ state
276
+ });
277
+ }
278
+ if (args.action === "off" && target === "all") {
279
+ const cleared = disableAllAgentFastMode();
280
+ await writeState(cleared);
281
+ return formatToolStatus({
282
+ configEnabled: config.enabled,
283
+ target: "all",
284
+ currentAgent: toolCtx.agent,
285
+ state: cleared
286
+ });
287
+ }
288
+ if (args.action === "on" && target === "all") {
289
+ return "fast_mode refused: target 'all' is only valid for status/off.";
290
+ }
291
+ const resolvedTarget = resolveToolTarget({
292
+ target,
293
+ fallbackAgent: toolCtx.agent
294
+ });
295
+ const nextState = setAgentFastModeEnabled(state, resolvedTarget, args.action === "on");
296
+ await writeState(nextState);
297
+ return JSON.stringify({
298
+ action: args.action,
299
+ target: resolvedTarget,
300
+ enabled: isAgentFastModeEnabled(nextState, resolvedTarget),
301
+ enabledAgents: getEnabledAgents(nextState)
302
+ }, null, 2);
303
+ }
304
+ });
305
+ const chatParamsHook = async (input, output) => {
306
+ const providerID = input.provider?.info?.id ?? input.model?.providerID ?? undefined;
307
+ if (!providerID)
308
+ return;
309
+ const [config, state] = await Promise.all([
310
+ loadFastModeConfig(workspaceRoot),
311
+ readState()
312
+ ]);
313
+ if (!shouldApplyFastMode({
314
+ config,
315
+ state,
316
+ request: {
317
+ providerID,
318
+ modelID: input.model.id,
319
+ agentName: input.agent
320
+ }
321
+ })) {
322
+ return;
323
+ }
324
+ applyFastModeServiceTier(output);
325
+ };
326
+ return {
327
+ name: "@op1/fast-mode",
328
+ tool: { fast_mode },
329
+ "chat.params": chatParamsHook
330
+ };
331
+ };
332
+ export {
333
+ FastModePlugin as default,
334
+ FastModePlugin
335
+ };
@@ -0,0 +1 @@
1
+ export { FastModePlugin, FastModePlugin as default } from "./plugin.js";
@@ -0,0 +1,4 @@
1
+ export declare function normalizeProviderID(value: string): string;
2
+ export declare function normalizeAgentName(value: string): string;
3
+ export declare function normalizeModelID(value: string): string;
4
+ export declare function normalizeAllowlist(values: string[] | undefined, normalize: (value: string) => string): string[];
@@ -0,0 +1,3 @@
1
+ import { type Plugin } from "@opencode-ai/plugin";
2
+ export declare const FastModePlugin: Plugin;
3
+ export default FastModePlugin;
@@ -0,0 +1,15 @@
1
+ import type { FastModeConfig } from "./config.js";
2
+ import { type FastModeState } from "./state.js";
3
+ export interface FastModeRequest {
4
+ providerID: string;
5
+ modelID: string;
6
+ agentName: string;
7
+ }
8
+ export declare function shouldApplyFastMode(input: {
9
+ config: FastModeConfig;
10
+ state: FastModeState;
11
+ request: FastModeRequest;
12
+ }): boolean;
13
+ export declare function applyFastModeServiceTier(output: {
14
+ options: Record<string, unknown>;
15
+ }): void;
@@ -0,0 +1,11 @@
1
+ export interface FastModeState {
2
+ agents: Record<string, boolean>;
3
+ }
4
+ export declare function parseFastModeState(input: unknown): FastModeState;
5
+ export declare function getFastModeStatePath(directory: string): string;
6
+ export declare function loadFastModeState(directory: string): Promise<FastModeState>;
7
+ export declare function saveFastModeState(directory: string, state: FastModeState): Promise<void>;
8
+ export declare function isAgentFastModeEnabled(state: FastModeState, agentName: string): boolean;
9
+ export declare function setAgentFastModeEnabled(state: FastModeState, agentName: string, enabled: boolean): FastModeState;
10
+ export declare function disableAllAgentFastMode(): FastModeState;
11
+ export declare function getEnabledAgents(state: FastModeState): string[];
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@op1/fast-mode",
3
+ "version": "0.5.2",
4
+ "description": "Fast mode plugin for OpenCode that enables priority service tier for approved provider/model/agent requests",
5
+ "type": "module",
6
+ "main": "dist/fast-mode.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/fast-mode.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "bun build src/index.ts --outfile dist/fast-mode.js --target bun --format esm --external @opencode-ai/plugin --external zod && tsc --emitDeclarationOnly --declaration --declarationMap false --outDir dist",
16
+ "typecheck": "tsc --noEmit",
17
+ "dev": "bun run build --watch"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "dependencies": {
23
+ "@opencode-ai/plugin": "^1.0.0",
24
+ "zod": "^4.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/bun": "^1.1.0",
28
+ "typescript": "^5.7.0"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/op1/op1.git",
36
+ "directory": "packages/fast-mode"
37
+ },
38
+ "license": "MIT",
39
+ "keywords": [
40
+ "opencode",
41
+ "plugin",
42
+ "fast-mode",
43
+ "priority"
44
+ ]
45
+ }