import { Effect } from "effect"; import { BindingError, type ClientResolver } from "../plugin/bindings"; const unknownMessage = (cause: unknown): string => { // oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- boundary: test resolver preserves promise rejection text as BindingError message return cause instanceof Error ? cause.message : String(cause); }; // --------------------------------------------------------------------------- // A ClientResolver that maps the daily-brief github client methods to real // GitHub REST calls against a base URL (the emulate GitHub emulator in the // e2e). This stands in for the platform invoke path: in production the resolver // routes through the catalog (credentials injected platform-side, policy/audit // applied); here it makes the real authenticated HTTP call with the bound // connection's token. Everything crossing is JSON. // --------------------------------------------------------------------------- export interface GithubResolverOptions { readonly baseUrl: string; /** Bearer token per connection name. */ readonly tokens: Readonly>; } export const makeGithubRestResolver = (options: GithubResolverOptions): ClientResolver => { const call = async ( connection: string, path: readonly string[], args: readonly unknown[], ): Promise => { const token = options.tokens[connection]; const headers: Record = { accept: "application/vnd.github+json", "content-type": "application/json", }; if (token) headers.authorization = `Bearer ${token}`; const method = path.join("."); const input = (args[0] ?? {}) as Record; if (method === "repos.listForAuthenticatedUser") { const perPage = input.per_page ?? 100; const res = await fetch(`${options.baseUrl}/user/repos?per_page=${perPage}`, { headers }); return (await res.json()) as Array<{ full_name: string }>; } if (method === "issues.listForRepo") { const owner = String(input.owner); const repo = String(input.repo); const state = String(input.state ?? "open"); const perPage = input.per_page ?? 100; const since = input.since ? `&since=${encodeURIComponent(String(input.since))}` : ""; const res = await fetch( `${options.baseUrl}/repos/${owner}/${repo}/issues?state=${state}&per_page=${perPage}${since}`, { headers }, ); const issues = (await res.json()) as Array>; // Normalize to the shape the tool expects. return issues.map((i) => ({ number: i.number, title: i.title, labels: Array.isArray(i.labels) ? (i.labels as Array<{ name?: string } | string>).map((l) => typeof l === "string" ? { name: l } : { name: l.name ?? "" }, ) : [], assignee: i.assignee ?? null, updated_at: i.updated_at, html_url: i.html_url, pull_request: i.pull_request, })); } // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: Promise-shaped test resolver rejects unsupported methods like fetch failures throw new Error(`unsupported github method: ${method}`); }; return { call: ({ connection, path, args }) => Effect.tryPromise({ try: () => call(connection, path, args), catch: (cause) => new BindingError({ message: unknownMessage(cause), role: "github", surface: "github", }), }), }; };