opencode-fixes-huihui 0.1.4-beta.1 → 0.1.4-beta.11
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 +55 -21
- package/dist/index.d.ts +3 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -4
- package/dist/index.js.map +4 -4
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/opencode-anthropic-cache.d.ts +9 -0
- package/dist/opencode-anthropic-cache.d.ts.map +1 -0
- package/dist/sticky-session-plugin.d.ts +21 -0
- package/dist/sticky-session-plugin.d.ts.map +1 -0
- package/dist/superpowers.d.ts +9 -0
- package/dist/superpowers.d.ts.map +1 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# opencode-fixes-huihui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
统一插件包:默认导出组合插件,同时包含:
|
|
4
|
+
|
|
5
|
+
- sticky session headers + `promptCacheKey` 注入
|
|
6
|
+
- Anthropic 渠道 `metadata.user_id` 缓存注入
|
|
4
7
|
|
|
5
8
|
## 安装
|
|
6
9
|
|
|
@@ -10,44 +13,75 @@ npm i opencode-fixes-huihui
|
|
|
10
13
|
|
|
11
14
|
## 使用
|
|
12
15
|
|
|
16
|
+
默认导出是组合插件(推荐):
|
|
17
|
+
|
|
13
18
|
```ts
|
|
14
|
-
import
|
|
19
|
+
import CombinedPlugin from "opencode-fixes-huihui";
|
|
15
20
|
|
|
16
21
|
export default {
|
|
17
|
-
|
|
22
|
+
plugins: [CombinedPlugin],
|
|
18
23
|
};
|
|
19
24
|
```
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
也支持具名导出:
|
|
22
27
|
|
|
23
28
|
```ts
|
|
24
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
CombinedPlugin,
|
|
31
|
+
StickySessionPlugin,
|
|
32
|
+
AnthropicCache,
|
|
33
|
+
AnthropicUltraCache,
|
|
34
|
+
FoxcodeAwsCache,
|
|
35
|
+
createAnthropicCachePlugin,
|
|
36
|
+
} from "opencode-fixes-huihui";
|
|
25
37
|
```
|
|
26
38
|
|
|
27
|
-
##
|
|
39
|
+
## 功能说明
|
|
40
|
+
|
|
41
|
+
### 1) Sticky session(`StickySessionPlugin`)
|
|
42
|
+
|
|
43
|
+
在 `chat.params` 阶段执行,仅对 `@ai-sdk/openai` 或 `isCodex` provider 生效。
|
|
44
|
+
|
|
45
|
+
会话值优先级:
|
|
46
|
+
|
|
47
|
+
1. `OPENCODE_PROMPT_CACHE_KEY`
|
|
48
|
+
2. `OPENCODE_STICKY_SESSION_ID`
|
|
49
|
+
3. 现有 headers:`x-session-id` / `conversation_id` / `session_id`
|
|
50
|
+
4. `input.sessionID`
|
|
51
|
+
|
|
52
|
+
写入位置:
|
|
53
|
+
|
|
54
|
+
- `input.model.headers.x-session-id`
|
|
55
|
+
- `input.model.headers.conversation_id`
|
|
56
|
+
- `input.model.headers.session_id`
|
|
57
|
+
- `output.options.promptCacheKey`
|
|
58
|
+
|
|
59
|
+
可选环境变量:
|
|
60
|
+
|
|
61
|
+
- `OPENCODE_PROMPT_CACHE_KEY`:强制覆盖会话值(最高优先级)
|
|
62
|
+
- `OPENCODE_STICKY_SESSION_ID`:次优先级覆盖会话值
|
|
63
|
+
|
|
64
|
+
### 2) Anthropic cache(`AnthropicCache` 系列)
|
|
65
|
+
|
|
66
|
+
在 auth loader 的请求 `fetch` 中拦截 Anthropic POST JSON 请求,自动补齐:
|
|
67
|
+
|
|
68
|
+
- `metadata.user_id = user_{projectId}_account__session_{sessionId}`
|
|
28
69
|
|
|
29
|
-
|
|
70
|
+
内置 provider:
|
|
30
71
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- `OPENCODE_STICKY_SESSION_ID`
|
|
35
|
-
- 现有 headers:`x-session-id` / `conversation_id` / `session_id`
|
|
36
|
-
- `input.sessionID`
|
|
37
|
-
3. 将解析值同时写入:
|
|
38
|
-
- `input.model.headers.x-session-id`
|
|
39
|
-
- `input.model.headers.conversation_id`
|
|
40
|
-
- `input.model.headers.session_id`
|
|
41
|
-
- `output.options.promptCacheKey`
|
|
72
|
+
- `AnthropicCache`(`anthropic`)
|
|
73
|
+
- `AnthropicUltraCache`(`anthropic-ultra`)
|
|
74
|
+
- `FoxcodeAwsCache`(`foxcode-aws`)
|
|
42
75
|
|
|
43
|
-
|
|
76
|
+
自定义 provider 可使用:
|
|
44
77
|
|
|
45
|
-
- `
|
|
46
|
-
- `OPENCODE_STICKY_SESSION_ID`: 次优先级覆盖会话值
|
|
78
|
+
- `createAnthropicCachePlugin("your-provider-name")`
|
|
47
79
|
|
|
48
80
|
## 开发
|
|
49
81
|
|
|
50
82
|
```bash
|
|
83
|
+
npm run format
|
|
84
|
+
npm run lint
|
|
51
85
|
npm run typecheck
|
|
52
86
|
npm run build
|
|
53
87
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Injects HTTP headers via `input.model.headers` so upstream proxies/load
|
|
6
|
-
* balancers can keep a conversation pinned to one account.
|
|
7
|
-
*
|
|
8
|
-
* Headers injected for @ai-sdk/openai providers:
|
|
9
|
-
* - x-session-id
|
|
10
|
-
* - conversation_id
|
|
11
|
-
* - session_id
|
|
12
|
-
*
|
|
13
|
-
* Source of truth (in order):
|
|
14
|
-
* - env: OPENCODE_PROMPT_CACHE_KEY (manual override)
|
|
15
|
-
* - env: OPENCODE_STICKY_SESSION_ID (manual override)
|
|
16
|
-
* - model headers (x-session-id / conversation_id / session_id)
|
|
17
|
-
* - opencode sessionID (default)
|
|
18
|
-
*/
|
|
19
|
-
export declare const StickySessionPlugin: Plugin;
|
|
20
|
-
export default StickySessionPlugin;
|
|
1
|
+
export { WormholeCache, AnthropicCache } from "./opencode-anthropic-cache";
|
|
2
|
+
export { StickySessionPlugin } from "./sticky-session-plugin";
|
|
3
|
+
export { SuperpowersPlugin } from "./superpowers";
|
|
21
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,142 @@
|
|
|
1
|
-
//
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
var ENV_DEBUG = "OPENCODE_ANTHROPIC_CACHE_DEBUG";
|
|
3
|
+
var _client = null;
|
|
4
|
+
function isDebugEnabled() {
|
|
5
|
+
const val = process.env[ENV_DEBUG];
|
|
6
|
+
return val === "1" || val?.toLowerCase() === "true";
|
|
7
|
+
}
|
|
8
|
+
function initLogger(client) {
|
|
9
|
+
_client = client;
|
|
10
|
+
}
|
|
11
|
+
function createLogger(module, prefix) {
|
|
12
|
+
const service = `${prefix}.${module}`;
|
|
13
|
+
const log2 = (level, message, extra) => {
|
|
14
|
+
if (_client?.app && typeof _client.app.log === "function") {
|
|
15
|
+
_client.app.log({ body: { service, level, message, extra } }).catch(() => {
|
|
16
|
+
});
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (isDebugEnabled()) {
|
|
20
|
+
const prefix2 = `[${service}]`;
|
|
21
|
+
const args = extra ? [prefix2, message, extra] : [prefix2, message];
|
|
22
|
+
switch (level) {
|
|
23
|
+
case "debug":
|
|
24
|
+
console.debug(...args);
|
|
25
|
+
break;
|
|
26
|
+
case "info":
|
|
27
|
+
console.info(...args);
|
|
28
|
+
break;
|
|
29
|
+
case "warn":
|
|
30
|
+
console.warn(...args);
|
|
31
|
+
break;
|
|
32
|
+
case "error":
|
|
33
|
+
console.error(...args);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
debug: (message, extra) => log2("debug", message, extra),
|
|
40
|
+
info: (message, extra) => log2("info", message, extra),
|
|
41
|
+
warn: (message, extra) => log2("warn", message, extra),
|
|
42
|
+
error: (message, extra) => log2("error", message, extra)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/opencode-anthropic-cache.ts
|
|
47
|
+
var log = createLogger("fetch", "anthropic-cache");
|
|
48
|
+
var currentSessionId = "";
|
|
49
|
+
var projectId = "";
|
|
50
|
+
var isInitialized = false;
|
|
51
|
+
function buildUserId() {
|
|
52
|
+
const pid = projectId || "unknown";
|
|
53
|
+
const sid = currentSessionId || "unknown";
|
|
54
|
+
return `user_${pid}_account__session_${sid}`;
|
|
55
|
+
}
|
|
56
|
+
function isJsonContentType(headers) {
|
|
57
|
+
const ct = headers.get("content-type") || "";
|
|
58
|
+
return ct.includes("application/json");
|
|
59
|
+
}
|
|
60
|
+
function parseBody(body) {
|
|
61
|
+
if (!body) return null;
|
|
62
|
+
if (typeof body === "string") return body;
|
|
63
|
+
if (body instanceof Uint8Array) return new TextDecoder().decode(body);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function createCacheFetch(providerName) {
|
|
67
|
+
return async (url, init) => {
|
|
68
|
+
const headers = new Headers(init?.headers);
|
|
69
|
+
const method = (init?.method || "GET").toUpperCase();
|
|
70
|
+
if (method !== "POST" || !isJsonContentType(headers)) {
|
|
71
|
+
return fetch(url, init);
|
|
72
|
+
}
|
|
73
|
+
const rawBody = parseBody(init?.body);
|
|
74
|
+
if (!rawBody) return fetch(url, init);
|
|
75
|
+
let payload;
|
|
76
|
+
try {
|
|
77
|
+
payload = JSON.parse(rawBody);
|
|
78
|
+
} catch {
|
|
79
|
+
log.warn(`[${providerName}] Failed to parse request body as JSON`);
|
|
80
|
+
return fetch(url, init);
|
|
81
|
+
}
|
|
82
|
+
if (payload && typeof payload === "object") {
|
|
83
|
+
if (!payload.metadata || typeof payload.metadata !== "object") {
|
|
84
|
+
payload.metadata = {};
|
|
85
|
+
}
|
|
86
|
+
const meta = payload.metadata;
|
|
87
|
+
if (meta.user_id == null) {
|
|
88
|
+
meta.user_id = buildUserId();
|
|
89
|
+
log.info(`[${providerName}] Injected user_id`, {
|
|
90
|
+
user_id: meta.user_id
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
headers.delete("content-length");
|
|
95
|
+
return fetch(url, { ...init, headers, body: JSON.stringify(payload) });
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createEventHandler() {
|
|
99
|
+
return async ({ event }) => {
|
|
100
|
+
if (event.type === "session.created" || event.type === "session.updated") {
|
|
101
|
+
const props = event.properties;
|
|
102
|
+
currentSessionId = props?.info?.id || "";
|
|
103
|
+
log.debug("Session updated", { sessionId: currentSessionId });
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function createProviderPlugin(providerName) {
|
|
108
|
+
return async ({ project, client }) => {
|
|
109
|
+
if (!isInitialized) {
|
|
110
|
+
initLogger(client);
|
|
111
|
+
projectId = project?.id || "";
|
|
112
|
+
isInitialized = true;
|
|
113
|
+
log.info("Plugin initialized", { projectId });
|
|
114
|
+
}
|
|
115
|
+
log.info(`Auth handler registered for provider: ${providerName}`);
|
|
116
|
+
return {
|
|
117
|
+
event: createEventHandler(),
|
|
118
|
+
auth: {
|
|
119
|
+
provider: providerName,
|
|
120
|
+
methods: [{ type: "api", label: "key" }],
|
|
121
|
+
loader: async () => ({
|
|
122
|
+
fetch: createCacheFetch(providerName)
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
var FoxcodeAwsCache = createProviderPlugin("foxcode-aws");
|
|
129
|
+
var AnthropicCache = createProviderPlugin("anthropic");
|
|
130
|
+
var AnthropicUltraCache = createProviderPlugin("anthropic-ultra");
|
|
131
|
+
var WormholeCache = createProviderPlugin("wormhole_a");
|
|
132
|
+
|
|
133
|
+
// src/sticky-session-plugin.ts
|
|
2
134
|
var OPENAI_PROVIDER_NPM = "@ai-sdk/openai";
|
|
3
|
-
var SESSION_HEADER_KEYS = [
|
|
135
|
+
var SESSION_HEADER_KEYS = [
|
|
136
|
+
"x-session-id",
|
|
137
|
+
"conversation_id",
|
|
138
|
+
"session_id"
|
|
139
|
+
];
|
|
4
140
|
var normalize = (value) => typeof value === "string" ? value.trim() : "";
|
|
5
141
|
var pickHeaderSessionValue = (headers) => {
|
|
6
142
|
for (const key of SESSION_HEADER_KEYS) {
|
|
@@ -51,9 +187,83 @@ var StickySessionPlugin = async ({ client }) => {
|
|
|
51
187
|
}
|
|
52
188
|
};
|
|
53
189
|
};
|
|
54
|
-
|
|
190
|
+
|
|
191
|
+
// src/superpowers.ts
|
|
192
|
+
import path from "path";
|
|
193
|
+
import fs from "fs";
|
|
194
|
+
import os from "os";
|
|
195
|
+
import { fileURLToPath } from "url";
|
|
196
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
197
|
+
var PROMPT_INJECTION_AGENTS = /* @__PURE__ */ new Set(["plan", "build", "compaction", "orchestrator"]);
|
|
198
|
+
var normalizeAgentName = (name) => typeof name === "string" ? name.trim().toLowerCase() : "";
|
|
199
|
+
var extractAndStripFrontmatter = (content) => {
|
|
200
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
201
|
+
if (!match) return { frontmatter: {}, content };
|
|
202
|
+
const frontmatterStr = match[1];
|
|
203
|
+
const body = match[2];
|
|
204
|
+
const frontmatter = {};
|
|
205
|
+
for (const line of frontmatterStr.split("\n")) {
|
|
206
|
+
const colonIdx = line.indexOf(":");
|
|
207
|
+
if (colonIdx > 0) {
|
|
208
|
+
const key = line.slice(0, colonIdx).trim();
|
|
209
|
+
const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
210
|
+
frontmatter[key] = value;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { frontmatter, content: body };
|
|
214
|
+
};
|
|
215
|
+
var SuperpowersPlugin = async ({ client, directory }) => {
|
|
216
|
+
const homeDir = os.homedir();
|
|
217
|
+
const superpowersSkillsDir = path.join(homeDir, ".agents/skills/superpowers");
|
|
218
|
+
const latestAgentBySession = /* @__PURE__ */ new Map();
|
|
219
|
+
const getBootstrapContent = () => {
|
|
220
|
+
const skillPath = path.join(superpowersSkillsDir, "using-superpowers", "SKILL.md");
|
|
221
|
+
if (!fs.existsSync(skillPath)) return null;
|
|
222
|
+
const fullContent = fs.readFileSync(skillPath, "utf8");
|
|
223
|
+
const { content } = extractAndStripFrontmatter(fullContent);
|
|
224
|
+
const toolMapping = `**Tool Mapping for OpenCode:**
|
|
225
|
+
When skills reference tools you don't have, substitute OpenCode equivalents:
|
|
226
|
+
- \`TodoWrite\` \u2192 \`update_plan\`
|
|
227
|
+
- \`Task\` tool with subagents \u2192 Use OpenCode's subagent system (@mention)
|
|
228
|
+
- \`Skill\` tool \u2192 OpenCode's native \`skill\` tool
|
|
229
|
+
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` \u2192 Your native tools
|
|
230
|
+
|
|
231
|
+
**Skills location:**
|
|
232
|
+
Superpowers skills are in \`${superpowersSkillsDir}/\`
|
|
233
|
+
Use OpenCode's native \`skill\` tool to list and load skills.`;
|
|
234
|
+
return `<EXTREMELY_IMPORTANT>
|
|
235
|
+
You have superpowers.
|
|
236
|
+
|
|
237
|
+
**IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load "using-superpowers" again - that would be redundant.**
|
|
238
|
+
|
|
239
|
+
${content}
|
|
240
|
+
|
|
241
|
+
${toolMapping}
|
|
242
|
+
</EXTREMELY_IMPORTANT>`;
|
|
243
|
+
};
|
|
244
|
+
return {
|
|
245
|
+
"chat.message": async (input) => {
|
|
246
|
+
const agentName = normalizeAgentName(input.agent);
|
|
247
|
+
if (!agentName) return;
|
|
248
|
+
latestAgentBySession.set(input.sessionID, agentName);
|
|
249
|
+
},
|
|
250
|
+
// Use system prompt transform to inject bootstrap (fixes #226 agent reset bug)
|
|
251
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
252
|
+
const sessionID = input.sessionID?.trim();
|
|
253
|
+
const agentName = sessionID ? latestAgentBySession.get(sessionID) : void 0;
|
|
254
|
+
if (!agentName || !PROMPT_INJECTION_AGENTS.has(agentName)) return;
|
|
255
|
+
const bootstrap = getBootstrapContent();
|
|
256
|
+
if (bootstrap) {
|
|
257
|
+
if (!output.system) output.system = [];
|
|
258
|
+
output.system.push(bootstrap);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
};
|
|
55
263
|
export {
|
|
264
|
+
AnthropicCache,
|
|
56
265
|
StickySessionPlugin,
|
|
57
|
-
|
|
266
|
+
SuperpowersPlugin,
|
|
267
|
+
WormholeCache
|
|
58
268
|
};
|
|
59
269
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../
|
|
4
|
-
"sourcesContent": ["import type { Plugin } from \"@opencode-ai/plugin\";\n\nconst OPENAI_PROVIDER_NPM = \"@ai-sdk/openai\";\nconst SESSION_HEADER_KEYS = [\"x-session-id\", \"conversation_id\", \"session_id\"] as const;\n\ntype HeaderMap = Record<string, unknown>;\n\nconst normalize = (value: unknown): string => (typeof value === \"string\" ? value.trim() : \"\");\n\nconst pickHeaderSessionValue = (headers: HeaderMap): string => {\n for (const key of SESSION_HEADER_KEYS) {\n const value = normalize(headers[key]);\n if (value) return value;\n }\n return \"\";\n};\n\nconst ensureHeaders = (model: { headers?: unknown }): HeaderMap => {\n if (model.headers && typeof model.headers === \"object\") {\n return model.headers as HeaderMap;\n }\n const headers: HeaderMap = {};\n model.headers = headers;\n return headers;\n};\n\n/**\n * set a session-scoped prompt cache key and session-scoped sticky routing headers for right.codes.\n *\n * Injects HTTP headers via `input.model.headers` so upstream proxies/load\n * balancers can keep a conversation pinned to one account.\n *\n * Headers injected for @ai-sdk/openai providers:\n * - x-session-id\n * - conversation_id\n * - session_id\n *\n * Source of truth (in order):\n * - env: OPENCODE_PROMPT_CACHE_KEY (manual override)\n * - env: OPENCODE_STICKY_SESSION_ID (manual override)\n * - model headers (x-session-id / conversation_id / session_id)\n * - opencode sessionID (default)\n */\nexport const StickySessionPlugin: Plugin = async ({ client }) => {\n const envPromptCacheKey = normalize(process.env.OPENCODE_PROMPT_CACHE_KEY);\n const envStickySessionID = normalize(process.env.OPENCODE_STICKY_SESSION_ID);\n const envOverride = envPromptCacheKey || envStickySessionID;\n const isProviderCodexMap: Record<string, boolean> = {};\n let inited = false;\n const init = async () => {\n if (inited) return;\n const resp = await client.config.providers();\n const providers = resp.data?.providers ?? [];\n for (const provider of providers) {\n isProviderCodexMap[provider.id] = Boolean(provider.options?.isCodex);\n }\n inited = true;\n };\n\n return {\n \"chat.params\": async (input, output) => {\n if (!inited) await init();\n const providerNpm = normalize(input.model.api.npm);\n const isCodex = isProviderCodexMap[input.model.providerID] ?? false;\n if (!providerNpm.includes(OPENAI_PROVIDER_NPM) && !isCodex) return;\n\n const headers = ensureHeaders(input.model);\n const sessionValue = envOverride || pickHeaderSessionValue(headers) || normalize(input.sessionID);\n if (!sessionValue) return;\n\n for (const key of SESSION_HEADER_KEYS) {\n headers[key] = sessionValue;\n }\n\n output.options.promptCacheKey = sessionValue;\n if (isCodex && !output.options.instructions) {\n output.options.instructions = \"\";\n }\n },\n };\n};\n\nexport default StickySessionPlugin;\n"],
|
|
5
|
-
"mappings": ";AAEA,IAAM,
|
|
6
|
-
"names": []
|
|
3
|
+
"sources": ["../src/logger.ts", "../src/opencode-anthropic-cache.ts", "../src/sticky-session-plugin.ts", "../src/superpowers.ts"],
|
|
4
|
+
"sourcesContent": ["import type { PluginInput } from \"@opencode-ai/plugin\";\n// Logger module for debugging\nconst ENV_DEBUG = \"OPENCODE_ANTHROPIC_CACHE_DEBUG\";\nlet _client: PluginInput[\"client\"] | null = null;\nfunction isDebugEnabled() {\n const val = process.env[ENV_DEBUG];\n return val === \"1\" || val?.toLowerCase() === \"true\";\n}\nexport function initLogger(client: PluginInput[\"client\"]) {\n _client = client;\n}\nexport function createLogger(module: string, prefix: string) {\n const service = `${prefix}.${module}`;\n const log = (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: any,\n ) => {\n if (_client?.app && typeof _client.app.log === \"function\") {\n _client.app\n .log({ body: { service, level, message, extra } })\n .catch(() => {});\n return;\n }\n if (isDebugEnabled()) {\n const prefix = `[${service}]`;\n const args = extra ? [prefix, message, extra] : [prefix, message];\n switch (level) {\n case \"debug\":\n console.debug(...args);\n break;\n case \"info\":\n console.info(...args);\n break;\n case \"warn\":\n console.warn(...args);\n break;\n case \"error\":\n console.error(...args);\n break;\n }\n }\n };\n return {\n debug: (message: string, extra?: any) => log(\"debug\", message, extra),\n info: (message: string, extra?: any) => log(\"info\", message, extra),\n warn: (message: string, extra?: any) => log(\"warn\", message, extra),\n error: (message: string, extra?: any) => log(\"error\", message, extra),\n };\n}\n", "import type { Hooks, Plugin } from \"@opencode-ai/plugin\";\nimport { createLogger, initLogger } from \"./logger\";\n\nconst log = createLogger(\"fetch\", \"anthropic-cache\");\n// Session and project tracking\nlet currentSessionId = \"\";\nlet projectId = \"\";\nlet isInitialized = false;\nfunction buildUserId() {\n const pid = projectId || \"unknown\";\n const sid = currentSessionId || \"unknown\";\n return `user_${pid}_account__session_${sid}`;\n}\nfunction isJsonContentType(headers: Headers) {\n const ct = headers.get(\"content-type\") || \"\";\n return ct.includes(\"application/json\");\n}\nfunction parseBody(body?: unknown) {\n if (!body) return null;\n if (typeof body === \"string\") return body;\n if (body instanceof Uint8Array) return new TextDecoder().decode(body);\n return null;\n}\ntype CachePayload = {\n metadata?: {\n user_id?: string;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n};\n\n// Create the fetch interceptor that injects metadata.user_id\nfunction createCacheFetch(providerName: string) {\n return async (url: string | Request | URL, init?: RequestInit) => {\n const headers = new Headers(init?.headers);\n const method = (init?.method || \"GET\").toUpperCase();\n if (method !== \"POST\" || !isJsonContentType(headers)) {\n return fetch(url, init);\n }\n const rawBody = parseBody(init?.body);\n if (!rawBody) return fetch(url, init);\n let payload: CachePayload;\n try {\n payload = JSON.parse(rawBody) as CachePayload;\n } catch {\n log.warn(`[${providerName}] Failed to parse request body as JSON`);\n return fetch(url, init);\n }\n if (payload && typeof payload === \"object\") {\n if (!payload.metadata || typeof payload.metadata !== \"object\") {\n payload.metadata = {};\n }\n const meta = payload.metadata;\n if (meta.user_id == null) {\n meta.user_id = buildUserId();\n log.info(`[${providerName}] Injected user_id`, {\n user_id: meta.user_id,\n });\n }\n }\n headers.delete(\"content-length\");\n return fetch(url, { ...init, headers, body: JSON.stringify(payload) });\n };\n}\n// Shared event handler\nfunction createEventHandler(): Hooks[\"event\"] {\n return async ({ event }) => {\n if (event.type === \"session.created\" || event.type === \"session.updated\") {\n const props = event.properties;\n currentSessionId = props?.info?.id || \"\";\n log.debug(\"Session updated\", { sessionId: currentSessionId });\n }\n };\n}\n// Factory to create plugin for a specific provider\nfunction createProviderPlugin(providerName: string): Plugin {\n return async ({ project, client }) => {\n if (!isInitialized) {\n initLogger(client);\n projectId = project?.id || \"\";\n isInitialized = true;\n log.info(\"Plugin initialized\", { projectId });\n }\n log.info(`Auth handler registered for provider: ${providerName}`);\n return {\n event: createEventHandler(),\n auth: {\n provider: providerName,\n methods: [{ type: \"api\", label: \"key\" }],\n loader: async () => ({\n fetch: createCacheFetch(providerName),\n }),\n },\n };\n };\n}\n// Pre-built plugins for common providers\nexport const FoxcodeAwsCache = createProviderPlugin(\"foxcode-aws\");\nexport const AnthropicCache = createProviderPlugin(\"anthropic\");\nexport const AnthropicUltraCache = createProviderPlugin(\"anthropic-ultra\");\nexport const WormholeCache = createProviderPlugin(\"wormhole_a\");\n// Generic factory for custom provider names\nexport const createAnthropicCachePlugin = createProviderPlugin;\n", "import type { Plugin } from \"@opencode-ai/plugin\";\n\nconst OPENAI_PROVIDER_NPM = \"@ai-sdk/openai\";\nconst SESSION_HEADER_KEYS = [\n \"x-session-id\",\n \"conversation_id\",\n \"session_id\",\n] as const;\n\ntype HeaderMap = Record<string, unknown>;\n\nconst normalize = (value: unknown): string =>\n typeof value === \"string\" ? value.trim() : \"\";\n\nconst pickHeaderSessionValue = (headers: HeaderMap): string => {\n for (const key of SESSION_HEADER_KEYS) {\n const value = normalize(headers[key]);\n if (value) return value;\n }\n return \"\";\n};\n\nconst ensureHeaders = (model: { headers?: unknown }): HeaderMap => {\n if (model.headers && typeof model.headers === \"object\") {\n return model.headers as HeaderMap;\n }\n const headers: HeaderMap = {};\n model.headers = headers;\n return headers;\n};\n\n/**\n * set a session-scoped prompt cache key and session-scoped sticky routing headers for right.codes.\n *\n * Injects HTTP headers via `input.model.headers` so upstream proxies/load\n * balancers can keep a conversation pinned to one account.\n *\n * Headers injected for @ai-sdk/openai providers:\n * - x-session-id\n * - conversation_id\n * - session_id\n *\n * Source of truth (in order):\n * - env: OPENCODE_PROMPT_CACHE_KEY (manual override)\n * - env: OPENCODE_STICKY_SESSION_ID (manual override)\n * - model headers (x-session-id / conversation_id / session_id)\n * - opencode sessionID (default)\n */\nexport const StickySessionPlugin: Plugin = async ({ client }) => {\n const envPromptCacheKey = normalize(process.env.OPENCODE_PROMPT_CACHE_KEY);\n const envStickySessionID = normalize(process.env.OPENCODE_STICKY_SESSION_ID);\n const envOverride = envPromptCacheKey || envStickySessionID;\n const isProviderCodexMap: Record<string, boolean> = {};\n let inited = false;\n const init = async () => {\n if (inited) return;\n const resp = await client.config.providers();\n const providers = resp.data?.providers ?? [];\n for (const provider of providers) {\n isProviderCodexMap[provider.id] = Boolean(provider.options?.isCodex);\n }\n inited = true;\n };\n\n return {\n \"chat.params\": async (input, output) => {\n if (!inited) await init();\n const providerNpm = normalize(input.model.api.npm);\n const isCodex = isProviderCodexMap[input.model.providerID] ?? false;\n if (!providerNpm.includes(OPENAI_PROVIDER_NPM) && !isCodex) return;\n\n const headers = ensureHeaders(input.model);\n const sessionValue =\n envOverride ||\n pickHeaderSessionValue(headers) ||\n normalize(input.sessionID);\n if (!sessionValue) return;\n\n for (const key of SESSION_HEADER_KEYS) {\n headers[key] = sessionValue;\n }\n\n output.options.promptCacheKey = sessionValue;\n if (isCodex && !output.options.instructions) {\n output.options.instructions = \"\";\n }\n },\n };\n};\n\nexport default StickySessionPlugin;\n", "/**\n * Superpowers plugin for OpenCode.ai\n *\n * Injects superpowers bootstrap context via system prompt transform.\n * Skills are discovered via OpenCode's native skill tool from symlinked directory.\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport os from 'os';\nimport { fileURLToPath } from 'url';\nimport type { Plugin } from '@opencode-ai/plugin';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst PROMPT_INJECTION_AGENTS = new Set(['plan', 'build', 'compaction', 'orchestrator']);\n\nconst normalizeAgentName = (name: unknown) =>\n typeof name === 'string' ? name.trim().toLowerCase() : '';\n\n// Simple frontmatter extraction (avoid dependency on skills-core for bootstrap)\nconst extractAndStripFrontmatter = (content: string) => {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n if (!match) return { frontmatter: {}, content };\n\n const frontmatterStr = match[1];\n const body = match[2];\n const frontmatter: Record<string, string> = {};\n\n for (const line of frontmatterStr.split('\\n')) {\n const colonIdx = line.indexOf(':');\n if (colonIdx > 0) {\n // \u55B5~ \u8FD9\u6BB5\u4EE3\u7801\u7684\u4F5C\u7528\u662F\u89E3\u6790 frontmatter \u7684\u6BCF\u4E00\u884C\uFF1A\n // 1. \u63D0\u53D6\u5192\u53F7\u524D\u7684\u90E8\u5206\u4F5C\u4E3A key \u5E76\u53BB\u9664\u7A7A\u683C\u3002\n // 2. \u63D0\u53D6\u5192\u53F7\u540E\u7684\u90E8\u5206\u4F5C\u4E3A value\uFF0C\u53BB\u9664\u7A7A\u683C\u5E76\u5265\u79BB\u4E24\u4FA7\u7684\u5F15\u53F7\u3002\n // 3. \u5C06\u7ED3\u679C\u5B58\u5165 frontmatter \u5BF9\u8C61\uFF08\u6B64\u5904\u9700\u65AD\u8A00\u7C7B\u578B\u4EE5\u907F\u514D TS \u7D22\u5F15\u9519\u8BEF\uFF09\u3002\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim().replace(/^[\"']|[\"']$/g, '');\n frontmatter[key] = value;\n }\n }\n\n return { frontmatter, content: body };\n};\n\n// Normalize a path: trim whitespace, expand ~, resolve to absolute\nconst normalizePath = (p: string, homeDir: string) => {\n if (!p || typeof p !== 'string') return null;\n let normalized = p.trim();\n if (!normalized) return null;\n if (normalized.startsWith('~/')) {\n normalized = path.join(homeDir, normalized.slice(2));\n } else if (normalized === '~') {\n normalized = homeDir;\n }\n return path.resolve(normalized);\n};\n\nexport const SuperpowersPlugin: Plugin = async ({ client, directory }) => {\n const homeDir = os.homedir();\n const superpowersSkillsDir = path.join(homeDir, '.agents/skills/superpowers');\n const latestAgentBySession = new Map<string, string>();\n // const superpowersSkillsDir = path.resolve(__dirname, '../../skills');\n // const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR ?? '', homeDir);\n // const configDir = envConfigDir || path.join(homeDir, '.config/opencode');\n\n // Helper to generate bootstrap content\n const getBootstrapContent = () => {\n // Try to load using-superpowers skill\n const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md');\n if (!fs.existsSync(skillPath)) return null;\n\n const fullContent = fs.readFileSync(skillPath, 'utf8');\n const { content } = extractAndStripFrontmatter(fullContent);\n\n const toolMapping = `**Tool Mapping for OpenCode:**\nWhen skills reference tools you don't have, substitute OpenCode equivalents:\n- \\`TodoWrite\\` \u2192 \\`update_plan\\`\n- \\`Task\\` tool with subagents \u2192 Use OpenCode's subagent system (@mention)\n- \\`Skill\\` tool \u2192 OpenCode's native \\`skill\\` tool\n- \\`Read\\`, \\`Write\\`, \\`Edit\\`, \\`Bash\\` \u2192 Your native tools\n\n**Skills location:**\nSuperpowers skills are in \\`${superpowersSkillsDir}/\\`\nUse OpenCode's native \\`skill\\` tool to list and load skills.`;\n\n return `<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load \"using-superpowers\" again - that would be redundant.**\n\n${content}\n\n${toolMapping}\n</EXTREMELY_IMPORTANT>`;\n };\n\n return {\n 'chat.message': async (input) => {\n const agentName = normalizeAgentName(input.agent);\n if (!agentName) return;\n latestAgentBySession.set(input.sessionID, agentName);\n },\n // Use system prompt transform to inject bootstrap (fixes #226 agent reset bug)\n 'experimental.chat.system.transform': async (input, output) => {\n const sessionID = input.sessionID?.trim();\n const agentName = sessionID ? latestAgentBySession.get(sessionID) : undefined;\n if (!agentName || !PROMPT_INJECTION_AGENTS.has(agentName)) return;\n\n const bootstrap = getBootstrapContent();\n if (bootstrap) {\n if (!output.system) output.system = [];\n output.system.push(bootstrap);\n }\n }\n };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["log", "prefix"]
|
|
7
7
|
}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
export declare function initLogger(client: PluginInput["client"]): void;
|
|
3
|
+
export declare function createLogger(module: string, prefix: string): {
|
|
4
|
+
debug: (message: string, extra?: any) => void;
|
|
5
|
+
info: (message: string, extra?: any) => void;
|
|
6
|
+
warn: (message: string, extra?: any) => void;
|
|
7
|
+
error: (message: string, extra?: any) => void;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAQvD,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,QAEvD;AACD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;qBAiCtC,MAAM,UAAU,GAAG;oBACpB,MAAM,UAAU,GAAG;oBACnB,MAAM,UAAU,GAAG;qBAClB,MAAM,UAAU,GAAG;EAEvC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
declare function createProviderPlugin(providerName: string): Plugin;
|
|
3
|
+
export declare const FoxcodeAwsCache: Plugin;
|
|
4
|
+
export declare const AnthropicCache: Plugin;
|
|
5
|
+
export declare const AnthropicUltraCache: Plugin;
|
|
6
|
+
export declare const WormholeCache: Plugin;
|
|
7
|
+
export declare const createAnthropicCachePlugin: typeof createProviderPlugin;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=opencode-anthropic-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-anthropic-cache.d.ts","sourceRoot":"","sources":["../src/opencode-anthropic-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA2EzD,iBAAS,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAED,eAAO,MAAM,eAAe,QAAsC,CAAC;AACnE,eAAO,MAAM,cAAc,QAAoC,CAAC;AAChE,eAAO,MAAM,mBAAmB,QAA0C,CAAC;AAC3E,eAAO,MAAM,aAAa,QAAqC,CAAC;AAEhE,eAAO,MAAM,0BAA0B,6BAAuB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
/**
|
|
3
|
+
* set a session-scoped prompt cache key and session-scoped sticky routing headers for right.codes.
|
|
4
|
+
*
|
|
5
|
+
* Injects HTTP headers via `input.model.headers` so upstream proxies/load
|
|
6
|
+
* balancers can keep a conversation pinned to one account.
|
|
7
|
+
*
|
|
8
|
+
* Headers injected for @ai-sdk/openai providers:
|
|
9
|
+
* - x-session-id
|
|
10
|
+
* - conversation_id
|
|
11
|
+
* - session_id
|
|
12
|
+
*
|
|
13
|
+
* Source of truth (in order):
|
|
14
|
+
* - env: OPENCODE_PROMPT_CACHE_KEY (manual override)
|
|
15
|
+
* - env: OPENCODE_STICKY_SESSION_ID (manual override)
|
|
16
|
+
* - model headers (x-session-id / conversation_id / session_id)
|
|
17
|
+
* - opencode sessionID (default)
|
|
18
|
+
*/
|
|
19
|
+
export declare const StickySessionPlugin: Plugin;
|
|
20
|
+
export default StickySessionPlugin;
|
|
21
|
+
//# sourceMappingURL=sticky-session-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sticky-session-plugin.d.ts","sourceRoot":"","sources":["../src/sticky-session-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA+BlD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAwCjC,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Superpowers plugin for OpenCode.ai
|
|
3
|
+
*
|
|
4
|
+
* Injects superpowers bootstrap context via system prompt transform.
|
|
5
|
+
* Skills are discovered via OpenCode's native skill tool from symlinked directory.
|
|
6
|
+
*/
|
|
7
|
+
import type { Plugin } from '@opencode-ai/plugin';
|
|
8
|
+
export declare const SuperpowersPlugin: Plugin;
|
|
9
|
+
//# sourceMappingURL=superpowers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"superpowers.d.ts","sourceRoot":"","sources":["../src/superpowers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA8ClD,eAAO,MAAM,iBAAiB,EAAE,MA0D/B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-fixes-huihui",
|
|
3
|
-
"version": "0.1.4-beta.
|
|
3
|
+
"version": "0.1.4-beta.11",
|
|
4
4
|
"description": "Unified sticky-session plugin for opencode with session headers and prompt cache key.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -19,10 +19,13 @@
|
|
|
19
19
|
"README.md"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
+
"clean": "rm -rf dist",
|
|
23
|
+
"lint": "biome check --config-path .biome.json src package.json tsconfig.json",
|
|
24
|
+
"format": "biome format --write --config-path .biome.json src package.json tsconfig.json",
|
|
22
25
|
"typecheck": "tsc --noEmit",
|
|
23
26
|
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
|
|
24
|
-
"build:js": "esbuild index.ts --bundle --platform=node --target=es2022 --format=esm --outfile=dist/index.js --loader:.txt=text --sourcemap",
|
|
25
|
-
"build": "npm run build:types && npm run build:js",
|
|
27
|
+
"build:js": "esbuild src/index.ts --bundle --platform=node --target=es2022 --format=esm --outfile=dist/index.js --loader:.txt=text --sourcemap",
|
|
28
|
+
"build": "npm run clean && npm run build:types && npm run build:js",
|
|
26
29
|
"prepublishOnly": "npm run typecheck && npm run build"
|
|
27
30
|
},
|
|
28
31
|
"keywords": [
|
|
@@ -46,9 +49,13 @@
|
|
|
46
49
|
"node": ">=18"
|
|
47
50
|
},
|
|
48
51
|
"devDependencies": {
|
|
52
|
+
"@biomejs/biome": "^2.4.4",
|
|
49
53
|
"@opencode-ai/plugin": "^1.2.6",
|
|
50
54
|
"@types/node": "^25.2.3",
|
|
51
55
|
"esbuild": "^0.27.3",
|
|
52
56
|
"typescript": "^5.9.2"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"opencode-anthropic-cache": "^1.0.0"
|
|
53
60
|
}
|
|
54
61
|
}
|