import { createBranch, createIssue, createPullRequest, fetchTransport, listIssues, } from "@code/primitives/git-remote-runtime"; import type { GitRemoteConfig, GitRemoteError, } from "@code/primitives/git-remote-runtime"; import { defineTool } from "@flue/runtime"; import { Effect } from "effect"; import * as v from "valibot"; /** * Canonical repo for the bootstrap: one self-hosted Gitea repo hosts all work. * Hard-coded owner/repo keeps the agent off credential-shaped free input. */ export const CANONICAL_REPO = { owner: "puter", repo: "zopu-code" } as const; export const makeGitRemoteConfig = (runtimeEnv: { readonly GITEA_TOKEN?: string; readonly GITEA_URL: string; }): GitRemoteConfig => { if (!runtimeEnv.GITEA_TOKEN) { throw new Error( "GITEA_TOKEN is required to configure the git remote tools" ); } return { baseUrl: runtimeEnv.GITEA_URL, owner: CANONICAL_REPO.owner, repo: CANONICAL_REPO.repo, token: runtimeEnv.GITEA_TOKEN, }; }; const run = (eff: Effect.Effect): Promise => Effect.runPromise(eff); const describeError = (error: GitRemoteError) => ({ error: error.message, reason: error.reason, }); // Tool returns are serialized to the model; round-trip through JSON so the // Effect-branded/readonly value objects become plain mutable JsonValue. // oxlint-disable-next-line unicorn/prefer-structured-clone -- structuredClone keeps readonly modifiers; JSON erases them for JsonValue const json = (value: unknown) => JSON.parse(JSON.stringify(value)); /** * Git-remote tools bound to the canonical repo. The agent creates issues, * branches, and pull requests on puter/zopu-code via the Gitea API using the * server-side token — it never accepts credentials or repo paths as input. */ export const createGitRemoteTools = (config: GitRemoteConfig) => [ defineTool({ description: "Create an issue on the canonical zopu-code repository. Use this to turn an agreed-upon piece of work into a tracked issue. Returns the issue number and URL. Always call list_issues first to avoid duplicates.", input: v.object({ body: v.string(), title: v.string(), }), name: "create_issue", async run({ input }) { const result = await run( createIssue(fetchTransport, config, input) ).catch(describeError); return json(result); }, }), defineTool({ description: "List open issues on the canonical zopu-code repository. Call this before creating an issue to check for duplicates and understand existing work.", name: "list_issues", async run() { const result = await run(listIssues(fetchTransport, config)).catch( describeError ); return json(result); }, }), defineTool({ description: "Create a remote branch on the canonical zopu-code repository. Usually you do not need this directly — start_workflow handles branching for a work run. Use only when explicitly setting up a branch by hand.", input: v.object({ from: v.optional(v.string()), name: v.string(), }), name: "create_branch", async run({ input }) { const result = await run( createBranch(fetchTransport, config, input) ).catch(describeError); return json(result); }, }), defineTool({ description: "Open a pull request on the canonical zopu-code repository. Provide the work branch, the base branch (usually main), a title, and a body. Returns the PR number and URL.", input: v.object({ baseBranch: v.string(), body: v.string(), branch: v.string(), title: v.string(), }), name: "create_pull_request", async run({ input }) { const result = await run( createPullRequest(fetchTransport, config, input) ).catch(describeError); return json(result); }, }), ];