Skip to content
73 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The Marketplace adds what g1t doesn't do and connects what you already use: extensions (Mail, Support, CRM, Recruiting, On-call, and bridges to a helpdesk, CRM or ERP you run, all Soon), starter kits, and integrations connected by the workspace or by each person; agents aren't installed there. Agent templates live in Agents, as starting points you configure, at Agents → Templates, and the Marketplace's old agent addresses lead there. The marketplace and agents guides say how.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/** Listings by department, in the order the templates come, "Other" for none. */
52export function byDepartment(listings: TemplateListing[]): [string, TemplateListing[]][] {
53 const departments = new Map<string, TemplateListing[]>();
54 for (const listing of listings) {
55 const key = listing.template.department || "Other";
56 departments.set(key, [...(departments.get(key) ?? []), listing]);
57 }
58 return [...departments];
59}
60
61/** "Software Engineer · Engineering": a role as one line. */
62export function roleLine(template: Pick<AgentTemplate, "title" | "department">): string {
63 return template.department ? `${template.title} · ${template.department}` : template.title;
64}
65
66/** How a routing limit reads: "Any model the work needs", "Large models or better", "Up to large models", "Large to frontier models". */
67export function routingWords(routing: Pick<AgentTemplate["routing"], "floor" | "ceiling">): string {
68 const name = (tier: string) => tier.charAt(0).toUpperCase() + tier.slice(1);
69 if (routing.floor && routing.ceiling) return `${name(routing.floor)} to ${routing.ceiling} models`;
70 if (routing.floor) return `${name(routing.floor)} models or better`;
71 if (routing.ceiling) return `Up to ${routing.ceiling} models`;
72 return "Any model the work needs";
73}