File size: 3,328 Bytes
3d700dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import type {
  ProviderConnection,
  CreateProviderConnectionRequest,
  UpdateProviderConnectionRequest,
} from "../provider-connections-service/provider-connections-service.api";
import { getActiveBackend } from "../backend-registry/active-store";
import type { Backend } from "../backend-registry/types";
import { callCloudProxy } from "./proxy";

/**
 * Cloud provider-connections service.
 *
 * Provider-connection CRUD is routed to the org-scoped endpoints
 * `/api/organizations/{orgId}/provider-connections`, which enforce
 * `EDIT_ORG_SETTINGS` server-side (listing requires `VIEW_ORG_SETTINGS`) — so a
 * member's mutation is rejected with 403 even on a direct API call, not just
 * hidden in the UI. This mirrors the cloud LLM-profile service.
 *
 * Unlike the local agent-server (which returns a bare array), the org list
 * route wraps results as `{ connections: [...] }`; this service unwraps that so
 * callers see the same `ProviderConnection[]` shape on both backends. Individual
 * mutations return a bare `ProviderConnection` on both backends. Neither route
 * exposes the stored key; each item carries `api_key_set` instead.
 *
 * Cloud has no unbound-org fallback here: provider connections are inherently
 * org-scoped, so an org must be bound. A cloud backend without an org id is a
 * programming error rather than a legacy-key case.
 */

interface CloudProviderConnectionListResponse {
  connections: ProviderConnection[];
}

/** Resolve the backend + org-scoped base path for the active cloud backend. */
function cloudProviderConnectionsTarget(): { backend: Backend; base: string } {
  const { backend, orgId } = getActiveBackend();
  if (backend.kind !== "cloud") {
    throw new Error(
      "Cloud provider-connections call requires a cloud backend.",
    );
  }
  if (!orgId) {
    throw new Error(
      "Cloud provider connections require an organization-bound backend.",
    );
  }
  return {
    backend,
    base: `/api/organizations/${encodeURIComponent(orgId)}/provider-connections`,
  };
}

export async function fetchCloudProviderConnections(): Promise<
  ProviderConnection[]
> {
  const { backend, base } = cloudProviderConnectionsTarget();
  const result = await callCloudProxy<CloudProviderConnectionListResponse>({
    backend,
    method: "GET",
    path: base,
  });
  return result.connections ?? [];
}

export async function createCloudProviderConnection(
  request: CreateProviderConnectionRequest,
): Promise<ProviderConnection> {
  const { backend, base } = cloudProviderConnectionsTarget();
  return callCloudProxy<ProviderConnection>({
    backend,
    method: "POST",
    path: base,
    body: request,
  });
}

export async function updateCloudProviderConnection(
  id: string,
  request: UpdateProviderConnectionRequest,
): Promise<ProviderConnection> {
  const { backend, base } = cloudProviderConnectionsTarget();
  return callCloudProxy<ProviderConnection>({
    backend,
    method: "PATCH",
    path: `${base}/${encodeURIComponent(id)}`,
    body: request,
  });
}

export async function deleteCloudProviderConnection(
  id: string,
): Promise<ProviderConnection> {
  const { backend, base } = cloudProviderConnectionsTarget();
  return callCloudProxy<ProviderConnection>({
    backend,
    method: "DELETE",
    path: `${base}/${encodeURIComponent(id)}`,
  });
}