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);
},
}),
];