g1t/services/runner/src/delegate.ts
| 1 | /** |
| 2 | * Putting an agent on something in one step (`RunnerService.delegate`): |
| 3 | * what is said about the agent once its issue is open, whatever became of |
| 4 | * it. Pure, so it is tested on its own. |
| 5 | */ |
| 6 | import type { AgentStart, Delegated, Issue, Pull } from "@g1t/contracts"; |
| 7 | |
| 8 | const SITE = "https://g1t.sh"; |
| 9 | |
| 10 | /** |
| 11 | * What the workspace's agents have no model is said as. `preview`: g1t's |
| 12 | * hosted models are closed to it because billing takes no real money yet. |
| 13 | */ |
| 14 | export function noModelMessage(workspace: string, preview = false): string { |
| 15 | if (preview) { |
| 16 | return `g1t's hosted models are not open to the ${workspace} workspace yet: during the preview they run only for a few invited workspaces. An owner can connect the workspace's own model provider under Integrations, and its agents start at once.`; |
| 17 | } |
| 18 | return `The ${workspace} workspace has no model for its agents: its free allowance on g1t's models is used up or over. An owner can connect the workspace's own model provider under Integrations, and its agents start at once.`; |
| 19 | } |
| 20 | |
| 21 | /** |
| 22 | * Where the fix for a refusal is: the workspace's billing page (its limit |
| 23 | * and per-issue caps have anchors there), its model settings, or support, |
| 24 | * which has no page to fix it on. |
| 25 | */ |
| 26 | export function fixUrlFor(code: string, workspace: string): string | null { |
| 27 | const slug = workspace.toLowerCase(); |
| 28 | switch (code) { |
| 29 | case "not_paid": |
| 30 | case "trial_used": |
| 31 | case "oss_pool_empty": |
| 32 | return `${SITE}/${slug}/-/billing`; |
| 33 | case "limit": |
| 34 | return `${SITE}/${slug}/-/billing#limit`; |
| 35 | case "issue_cap": |
| 36 | return `${SITE}/${slug}/-/billing#caps`; |
| 37 | case "no_model": |
| 38 | return `${SITE}/${slug}/-/integrations`; |
| 39 | default: |
| 40 | return null; |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | /** The agent at work on `pull`. */ |
| 45 | export function started(issue: Issue, pull: Pull): Delegated { |
| 46 | return { issue, pull, agent: { status: "started", code: null, message: null, fixUrl: null } }; |
| 47 | } |
| 48 | |
| 49 | /** Every agent slot is busy: it starts by itself when one frees up. */ |
| 50 | export function queued(issue: Issue, message: string): Delegated { |
| 51 | return { issue, pull: null, agent: { status: "queued", code: "waiting", message, fixUrl: null } }; |
| 52 | } |
| 53 | |
| 54 | /** It did not start: why, and where to fix it. */ |
| 55 | export function notStarted(issue: Issue, code: string, message: string, workspace: string): Delegated { |
| 56 | const agent: AgentStart = { status: "not_started", code, message, fixUrl: fixUrlFor(code, workspace) }; |
| 57 | return { issue, pull: null, agent }; |
| 58 | } |
| 59 | |
| 60 | /** The issue's title and body, tidied, and the checks with blank ones left out. */ |
| 61 | export function delegateInput(input: { title?: unknown; body?: unknown; checks?: unknown; labels?: unknown }): { |
| 62 | title: string; |
| 63 | body: string; |
| 64 | checks: string[]; |
| 65 | labels: string[]; |
| 66 | } { |
| 67 | const list = (value: unknown) => |
| 68 | (Array.isArray(value) ? value : typeof value === "string" ? value.split("\n") : []) |
| 69 | .map((item) => String(item).trim()) |
| 70 | .filter(Boolean); |
| 71 | return { |
| 72 | title: typeof input.title === "string" ? input.title.trim() : "", |
| 73 | body: typeof input.body === "string" ? input.body.trim() : "", |
| 74 | checks: list(input.checks), |
| 75 | labels: list(input.labels), |
| 76 | }; |
| 77 | } |