g1t/packages/contracts/src/mentions.ts
| 1 | /** |
| 2 | * `@g1t-agent` in comments, and rules that put g1t-agent to work by |
| 3 | * itself. Kept by the work service; mirrors `services/work/src/mentions.rs`. |
| 4 | */ |
| 5 | import type { ServiceBinding } from "./clients"; |
| 6 | import type { User, Viewer } from "./identity"; |
| 7 | import type { RepoPath } from "./repos"; |
| 8 | import type { Result } from "./result"; |
| 9 | import type { LifecycleJob, PullStatus } from "./work"; |
| 10 | |
| 11 | /** How g1t's agent is mentioned in a comment. */ |
| 12 | export const AGENT_HANDLE = "@g1t-agent"; |
| 13 | |
| 14 | /** What someone who mentioned `@g1t-agent` wants. */ |
| 15 | export type MentionIntent = "work" | "question" | "review"; |
| 16 | |
| 17 | /** What the runner needs to act on a comment that mentioned `@g1t-agent`. */ |
| 18 | export type MentionJob = { |
| 19 | commentId: string; |
| 20 | /** Who wrote it, with the memberships they had then. */ |
| 21 | actor: User; |
| 22 | repo: RepoPath; |
| 23 | number: number; |
| 24 | /** The comment as written. */ |
| 25 | body: string; |
| 26 | intent: MentionIntent; |
| 27 | /** Whether they belong to the repository's workspace. */ |
| 28 | member: boolean; |
| 29 | defaultBranch: string; |
| 30 | /** Set when the comment is on an issue: whether it is still open. */ |
| 31 | issueOpen: boolean | null; |
| 32 | /** On an issue: the pull request g1t-agent is already working on for it, if any. */ |
| 33 | workingPull: number | null; |
| 34 | /** Set when the comment is on a pull request. */ |
| 35 | pull: { |
| 36 | id: string; |
| 37 | status: PullStatus; |
| 38 | /** Made by g1t-agent, which g1t sees through. */ |
| 39 | agentAuthored: boolean; |
| 40 | /** Where its change is: its fork, or the repository itself. */ |
| 41 | source: RepoPath; |
| 42 | /** The head is a branch of the repository itself, not a fork. */ |
| 43 | inRepo: boolean; |
| 44 | branch: string | null; |
| 45 | headCommit: string | null; |
| 46 | files: string[]; |
| 47 | } | null; |
| 48 | }; |
| 49 | |
| 50 | /** A repository's rules for putting g1t-agent to work by itself. */ |
| 51 | export type AgentRules = { |
| 52 | /** When an issue is given this label, g1t-agent takes it. */ |
| 53 | label: string | null; |
| 54 | updatedBy: string | null; |
| 55 | updatedAt: string | null; |
| 56 | }; |
| 57 | |
| 58 | export interface MentionsApi { |
| 59 | /** For the runner: claims a comment's mention, once. Null when there is none to take. */ |
| 60 | takeMention(commentId: string): Promise<MentionJob | null>; |
| 61 | /** For the runner: sends the author of a g1t pull request back to address the comment. */ |
| 62 | mentionRevision(commentId: string): Promise<Result<LifecycleJob>>; |
| 63 | /** For the runner: says something in the mention's thread as g1t-agent, once. */ |
| 64 | replyMention(commentId: string, body: string): Promise<boolean>; |
| 65 | getAgentRules(repo: RepoPath, viewer: Viewer): Promise<Result<AgentRules>>; |
| 66 | /** Members only. A null or empty label turns the rule off. */ |
| 67 | setAgentRules(actor: User, repo: RepoPath, rules: { label: string | null }): Promise<Result<AgentRules>>; |
| 68 | } |
| 69 | |
| 70 | /** The mention and rule methods of the work service. */ |
| 71 | export function mentionsClient(service: ServiceBinding): MentionsApi { |
| 72 | const call = async <T>(method: string, args: object): Promise<T> => { |
| 73 | const response = await service.fetch(`https://service/rpc/${method}`, { |
| 74 | method: "POST", |
| 75 | headers: { "content-type": "application/json" }, |
| 76 | body: JSON.stringify(args), |
| 77 | }); |
| 78 | if (!response.ok) throw new Error(`${method} failed with status ${response.status}`); |
| 79 | return (await response.json()) as T; |
| 80 | }; |
| 81 | return { |
| 82 | takeMention: (commentId) => call("take_mention", { commentId }), |
| 83 | mentionRevision: (commentId) => call("mention_revision", { commentId }), |
| 84 | replyMention: (commentId, body) => call("reply_mention", { commentId, body }), |
| 85 | getAgentRules: (repo, viewer) => call("get_agent_rules", { repo, viewer }), |
| 86 | setAgentRules: (actor, repo, rules) => call("set_agent_rules", { actor, repo, label: rules.label }), |
| 87 | }; |
| 88 | } |