Skip to content
1,024 linesCodeBlameRaw
1/**
2 * A workspace's own agents: named members with a job, a personality,
3 * routing limits and a budget, kept by the agents service
4 * (`services/agents`). Every workspace also has `@g1t`, its built-in
5 * orchestrator, kept the same way (`builtin`).
6 *
7 * Wire shapes are snake_case end to end.
8 */
9import type { ServiceBinding } from "./clients";
10import type { Role, User } from "./identity";
11import type { Result } from "./result";
12import type { CardActionResult } from "./chat";
13
14// Model tiers (`ModelTier`, `MODEL_TIERS`) are integrations.ts's, the
15// same ones runs are routed between.
16import type { ModelTier } from "./integrations";
17import type { ExtensionInstall, InstallRequest, InstallRequestStatus, InstallRequests } from "./marketplace";
18
19/** The built-in orchestrator's handle; nobody else's agent may take it. */
20export const BUILTIN_AGENT_HANDLE = "g1t";
21
22/** The built-in orchestrator's template id: not one a workspace can adopt. */
23export const ORCHESTRATOR_TEMPLATE = "orchestrator";
24
25/** Voice presets; free text in `personality` refines them. */
26export type PersonalityPreset = "crisp" | "friendly" | "socratic" | "terse";
27
28export type AgentRouting = {
29 /** Never route below this tier. Null: no floor. */
30 floor: ModelTier | null;
31 /** Never route above this tier. Null: no ceiling. */
32 ceiling: ModelTier | null;
33 /**
34 * Where its model calls may go. Empty: anything the workspace allows.
35 * `g1t`: g1t's hosted models. `workspace`: the workspace's own providers
36 * (Integrations), whichever they are. Any other entry: one of the
37 * workspace's own providers by integration id. Entries combine, so
38 * `["g1t", "workspace"]` is both.
39 */
40 providers: string[];
41 /** Advanced: a fixed `provider/model`, for own endpoints. Usually null. */
42 pinned: string | null;
43 /**
44 * How hard it works (docs.g1t.sh/guides/agents/#effort): the tier its
45 * work starts on, how hard the model reasons, and how many steps a
46 * session may take. `auto` (the default; absent means it) picks per
47 * piece of work. The floor and ceiling still hold.
48 */
49 effort?: AgentEffort;
50};
51
52/** An agent's effort setting, cheapest first after `auto`. */
53export type AgentEffort = "auto" | "low" | "medium" | "high" | "max";
54
55export const AGENT_EFFORTS: readonly AgentEffort[] = ["auto", "low", "medium", "high", "max"];
56
57/** A level work actually ran at: what `auto` resolves to each time. */
58export type EffortLevel = Exclude<AgentEffort, "auto">;
59
60export const EFFORT_LEVELS: readonly EffortLevel[] = ["low", "medium", "high", "max"];
61
62export type AgentBudget = {
63 /** Monthly cap in micro-dollars. Null: only the workspace limit applies. */
64 monthly_micros: number | null;
65 daily_micros: number | null;
66 /** Default cap for one task. */
67 task_micros: number | null;
68};
69
70export type AgentAutonomy = {
71 open_pull_requests: "alone" | "approval";
72 merge: "alone" | "approval" | "never";
73 deploy_production: "approval" | "never";
74 edit_docs: "alone" | "suggest";
75};
76
77export type AgentStatus = "idle" | "working" | "waiting" | "out_of_budget" | "paused";
78
79export type WorkspaceAgent = {
80 id: string;
81 workspace_id: string;
82 /** Lowercase, unique in the workspace, never `g1t`. Mentioned as `@handle`. */
83 handle: string;
84 display_name: string;
85 /** Uploaded avatar hash, or null for the generated mark. */
86 avatar: string | null;
87 /**
88 * What its generated avatar is drawn from: a little pixel creature, the
89 * same for the same seed everywhere. Set from the handle when it is
90 * made; changing it gives the agent a new face.
91 */
92 avatar_seed: string;
93 /**
94 * One line, as lists show it: "QA Engineer". Its title when not written.
95 */
96 role: string;
97 /**
98 * Agents are hired into roles, not tasks: a title and broad
99 * responsibilities. The teams it is on are team memberships, as a
100 * person's are (identity `team_agents`), never part of the agent.
101 */
102 title: string;
103 /** What it is responsible for: 2 to 8 short duties, or none yet. */
104 responsibilities: string[];
105 /**
106 * Specialised help it will use inside its own work. Never members, never
107 * wider than their agent. Stored now; they run with tasks and sessions.
108 */
109 subagents: SubagentDef[];
110 /**
111 * Its required reading: Docs spaces (by id) it checks first, every time
112 * it answers or works. It still reads only what the person it acts for,
113 * and everyone reading its answer, can read.
114 */
115 reading: string[];
116 /**
117 * g1t's foundational skills turned off for it, by id (./skills.ts):
118 * every one is on unless named here. Off takes the skill's playbook out of
119 * its instructions; its tools stay as they are.
120 */
121 skills_off: string[];
122 /**
123 * Who it works with: `internal`, the workspace's own people (back
124 * office), or `customers` (front office). Only `internal` for now.
125 */
126 faces: AgentFaces;
127 /** The job: what it is responsible for and how it works. */
128 instructions: string;
129 personality_preset: PersonalityPreset;
130 /** Free text refining the voice. Never changes what it may do. */
131 personality: string;
132 routing: AgentRouting;
133 budget: AgentBudget;
134 autonomy: AgentAutonomy;
135 /** Tasks it works at once; more queue on its desk. */
136 capacity: number;
137 /** The template it was made from, if any. */
138 template: string | null;
139 /**
140 * The workspace's built-in orchestrator, `@g1t`: every workspace has one,
141 * made the first time its agents are asked for. It cannot be archived,
142 * and its handle, name, role and job are fixed; its `instructions` are
143 * added to that job. Listed first.
144 */
145 builtin: boolean;
146 /**
147 * Who it belongs to (docs.g1t.sh/guides/agents/, "Personal agents"):
148 * `workspace`, the workspace's own, which owners keep; or `personal`, a
149 * member's own, which only that member talks to, in their direct message
150 * with it, and whose spend counts against that member's budget.
151 */
152 scope: WorkspaceAgentScope;
153 /** For a personal agent, the member it belongs to: their user id and username. Null for a workspace agent. */
154 personal_owner_id: string | null;
155 personal_owner: string | null;
156 version: number;
157 status: AgentStatus;
158 /** Spend this calendar month, in micro-dollars. */
159 spent_month_micros: number;
160 created_by: string;
161 created_at: string;
162 updated_at: string;
163 archived_at: string | null;
164};
165
166/**
167 * Back office or front office (docs.g1t.sh/guides/agents/, "Back office
168 * and front office"). Customer-facing agents are not available yet.
169 */
170export type AgentFaces = "internal" | "customers";
171
172/** A workspace agent, which owners keep, or a member's personal agent. */
173export type WorkspaceAgentScope = "workspace" | "personal";
174
175/** What a personal agent starts with, unless its creator gives it other caps: $20 a month and $2 a session. */
176export const PERSONAL_AGENT_BUDGET = { monthly_micros: 20_000_000, daily_micros: null, task_micros: 2_000_000 } as const;
177
178/** Most personal agents one member keeps in a workspace. */
179export const MAX_PERSONAL_AGENTS = 10;
180
181/**
182 * A subagent: help an agent keeps for its own work, such as Margo's
183 * `flake-hunter`. Its routing limits sit within its agent's: a floor below
184 * the agent's is raised to it, a ceiling above is lowered to it.
185 */
186export type SubagentDef = {
187 /** Lowercase letters, digits and hyphens: `flake-hunter`. Unique on the agent. */
188 name: string;
189 /** One line: what it is for. */
190 description: string;
191 instructions: string;
192 routing: { floor: ModelTier | null; ceiling: ModelTier | null };
193 /** How many of it may run at once inside one task, 1 to 8. */
194 max_parallel: number;
195};
196
197export type NewWorkspaceAgent = {
198 handle: string;
199 display_name: string;
200 /** Left out or empty: its title. */
201 role?: string;
202 title?: string;
203 responsibilities?: string[];
204 subagents?: SubagentDef[];
205 /** Docs spaces (by id) it reads first; at most 10. */
206 reading?: string[];
207 /** Foundational skills to turn off, by id (./skills.ts). */
208 skills_off?: string[];
209 /** Only `internal` for now; `customers` is refused. */
210 faces?: AgentFaces;
211 instructions: string;
212 personality_preset?: PersonalityPreset;
213 personality?: string;
214 routing?: Partial<AgentRouting>;
215 budget?: Partial<AgentBudget>;
216 autonomy?: Partial<AgentAutonomy>;
217 capacity?: number;
218 template?: string | null;
219 /** Its avatar's seed; left out, the handle. */
220 avatar_seed?: string;
221 /**
222 * When creating: `workspace` (owners only, and their default) or
223 * `personal` (any member, when the workspace lets members make them; a
224 * member's default). Ignored on a change: an owner promotes instead.
225 */
226 scope?: WorkspaceAgentScope;
227};
228
229/**
230 * What g1t drafted from a description (docs.g1t.sh/guides/agents/,
231 * "Describe it"): a whole definition to edit before anything is saved,
232 * and what the agent will want around it. Nothing here is saved until the
233 * agent is created.
234 */
235export type AgentProposal = {
236 /** A complete definition, ready to create as it is. */
237 definition: NewWorkspaceAgent & { scope: WorkspaceAgentScope };
238 /** Other names that suit it, for the shuffle. */
239 name_ideas: string[];
240 /** Foundational skills it should keep on, by id (./skills.ts); the rest are off in `definition.skills_off`. */
241 skills: string[];
242 /** Integrations the job needs, from the catalog (./connectors.ts), with why. */
243 integrations: { id: string; why: string }[];
244 /** Routines that would suit it, to set up on its Routines tab once it exists. */
245 routines: { name: string; when: string; instructions: string }[];
246 /** What drafting it cost, charged to the person who asked (micro-dollars). */
247 charged_micros: number;
248};
249
250/** One line of a Try it conversation, held by the page, never saved. */
251export type DraftTurn = { role: "user" | "assistant"; content: string };
252
253/** A Try it answer, and what it cost the person trying it. */
254export type DraftReply = { text: string; charged_micros: number };
255
256/** Changes drafted from "Tell <name> what to change", to review before they are saved as a new version. */
257export type AgentRedraft = {
258 /** The changes to save, as `update` takes them: only fields that differ. */
259 changes: Partial<NewWorkspaceAgent>;
260 /** One line on what changed. */
261 summary: string;
262 /** The version it was drafted from. */
263 from_version: number;
264 charged_micros: number;
265};
266
267/**
268 * A role to hire an agent into, by title. Agents get names, not job
269 * titles ("Margo", the QA Engineer). A template never puts an agent on a
270 * team.
271 */
272export type AgentTemplate = {
273 id: string;
274 /** The name it suggests first. */
275 display_name: string;
276 handle: string;
277 /** Other names that suit it, for the form's shuffle. Each is also a valid handle, lowercased. */
278 name_ideas: string[];
279 role: string;
280 title: string;
281 responsibilities: string[];
282 subagents: SubagentDef[];
283 instructions: string;
284 personality_preset: PersonalityPreset;
285 routing: AgentRouting;
286};
287
288/** What the chat service hands an agent: a message it should answer. */
289export type AgentDelivery = {
290 workspace: string;
291 workspace_id: string;
292 channel_id: string;
293 channel_kind: "channel" | "dm";
294 channel_name: string | null;
295 agent_id: string;
296 /** The message that woke it. */
297 message_id: string;
298 thread_root: string | null;
299 /** Who asked: the person's user id. */
300 asked_by: string;
301 /** Agent-to-agent hops so far in this chain. */
302 hops: number;
303 /**
304 * What the person who asked may do, from the viewer the chat service
305 * already holds when the message is posted (`askerAccess`). An agent
306 * never does more for someone than they could do themselves. Absent
307 * from an older chat service: the agent then treats the asker as unable
308 * to change code.
309 */
310 asker?: AskerAccess | null;
311 /**
312 * The agents that handled this request before this one, by id, oldest
313 * first; the last sent the work here. An agent never hands back or
314 * consults the one that sent it work, and the hop limit counts every
315 * hand-off and consult along the chain. Absent: none (a person asked).
316 */
317 chain?: string[];
318 /**
319 * Where the conversation is: g1t's own chat, or later another chat app
320 * the workspace connected. The agent reads and replies through that
321 * surface; its definition, budget and replies are the same everywhere.
322 * Absent: `g1t`.
323 */
324 surface?: AgentSurface;
325};
326
327/** The chat surfaces an agent answers on. Only g1t's own today. */
328export type AgentSurface = "g1t";
329
330/** Who asked an agent, as far as its reply needs to know. */
331export type AskerAccess = {
332 username: string;
333 /** Their role in the workspace; `outside` for someone who is not a member. */
334 role: Role | "outside";
335 /**
336 * Whether they can change code in the workspace: Code is on for them
337 * and they hold write access (or more) on at least one of its
338 * repositories, through the base permission or a grant.
339 */
340 can_write: boolean;
341};
342
343const WRITING_ROLES = new Set(["write", "maintain", "admin"]);
344
345/**
346 * `user`'s access in `workspace`, for `AgentDelivery.asker`. Pure, with no
347 * imports, so the chat service computes it from its viewer for free.
348 */
349export function askerAccess(user: User, workspace: string): AskerAccess {
350 const slug = workspace.toLowerCase();
351 const membership = user.workspaces?.find((m) => m.slug.toLowerCase() === slug);
352 // Someone who uses only Chat, Docs and agents sees no repository at all.
353 const code = membership?.code_access !== false;
354 const base = membership ? membership.role === "owner" || WRITING_ROLES.has(membership.base_permission ?? "write") : false;
355 const granted = (user.grants ?? []).some((grant) => grant.workspace.toLowerCase() === slug && WRITING_ROLES.has(grant.role));
356 return {
357 username: user.username,
358 role: membership?.role ?? "outside",
359 can_write: code && (base || granted),
360 };
361}
362
363/**
364 * A session: one bounded piece of work an agent took on
365 * (docs.g1t.sh/guides/agent-sessions/). A conversation with an agent is
366 * not a session: talking stays cheap and quick, and when a request needs
367 * real work the agent spins off a session for it, with its own context, transcript, budget and live card in
368 * the conversation. Sessions start other sessions (one of the agent's
369 * subagents, or a colleague brought in), and everything a tree of sessions
370 * spends is charged to the agent at its root, so a chain never escapes the
371 * budget that started it.
372 */
373export type AgentSessionKind =
374 /** Spun off from a conversation: someone asked for work. */
375 | "chat"
376 /** A routine's run. */
377 | "routine"
378 /** A colleague brought in by another session. */
379 | "helper"
380 /** One of the agent's own subagents, inside another session. */
381 | "subagent";
382
383export type AgentSessionStatus =
384 | "queued"
385 | "working"
386 /** Waiting on sessions it started. */
387 | "waiting"
388 /** Stopped at its spend cap: someone who may raise it decides. */
389 | "needs_approval"
390 | "done"
391 | "failed"
392 | "stopped";
393
394/** The statuses of a session that is not over. */
395export const SESSION_LIVE: readonly AgentSessionStatus[] = ["queued", "working", "waiting", "needs_approval"];
396
397export type AgentSession = {
398 id: string;
399 workspace_id: string;
400 agent_id: string;
401 /** The agent's handle, name and face, for lists. */
402 agent_handle: string;
403 agent_name: string;
404 agent_avatar_seed: string;
405 /** The subagent running it, by name, when kind is `subagent`. */
406 subagent: string | null;
407 kind: AgentSessionKind;
408 /** The session that started it, and the root of its tree. */
409 parent_id: string | null;
410 root_id: string;
411 /** Whose budget pays for it: the agent at the root of its tree. */
412 payer_agent_id: string;
413 title: string;
414 goal: string;
415 status: AgentSessionStatus;
416 /** Why it is waiting, stopped or failed, in a line. */
417 status_note: string | null;
418 /** What it found or did, once done: its report. */
419 summary: string | null;
420 /** Where it reports: the conversation it was started from. */
421 channel_id: string;
422 channel_kind: "channel" | "dm";
423 channel_name: string | null;
424 /** Its live card in that conversation; its updates go in the card's thread. */
425 card_message_id: string | null;
426 asked_by: string | null;
427 asked_by_username: string | null;
428 routine_id: string | null;
429 steps: number;
430 tool_calls: number;
431 input_tokens: number;
432 output_tokens: number;
433 /**
434 * At list price, what it counts against budgets: the model at the
435 * provider's price with billing's model margin, plus g1t's agent rate on
436 * every token. A root session's includes everything its tree spent.
437 */
438 charged_micros: number;
439 /** What its own steps' model answers cost at the provider's price, its tree's not included. */
440 cost_micros: number;
441 /** The most it may spend before someone approves more. */
442 cap_micros: number | null;
443 model: string | null;
444 /** The effort level it ran at (the highest, when `auto` raised it); null for sessions from before effort was recorded. */
445 effort: EffortLevel | null;
446 /** What it produced: issues filed, sessions started. */
447 outputs: SessionOutput[];
448 created_at: string;
449 updated_at: string;
450 finished_at: string | null;
451 /**
452 * False when the viewer is not among the people of the conversation it
453 * came from: they see that it ran and what it cost, never its title,
454 * goal, report or transcript.
455 */
456 visible: boolean;
457};
458
459export type SessionOutput =
460 | { kind: "issue"; repo: string; number: number; title: string }
461 | { kind: "session"; id: string; agent_handle: string; title: string }
462 | { kind: "memory"; id: string; body: string };
463
464/** One entry of a session's transcript, as its page shows it. */
465export type SessionEvent = {
466 seq: number;
467 kind: "goal" | "text" | "tool" | "steer" | "update" | "child" | "result" | "note";
468 /** Who: the agent's handle, a person's username (steering), or null for g1t's notes. */
469 by: string | null;
470 body: string;
471 /** For `tool`: the tool, and whether it read, was withheld, refused or failed. */
472 tool: string | null;
473 outcome: string | null;
474 created_at: string;
475};
476
477export type AgentSessionDetail = {
478 session: AgentSession;
479 events: SessionEvent[];
480 /** Every session in its tree, root first. */
481 tree: AgentSession[];
482 /** Whether the viewer may stop it, steer it, or approve more spend. */
483 can_stop: boolean;
484 can_steer: boolean;
485 can_approve: boolean;
486};
487
488/**
489 * What an agent remembers (docs.g1t.sh/guides/agent-memory/). Every fact
490 * carries where it came from, and its scope decides, in code, where it may
491 * be recalled and who may see it:
492 *
493 * - `workspace`: anywhere in the workspace. Owners write these, or an agent
494 * from a public channel, which every member can read already.
495 * - `channel`: only in that channel and its threads.
496 * - `person`: only in a direct message with that one person.
497 */
498export type AgentMemoryScope = "workspace" | "channel" | "person";
499
500export type AgentMemory = {
501 id: string;
502 agent_id: string;
503 scope: AgentMemoryScope;
504 /** The channel's id or the person's user id; empty for `workspace`. */
505 scope_ref: string;
506 /** The channel's name or the person's username, for display. */
507 scope_label: string | null;
508 body: string;
509 source_kind: "message" | "session" | "person";
510 /** A message id, a session id, or the username of who wrote it. */
511 source_ref: string | null;
512 source_label: string | null;
513 /** The channel the source is in, for a link. */
514 source_channel_id: string | null;
515 created_by: string;
516 created_by_kind: "agent" | "user";
517 pinned: boolean;
518 created_at: string;
519 updated_at: string;
520};
521
522/** When a routine runs, in UTC. */
523export type RoutineSchedule = {
524 every: "hour" | "day" | "weekday" | "week";
525 /** Minute of the hour, 0 to 59. */
526 minute: number;
527 /** Hour of the day (UTC), 0 to 23; not used for `hour`. */
528 hour: number;
529 /** Day of the week for `week`, 0 (Sunday) to 6. */
530 weekday: number;
531};
532
533/**
534 * Things that happen in the workspace a routine can run on
535 * (docs.g1t.sh/guides/agent-routines/). Each run is one session about the one
536 * thing that happened, in a repository its sponsor can read.
537 */
538export const ROUTINE_EVENTS = [
539 { key: "pull_ready", label: "A pull request is ready for review", hint: "Opened ready, or moved out of draft." },
540 { key: "pull_merged", label: "A pull request is merged", hint: "On any branch it targets." },
541 { key: "checks_failed", label: "Checks fail on a pull request", hint: "Its required checks failed or errored." },
542 { key: "issue_opened", label: "An issue is opened", hint: "By a person or an agent." },
543 { key: "deploy_failed", label: "A deploy fails", hint: "A production or preview deploy." },
544] as const;
545
546export type RoutineEvent = (typeof ROUTINE_EVENTS)[number]["key"];
547
548/**
549 * A routine: work an agent does on a schedule or when something happens, such as Sam's Monday digest
550 * of support themes. Each run is a session posted in the routine's channel,
551 * paid from the agent's budget, and run with the access of the person who
552 * set it up (its sponsor), never more.
553 */
554export type AgentRoutine = {
555 id: string;
556 agent_id: string;
557 name: string;
558 instructions: string;
559 /** When it runs on a clock; null when it runs only on events. */
560 schedule: RoutineSchedule | null;
561 /** What it runs on; empty when it runs only on its schedule. */
562 events: RoutineEvent[];
563 /** Which repositories its events come from, by `workspace/name`; empty: every one its sponsor can read. */
564 repos: string[];
565 channel_id: string;
566 channel_name: string | null;
567 sponsor: string;
568 sponsor_username: string | null;
569 enabled: boolean;
570 /** Why g1t paused it, when it did. */
571 paused_note: string | null;
572 next_run_at: string | null;
573 last_run_at: string | null;
574 last_session_id: string | null;
575 runs: number;
576 created_at: string;
577 updated_at: string;
578};
579
580/** A routine suggested from an agent's responsibilities, for an owner to add in one step. */
581export type RoutineSuggestion = { responsibility: string; routine: Omit<NewRoutine, "channel_id"> };
582
583export type NewRoutine = {
584 name: string;
585 instructions: string;
586 /** A schedule, events, or both; at least one. */
587 schedule: RoutineSchedule | null;
588 events?: RoutineEvent[];
589 repos?: string[];
590 /** A channel the agent is in, by id. */
591 channel_id: string;
592 enabled?: boolean;
593};
594
595/**
596 * The workspace's say over all its agents together, set by owners: one
597 * monthly budget across every agent, the budget a new agent starts with,
598 * and the cap a session starts with. The workspace's spend limit and AI
599 * credit (billing) sit above all of it.
600 */
601export type AgentPolicy = {
602 /** Every agent's spend together in a month. Null: only the workspace's spend limit. */
603 monthly_micros: number | null;
604 /** The monthly budget a new agent gets. Null: none. */
605 default_agent_monthly_micros: number | null;
606 /** The cap one session starts with, unless its agent's per-task cap is lower. */
607 default_session_micros: number;
608 /**
609 * What the agents working for one person (their replies and sessions,
610 * asked for by that person) may spend together in a month, unless the
611 * person has a budget of their own. Null: no budget per person.
612 */
613 person_monthly_micros: number | null;
614 /**
615 * Whether members who aren't owners may create personal agents
616 * (docs.g1t.sh/guides/agents/, "Personal agents"). On unless an owner
617 * turns it off; off, existing personal agents keep working.
618 */
619 members_create_agents: boolean;
620};
621
622/** One person's budget: what agents working for them may spend in a month, and what they have. */
623export type PersonBudget = {
624 username: string;
625 /** The budget that applies: their own, or the workspace's per-person default. Null: none. */
626 monthly_micros: number | null;
627 /** Whether it is their own, set by an owner, rather than the default. */
628 own: boolean;
629 /** What agents spent for them this month (UTC). */
630 spent_micros: number;
631};
632
633/** Budgets per person: the default, and each person who has one of their own or has spent this month. */
634export type PersonBudgets = {
635 period: string;
636 default_micros: number | null;
637 /** Owners see everyone; anyone else sees only themselves. Most spent first. */
638 people: PersonBudget[];
639};
640
641export type SpendSlice = { key: string; label: string; micros: number; count: number };
642
643/** Which days a breakdown covers: this month (the default), last month, or the last 7 or 30 days, in UTC. */
644export type SpendPeriod = "month" | "last_month" | "7d" | "30d";
645
646/** Where an agent's (or every agent's) spend went over a period. */
647export type AgentSpendBreakdown = {
648 /** `YYYY-MM` for a month; for a span of days, the month it ends in. */
649 period: string;
650 /** The span asked for, and its first and last day (`YYYY-MM-DD`, both included). */
651 span: SpendPeriod;
652 from: string;
653 until: string;
654 /** The one person it is about (work asked for by them), or null for everyone's. */
655 person: string | null;
656 total_micros: number;
657 /** Chat replies, sessions, routines, helping colleagues. */
658 by_kind: SpendSlice[];
659 by_model: SpendSlice[];
660 /** Who asked: the work done for each person. */
661 by_person: SpendSlice[];
662 by_agent: SpendSlice[];
663 by_team: SpendSlice[];
664 /**
665 * Where it was asked: a channel by id (labelled `#name`), direct
666 * messages together (`dm`), and channels the viewer can't read together
667 * (`private`).
668 */
669 by_channel: SpendSlice[];
670 /** The costliest sessions in the period. */
671 top_sessions: AgentSession[];
672 /** Spend by day in the period. */
673 days: { day: string; micros: number }[];
674};
675
676/**
677 * What each effort level has cost one agent, from its own finished
678 * sessions (docs.g1t.sh/guides/spend/#effort): measured, never estimated
679 * from other agents or list prices. A level it has not run at has no
680 * figures.
681 */
682export type EffortCost = {
683 effort: EffortLevel;
684 /** Its sessions (with everything they brought in) that finished in the window. */
685 sessions: number;
686 /** The median charged for one of them: a typical task. Null with none. */
687 typical_micros: number | null;
688 /** Of those, the share finished with nobody having to step in: no steering, not stopped or failed. Null with none. */
689 accepted_share: number | null;
690};
691
692export type AgentEffortCosts = {
693 handle: string;
694 /** Its setting now. */
695 effort: AgentEffort;
696 /** Days of history the figures cover. */
697 window_days: number;
698 levels: EffortCost[];
699};
700
701/** One side of a recommendation's evidence: the agent's sessions at one level. */
702export type EffortEvidence = {
703 effort: EffortLevel;
704 sessions: number;
705 /** Finished with nobody having to step in. */
706 accepted: number;
707 typical_micros: number;
708 mean_micros: number;
709};
710
711/**
712 * A way to spend less without losing quality, checked against the agent's
713 * own past work (docs.g1t.sh/guides/spend/#spend-less-keep-quality). The
714 * weekly check proposes one only when the cheaper level's measured
715 * outcomes hold up; when there is too little history to tell, it says so
716 * (`thin`) instead of proposing anything.
717 */
718export type AgentRecommendation = {
719 id: string;
720 agent_id: string;
721 agent_handle: string;
722 agent_name: string;
723 agent_avatar_seed: string;
724 /** Lowering its effort setting. */
725 kind: "effort";
726 /** `thin`: not enough history to recommend anything yet. */
727 status: "open" | "applied" | "dismissed" | "thin";
728 from_effort: AgentEffort;
729 to_effort: EffortLevel;
730 /** What it says to do, in a line. */
731 title: string;
732 /** Why, from the numbers in `evidence`, in a sentence. */
733 reason: string;
734 /** What it was measured on: the level it runs at now, and the cheaper one. Null sides had no sessions. */
735 evidence: { window_days: number; current: EffortEvidence | null; cheaper: EffortEvidence | null; needed: number };
736 /** About what a month it would save at the recent pace, from the measured costs. Null when thin. */
737 saving_month_micros: number | null;
738 checked_at: string;
739 resolved_by: string | null;
740 resolved_at: string | null;
741};
742
743export type AgentRecommendations = {
744 /** When the check last ran for the workspace; null before the first. */
745 checked_at: string | null;
746 window_days: number;
747 /** To act on, largest saving first. */
748 open: AgentRecommendation[];
749 /** Agents with too little history to say. */
750 thin: AgentRecommendation[];
751 /** Applied or dismissed in the last 30 days. */
752 resolved: AgentRecommendation[];
753};
754
755/** Agents mode's front page. */
756export type AgentsOverview = {
757 policy: AgentPolicy;
758 /** Every agent's spend this month, against the policy's budget. */
759 spent_month_micros: number;
760 /** The highest alert this month: 75, 90 or 100 (% of the workspace's agent budget). */
761 alert: number | null;
762 agents: WorkspaceAgent[];
763 /** Live sessions, counted by agent id, for the roster. */
764 live_by_agent: Record<string, number>;
765 /** Sessions live now that the viewer can see. */
766 live: AgentSession[];
767 /** Sessions waiting on the viewer: spend they may approve. */
768 waiting_on_you: AgentSession[];
769 /** Recently finished sessions the viewer can see. */
770 recent: AgentSession[];
771 /** The next routines to run on a schedule. */
772 upcoming: (AgentRoutine & { agent_handle: string; agent_name: string })[];
773 spend: AgentSpendBreakdown;
774 can_manage: boolean;
775};
776
777/** One thing an agent did, for its Activity tab. */
778export type AgentActivity = {
779 id: string;
780 kind: "reply" | "session";
781 status: string;
782 channel_id: string;
783 channel_name: string | null;
784 /** The session's title; null for a reply or one the viewer can't see. */
785 title: string | null;
786 asked_by_username: string | null;
787 model: string | null;
788 tools: number;
789 charged_micros: number;
790 created_at: string;
791 visible: boolean;
792 /** For a reply, the message it posted; for a session, its id. */
793 ref: string | null;
794};
795
796export type AgentVersion = { version: number; changed_by: string; created_at: string; definition: Partial<NewWorkspaceAgent> };
797
798/** A card action, as chat hands it to agents. */
799export type AgentCardAction = {
800 workspace: string;
801 channel_id: string;
802 message_id: string;
803 viewer: User;
804 card: { kind: string; ref: string | null };
805 action_id: string;
806 input: string | null;
807};
808
809export type WorkspaceAgentsApi = {
810 /**
811 * The workspace's agents. Personal agents are left out unless asked
812 * for: `mine`, the viewer's own; `all`, every member's for an owner (the
813 * viewer's own for anyone else).
814 */
815 list(workspace: string, viewer: User, options?: { personal?: "mine" | "all" | null }): Promise<Result<WorkspaceAgent[]>>;
816 get(workspace: string, handle: string, viewer: User): Promise<Result<WorkspaceAgent>>;
817 /** Internal: by id, for the chat service resolving members. */
818 byIds(ids: string[]): Promise<WorkspaceAgent[]>;
819 /**
820 * `teams`: the workspace's teams to add it to as it is made, by slug,
821 * each one the viewer manages (owners and the team's maintainers). The
822 * membership is the team's, as anyone's is; a personal agent joins none.
823 */
824 create(workspace: string, viewer: User, input: NewWorkspaceAgent, options?: { teams?: string[] }): Promise<Result<WorkspaceAgent>>;
825 update(
826 workspace: string,
827 handle: string,
828 viewer: User,
829 changes: Partial<NewWorkspaceAgent>,
830 ): Promise<Result<WorkspaceAgent>>;
831 archive(workspace: string, handle: string, viewer: User): Promise<Result<null>>;
832 /**
833 * Drafts a whole agent from a description, with a fast model call charged
834 * to the viewer. Nothing is saved. Whoever may create the agent may draft it.
835 */
836 draft(workspace: string, viewer: User, input: { description: string; scope?: WorkspaceAgentScope | null }): Promise<Result<AgentProposal>>;
837 /**
838 * Try it: the unsaved definition answers the conversation so far, as it
839 * would in a direct message, without tools or memory. Charged to the
840 * viewer; nothing is saved.
841 */
842 tryDraft(workspace: string, viewer: User, input: { definition: NewWorkspaceAgent; messages: DraftTurn[] }): Promise<Result<DraftReply>>;
843 /** Drafts changes to an agent from a request in words; saved only through `update`. Whoever may change the agent may ask. */
844 redraft(workspace: string, handle: string, viewer: User, request: string): Promise<Result<AgentRedraft>>;
845 /**
846 * Owners make a member's personal agent a workspace agent: its definition
847 * and every version move to a workspace agent with the same handle, and
848 * the personal one is archived, with its memory and direct messages.
849 */
850 promote(workspace: string, handle: string, viewer: User): Promise<Result<WorkspaceAgent>>;
851 templates(): Promise<AgentTemplate[]>;
852 /**
853 * Internal: the workspace's built-in `@g1t` agent, made if it does not
854 * exist yet. The chat service asks for it when someone mentions @g1t in
855 * a channel it is not in yet.
856 */
857 builtin(workspace: string, workspaceId: string): Promise<Result<WorkspaceAgent>>;
858 /** The chat service hands over a message for an agent to answer. Returns at once. */
859 deliver(delivery: AgentDelivery): Promise<Result<null>>;
860 overview(workspace: string, viewer: User): Promise<Result<AgentsOverview>>;
861 sessions(
862 workspace: string,
863 viewer: User,
864 filter?: { handle?: string | null; status?: "live" | "done" | null; limit?: number | null },
865 ): Promise<Result<AgentSession[]>>;
866 session(workspace: string, id: string, viewer: User): Promise<Result<AgentSessionDetail>>;
867 /** Stops a session and every session under it. */
868 stopSession(workspace: string, id: string, viewer: User): Promise<Result<AgentSession>>;
869 /** Raises a stopped session's cap and lets it go on. Owners only. */
870 approveSession(workspace: string, id: string, viewer: User, capMicros: number): Promise<Result<AgentSession>>;
871 /** A person's message to a session, running or finished: it reads it and goes on. */
872 steerSession(workspace: string, id: string, viewer: User, body: string): Promise<Result<AgentSession>>;
873 memories(workspace: string, handle: string, viewer: User): Promise<Result<AgentMemory[]>>;
874 remember(
875 workspace: string,
876 handle: string,
877 viewer: User,
878 input: { body: string; scope: AgentMemoryScope; scope_ref?: string | null },
879 ): Promise<Result<AgentMemory>>;
880 updateMemory(
881 workspace: string,
882 handle: string,
883 viewer: User,
884 id: string,
885 changes: { body?: string; pinned?: boolean },
886 ): Promise<Result<AgentMemory>>;
887 forget(workspace: string, handle: string, viewer: User, id: string): Promise<Result<null>>;
888 routines(workspace: string, handle: string, viewer: User): Promise<Result<{ routines: AgentRoutine[]; suggestions: RoutineSuggestion[] }>>;
889 saveRoutine(workspace: string, handle: string, viewer: User, input: NewRoutine, id?: string | null): Promise<Result<AgentRoutine>>;
890 deleteRoutine(workspace: string, handle: string, viewer: User, id: string): Promise<Result<null>>;
891 /** Runs a routine now, as a session. */
892 runRoutine(workspace: string, handle: string, viewer: User, id: string): Promise<Result<AgentSession>>;
893 /**
894 * Where the spend went: one agent's, or every agent's; this month unless
895 * `period` says otherwise; for everyone, or only the work one `person`
896 * (by username) asked for.
897 */
898 spend(workspace: string, viewer: User, handle?: string | null, options?: { period?: SpendPeriod | null; person?: string | null }): Promise<Result<AgentSpendBreakdown>>;
899 /** Budgets per person this month: owners see everyone's, anyone else their own. */
900 personBudgets(workspace: string, viewer: User): Promise<Result<PersonBudgets>>;
901 /**
902 * Gives one person a monthly budget of their own (`monthly_micros`; 0 for
903 * no budget at all), or with null puts them back on the default. Owners only.
904 */
905 setPersonBudget(workspace: string, viewer: User, username: string, monthlyMicros: number | null): Promise<Result<PersonBudgets>>;
906 activity(workspace: string, handle: string, viewer: User): Promise<Result<AgentActivity[]>>;
907 /** What each effort level has cost this agent, from its own finished sessions. Members only. */
908 effortCosts(workspace: string, handle: string, viewer: User): Promise<Result<AgentEffortCosts>>;
909 /** Ways to spend less, checked against past work: every agent's, or one's. Members only. */
910 recommendations(workspace: string, viewer: User, handle?: string | null): Promise<Result<AgentRecommendations>>;
911 /**
912 * Owners apply one (the agent's effort changes, as a new version, and
913 * the audit log says so) or dismiss it.
914 */
915 resolveRecommendation(workspace: string, viewer: User, id: string, action: "apply" | "dismiss"): Promise<Result<AgentRecommendation>>;
916 /** What an agent is told about its teams this turn, word for word. Members only. */
917 teamContext(workspace: string, handle: string, viewer: User): Promise<Result<AgentTeamContext>>;
918 versions(workspace: string, handle: string, viewer: User): Promise<Result<AgentVersion[]>>;
919 /**
920 * Internal, from chat: a person pressed an action on one of agents'
921 * cards. Agents checks they may, acts, and updates the card.
922 */
923 cardAction(input: AgentCardAction): Promise<Result<CardActionResult>>;
924 policy(workspace: string, viewer: User): Promise<Result<AgentPolicy>>;
925 setPolicy(workspace: string, viewer: User, policy: Partial<AgentPolicy>): Promise<Result<AgentPolicy>>;
926 /**
927 * The Marketplace's install requests (./marketplace.ts): every one in the
928 * workspace for an owner, a member's own for anyone else.
929 */
930 installRequests(workspace: string, viewer: User): Promise<Result<InstallRequests>>;
931 /**
932 * A member asks the workspace's owners to add a listing
933 * (`extension:<id>` or `integration:<connector>`), and every owner is
934 * notified. Owners add
935 * things themselves, so they don't ask. Asking again while a request for
936 * the same listing is open is a conflict.
937 */
938 requestInstall(workspace: string, viewer: User, listing: string, note?: string | null): Promise<Result<InstallRequest>>;
939 /** An owner marks a request added (`done`) or turns it down (`declined`); whoever asked is told. */
940 resolveInstallRequest(workspace: string, viewer: User, id: string, status: Exclude<InstallRequestStatus, "open">): Promise<Result<InstallRequest>>;
941 /** The extensions installed in the workspace; any member sees them. */
942 extensionInstalls(workspace: string, viewer: User): Promise<Result<ExtensionInstall[]>>;
943 /** Owners install a published extension at its current version; open requests for it are answered. */
944 installExtension(workspace: string, viewer: User, extension: string): Promise<Result<ExtensionInstall>>;
945 /** Owners switch an install on or off: off is the kill switch. */
946 setExtensionEnabled(workspace: string, viewer: User, listing: string, enabled: boolean): Promise<Result<ExtensionInstall>>;
947 /** Owners cap what an install spends a month; null leaves it to the workspace's limit. */
948 setExtensionBudget(workspace: string, viewer: User, listing: string, monthlyMicros: number | null): Promise<Result<ExtensionInstall>>;
949 uninstallExtension(workspace: string, viewer: User, listing: string): Promise<Result<null>>;
950};
951
952async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
953 const response = await service.fetch(`https://service/rpc/${method}`, {
954 method: "POST",
955 headers: { "content-type": "application/json" },
956 body: JSON.stringify(args),
957 });
958 if (!response.ok) {
959 throw new Error(`${method} failed with status ${response.status}`);
960 }
961 return (await response.json()) as T;
962}
963
964/**
965 * What an agent is told about its teams every turn (docs.g1t.sh/guides/people-and-teams/,
966 * "What agents are told"): the visible teams it is on, by slug, and the text
967 * itself; null when it is on none.
968 */
969export type AgentTeamContext = { handle: string; teams: string[]; text: string | null };
970
971export function workspaceAgentsClient(service: ServiceBinding): WorkspaceAgentsApi {
972 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
973 return {
974 list: (workspace, viewer, options) => call("list", { workspace, viewer, personal: options?.personal ?? null }),
975 get: (workspace, handle, viewer) => call("get", { workspace, handle, viewer }),
976 byIds: (ids) => call("by_ids", { ids }),
977 create: (workspace, viewer, input, options) => call("create", { workspace, viewer, input, teams: options?.teams ?? [] }),
978 update: (workspace, handle, viewer, changes) => call("update", { workspace, handle, viewer, changes }),
979 archive: (workspace, handle, viewer) => call("archive", { workspace, handle, viewer }),
980 draft: (workspace, viewer, input) => call("draft", { workspace, viewer, description: input.description, scope: input.scope ?? null }),
981 tryDraft: (workspace, viewer, input) => call("try_draft", { workspace, viewer, definition: input.definition, messages: input.messages }),
982 redraft: (workspace, handle, viewer, request) => call("redraft", { workspace, handle, viewer, request }),
983 promote: (workspace, handle, viewer) => call("promote", { workspace, handle, viewer }),
984 templates: () => call("templates", {}),
985 builtin: (workspace, workspaceId) => call("builtin", { workspace, workspace_id: workspaceId }),
986 deliver: (delivery) => call("deliver", delivery),
987 overview: (workspace, viewer) => call("overview", { workspace, viewer }),
988 sessions: (workspace, viewer, filter) => call("sessions", { workspace, viewer, ...(filter ?? {}) }),
989 session: (workspace, id, viewer) => call("session", { workspace, id, viewer }),
990 stopSession: (workspace, id, viewer) => call("stop_session", { workspace, id, viewer }),
991 approveSession: (workspace, id, viewer, capMicros) => call("approve_session", { workspace, id, viewer, cap_micros: capMicros }),
992 steerSession: (workspace, id, viewer, body) => call("steer_session", { workspace, id, viewer, body }),
993 memories: (workspace, handle, viewer) => call("memories", { workspace, handle, viewer }),
994 remember: (workspace, handle, viewer, input) => call("remember", { workspace, handle, viewer, input }),
995 updateMemory: (workspace, handle, viewer, id, changes) => call("update_memory", { workspace, handle, viewer, id, changes }),
996 forget: (workspace, handle, viewer, id) => call("forget", { workspace, handle, viewer, id }),
997 routines: (workspace, handle, viewer) => call("routines", { workspace, handle, viewer }),
998 saveRoutine: (workspace, handle, viewer, input, id) => call("save_routine", { workspace, handle, viewer, input, id: id ?? null }),
999 deleteRoutine: (workspace, handle, viewer, id) => call("delete_routine", { workspace, handle, viewer, id }),
1000 runRoutine: (workspace, handle, viewer, id) => call("run_routine", { workspace, handle, viewer, id }),
1001 spend: (workspace, viewer, handle, options) =>
1002 call("spend", { workspace, viewer, handle: handle ?? null, period: options?.period ?? null, person: options?.person ?? null }),
1003 personBudgets: (workspace, viewer) => call("person_budgets", { workspace, viewer }),
1004 teamContext: (workspace, handle, viewer) => call("team_context", { workspace, handle, viewer }),
1005 setPersonBudget: (workspace, viewer, username, monthlyMicros) =>
1006 call("set_person_budget", { workspace, viewer, username, monthly_micros: monthlyMicros }),
1007 activity: (workspace, handle, viewer) => call("activity", { workspace, handle, viewer }),
1008 effortCosts: (workspace, handle, viewer) => call("effort_costs", { workspace, handle, viewer }),
1009 recommendations: (workspace, viewer, handle) => call("recommendations", { workspace, viewer, handle: handle ?? null }),
1010 resolveRecommendation: (workspace, viewer, id, action) => call("resolve_recommendation", { workspace, viewer, id, action }),
1011 versions: (workspace, handle, viewer) => call("versions", { workspace, handle, viewer }),
1012 cardAction: (input) => call("card_action", input),
1013 policy: (workspace, viewer) => call("policy", { workspace, viewer }),
1014 setPolicy: (workspace, viewer, policy) => call("set_policy", { workspace, viewer, policy }),
1015 installRequests: (workspace, viewer) => call("install_requests", { workspace, viewer }),
1016 requestInstall: (workspace, viewer, listing, note) => call("request_install", { workspace, viewer, listing, note: note ?? null }),
1017 resolveInstallRequest: (workspace, viewer, id, status) => call("resolve_install_request", { workspace, viewer, id, status }),
1018 extensionInstalls: (workspace, viewer) => call("extension_installs", { workspace, viewer }),
1019 installExtension: (workspace, viewer, extension) => call("install_extension", { workspace, viewer, extension }),
1020 setExtensionEnabled: (workspace, viewer, listing, enabled) => call("set_extension_enabled", { workspace, viewer, listing, enabled }),
1021 setExtensionBudget: (workspace, viewer, listing, monthlyMicros) => call("set_extension_budget", { workspace, viewer, listing, monthly_micros: monthlyMicros }),
1022 uninstallExtension: (workspace, viewer, listing) => call("uninstall_extension", { workspace, viewer, listing }),
1023 };
1024}