Skip to content
77 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.

Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.1/**
2 * What Customize's pages do on the server besides reading: the connector
3 * actions every page with a connector shares (signing in and out, a key,
4 * listing tools again, where it reaches, removing it), and what follows
5 * adding one: the browser goes to sign in, or to the connector's page.
6 */
Merge branch 'main' into worktree-agent-a0d19db53ce9b29d87import { redirect } from "react-router";
Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.8
9import type { ConnectorInput, User, WorkspaceConnector } from "@g1t/contracts";
10
11import { addresses } from "./addresses.server";
Merge branch 'main' into worktree-agent-a0d19db53ce9b29d812import { connectorCallbackPath, connectorPagePath, splitTarget } from "./customize";
Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.13import { workspaceAgents } from "./services.server";
14import type { ActionResult } from "../components/agents/dialogs";
15import { answer } from "../components/agents/actions.server";
16
17/** Where a connector's sign-in comes back to: this g1t's own page. */
18export function callbackUri(slug: string): string {
19 return `${addresses().site}${connectorCallbackPath(slug)}`;
20}
21
22/** The browser leaves for the connector's sign-in page, or lands on the connector's own page with what happened. */
23async function toSignIn(slug: string, viewer: User, id: string): Promise<Response> {
24 const started = await workspaceAgents.startConnectorSignIn(slug, viewer, id, callbackUri(slug)).catch(() => null);
25 if (started?.ok) return redirect(started.value.url);
26 const problem = started ? started.error.message : "The agents service didn't answer. Try again in a moment.";
27 return redirect(`${connectorPagePath(slug, id)}?problem=${encodeURIComponent(problem)}`);
28}
29
30/**
31 * Adds a connector and takes the browser where it goes next: to sign in
32 * when the server asks for that, or to the connector's page otherwise. An
33 * answer (not a redirect) says why it couldn't be added.
34 */
35export async function addConnectorThen(slug: string, viewer: User, input: ConnectorInput): Promise<Response | ActionResult> {
36 const added = await workspaceAgents.addConnector(slug, viewer, input).catch(() => null);
37 if (!added) return { ok: false, intent: "connect", error: "The agents service didn't answer. Try again in a moment." };
38 if (!added.ok) return { ok: false, intent: "connect", error: added.error.message, code: added.error.code };
39 return afterAdd(slug, viewer, added.value);
40}
41
42/** After a connector is added: the sign-in when it needs one, its page otherwise. */
43export function afterAdd(slug: string, viewer: User, connector: WorkspaceConnector): Promise<Response> | Response {
44 if (connector.state === "needs_sign_in") return toSignIn(slug, viewer, connector.id);
45 return redirect(`${connectorPagePath(slug, connector.id)}?added=1`);
46}
47
48/**
49 * The actions a connector has wherever it is shown. `intent` names one;
50 * null says it isn't one of them, so the page's own action goes on.
51 * `afterRemove` is where the browser goes once it is removed.
52 */
53export async function connectorAction(intent: string, form: FormData, slug: string, viewer: User, afterRemove: string | null): Promise<Response | ActionResult | null> {
54 const id = String(form.get("connector") ?? "");
55 switch (intent) {
56 case "sign_in":
57 return toSignIn(slug, viewer, id);
58 case "sign_out":
59 return answer(intent, workspaceAgents.signOutConnector(slug, viewer, id));
60 case "key":
61 return answer(intent, workspaceAgents.setConnectorKey(slug, viewer, id, String(form.get("header_name") ?? "") || null, String(form.get("value") ?? "")));
62 case "refresh":
63 return answer(intent, workspaceAgents.refreshConnector(slug, viewer, id));
64 case "grant": {
65 const { scope, target } = splitTarget(String(form.get("where") ?? ""));
66 return answer(intent, workspaceAgents.grantConnector(slug, viewer, id, scope, target));
67 }
68 case "revoke":
69 return answer(intent, workspaceAgents.revokeConnector(slug, viewer, id, String(form.get("grant") ?? "")));
70 case "remove": {
71 const result = await answer(intent, workspaceAgents.removeConnector(slug, viewer, id));
72 return result.ok && afterRemove ? redirect(afterRemove) : result;
73 }
74 default:
75 return null;
76 }
77}

This file's history is long; its oldest lines are credited to the oldest commit read.