CoolFace
Apppublic

delqhi/sin-github-issues

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
a2a-http.ts120 linesDownload Raw Back to src
1import { randomUUID } from 'node:crypto';2import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';3import type { GitHubAgentAction } from './runtime.js';4import { listGitHubAppWebhookPaths, routeGitHubWebhook } from './github-app-routing.js';5import { buildAgentCard, resolveTemplateAgentConfig, TEMPLATE_AGENT_ID, TEMPLATE_AGENT_NAME } from './metadata.js';6import { executeGitHubAgentAction } from './runtime.js';7 8type RpcRequest = { jsonrpc?: string; id?: string | number | null; method?: string; params?: Record<string, unknown> };9 10export function createTemplateAgentHttpServer() {11  const config = resolveTemplateAgentConfig();12  const server = createServer((request, response) => void handleRequest(request, response, config.publicBaseUrl));13  return {14    server,15    async start() {16      await new Promise<void>((resolve, reject) => {17        server.once('error', reject);18        server.listen(config.port, config.host, () => resolve());19      });20    },21    async stop() {22      await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));23    },24  };25}26 27async function handleRequest(request: IncomingMessage, response: ServerResponse, baseUrl: string) {28  const routePath = normalizePath(request.url || '/');29  if (request.method === 'GET' && request.url === '/health') return sendJson(response, 200, { ok: true, agent: TEMPLATE_AGENT_ID });30  if (request.method === 'GET' && request.url === '/') return sendHtml(response, 200, `<html><body><h1>${TEMPLATE_AGENT_NAME}</h1></body></html>`);31  if (request.method === 'GET' && (request.url === '/.well-known/agent-card.json' || request.url === '/.well-known/agent.json')) {32    return sendJson(response, 200, buildAgentCard(baseUrl));33  }34  const configuredWebhookPaths = request.method === 'POST' ? await listGitHubAppWebhookPaths() : [];35  if (request.method === 'POST' && configuredWebhookPaths.some((configuredPath) => routePath === configuredPath || routePath.startsWith(`${configuredPath}/`))) {36    const rawBody = await readRawBody(request);37    const payload = rawBody.trim() ? JSON.parse(rawBody) : {};38    const result = await routeGitHubWebhook({39      payload,40      rawBody,41      routePath,42      signature256: readHeader(request, 'x-hub-signature-256'),43      eventName: readHeader(request, 'x-github-event'),44      deliveryId: readHeader(request, 'x-github-delivery'),45    });46    return sendJson(response, result.ok ? 200 : 400, result);47  }48  if (request.method === 'POST' && request.url === '/a2a/v1') {49    const rpc = ((await readJson(request)) || {}) as RpcRequest;50    if (rpc.method === 'agent/getCard') return sendJson(response, 200, { jsonrpc: '2.0', id: rpc.id ?? null, result: buildAgentCard(baseUrl) });51    if (rpc.method === 'message/send') {52      const action = parseAction((rpc.params?.message as { parts?: Array<{ text?: string }> } | undefined)?.parts?.map((part) => part.text || '').join(' ').trim() || '');53      const result = await executeGitHubAgentAction(action);54      return sendJson(response, 200, {55        jsonrpc: '2.0',56        id: rpc.id ?? null,57        result: {58          id: randomUUID(),59          kind: 'task',60          status: { state: 'completed', timestamp: new Date().toISOString(), message: { role: 'agent', parts: [{ type: 'text', text: 'done' }] } },61          artifacts: [{ id: randomUUID(), name: action.action, description: action.action, parts: [{ type: 'data', data: result }] }],62          metadata: { action: action.action },63        },64      });65    }66  }67  sendJson(response, 404, { error: 'not_found' });68}69 70function parseAction(text: string): GitHubAgentAction {71  try {72    const parsed = JSON.parse(text);73    if (parsed && typeof parsed === 'object' && typeof parsed.action === 'string') return parsed as GitHubAgentAction;74  } catch { /* not JSON, fall through to text matching */ }75 76  const value = text.toLowerCase().trim();77  if (value.includes('health')) return { action: 'sin.github.health' };78  if (value.includes('project')) return { action: 'sin.github.project.orchestrate', prompt: text };79  if (value.includes('wiki')) return { action: 'sin.github.wiki.sync', prompt: text };80  if (value.includes('discussion')) return { action: 'sin.github.discussion.start', prompt: text };81  if (value.includes('gist')) return { action: 'sin.github.gist.publish', prompt: text };82  if (value.includes('security') || value.includes('audit')) return { action: 'sin.github.security.audit', prompt: text };83  if (value.includes('pr') || value.includes('pull request') || value.includes('review')) return { action: 'sin.github.pr.review', prNumber: 0 };84  if (value.includes('ledger') || value.includes('showcase')) return { action: 'sin.github.ledger.log', agentName: 'unknown', activityTitle: text, details: text };85  if (value.includes('issue')) return { action: 'sin.github.issue.manage', prompt: text };86  return { action: 'agent.help' };87}88 89async function readJson(request: IncomingMessage) {90  const raw = (await readRawBody(request)).trim();91  return raw ? JSON.parse(raw) : null;92}93 94async function readRawBody(request: IncomingMessage) {95  const chunks: Buffer[] = [];96  for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));97  return Buffer.concat(chunks).toString('utf8');98}99 100function readHeader(request: IncomingMessage, name: string) {101  const value = request.headers[name];102  return typeof value === 'string' ? value : Array.isArray(value) ? value.join(',') : undefined;103}104 105function normalizePath(url: string) {106  return url.split('?', 1)[0] || '/';107}108 109function sendJson(response: ServerResponse, statusCode: number, payload: unknown) {110  response.statusCode = statusCode;111  response.setHeader('content-type', 'application/json; charset=utf-8');112  response.end(JSON.stringify(payload, null, 2));113}114 115function sendHtml(response: ServerResponse, statusCode: number, payload: string) {116  response.statusCode = statusCode;117  response.setHeader('content-type', 'text/html; charset=utf-8');118  response.end(payload);119}120