Skip to content
767 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";
17
18/** The built-in orchestrator's handle; nobody else's agent may take it. */
19export const BUILTIN_AGENT_HANDLE = "g1t";
20
21/** The built-in orchestrator's template id: not one a workspace can adopt. */
22export const ORCHESTRATOR_TEMPLATE = "orchestrator";
23
24/** Voice presets; free text in `personality` refines them. */
25export type PersonalityPreset = "crisp" | "friendly" | "socratic" | "terse";
26
27export type AgentRouting = {
28 /** Never route below this tier. Null: no floor. */
29 floor: ModelTier | null;
30 /** Never route above this tier. Null: no ceiling. */
31 ceiling: ModelTier | null;
32 /**
33 * Where its model calls may go. Empty: anything the workspace allows.
34 * `g1t`: g1t's hosted models. `workspace`: the workspace's own providers
35 * (Integrations), whichever they are. Any other entry: one of the
36 * workspace's own providers by integration id. Entries combine, so
37 * `["g1t", "workspace"]` is both.
38 */
39 providers: string[];
40 /** Advanced: a fixed `provider/model`, for own endpoints. Usually null. */
41 pinned: string | null;
42};
43
44export type AgentBudget = {
45 /** Monthly cap in micro-dollars. Null: only the workspace limit applies. */
46 monthly_micros: number | null;
47 daily_micros: number | null;
48 /** Default cap for one task. */
49 task_micros: number | null;
50};
51
52export type AgentAutonomy = {
53 open_pull_requests: "alone" | "approval";
54 merge: "alone" | "approval" | "never";
55 deploy_production: "approval" | "never";
56 edit_docs: "alone" | "suggest";
57};
58
59export type AgentStatus = "idle" | "working" | "waiting" | "out_of_budget" | "paused";
60
61export type WorkspaceAgent = {
62 id: string;
63 workspace_id: string;
64 /** Lowercase, unique in the workspace, never `g1t`. Mentioned as `@handle`. */
65 handle: string;
66 display_name: string;
67 /** Uploaded avatar hash, or null for the generated mark. */
68 avatar: string | null;
69 /**
70 * What its generated avatar is drawn from: a little pixel creature, the
71 * same for the same seed everywhere. Set from the handle when it is
72 * made; changing it gives the agent a new face.
73 */
74 avatar_seed: string;
75 /**
76 * One line, as lists show it: "QA Engineer on the QA team". Made from
77 * the title and team (or department) when not written.
78 */
79 role: string;
80 /**
81 * Agents are hired into roles, not tasks: a title, a team, and broad
82 * responsibilities.
83 */
84 title: string;
85 /** The team it is on, by slug, from the workspace's teams; null for none. */
86 team: string | null;
87 /** A label for where it works when it is on no team: "QA", "Sales". */
88 department: string;
89 /** What it is responsible for: 2 to 8 short duties, or none yet. */
90 responsibilities: string[];
91 /**
92 * Specialised help it will use inside its own work. Never members, never
93 * wider than their agent. Stored now; they run with tasks and sessions.
94 */
95 subagents: SubagentDef[];
96 /**
97 * Its required reading: Docs spaces (by id) it checks first, every time
98 * it answers or works. It still reads only what the person it acts for,
99 * and everyone reading its answer, can read.
100 */
101 reading: string[];
102 /**
103 * Who it works with: `internal`, the workspace's own people (back
104 * office), or `customers` (front office). Only `internal` for now.
105 */
106 faces: AgentFaces;
107 /** The job: what it is responsible for and how it works. */
108 instructions: string;
109 personality_preset: PersonalityPreset;
110 /** Free text refining the voice. Never changes what it may do. */
111 personality: string;
112 routing: AgentRouting;
113 budget: AgentBudget;
114 autonomy: AgentAutonomy;
115 /** Tasks it works at once; more queue on its desk. */
116 capacity: number;
117 /** The template it was made from, if any. */
118 template: string | null;
119 /**
120 * The workspace's built-in orchestrator, `@g1t`: every workspace has one,
121 * made the first time its agents are asked for. It cannot be archived,
122 * and its handle, name, role and job are fixed; its `instructions` are
123 * added to that job. Listed first.
124 */
125 builtin: boolean;
126 version: number;
127 status: AgentStatus;
128 /** Spend this calendar month, in micro-dollars. */
129 spent_month_micros: number;
130 created_by: string;
131 created_at: string;
132 updated_at: string;
133 archived_at: string | null;
134};
135
136/**
137 * Back office or front office (docs.g1t.sh/guides/agents/, "Back office
138 * and front office"). Customer-facing agents are not available yet.
139 */
140export type AgentFaces = "internal" | "customers";
141
142/**
143 * A subagent: help an agent keeps for its own work, such as Margo's
144 * `flake-hunter`. Its routing limits sit within its agent's: a floor below
145 * the agent's is raised to it, a ceiling above is lowered to it.
146 */
147export type SubagentDef = {
148 /** Lowercase letters, digits and hyphens: `flake-hunter`. Unique on the agent. */
149 name: string;
150 /** One line: what it is for. */
151 description: string;
152 instructions: string;
153 routing: { floor: ModelTier | null; ceiling: ModelTier | null };
154 /** How many of it may run at once inside one task, 1 to 8. */
155 max_parallel: number;
156};
157
158export type NewWorkspaceAgent = {
159 handle: string;
160 display_name: string;
161 /** Left out or empty: made from the title and team. */
162 role?: string;
163 title?: string;
164 team?: string | null;
165 department?: string;
166 responsibilities?: string[];
167 subagents?: SubagentDef[];
168 /** Docs spaces (by id) it reads first; at most 10. */
169 reading?: string[];
170 /** Only `internal` for now; `customers` is refused. */
171 faces?: AgentFaces;
172 instructions: string;
173 personality_preset?: PersonalityPreset;
174 personality?: string;
175 routing?: Partial<AgentRouting>;
176 budget?: Partial<AgentBudget>;
177 autonomy?: Partial<AgentAutonomy>;
178 capacity?: number;
179 template?: string | null;
180 /** Its avatar's seed; left out, the handle. */
181 avatar_seed?: string;
182};
183
184/**
185 * A role to hire an agent into, by department. Agents get names, not job
186 * titles ("Margo", the QA Engineer).
187 */
188export type AgentTemplate = {
189 id: string;
190 /** The name it suggests first. */
191 display_name: string;
192 handle: string;
193 /** Other names that suit it, for the form's shuffle. Each is also a valid handle, lowercased. */
194 name_ideas: string[];
195 role: string;
196 title: string;
197 department: string;
198 responsibilities: string[];
199 subagents: SubagentDef[];
200 instructions: string;
201 personality_preset: PersonalityPreset;
202 routing: AgentRouting;
203};
204
205/** What the chat service hands an agent: a message it should answer. */
206export type AgentDelivery = {
207 workspace: string;
208 workspace_id: string;
209 channel_id: string;
210 channel_kind: "channel" | "dm";
211 channel_name: string | null;
212 agent_id: string;
213 /** The message that woke it. */
214 message_id: string;
215 thread_root: string | null;
216 /** Who asked: the person's user id. */
217 asked_by: string;
218 /** Agent-to-agent hops so far in this chain. */
219 hops: number;
220 /**
221 * What the person who asked may do, from the viewer the chat service
222 * already holds when the message is posted (`askerAccess`). An agent
223 * never does more for someone than they could do themselves. Absent
224 * from an older chat service: the agent then treats the asker as unable
225 * to change code.
226 */
227 asker?: AskerAccess | null;
228 /**
229 * The agents that handled this request before this one, by id, oldest
230 * first; the last sent the work here. An agent never hands back or
231 * consults the one that sent it work, and the hop limit counts every
232 * hand-off and consult along the chain. Absent: none (a person asked).
233 */
234 chain?: string[];
235 /**
236 * Where the conversation is: g1t's own chat, or later another chat app
237 * the workspace connected. The agent reads and replies through that
238 * surface; its definition, budget and replies are the same everywhere.
239 * Absent: `g1t`.
240 */
241 surface?: AgentSurface;
242};
243
244/** The chat surfaces an agent answers on. Only g1t's own today. */
245export type AgentSurface = "g1t";
246
247/** Who asked an agent, as far as its reply needs to know. */
248export type AskerAccess = {
249 username: string;
250 /** Their role in the workspace; `outside` for someone who is not a member. */
251 role: Role | "outside";
252 /**
253 * Whether they can change code in the workspace: Code is on for them
254 * and they hold write access (or more) on at least one of its
255 * repositories, through the base permission or a grant.
256 */
257 can_write: boolean;
258};
259
260const WRITING_ROLES = new Set(["write", "maintain", "admin"]);
261
262/**
263 * `user`'s access in `workspace`, for `AgentDelivery.asker`. Pure, with no
264 * imports, so the chat service computes it from its viewer for free.
265 */
266export function askerAccess(user: User, workspace: string): AskerAccess {
267 const slug = workspace.toLowerCase();
268 const membership = user.workspaces?.find((m) => m.slug.toLowerCase() === slug);
269 // Someone who uses only Chat, Docs and agents sees no repository at all.
270 const code = membership?.code_access !== false;
271 const base = membership ? membership.role === "owner" || WRITING_ROLES.has(membership.base_permission ?? "write") : false;
272 const granted = (user.grants ?? []).some((grant) => grant.workspace.toLowerCase() === slug && WRITING_ROLES.has(grant.role));
273 return {
274 username: user.username,
275 role: membership?.role ?? "outside",
276 can_write: code && (base || granted),
277 };
278}
279
280/**
281 * A session: one bounded piece of work an agent took on
282 * (docs.g1t.sh/guides/agent-sessions/). A conversation with an agent is
283 * not a session: talking stays cheap and quick, and when a request needs
284 * real work the agent spins off a session for it, with its own context, transcript, budget and live card in
285 * the conversation. Sessions start other sessions (one of the agent's
286 * subagents, or a colleague brought in), and everything a tree of sessions
287 * spends is charged to the agent at its root, so a chain never escapes the
288 * budget that started it.
289 */
290export type AgentSessionKind =
291 /** Spun off from a conversation: someone asked for work. */
292 | "chat"
293 /** A routine's run. */
294 | "routine"
295 /** A colleague brought in by another session. */
296 | "helper"
297 /** One of the agent's own subagents, inside another session. */
298 | "subagent";
299
300export type AgentSessionStatus =
301 | "queued"
302 | "working"
303 /** Waiting on sessions it started. */
304 | "waiting"
305 /** Stopped at its spend cap: someone who may raise it decides. */
306 | "needs_approval"
307 | "done"
308 | "failed"
309 | "stopped";
310
311/** The statuses of a session that is not over. */
312export const SESSION_LIVE: readonly AgentSessionStatus[] = ["queued", "working", "waiting", "needs_approval"];
313
314export type AgentSession = {
315 id: string;
316 workspace_id: string;
317 agent_id: string;
318 /** The agent's handle, name and face, for lists. */
319 agent_handle: string;
320 agent_name: string;
321 agent_avatar_seed: string;
322 /** The subagent running it, by name, when kind is `subagent`. */
323 subagent: string | null;
324 kind: AgentSessionKind;
325 /** The session that started it, and the root of its tree. */
326 parent_id: string | null;
327 root_id: string;
328 /** Whose budget pays for it: the agent at the root of its tree. */
329 payer_agent_id: string;
330 title: string;
331 goal: string;
332 status: AgentSessionStatus;
333 /** Why it is waiting, stopped or failed, in a line. */
334 status_note: string | null;
335 /** What it found or did, once done: its report. */
336 summary: string | null;
337 /** Where it reports: the conversation it was started from. */
338 channel_id: string;
339 channel_kind: "channel" | "dm";
340 channel_name: string | null;
341 /** Its live card in that conversation; its updates go in the card's thread. */
342 card_message_id: string | null;
343 asked_by: string | null;
344 asked_by_username: string | null;
345 routine_id: string | null;
346 steps: number;
347 tool_calls: number;
348 input_tokens: number;
349 output_tokens: number;
350 /**
351 * At list price, what it counts against budgets: the model at the
352 * provider's price with billing's model margin, plus g1t's agent rate on
353 * every token. A root session's includes everything its tree spent.
354 */
355 charged_micros: number;
356 /** What its own steps' model answers cost at the provider's price, its tree's not included. */
357 cost_micros: number;
358 /** The most it may spend before someone approves more. */
359 cap_micros: number | null;
360 model: string | null;
361 /** What it produced: issues filed, sessions started. */
362 outputs: SessionOutput[];
363 created_at: string;
364 updated_at: string;
365 finished_at: string | null;
366 /**
367 * False when the viewer is not among the people of the conversation it
368 * came from: they see that it ran and what it cost, never its title,
369 * goal, report or transcript.
370 */
371 visible: boolean;
372};
373
374export type SessionOutput =
375 | { kind: "issue"; repo: string; number: number; title: string }
376 | { kind: "session"; id: string; agent_handle: string; title: string }
377 | { kind: "memory"; id: string; body: string };
378
379/** One entry of a session's transcript, as its page shows it. */
380export type SessionEvent = {
381 seq: number;
382 kind: "goal" | "text" | "tool" | "steer" | "update" | "child" | "result" | "note";
383 /** Who: the agent's handle, a person's username (steering), or null for g1t's notes. */
384 by: string | null;
385 body: string;
386 /** For `tool`: the tool, and whether it read, was withheld, refused or failed. */
387 tool: string | null;
388 outcome: string | null;
389 created_at: string;
390};
391
392export type AgentSessionDetail = {
393 session: AgentSession;
394 events: SessionEvent[];
395 /** Every session in its tree, root first. */
396 tree: AgentSession[];
397 /** Whether the viewer may stop it, steer it, or approve more spend. */
398 can_stop: boolean;
399 can_steer: boolean;
400 can_approve: boolean;
401};
402
403/**
404 * What an agent remembers (docs.g1t.sh/guides/agent-memory/). Every fact
405 * carries where it came from, and its scope decides, in code, where it may
406 * be recalled and who may see it:
407 *
408 * - `workspace`: anywhere in the workspace. Owners write these, or an agent
409 * from a public channel, which every member can read already.
410 * - `channel`: only in that channel and its threads.
411 * - `person`: only in a direct message with that one person.
412 */
413export type AgentMemoryScope = "workspace" | "channel" | "person";
414
415export type AgentMemory = {
416 id: string;
417 agent_id: string;
418 scope: AgentMemoryScope;
419 /** The channel's id or the person's user id; empty for `workspace`. */
420 scope_ref: string;
421 /** The channel's name or the person's username, for display. */
422 scope_label: string | null;
423 body: string;
424 source_kind: "message" | "session" | "person";
425 /** A message id, a session id, or the username of who wrote it. */
426 source_ref: string | null;
427 source_label: string | null;
428 /** The channel the source is in, for a link. */
429 source_channel_id: string | null;
430 created_by: string;
431 created_by_kind: "agent" | "user";
432 pinned: boolean;
433 created_at: string;
434 updated_at: string;
435};
436
437/** When a routine runs, in UTC. */
438export type RoutineSchedule = {
439 every: "hour" | "day" | "weekday" | "week";
440 /** Minute of the hour, 0 to 59. */
441 minute: number;
442 /** Hour of the day (UTC), 0 to 23; not used for `hour`. */
443 hour: number;
444 /** Day of the week for `week`, 0 (Sunday) to 6. */
445 weekday: number;
446};
447
448/**
449 * Things that happen in the workspace a routine can run on
450 * (docs.g1t.sh/guides/agent-routines/). Each run is one session about the one
451 * thing that happened, in a repository its sponsor can read.
452 */
453export const ROUTINE_EVENTS = [
454 { key: "pull_ready", label: "A pull request is ready for review", hint: "Opened ready, or moved out of draft." },
455 { key: "pull_merged", label: "A pull request is merged", hint: "On any branch it targets." },
456 { key: "checks_failed", label: "Checks fail on a pull request", hint: "Its required checks failed or errored." },
457 { key: "issue_opened", label: "An issue is opened", hint: "By a person or an agent." },
458 { key: "deploy_failed", label: "A deploy fails", hint: "A production or preview deploy." },
459] as const;
460
461export type RoutineEvent = (typeof ROUTINE_EVENTS)[number]["key"];
462
463/**
464 * A routine: work an agent does on a schedule or when something happens, such as Sam's Monday digest
465 * of support themes. Each run is a session posted in the routine's channel,
466 * paid from the agent's budget, and run with the access of the person who
467 * set it up (its sponsor), never more.
468 */
469export type AgentRoutine = {
470 id: string;
471 agent_id: string;
472 name: string;
473 instructions: string;
474 /** When it runs on a clock; null when it runs only on events. */
475 schedule: RoutineSchedule | null;
476 /** What it runs on; empty when it runs only on its schedule. */
477 events: RoutineEvent[];
478 /** Which repositories its events come from, by `workspace/name`; empty: every one its sponsor can read. */
479 repos: string[];
480 channel_id: string;
481 channel_name: string | null;
482 sponsor: string;
483 sponsor_username: string | null;
484 enabled: boolean;
485 /** Why g1t paused it, when it did. */
486 paused_note: string | null;
487 next_run_at: string | null;
488 last_run_at: string | null;
489 last_session_id: string | null;
490 runs: number;
491 created_at: string;
492 updated_at: string;
493};
494
495/** A routine suggested from an agent's responsibilities, for an owner to add in one step. */
496export type RoutineSuggestion = { responsibility: string; routine: Omit<NewRoutine, "channel_id"> };
497
498export type NewRoutine = {
499 name: string;
500 instructions: string;
501 /** A schedule, events, or both; at least one. */
502 schedule: RoutineSchedule | null;
503 events?: RoutineEvent[];
504 repos?: string[];
505 /** A channel the agent is in, by id. */
506 channel_id: string;
507 enabled?: boolean;
508};
509
510/**
511 * The workspace's say over all its agents together, set by owners: one
512 * monthly budget across every agent, the budget a new agent starts with,
513 * and the cap a session starts with. The workspace's spend limit and AI
514 * credit (billing) sit above all of it.
515 */
516export type AgentPolicy = {
517 /** Every agent's spend together in a month. Null: only the workspace's spend limit. */
518 monthly_micros: number | null;
519 /** The monthly budget a new agent gets. Null: none. */
520 default_agent_monthly_micros: number | null;
521 /** The cap one session starts with, unless its agent's per-task cap is lower. */
522 default_session_micros: number;
523 /**
524 * What the agents working for one person (their replies and sessions,
525 * asked for by that person) may spend together in a month, unless the
526 * person has a budget of their own. Null: no budget per person.
527 */
528 person_monthly_micros: number | null;
529};
530
531/** One person's budget: what agents working for them may spend in a month, and what they have. */
532export type PersonBudget = {
533 username: string;
534 /** The budget that applies: their own, or the workspace's per-person default. Null: none. */
535 monthly_micros: number | null;
536 /** Whether it is their own, set by an owner, rather than the default. */
537 own: boolean;
538 /** What agents spent for them this month (UTC). */
539 spent_micros: number;
540};
541
542/** Budgets per person: the default, and each person who has one of their own or has spent this month. */
543export type PersonBudgets = {
544 period: string;
545 default_micros: number | null;
546 /** Owners see everyone; anyone else sees only themselves. Most spent first. */
547 people: PersonBudget[];
548};
549
550export type SpendSlice = { key: string; label: string; micros: number; count: number };
551
552/** Which days a breakdown covers: this month (the default), last month, or the last 7 or 30 days, in UTC. */
553export type SpendPeriod = "month" | "last_month" | "7d" | "30d";
554
555/** Where an agent's (or every agent's) spend went over a period. */
556export type AgentSpendBreakdown = {
557 /** `YYYY-MM` for a month; for a span of days, the month it ends in. */
558 period: string;
559 /** The span asked for, and its first and last day (`YYYY-MM-DD`, both included). */
560 span: SpendPeriod;
561 from: string;
562 until: string;
563 /** The one person it is about (work asked for by them), or null for everyone's. */
564 person: string | null;
565 total_micros: number;
566 /** Chat replies, sessions, routines, helping colleagues. */
567 by_kind: SpendSlice[];
568 by_model: SpendSlice[];
569 /** Who asked: the work done for each person. */
570 by_person: SpendSlice[];
571 by_agent: SpendSlice[];
572 by_team: SpendSlice[];
573 /**
574 * Where it was asked: a channel by id (labelled `#name`), direct
575 * messages together (`dm`), and channels the viewer can't read together
576 * (`private`).
577 */
578 by_channel: SpendSlice[];
579 /** The costliest sessions in the period. */
580 top_sessions: AgentSession[];
581 /** Spend by day in the period. */
582 days: { day: string; micros: number }[];
583};
584
585/** Agents mode's front page. */
586export type AgentsOverview = {
587 policy: AgentPolicy;
588 /** Every agent's spend this month, against the policy's budget. */
589 spent_month_micros: number;
590 /** The highest alert this month: 75, 90 or 100 (% of the workspace's agent budget). */
591 alert: number | null;
592 agents: WorkspaceAgent[];
593 /** Live sessions, counted by agent id, for the roster. */
594 live_by_agent: Record<string, number>;
595 /** Sessions live now that the viewer can see. */
596 live: AgentSession[];
597 /** Sessions waiting on the viewer: spend they may approve. */
598 waiting_on_you: AgentSession[];
599 /** Recently finished sessions the viewer can see. */
600 recent: AgentSession[];
601 /** The next routines to run on a schedule. */
602 upcoming: (AgentRoutine & { agent_handle: string; agent_name: string })[];
603 spend: AgentSpendBreakdown;
604 can_manage: boolean;
605};
606
607/** One thing an agent did, for its Activity tab. */
608export type AgentActivity = {
609 id: string;
610 kind: "reply" | "session";
611 status: string;
612 channel_id: string;
613 channel_name: string | null;
614 /** The session's title; null for a reply or one the viewer can't see. */
615 title: string | null;
616 asked_by_username: string | null;
617 model: string | null;
618 tools: number;
619 charged_micros: number;
620 created_at: string;
621 visible: boolean;
622 /** For a reply, the message it posted; for a session, its id. */
623 ref: string | null;
624};
625
626export type AgentVersion = { version: number; changed_by: string; created_at: string; definition: Partial<NewWorkspaceAgent> };
627
628/** A card action, as chat hands it to agents. */
629export type AgentCardAction = {
630 workspace: string;
631 channel_id: string;
632 message_id: string;
633 viewer: User;
634 card: { kind: string; ref: string | null };
635 action_id: string;
636 input: string | null;
637};
638
639export type WorkspaceAgentsApi = {
640 list(workspace: string, viewer: User): Promise<Result<WorkspaceAgent[]>>;
641 get(workspace: string, handle: string, viewer: User): Promise<Result<WorkspaceAgent>>;
642 /** Internal: by id, for the chat service resolving members. */
643 byIds(ids: string[]): Promise<WorkspaceAgent[]>;
644 create(workspace: string, viewer: User, input: NewWorkspaceAgent): Promise<Result<WorkspaceAgent>>;
645 update(
646 workspace: string,
647 handle: string,
648 viewer: User,
649 changes: Partial<NewWorkspaceAgent>,
650 ): Promise<Result<WorkspaceAgent>>;
651 archive(workspace: string, handle: string, viewer: User): Promise<Result<null>>;
652 templates(): Promise<AgentTemplate[]>;
653 /**
654 * Internal: the workspace's built-in `@g1t` agent, made if it does not
655 * exist yet. The chat service asks for it when someone mentions @g1t in
656 * a channel it is not in yet.
657 */
658 builtin(workspace: string, workspaceId: string): Promise<Result<WorkspaceAgent>>;
659 /** The chat service hands over a message for an agent to answer. Returns at once. */
660 deliver(delivery: AgentDelivery): Promise<Result<null>>;
661 overview(workspace: string, viewer: User): Promise<Result<AgentsOverview>>;
662 sessions(
663 workspace: string,
664 viewer: User,
665 filter?: { handle?: string | null; status?: "live" | "done" | null; limit?: number | null },
666 ): Promise<Result<AgentSession[]>>;
667 session(workspace: string, id: string, viewer: User): Promise<Result<AgentSessionDetail>>;
668 /** Stops a session and every session under it. */
669 stopSession(workspace: string, id: string, viewer: User): Promise<Result<AgentSession>>;
670 /** Raises a stopped session's cap and lets it go on. Owners only. */
671 approveSession(workspace: string, id: string, viewer: User, capMicros: number): Promise<Result<AgentSession>>;
672 /** A person's message to a session, running or finished: it reads it and goes on. */
673 steerSession(workspace: string, id: string, viewer: User, body: string): Promise<Result<AgentSession>>;
674 memories(workspace: string, handle: string, viewer: User): Promise<Result<AgentMemory[]>>;
675 remember(
676 workspace: string,
677 handle: string,
678 viewer: User,
679 input: { body: string; scope: AgentMemoryScope; scope_ref?: string | null },
680 ): Promise<Result<AgentMemory>>;
681 updateMemory(
682 workspace: string,
683 handle: string,
684 viewer: User,
685 id: string,
686 changes: { body?: string; pinned?: boolean },
687 ): Promise<Result<AgentMemory>>;
688 forget(workspace: string, handle: string, viewer: User, id: string): Promise<Result<null>>;
689 routines(workspace: string, handle: string, viewer: User): Promise<Result<{ routines: AgentRoutine[]; suggestions: RoutineSuggestion[] }>>;
690 saveRoutine(workspace: string, handle: string, viewer: User, input: NewRoutine, id?: string | null): Promise<Result<AgentRoutine>>;
691 deleteRoutine(workspace: string, handle: string, viewer: User, id: string): Promise<Result<null>>;
692 /** Runs a routine now, as a session. */
693 runRoutine(workspace: string, handle: string, viewer: User, id: string): Promise<Result<AgentSession>>;
694 /**
695 * Where the spend went: one agent's, or every agent's; this month unless
696 * `period` says otherwise; for everyone, or only the work one `person`
697 * (by username) asked for.
698 */
699 spend(workspace: string, viewer: User, handle?: string | null, options?: { period?: SpendPeriod | null; person?: string | null }): Promise<Result<AgentSpendBreakdown>>;
700 /** Budgets per person this month: owners see everyone's, anyone else their own. */
701 personBudgets(workspace: string, viewer: User): Promise<Result<PersonBudgets>>;
702 /**
703 * Gives one person a monthly budget of their own (`monthly_micros`; 0 for
704 * no budget at all), or with null puts them back on the default. Owners only.
705 */
706 setPersonBudget(workspace: string, viewer: User, username: string, monthlyMicros: number | null): Promise<Result<PersonBudgets>>;
707 activity(workspace: string, handle: string, viewer: User): Promise<Result<AgentActivity[]>>;
708 versions(workspace: string, handle: string, viewer: User): Promise<Result<AgentVersion[]>>;
709 /**
710 * Internal, from chat: a person pressed an action on one of agents'
711 * cards. Agents checks they may, acts, and updates the card.
712 */
713 cardAction(input: AgentCardAction): Promise<Result<CardActionResult>>;
714 policy(workspace: string, viewer: User): Promise<Result<AgentPolicy>>;
715 setPolicy(workspace: string, viewer: User, policy: Partial<AgentPolicy>): Promise<Result<AgentPolicy>>;
716};
717
718async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
719 const response = await service.fetch(`https://service/rpc/${method}`, {
720 method: "POST",
721 headers: { "content-type": "application/json" },
722 body: JSON.stringify(args),
723 });
724 if (!response.ok) {
725 throw new Error(`${method} failed with status ${response.status}`);
726 }
727 return (await response.json()) as T;
728}
729
730export function workspaceAgentsClient(service: ServiceBinding): WorkspaceAgentsApi {
731 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
732 return {
733 list: (workspace, viewer) => call("list", { workspace, viewer }),
734 get: (workspace, handle, viewer) => call("get", { workspace, handle, viewer }),
735 byIds: (ids) => call("by_ids", { ids }),
736 create: (workspace, viewer, input) => call("create", { workspace, viewer, input }),
737 update: (workspace, handle, viewer, changes) => call("update", { workspace, handle, viewer, changes }),
738 archive: (workspace, handle, viewer) => call("archive", { workspace, handle, viewer }),
739 templates: () => call("templates", {}),
740 builtin: (workspace, workspaceId) => call("builtin", { workspace, workspace_id: workspaceId }),
741 deliver: (delivery) => call("deliver", delivery),
742 overview: (workspace, viewer) => call("overview", { workspace, viewer }),
743 sessions: (workspace, viewer, filter) => call("sessions", { workspace, viewer, ...(filter ?? {}) }),
744 session: (workspace, id, viewer) => call("session", { workspace, id, viewer }),
745 stopSession: (workspace, id, viewer) => call("stop_session", { workspace, id, viewer }),
746 approveSession: (workspace, id, viewer, capMicros) => call("approve_session", { workspace, id, viewer, cap_micros: capMicros }),
747 steerSession: (workspace, id, viewer, body) => call("steer_session", { workspace, id, viewer, body }),
748 memories: (workspace, handle, viewer) => call("memories", { workspace, handle, viewer }),
749 remember: (workspace, handle, viewer, input) => call("remember", { workspace, handle, viewer, input }),
750 updateMemory: (workspace, handle, viewer, id, changes) => call("update_memory", { workspace, handle, viewer, id, changes }),
751 forget: (workspace, handle, viewer, id) => call("forget", { workspace, handle, viewer, id }),
752 routines: (workspace, handle, viewer) => call("routines", { workspace, handle, viewer }),
753 saveRoutine: (workspace, handle, viewer, input, id) => call("save_routine", { workspace, handle, viewer, input, id: id ?? null }),
754 deleteRoutine: (workspace, handle, viewer, id) => call("delete_routine", { workspace, handle, viewer, id }),
755 runRoutine: (workspace, handle, viewer, id) => call("run_routine", { workspace, handle, viewer, id }),
756 spend: (workspace, viewer, handle, options) =>
757 call("spend", { workspace, viewer, handle: handle ?? null, period: options?.period ?? null, person: options?.person ?? null }),
758 personBudgets: (workspace, viewer) => call("person_budgets", { workspace, viewer }),
759 setPersonBudget: (workspace, viewer, username, monthlyMicros) =>
760 call("set_person_budget", { workspace, viewer, username, monthly_micros: monthlyMicros }),
761 activity: (workspace, handle, viewer) => call("activity", { workspace, handle, viewer }),
762 versions: (workspace, handle, viewer) => call("versions", { workspace, handle, viewer }),
763 cardAction: (input) => call("card_action", input),
764 policy: (workspace, viewer) => call("policy", { workspace, viewer }),
765 setPolicy: (workspace, viewer, policy) => call("set_policy", { workspace, viewer, policy }),
766 };
767}