Skip to content
63 linesCodeBlameRaw
1/**
2 * Agent templates (routes/workspace/agents/templates.tsx): the starting
3 * points g1t provides for an agent, each a role with responsibilities,
4 * helpers, a voice, model limits and instructions. A template isn't
5 * installed: an owner starts a new agent from one and configures it, and
6 * the agent is the workspace's own from then on.
7 *
8 * No Workers or React imports, so it can be tested under Node.
9 */
10import type { AgentTemplate, WorkspaceAgent } from "@g1t/contracts";
11
12/** Every template, under Agents. */
13export function templatesPath(slug: string): string {
14 return `/${slug}/-/agents/templates`;
15}
16
17/** One template's page. */
18export function templatePath(slug: string, template: string): string {
19 return `/${slug}/-/agents/templates/${template}`;
20}
21
22/** Where an owner starts an agent from a template: the new-agent form, with the template chosen. */
23export function startPath(slug: string, template: string): string {
24 return `/${slug}/-/agents/new?template=${encodeURIComponent(template)}`;
25}
26
27/** An agent as a template's page needs it: who it is and which template it started from. */
28export type StartedAgent = Pick<WorkspaceAgent, "id" | "handle" | "display_name" | "template" | "archived_at">;
29
30/** A template, with the workspace's agents that started from it. */
31export type TemplateListing = {
32 template: AgentTemplate;
33 /** Agents started from it, not archived. */
34 agents: Pick<WorkspaceAgent, "id" | "handle" | "display_name">[];
35};
36
37/**
38 * Every template, in the order the agents service gives them, with the
39 * agents started from each. `agents` null: the agents service could not
40 * say, so none is counted.
41 */
42export function templateListings(templates: AgentTemplate[], agents: StartedAgent[] | null): TemplateListing[] {
43 return templates.map((template) => ({
44 template,
45 agents: (agents ?? [])
46 .filter((agent) => agent.template === template.id && !agent.archived_at)
47 .map(({ id, handle, display_name }) => ({ id, handle, display_name })),
48 }));
49}
50
51/** "Software Engineer · Suggests Otto": a role as one line. A template names a role, never a team. */
52export function roleLine(template: Pick<AgentTemplate, "title" | "display_name">): string {
53 return `${template.title} · Suggests ${template.display_name}`;
54}
55
56/** How a routing limit reads: "Any model the work needs", "Large models or better", "Up to large models", "Large to frontier models". */
57export function routingWords(routing: Pick<AgentTemplate["routing"], "floor" | "ceiling">): string {
58 const name = (tier: string) => tier.charAt(0).toUpperCase() + tier.slice(1);
59 if (routing.floor && routing.ceiling) return `${name(routing.floor)} to ${routing.ceiling} models`;
60 if (routing.floor) return `${name(routing.floor)} models or better`;
61 if (routing.ceiling) return `Up to ${routing.ceiling} models`;
62 return "Any model the work needs";
63}