chat-agent-toolkit 1.2.73 → 1.2.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "oomol-saas-server",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "start": "node server.js",
9
+ "build": "node --check server.js && node smoke-test.js"
10
+ },
11
+ "keywords": [],
12
+ "author": "",
13
+ "license": "ISC",
14
+ "type": "module",
15
+ "dependencies": {
16
+ "@oomol-lab/connector": "^1.0.0",
17
+ "express": "^5.2.1"
18
+ }
19
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Multi-tenant connector server on OOMOL Cloud (https://connector.oomol.com/v1).
3
+ *
4
+ * OOMOL hosts everything: OAuth apps, credential vault, token refresh, provider calls.
5
+ * Each of YOUR app's users links THEIR OWN accounts (Gmail, Slack, GitHub, ...) via
6
+ * ProjectConnector — the composio/pipedream "managed auth" model.
7
+ *
8
+ * Setup:
9
+ * 1. Create a project in the OOMOL console and copy its API key (oo_proj_...).
10
+ * 2. OOMOL_PROJECT_API_KEY=oo_proj_... npm start
11
+ *
12
+ * externalUserId = your app's own user id (from your auth/session layer). NEVER let the
13
+ * client pick another user's id — derive it server-side from the authenticated session.
14
+ */
15
+ import express from "express";
16
+ import { readFile } from "node:fs/promises";
17
+ import { ConnectorError, ProjectConnector } from "@oomol-lab/connector";
18
+
19
+ const PORT = process.env.PORT ?? 8787;
20
+ const APP_ORIGIN = process.env.APP_ORIGIN ?? `http://localhost:${PORT}`;
21
+
22
+ const project = new ProjectConnector({
23
+ apiKey: process.env.OOMOL_PROJECT_API_KEY ?? "oo_proj_missing",
24
+ // baseUrl defaults to OOMOL Cloud: https://connector.oomol.com/v1
25
+ });
26
+
27
+ const catalog = JSON.parse(await readFile(new URL("./catalog.json", import.meta.url), "utf8"));
28
+ const byService = new Map(catalog.map((p) => [p.service, p]));
29
+
30
+ const app = express();
31
+ app.use(express.json());
32
+
33
+ // DEMO ONLY: replace with your real session auth. Everything below trusts req.userId.
34
+ app.use((req, _res, next) => {
35
+ req.userId = req.get("x-user-id") ?? "demo_user";
36
+ next();
37
+ });
38
+
39
+ /** Provider catalog for your "add a connection" picker. */
40
+ app.get("/api/catalog", (req, res) => {
41
+ const q = (req.query.q ?? "").toString().toLowerCase();
42
+ const list = q
43
+ ? catalog.filter((p) => p.service.includes(q) || p.displayName.toLowerCase().includes(q))
44
+ : catalog;
45
+ res.json(list.slice(0, Number(req.query.limit ?? 100)));
46
+ });
47
+
48
+ /**
49
+ * Start a connection for the logged-in user.
50
+ * OAuth providers -> returns { authorizationUrl, requestId } — redirect the user there.
51
+ * API-key/custom -> pass { apiKey } or { values } — connects synchronously.
52
+ */
53
+ app.post("/api/connections", async (req, res) => {
54
+ const { service, connectionName, apiKey, values } = req.body ?? {};
55
+ const provider = byService.get(service);
56
+ if (!provider) return res.status(404).json({ error: "unknown_service", service });
57
+
58
+ const user = project.forUser(req.userId);
59
+ try {
60
+ if (provider.authTypes.includes("oauth2") && !apiKey && !values) {
61
+ const request = await user.connect.oauth({
62
+ service,
63
+ connectionName,
64
+ returnUri: `${APP_ORIGIN}/connected?service=${encodeURIComponent(service)}`,
65
+ });
66
+ // Redirect your user to authorizationUrl; poll GET /api/connections/requests/:id after.
67
+ return res.json({
68
+ pending: true,
69
+ requestId: request.id,
70
+ authorizationUrl: request.authorizationUrl,
71
+ status: request.status,
72
+ });
73
+ }
74
+ const account = apiKey
75
+ ? await user.connect.apiKey({ service, apiKey, connectionName })
76
+ : await user.connect.customCredential({ service, values: values ?? {}, connectionName });
77
+ return res.json({
78
+ pending: false,
79
+ connectedAccountId: account.connectedAccountId,
80
+ status: account.status,
81
+ available: account.available,
82
+ });
83
+ } catch (err) {
84
+ return sendConnectorError(res, err);
85
+ }
86
+ });
87
+
88
+ /** Poll an OAuth connection request until the user finishes authorizing. */
89
+ app.get("/api/connections/requests/:id", async (req, res) => {
90
+ try {
91
+ const request = await project.getConnectionRequest(req.params.id);
92
+ res.json(request); // status: initiated | connected | failed | expired
93
+ } catch (err) {
94
+ sendConnectorError(res, err);
95
+ }
96
+ });
97
+
98
+ /** Run any catalog action as the logged-in user (e.g. "gmail.search_threads"). */
99
+ app.post("/api/actions/:actionId", async (req, res) => {
100
+ const { input = {}, connectionName } = req.body ?? {};
101
+ try {
102
+ const out = await project
103
+ .forUser(req.userId)
104
+ .executeRaw(req.params.actionId, input, connectionName ? { connectionName } : undefined);
105
+ res.json(out); // { data, executionId, actionId, message }
106
+ } catch (err) {
107
+ sendConnectorError(res, err);
108
+ }
109
+ });
110
+
111
+ /** Landing page OOMOL returns the user to after OAuth consent. */
112
+ app.get("/connected", (req, res) => {
113
+ res.send(`<p>${req.query.service ?? "Account"} connected — you can close this tab.</p>`);
114
+ });
115
+
116
+ function sendConnectorError(res, err) {
117
+ if (err instanceof ConnectorError) {
118
+ // e.g. provider_config_not_found, connection_alias_conflict, app_not_ready, client_wait_timeout
119
+ return res.status(err.status || 502).json({ error: err.code, message: err.message });
120
+ }
121
+ console.error(err);
122
+ return res.status(500).json({ error: "internal_error" });
123
+ }
124
+
125
+ app.listen(PORT, () => console.log(`OOMOL SaaS connector server on :${PORT} (gateway: connector.oomol.com)`));
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Multi-tenant connector server on OOMOL Cloud.
3
+ *
4
+ * This is a standalone Node.js Express server that manages OAuth credentials
5
+ * and API key connections for your app's users. It's NOT for browser/client code.
6
+ *
7
+ * OOMOL handles everything server-side:
8
+ * - OAuth apps & client secrets
9
+ * - Credential vault (encrypted storage)
10
+ * - Token refresh
11
+ * - Provider API calls
12
+ *
13
+ * Your app's users link THEIR OWN accounts (Gmail, Slack, GitHub, ...) via
14
+ * ProjectConnector — similar to Composio or Pipedream's "managed auth" model.
15
+ *
16
+ * @fileoverview OOMOL SaaS Connector Server
17
+ * @module connectors/server
18
+ *
19
+ * Setup:
20
+ * 1. Create a project in the OOMOL console and copy its API key (oo_proj_...).
21
+ * 2. Set OOMOL_PROJECT_API_KEY=oo_proj_... and run:
22
+ * ```bash
23
+ * cd packages/agent-toolkit/src/connectors
24
+ * npm install
25
+ * npm start
26
+ * ```
27
+ *
28
+ * Environment variables:
29
+ * - OOMOL_PROJECT_API_KEY: Your OOMOL project API key (required)
30
+ * - PORT: Server port (default: 8787)
31
+ * - APP_ORIGIN: Your app's origin for OAuth redirect (default: http://localhost:8787)
32
+ *
33
+ * API Endpoints:
34
+ * GET /api/catalog - List/search providers
35
+ * POST /api/connections - Start OAuth or API-key connection
36
+ * GET /api/connections/requests/:id - Poll OAuth status
37
+ * POST /api/actions/:actionId - Execute a provider action
38
+ * GET /connected - OAuth redirect landing page
39
+ *
40
+ * SECURITY: externalUserId = your app's own user id (from auth/session layer).
41
+ * NEVER let the client pick another user's id — derive it server-side from
42
+ * the authenticated session.
43
+ */
44
+
45
+ import express, { Request, Response, NextFunction } from "express";
46
+ import { readFile } from "node:fs/promises";
47
+ import { ConnectorError, ProjectConnector } from "@oomol-lab/connector";
48
+ import { catalog } from "./index.js";
49
+
50
+ const PORT = process.env.PORT ?? 8787;
51
+ const APP_ORIGIN = process.env.APP_ORIGIN ?? `http://localhost:${PORT}`;
52
+
53
+ const project = new ProjectConnector({
54
+ apiKey: process.env.OOMOL_PROJECT_API_KEY ?? "oo_proj_missing",
55
+ // baseUrl defaults to OOMOL Cloud: https://connector.oomol.com/v1
56
+ });
57
+
58
+ // Build a Map for fast service lookup
59
+ const byService = new Map(catalog.map((p) => [p.service, p]));
60
+
61
+ const app = express();
62
+ app.use(express.json());
63
+
64
+ // DEMO ONLY: replace with your real session auth. Everything below trusts req.userId.
65
+ // In production, use middleware like passport, express-session, or JWT.
66
+ app.use((req: Request, _res: Response, next: NextFunction) => {
67
+ (req as any).userId = req.get("x-user-id") ?? "demo_user";
68
+ next();
69
+ });
70
+
71
+ /**
72
+ * GET /api/catalog
73
+ * Provider catalog for your "add a connection" picker.
74
+ * @query q Search term (optional, case-insensitive)
75
+ * @query limit Max results (default: 100)
76
+ */
77
+ app.get("/api/catalog", (req: Request, res: Response) => {
78
+ const q = (req.query.q ?? "").toString().toLowerCase();
79
+ const list = q
80
+ ? catalog.filter(
81
+ (p) =>
82
+ p.service.includes(q) || p.displayName.toLowerCase().includes(q)
83
+ )
84
+ : catalog;
85
+ res.json(list.slice(0, Number(req.query.limit ?? 100)));
86
+ });
87
+
88
+ /**
89
+ * POST /api/connections
90
+ * Start a connection for the logged-in user.
91
+ *
92
+ * OAuth providers: returns { pending: true, authorizationUrl, requestId }
93
+ * → redirect the user to authorizationUrl, then poll /api/connections/requests/:id
94
+ *
95
+ * API-key or custom credential: pass { apiKey } or { values }
96
+ * → returns { pending: false, connectedAccountId, status, available }
97
+ *
98
+ * @body service The provider service ID (e.g., "gmail", "slack")
99
+ * @body connectionName Optional friendly name for this connection
100
+ * @body apiKey Optional API key (for api_key auth providers)
101
+ * @body values Optional custom credential values object
102
+ */
103
+ app.post("/api/connections", async (req: Request, res: Response) => {
104
+ const { service, connectionName, apiKey, values } = req.body ?? {};
105
+ const provider = byService.get(service);
106
+ if (!provider)
107
+ return res.status(404).json({ error: "unknown_service", service });
108
+
109
+ const userId = (req as any).userId;
110
+ const user = project.forUser(userId);
111
+ try {
112
+ if (provider.authTypes.includes("oauth2") && !apiKey && !values) {
113
+ const request = await user.connect.oauth({
114
+ service,
115
+ connectionName,
116
+ returnUri: `${APP_ORIGIN}/connected?service=${encodeURIComponent(service)}`,
117
+ });
118
+ // Redirect your user to authorizationUrl; poll GET /api/connections/requests/:id after.
119
+ return res.json({
120
+ pending: true,
121
+ requestId: request.id,
122
+ authorizationUrl: request.authorizationUrl,
123
+ status: request.status,
124
+ });
125
+ }
126
+ const account = apiKey
127
+ ? await user.connect.apiKey({ service, apiKey, connectionName })
128
+ : await user.connect.customCredential({
129
+ service,
130
+ values: values ?? {},
131
+ connectionName,
132
+ });
133
+ return res.json({
134
+ pending: false,
135
+ connectedAccountId: account.connectedAccountId,
136
+ status: account.status,
137
+ available: account.available,
138
+ });
139
+ } catch (err) {
140
+ return sendConnectorError(res, err);
141
+ }
142
+ });
143
+
144
+ /**
145
+ * GET /api/connections/requests/:id
146
+ * Poll an OAuth connection request until the user finishes authorizing.
147
+ * @param id The request ID from POST /api/connections
148
+ * @returns { status: "initiated" | "connected" | "failed" | "expired", ... }
149
+ */
150
+ app.get("/api/connections/requests/:id", async (req: Request, res: Response) => {
151
+ try {
152
+ const request = await project.getConnectionRequest(req.params.id);
153
+ res.json(request);
154
+ } catch (err) {
155
+ sendConnectorError(res, err);
156
+ }
157
+ });
158
+
159
+ /**
160
+ * POST /api/actions/:actionId
161
+ * Run any catalog action as the logged-in user.
162
+ * @param actionId The action ID (e.g., "gmail.search_threads")
163
+ * @body input Input parameters for the action
164
+ * @body connectionName Optional connection name to use (if user has multiple)
165
+ * @returns { data, executionId, actionId, message }
166
+ */
167
+ app.post("/api/actions/:actionId", async (req: Request, res: Response) => {
168
+ const { input = {}, connectionName } = req.body ?? {};
169
+ try {
170
+ const userId = (req as any).userId;
171
+ const out = await project
172
+ .forUser(userId)
173
+ .executeRaw(
174
+ req.params.actionId,
175
+ input,
176
+ connectionName ? { connectionName } : undefined
177
+ );
178
+ res.json(out);
179
+ } catch (err) {
180
+ sendConnectorError(res, err);
181
+ }
182
+ });
183
+
184
+ /**
185
+ * GET /connected
186
+ * Landing page: OOMOL redirects the user here after OAuth consent.
187
+ * You can customize this to show a success message or redirect to your app.
188
+ */
189
+ app.get("/connected", (req: Request, res: Response) => {
190
+ res.send(
191
+ `<p>${req.query.service ?? "Account"} connected — you can close this tab.</p>`
192
+ );
193
+ });
194
+
195
+ /**
196
+ * Handle OOMOL connector errors with proper HTTP status codes.
197
+ */
198
+ function sendConnectorError(res: Response, err: unknown) {
199
+ if (err instanceof ConnectorError) {
200
+ // OOMOL error codes: provider_config_not_found, connection_alias_conflict,
201
+ // app_not_ready, client_wait_timeout, etc.
202
+ return res
203
+ .status(err.status || 502)
204
+ .json({ error: err.code, message: err.message });
205
+ }
206
+ console.error("Server error:", err);
207
+ return res.status(500).json({ error: "internal_error" });
208
+ }
209
+
210
+ const server = app.listen(PORT, () => {
211
+ console.log(
212
+ `\n✓ OOMOL SaaS Connector server started on http://localhost:${PORT}`
213
+ );
214
+ console.log(
215
+ ` Gateway: https://connector.oomol.com/v1 (OOMOL_PROJECT_API_KEY required)`
216
+ );
217
+ console.log(` Environment: APP_ORIGIN=${APP_ORIGIN}\n`);
218
+ });
219
+
220
+ export { app, server };
@@ -0,0 +1,14 @@
1
+ // Build-time smoke test: verify the SDK imports, client constructs, and routes register.
2
+ import { ProjectConnector, ConnectorError } from "@oomol-lab/connector";
3
+ const p = new ProjectConnector({ apiKey: "oo_proj_smoke" });
4
+ const u = p.forUser("user_1");
5
+ for (const [obj, keys] of [
6
+ [p, ["connect", "getConnectionRequest", "waitForConnection", "execute", "executeRaw", "forUser"]],
7
+ [u, ["connect", "execute", "executeRaw", "waitForConnection"]],
8
+ ]) for (const k of keys) if (typeof obj[k] !== "function" && typeof obj[k] !== "object") throw new Error(`missing ${k}`);
9
+ if (typeof ConnectorError !== "function") throw new Error("missing ConnectorError");
10
+ // Boot the server without a listener conflict, then exit.
11
+ process.env.PORT = "0";
12
+ await import("./server.js");
13
+ console.log("smoke test passed");
14
+ setTimeout(() => process.exit(0), 300);
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Build-time smoke test: verify the OOMOL SDK imports, client constructs, and routes register.
3
+ * Run with: npm run build (from the connectors directory)
4
+ */
5
+
6
+ import { ProjectConnector, ConnectorError } from "@oomol-lab/connector";
7
+
8
+ const p = new ProjectConnector({ apiKey: "oo_proj_smoke" });
9
+ const u = p.forUser("user_1");
10
+
11
+ // Verify ProjectConnector and UserConnector have expected methods
12
+ for (const [obj, keys] of [
13
+ [
14
+ p,
15
+ [
16
+ "connect",
17
+ "getConnectionRequest",
18
+ "waitForConnection",
19
+ "execute",
20
+ "executeRaw",
21
+ "forUser",
22
+ ],
23
+ ],
24
+ [u, ["connect", "execute", "executeRaw", "waitForConnection"]],
25
+ ] as const) {
26
+ for (const k of keys) {
27
+ const val = (obj as any)[k];
28
+ if (typeof val !== "function" && typeof val !== "object") {
29
+ throw new Error(`missing ${k} on ${obj.constructor.name}`);
30
+ }
31
+ }
32
+ }
33
+
34
+ // Verify ConnectorError is exported
35
+ if (typeof ConnectorError !== "function") {
36
+ throw new Error("missing ConnectorError class");
37
+ }
38
+
39
+ // Boot the server without a listener conflict, then exit.
40
+ process.env.PORT = "0";
41
+ await import("./server.js");
42
+
43
+ console.log("✓ OOMOL Connector smoke test passed");
44
+ setTimeout(() => process.exit(0), 300);
package/src/index.ts CHANGED
@@ -25,3 +25,7 @@ export { configManager, ModelRegistry, getEnv, getModelProvidersUIConfigSection
25
25
  export type { Config, ConfigModelProvider, MCPServerConfig, UIConfigSections, Model, ModelWithProvider } from "./config";
26
26
  export { cropProvider, cropProviderAsBlob, cropProviderAsDataURL, getProviderImage, getProviderNames } from "./utils/provider-image-cropper";
27
27
  export type { Provider } from "./utils/provider-image-cropper";
28
+
29
+ // OpenConnector integration: multi-tenant OAuth & credential management
30
+ export * from "./connectors";
31
+ export * as connectors from "./connectors";