mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-09 07:29:51 +00:00
feat: support opencode
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const PLUGIN_FILENAME = 'hapi-hook.ts';
|
||||
|
||||
function buildPluginSource(hookUrl: string, token: string): string {
|
||||
const escapedUrl = JSON.stringify(hookUrl);
|
||||
const escapedToken = JSON.stringify(token);
|
||||
|
||||
return [
|
||||
'// Generated by HAPI. Do not edit manually.',
|
||||
'',
|
||||
`const DEFAULT_HOOK_URL = ${escapedUrl};`,
|
||||
`const DEFAULT_HOOK_TOKEN = ${escapedToken};`,
|
||||
'const HOOK_URL = process.env.HAPI_OPENCODE_HOOK_URL || DEFAULT_HOOK_URL;',
|
||||
'const HOOK_TOKEN = process.env.HAPI_OPENCODE_HOOK_TOKEN || DEFAULT_HOOK_TOKEN;',
|
||||
'',
|
||||
'const EVENT_NAMES = new Set([',
|
||||
" 'message.updated',",
|
||||
" 'message.part.updated',",
|
||||
" 'permission.updated',",
|
||||
" 'permission.asked',",
|
||||
" 'permission.replied',",
|
||||
" 'session.created',",
|
||||
" 'session.updated',",
|
||||
" 'tool.execute.before',",
|
||||
" 'tool.execute.after',",
|
||||
']);',
|
||||
'',
|
||||
'function pickString(value) {',
|
||||
" return typeof value === 'string' && value.length > 0 ? value : null;",
|
||||
'}',
|
||||
'',
|
||||
'function extractSessionId(value) {',
|
||||
" if (!value || typeof value !== 'object') return null;",
|
||||
' const record = value;',
|
||||
' const direct = (',
|
||||
' pickString(record.sessionId)',
|
||||
' || pickString(record.sessionID)',
|
||||
' || pickString(record.session_id)',
|
||||
' || (record.session && pickString(record.session.id))',
|
||||
' );',
|
||||
' if (direct) return direct;',
|
||||
' if (record.part && typeof record.part === \'object\') {',
|
||||
' const nested = extractSessionId(record.part);',
|
||||
' if (nested) return nested;',
|
||||
' }',
|
||||
' if (record.info && typeof record.info === \'object\') {',
|
||||
' const nested = extractSessionId(record.info);',
|
||||
' if (nested) return nested;',
|
||||
' }',
|
||||
' return null;',
|
||||
'}',
|
||||
'',
|
||||
'function extractSessionIdFromEvent(eventName, payload) {',
|
||||
' const direct = extractSessionId(payload);',
|
||||
' if (direct) return direct;',
|
||||
' if (!payload || typeof payload !== \'object\') return null;',
|
||||
' const record = payload;',
|
||||
' if (record.info && typeof record.info === \'object\') {',
|
||||
' const fromInfo = extractSessionId(record.info);',
|
||||
' if (fromInfo) return fromInfo;',
|
||||
' if (eventName.startsWith(\'session.\')) {',
|
||||
' return pickString(record.info.id);',
|
||||
' }',
|
||||
' }',
|
||||
' return null;',
|
||||
'}',
|
||||
'',
|
||||
'async function sendHook(eventName, payload, sessionId) {',
|
||||
' if (!HOOK_URL || !HOOK_TOKEN) {',
|
||||
' return;',
|
||||
' }',
|
||||
'',
|
||||
' const body = JSON.stringify({',
|
||||
' event: eventName,',
|
||||
' payload,',
|
||||
' sessionId',
|
||||
' });',
|
||||
'',
|
||||
' try {',
|
||||
' await fetch(HOOK_URL, {',
|
||||
" method: 'POST',",
|
||||
' headers: {',
|
||||
" 'Content-Type': 'application/json',",
|
||||
" 'x-hapi-hook-token': HOOK_TOKEN",
|
||||
' },',
|
||||
' body',
|
||||
' });',
|
||||
' } catch {',
|
||||
' // Ignore hook errors to avoid disrupting OpenCode',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
'export const HapiHookPlugin = async () => {',
|
||||
' return {',
|
||||
' event: async ({ event }) => {',
|
||||
' if (!event || typeof event.type !== \'string\') {',
|
||||
' return;',
|
||||
' }',
|
||||
' if (EVENT_NAMES.size > 0 && !EVENT_NAMES.has(event.type)) {',
|
||||
' return;',
|
||||
' }',
|
||||
' const sessionId = extractSessionIdFromEvent(event.type, event.properties);',
|
||||
' await sendHook(event.type, event.properties, sessionId);',
|
||||
' }',
|
||||
' };',
|
||||
'};',
|
||||
''
|
||||
].join('\\n');
|
||||
}
|
||||
|
||||
function resolvePluginDir(rootPath: string): string {
|
||||
return join(rootPath, 'plugins');
|
||||
}
|
||||
|
||||
export function ensureOpencodeHookPlugin(rootPath: string, hookUrl: string, token: string): string {
|
||||
const pluginDir = resolvePluginDir(rootPath);
|
||||
mkdirSync(pluginDir, { recursive: true });
|
||||
|
||||
const pluginPath = join(pluginDir, PLUGIN_FILENAME);
|
||||
const nextSource = buildPluginSource(hookUrl, token);
|
||||
|
||||
try {
|
||||
const current = readFileSync(pluginPath, 'utf-8');
|
||||
if (current === nextSource) {
|
||||
return pluginPath;
|
||||
}
|
||||
} catch {
|
||||
// Ignore missing or unreadable file.
|
||||
}
|
||||
|
||||
writeFileSync(pluginPath, nextSource, 'utf-8');
|
||||
return pluginPath;
|
||||
}
|
||||
Reference in New Issue
Block a user