Skip to content
110 linesCodeBlameRaw

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

The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.1/**
2 * Extensions installed in a workspace (docs.g1t.sh/guides/marketplace/):
3 * the version an install keeps, its plan, who installed it, whether it is
4 * switched on (the kill switch), and its monthly spend cap. Kept in
5 * `extension_installs`; one live install per workspace and extension.
6 *
7 * Only a published listing installs: one whose manifest is `available`,
8 * with a source and a version. The catalog of manifests is passed in
9 * (`Catalog`): today g1t's own (@g1t/contracts FIRST_PARTY_EXTENSIONS),
10 * later every published listing. Who may do what, the audit log and
11 * telling people are index.ts's; this file is the rules and the table,
12 * tested on SQLite.
13 */
14import type { ExtensionInstall, ExtensionManifest, FailureCode, Result } from "@g1t/contracts";
15import { FREE_PLAN, listingRef, parseListing } from "@g1t/contracts/marketplace";
16
17const ok = <T>(value: T): Result<T> => ({ ok: true, value });
18const fail = (code: FailureCode, message: string): Result<never> => ({ ok: false, error: { code, message } });
19
20/** Finds an extension's manifest by id. */
21export type Catalog = (id: string) => ExtensionManifest | undefined;
22
23type Row = {
24 id: string;
25 listing: string;
26 version: string;
27 plan: string;
28 installed_by: string;
29 installed_at: string;
30 enabled: number;
31 disabled_by: string | null;
32 disabled_at: string | null;
33 budget_monthly_micros: number | null;
34};
35
36const COLUMNS = "id, listing, version, plan, installed_by, installed_at, enabled, disabled_by, disabled_at, budget_monthly_micros";
37
38function toInstall(row: Row): ExtensionInstall {
39 return { ...row, enabled: row.enabled === 1 };
40}
41
42/** The workspace's live installs, oldest first. */
43export async function listInstalls(db: D1Database, workspaceId: string): Promise<ExtensionInstall[]> {
44 const { results } = await db.prepare(`SELECT ${COLUMNS} FROM extension_installs WHERE workspace_id = ? AND uninstalled_at IS NULL ORDER BY installed_at`).bind(workspaceId).all<Row>();
45 return results.map(toInstall);
46}
47
48/** Installs a published extension at its current version, on the free plan. A conflict when it is installed already. */
49export async function install(db: D1Database, catalog: Catalog, workspaceId: string, id: string, extensionId: string, by: string, now = new Date()): Promise<Result<ExtensionInstall>> {
50 const manifest = catalog(extensionId);
51 if (!manifest) return fail("not_found", "There is no such extension.");
52 if (manifest.status !== "available" || !manifest.version || !manifest.source) return fail("invalid", `${manifest.name} isn't published yet, so it can't be installed.`);
53 const row: Row = {
54 id,
55 listing: listingRef("extension", manifest.id),
56 version: manifest.version,
57 plan: FREE_PLAN,
58 installed_by: by,
59 installed_at: now.toISOString(),
60 enabled: 1,
61 disabled_by: null,
62 disabled_at: null,
63 budget_monthly_micros: null,
64 };
65 const inserted = await db
66 .prepare(
67 `INSERT INTO extension_installs (id, workspace_id, listing, version, plan, installed_by, installed_at, enabled)
68 VALUES (?, ?, ?, ?, ?, ?, ?, 1) ON CONFLICT DO NOTHING RETURNING id`,
69 )
70 .bind(row.id, workspaceId, row.listing, row.version, row.plan, row.installed_by, row.installed_at)
71 .first<{ id: string }>();
72 return inserted ? ok(toInstall(row)) : fail("conflict", `${manifest.name} is installed already.`);
73}
74
75/** Switches an install on or off. Off is the kill switch: it takes effect at once. */
76export async function setEnabled(db: D1Database, workspaceId: string, listing: string, enabled: boolean, by: string, now = new Date()): Promise<Result<ExtensionInstall>> {
77 const row = await db
78 .prepare(
79 `UPDATE extension_installs SET enabled = ?, disabled_by = ?, disabled_at = ?
80 WHERE workspace_id = ? AND listing = ? AND uninstalled_at IS NULL RETURNING ${COLUMNS}`,
81 )
82 .bind(enabled ? 1 : 0, enabled ? null : by, enabled ? null : now.toISOString(), workspaceId, listing)
83 .first<Row>();
84 return row ? ok(toInstall(row)) : fail("not_found", "That extension isn't installed.");
85}
86
87/** Sets an install's monthly spend cap; null leaves it to the workspace's limit. */
88export async function setBudget(db: D1Database, workspaceId: string, listing: string, micros: number | null): Promise<Result<ExtensionInstall>> {
89 if (micros !== null && (!Number.isSafeInteger(micros) || micros < 0)) return fail("invalid", "A budget is a whole, non-negative number of micro-dollars.");
90 const row = await db
91 .prepare(`UPDATE extension_installs SET budget_monthly_micros = ? WHERE workspace_id = ? AND listing = ? AND uninstalled_at IS NULL RETURNING ${COLUMNS}`)
92 .bind(micros, workspaceId, listing)
93 .first<Row>();
94 return row ? ok(toInstall(row)) : fail("not_found", "That extension isn't installed.");
95}
96
97/** Removes an install, keeping its row with who removed it and when. */
98export async function uninstall(db: D1Database, workspaceId: string, listing: string, by: string, now = new Date()): Promise<Result<null>> {
99 const row = await db
100 .prepare("UPDATE extension_installs SET uninstalled_by = ?, uninstalled_at = ?, enabled = 0 WHERE workspace_id = ? AND listing = ? AND uninstalled_at IS NULL RETURNING id")
101 .bind(by, now.toISOString(), workspaceId, listing)
102 .first<{ id: string }>();
103 return row ? ok(null) : fail("not_found", "That extension isn't installed.");
104}
105
106/** The extension id in an `extension:<id>` reference, or null. */
107export function extensionIdOf(listing: unknown): string | null {
108 const parsed = parseListing(listing);
109 return parsed?.kind === "extension" ? parsed.id : null;
110}