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