@tangle-network/agent-integrations 0.26.0 → 0.28.0
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.
- package/dist/bin/tangle-catalog-runtime.js +6 -2
- package/dist/bin/tangle-catalog-runtime.js.map +1 -1
- package/dist/catalog.d.ts +5 -1
- package/dist/catalog.js +6 -2
- package/dist/chunk-ATYHZXLL.js +457 -0
- package/dist/chunk-ATYHZXLL.js.map +1 -0
- package/dist/chunk-H4XYLS7T.js +75 -0
- package/dist/chunk-H4XYLS7T.js.map +1 -0
- package/dist/{chunk-GA4VTE3U.js → chunk-JU25UDN2.js} +5 -58
- package/dist/chunk-JU25UDN2.js.map +1 -0
- package/dist/chunk-P24T3MLM.js +106 -0
- package/dist/chunk-P24T3MLM.js.map +1 -0
- package/dist/chunk-SVQ4PHDZ.js +129 -0
- package/dist/chunk-SVQ4PHDZ.js.map +1 -0
- package/dist/{chunk-ALCIWTIR.js → chunk-UWRYFPJW.js} +41 -83
- package/dist/chunk-UWRYFPJW.js.map +1 -0
- package/dist/connect/index.d.ts +112 -0
- package/dist/connect/index.js +14 -0
- package/dist/connect/index.js.map +1 -0
- package/dist/connectors/adapters/index.d.ts +593 -1
- package/dist/connectors/adapters/index.js +15 -1
- package/dist/connectors/index.d.ts +2 -1
- package/dist/connectors/index.js +19 -5
- package/dist/errors-Bg3_rxnQ.d.ts +32 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +47 -9
- package/dist/middleware/index.d.ts +137 -0
- package/dist/middleware/index.js +14 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/registry.d.ts +29 -33
- package/dist/registry.js +6 -2
- package/dist/router-BncoovUh.d.ts +149 -0
- package/dist/runtime.d.ts +5 -1
- package/dist/runtime.js +6 -2
- package/dist/specs.d.ts +5 -1
- package/dist/stripe/index.d.ts +812 -0
- package/dist/stripe/index.js +866 -0
- package/dist/stripe/index.js.map +1 -0
- package/dist/tangle-catalog-runtime.d.ts +5 -1
- package/dist/tangle-catalog-runtime.js +6 -2
- package/dist/tangle-id-CTU4kGId.d.ts +553 -0
- package/dist/webhooks/index.d.ts +3 -148
- package/package.json +16 -1
- package/dist/chunk-ALCIWTIR.js.map +0 -1
- package/dist/chunk-GA4VTE3U.js.map +0 -1
- package/dist/index-D4D4CEKX.d.ts +0 -976
|
@@ -4,11 +4,15 @@ import {
|
|
|
4
4
|
buildTangleCatalogRuntimePackageManifest,
|
|
5
5
|
renderTangleCatalogRuntimePnpmAddCommand,
|
|
6
6
|
startTangleCatalogRuntimeNodeServer
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-UWRYFPJW.js";
|
|
8
|
+
import "../chunk-SVQ4PHDZ.js";
|
|
9
|
+
import "../chunk-H4XYLS7T.js";
|
|
8
10
|
import "../chunk-4JQ754PA.js";
|
|
9
11
|
import "../chunk-376UBTNB.js";
|
|
10
|
-
import "../chunk-
|
|
12
|
+
import "../chunk-JU25UDN2.js";
|
|
11
13
|
import "../chunk-2TW2QKGZ.js";
|
|
14
|
+
import "../chunk-P24T3MLM.js";
|
|
15
|
+
import "../chunk-ATYHZXLL.js";
|
|
12
16
|
|
|
13
17
|
// src/bin/tangle-catalog-runtime.ts
|
|
14
18
|
var args = new Set(process.argv.slice(2));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/tangle-catalog-runtime.ts"],"sourcesContent":["#!/usr/bin/env node\nimport {\n buildTangleCatalogRuntimePackageManifest,\n renderTangleCatalogRuntimePnpmAddCommand,\n} from '../tangle-catalog.js'\nimport { auditTangleCatalogRuntimePackages } from '../tangle-catalog-runtime.js'\nimport { startTangleCatalogRuntimeNodeServer } from '../tangle-catalog-runtime-server.js'\n\nconst args = new Set(process.argv.slice(2))\nif (args.has('--print-package-json')) {\n console.log(JSON.stringify(buildTangleCatalogRuntimePackageManifest({\n agentIntegrationsVersion: process.env.TANGLE_AGENT_INTEGRATIONS_VERSION,\n }), null, 2))\n process.exit(0)\n}\n\nif (args.has('--print-pnpm-add')) {\n console.log(renderTangleCatalogRuntimePnpmAddCommand({\n agentIntegrationsVersion: process.env.TANGLE_AGENT_INTEGRATIONS_VERSION,\n }))\n process.exit(0)\n}\n\nif (args.has('--audit-packages')) {\n const connectorIds = process.env.TANGLE_CATALOG_AUDIT_CONNECTORS\n ?.split(',')\n .map((id) => id.trim())\n .filter(Boolean)\n console.log(JSON.stringify(await auditTangleCatalogRuntimePackages({ connectorIds }), null, 2))\n process.exit(0)\n}\n\nconst secret = process.env.TANGLE_CATALOG_RUNTIME_SECRET\nif (!secret || secret.length < 32) {\n console.error('TANGLE_CATALOG_RUNTIME_SECRET must be set to at least 32 characters.')\n process.exit(1)\n}\n\nconst authResolverUrl = process.env.TANGLE_CATALOG_AUTH_RESOLVER_URL\nconst authResolverSecret = process.env.TANGLE_CATALOG_AUTH_RESOLVER_SECRET\nif (Boolean(authResolverUrl) !== Boolean(authResolverSecret)) {\n console.error('TANGLE_CATALOG_AUTH_RESOLVER_URL and TANGLE_CATALOG_AUTH_RESOLVER_SECRET must be set together.')\n process.exit(1)\n}\n\nconst port = Number(process.env.PORT ?? process.env.TANGLE_CATALOG_RUNTIME_PORT ?? 4109)\nif (!Number.isInteger(port) || port < 1 || port > 65_535) {\n console.error('PORT must be an integer between 1 and 65535.')\n process.exit(1)\n}\n\nconst server = await startTangleCatalogRuntimeNodeServer({\n secret,\n host: process.env.HOST ?? process.env.TANGLE_CATALOG_RUNTIME_HOST ?? '0.0.0.0',\n port,\n authResolver: authResolverUrl && authResolverSecret\n ? {\n endpoint: authResolverUrl,\n secret: authResolverSecret,\n }\n : false,\n onLog: (event) => {\n const line = JSON.stringify({\n level: event.level,\n message: event.message,\n ...event.metadata,\n })\n if (event.level === 'error') console.error(line)\n else console.log(line)\n },\n})\n\nconsole.log(JSON.stringify({\n level: 'info',\n message: 'Tangle catalog runtime listening.',\n url: server.url,\n}))\n\nfor (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, async () => {\n await server.close()\n process.exit(0)\n })\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/bin/tangle-catalog-runtime.ts"],"sourcesContent":["#!/usr/bin/env node\nimport {\n buildTangleCatalogRuntimePackageManifest,\n renderTangleCatalogRuntimePnpmAddCommand,\n} from '../tangle-catalog.js'\nimport { auditTangleCatalogRuntimePackages } from '../tangle-catalog-runtime.js'\nimport { startTangleCatalogRuntimeNodeServer } from '../tangle-catalog-runtime-server.js'\n\nconst args = new Set(process.argv.slice(2))\nif (args.has('--print-package-json')) {\n console.log(JSON.stringify(buildTangleCatalogRuntimePackageManifest({\n agentIntegrationsVersion: process.env.TANGLE_AGENT_INTEGRATIONS_VERSION,\n }), null, 2))\n process.exit(0)\n}\n\nif (args.has('--print-pnpm-add')) {\n console.log(renderTangleCatalogRuntimePnpmAddCommand({\n agentIntegrationsVersion: process.env.TANGLE_AGENT_INTEGRATIONS_VERSION,\n }))\n process.exit(0)\n}\n\nif (args.has('--audit-packages')) {\n const connectorIds = process.env.TANGLE_CATALOG_AUDIT_CONNECTORS\n ?.split(',')\n .map((id) => id.trim())\n .filter(Boolean)\n console.log(JSON.stringify(await auditTangleCatalogRuntimePackages({ connectorIds }), null, 2))\n process.exit(0)\n}\n\nconst secret = process.env.TANGLE_CATALOG_RUNTIME_SECRET\nif (!secret || secret.length < 32) {\n console.error('TANGLE_CATALOG_RUNTIME_SECRET must be set to at least 32 characters.')\n process.exit(1)\n}\n\nconst authResolverUrl = process.env.TANGLE_CATALOG_AUTH_RESOLVER_URL\nconst authResolverSecret = process.env.TANGLE_CATALOG_AUTH_RESOLVER_SECRET\nif (Boolean(authResolverUrl) !== Boolean(authResolverSecret)) {\n console.error('TANGLE_CATALOG_AUTH_RESOLVER_URL and TANGLE_CATALOG_AUTH_RESOLVER_SECRET must be set together.')\n process.exit(1)\n}\n\nconst port = Number(process.env.PORT ?? process.env.TANGLE_CATALOG_RUNTIME_PORT ?? 4109)\nif (!Number.isInteger(port) || port < 1 || port > 65_535) {\n console.error('PORT must be an integer between 1 and 65535.')\n process.exit(1)\n}\n\nconst server = await startTangleCatalogRuntimeNodeServer({\n secret,\n host: process.env.HOST ?? process.env.TANGLE_CATALOG_RUNTIME_HOST ?? '0.0.0.0',\n port,\n authResolver: authResolverUrl && authResolverSecret\n ? {\n endpoint: authResolverUrl,\n secret: authResolverSecret,\n }\n : false,\n onLog: (event) => {\n const line = JSON.stringify({\n level: event.level,\n message: event.message,\n ...event.metadata,\n })\n if (event.level === 'error') console.error(line)\n else console.log(line)\n },\n})\n\nconsole.log(JSON.stringify({\n level: 'info',\n message: 'Tangle catalog runtime listening.',\n url: server.url,\n}))\n\nfor (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, async () => {\n await server.close()\n process.exit(0)\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAQA,IAAM,OAAO,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC1C,IAAI,KAAK,IAAI,sBAAsB,GAAG;AACpC,UAAQ,IAAI,KAAK,UAAU,yCAAyC;AAAA,IAClE,0BAA0B,QAAQ,IAAI;AAAA,EACxC,CAAC,GAAG,MAAM,CAAC,CAAC;AACZ,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAI,KAAK,IAAI,kBAAkB,GAAG;AAChC,UAAQ,IAAI,yCAAyC;AAAA,IACnD,0BAA0B,QAAQ,IAAI;AAAA,EACxC,CAAC,CAAC;AACF,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAI,KAAK,IAAI,kBAAkB,GAAG;AAChC,QAAM,eAAe,QAAQ,IAAI,iCAC7B,MAAM,GAAG,EACV,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,EACrB,OAAO,OAAO;AACjB,UAAQ,IAAI,KAAK,UAAU,MAAM,kCAAkC,EAAE,aAAa,CAAC,GAAG,MAAM,CAAC,CAAC;AAC9F,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,SAAS,QAAQ,IAAI;AAC3B,IAAI,CAAC,UAAU,OAAO,SAAS,IAAI;AACjC,UAAQ,MAAM,sEAAsE;AACpF,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,kBAAkB,QAAQ,IAAI;AACpC,IAAM,qBAAqB,QAAQ,IAAI;AACvC,IAAI,QAAQ,eAAe,MAAM,QAAQ,kBAAkB,GAAG;AAC5D,UAAQ,MAAM,gGAAgG;AAC9G,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,QAAQ,IAAI,+BAA+B,IAAI;AACvF,IAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,OAAQ;AACxD,UAAQ,MAAM,8CAA8C;AAC5D,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,SAAS,MAAM,oCAAoC;AAAA,EACvD;AAAA,EACA,MAAM,QAAQ,IAAI,QAAQ,QAAQ,IAAI,+BAA+B;AAAA,EACrE;AAAA,EACA,cAAc,mBAAmB,qBAC7B;AAAA,IACE,UAAU;AAAA,IACV,QAAQ;AAAA,EACV,IACA;AAAA,EACJ,OAAO,CAAC,UAAU;AAChB,UAAM,OAAO,KAAK,UAAU;AAAA,MAC1B,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,GAAG,MAAM;AAAA,IACX,CAAC;AACD,QAAI,MAAM,UAAU,QAAS,SAAQ,MAAM,IAAI;AAAA,QAC1C,SAAQ,IAAI,IAAI;AAAA,EACvB;AACF,CAAC;AAED,QAAQ,IAAI,KAAK,UAAU;AAAA,EACzB,OAAO;AAAA,EACP,SAAS;AAAA,EACT,KAAK,OAAO;AACd,CAAC,CAAC;AAEF,WAAW,UAAU,CAAC,UAAU,SAAS,GAAY;AACnD,UAAQ,KAAK,QAAQ,YAAY;AAC/B,UAAM,OAAO,MAAM;AACnB,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":[]}
|
package/dist/catalog.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { I as IntegrationCatalogView, a as IntegrationToolDefinition, b as IntegrationToolSearchFilters, c as IntegrationToolSearchResult, M as McpToolDefinition, d as buildIntegrationCatalogView, e as buildIntegrationToolCatalog, i as integrationToolName, p as parseIntegrationToolName, s as searchIntegrationTools, t as toMcpTools } from './registry.js';
|
|
2
|
-
import './
|
|
2
|
+
import './tangle-id-CTU4kGId.js';
|
|
3
|
+
import './errors-Bg3_rxnQ.js';
|
|
4
|
+
import './connect/index.js';
|
|
5
|
+
import './middleware/index.js';
|
|
3
6
|
import './connectors/index.js';
|
|
7
|
+
import './connectors/adapters/index.js';
|
|
4
8
|
import 'node:http';
|
package/dist/catalog.js
CHANGED
|
@@ -5,11 +5,15 @@ import {
|
|
|
5
5
|
parseIntegrationToolName,
|
|
6
6
|
searchIntegrationTools,
|
|
7
7
|
toMcpTools
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-UWRYFPJW.js";
|
|
9
|
+
import "./chunk-SVQ4PHDZ.js";
|
|
10
|
+
import "./chunk-H4XYLS7T.js";
|
|
9
11
|
import "./chunk-4JQ754PA.js";
|
|
10
12
|
import "./chunk-376UBTNB.js";
|
|
11
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-JU25UDN2.js";
|
|
12
14
|
import "./chunk-2TW2QKGZ.js";
|
|
15
|
+
import "./chunk-P24T3MLM.js";
|
|
16
|
+
import "./chunk-ATYHZXLL.js";
|
|
13
17
|
export {
|
|
14
18
|
buildIntegrationCatalogView,
|
|
15
19
|
buildIntegrationToolCatalog,
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
// src/connectors/types.ts
|
|
2
|
+
var ResourceContention = class extends Error {
|
|
3
|
+
constructor(message, alternatives = [], currentState) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.alternatives = alternatives;
|
|
6
|
+
this.currentState = currentState;
|
|
7
|
+
}
|
|
8
|
+
alternatives;
|
|
9
|
+
currentState;
|
|
10
|
+
name = "ResourceContention";
|
|
11
|
+
};
|
|
12
|
+
var CredentialsExpired = class extends Error {
|
|
13
|
+
constructor(message, dataSourceId) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.dataSourceId = dataSourceId;
|
|
16
|
+
}
|
|
17
|
+
dataSourceId;
|
|
18
|
+
name = "CredentialsExpired";
|
|
19
|
+
};
|
|
20
|
+
function validateConnectorManifest(manifest) {
|
|
21
|
+
const issues = [];
|
|
22
|
+
if (!manifest.kind.trim()) issues.push({ path: "kind", message: "kind is required" });
|
|
23
|
+
if (!manifest.displayName.trim()) issues.push({ path: "displayName", message: "displayName is required" });
|
|
24
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25
|
+
for (const [index, capability] of manifest.capabilities.entries()) {
|
|
26
|
+
const path = `capabilities[${index}]`;
|
|
27
|
+
if (!capability.name.trim()) issues.push({ path: `${path}.name`, message: "capability name is required" });
|
|
28
|
+
if (seen.has(capability.name)) issues.push({ path: `${path}.name`, message: `duplicate capability name: ${capability.name}` });
|
|
29
|
+
seen.add(capability.name);
|
|
30
|
+
if (capability.class === "mutation") {
|
|
31
|
+
if (!capability.cas) issues.push({ path: `${path}.cas`, message: "mutation capability must declare a CAS strategy" });
|
|
32
|
+
if (manifest.defaultConsistencyModel === "authoritative" && capability.cas === "none") {
|
|
33
|
+
issues.push({ path: `${path}.cas`, message: 'authoritative mutations cannot use cas="none"' });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (manifest.rateLimit) {
|
|
38
|
+
if (!Number.isFinite(manifest.rateLimit.requests) || manifest.rateLimit.requests <= 0) {
|
|
39
|
+
issues.push({ path: "rateLimit.requests", message: "rateLimit.requests must be positive" });
|
|
40
|
+
}
|
|
41
|
+
if (!Number.isFinite(manifest.rateLimit.windowMs) || manifest.rateLimit.windowMs <= 0) {
|
|
42
|
+
issues.push({ path: "rateLimit.windowMs", message: "rateLimit.windowMs must be positive" });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { ok: issues.length === 0, issues };
|
|
46
|
+
}
|
|
47
|
+
function assertValidConnectorManifest(manifest) {
|
|
48
|
+
const result = validateConnectorManifest(manifest);
|
|
49
|
+
if (!result.ok) {
|
|
50
|
+
throw new Error(`Invalid connector manifest ${manifest.kind || "<unknown>"}: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/connectors/adapters/tangle-id.ts
|
|
55
|
+
var DEFAULT_TANGLE_PLATFORM_URL = "https://id.tangle.tools";
|
|
56
|
+
var PLATFORM_FETCH_TIMEOUT_MS = 5e3;
|
|
57
|
+
var TANGLE_API_KEY_PREFIX = "sk-tan-";
|
|
58
|
+
var TANGLE_SERVICE_TOKEN_PREFIX = "svc_";
|
|
59
|
+
var TangleIdentityUnreachableError = class extends Error {
|
|
60
|
+
name = "TangleIdentityUnreachableError";
|
|
61
|
+
status;
|
|
62
|
+
constructor(message, opts) {
|
|
63
|
+
super(message, opts);
|
|
64
|
+
this.status = opts?.status;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function tangleIdentity(opts = {}) {
|
|
68
|
+
const client = createTangleIdentityClient(opts);
|
|
69
|
+
const adapter = {
|
|
70
|
+
manifest: {
|
|
71
|
+
kind: "tangle-id",
|
|
72
|
+
displayName: "Tangle Identity",
|
|
73
|
+
description: "Verify Tangle session cookies and API keys issued by id.tangle.tools, resolve user + workspace identity, and gate capability discovery against the calling workspace scopes.",
|
|
74
|
+
auth: { kind: "api-key", hint: "Tangle platform service token (svc_*)." },
|
|
75
|
+
category: "other",
|
|
76
|
+
defaultConsistencyModel: "authoritative",
|
|
77
|
+
// 50 req/s per service token matches the platform's documented
|
|
78
|
+
// S2S budget. We meter on our side so a chatty product doesn't
|
|
79
|
+
// burn the shared service-token quota and block the rest.
|
|
80
|
+
rateLimit: { requests: 50, windowMs: 1e3, scope: "oauth-client" },
|
|
81
|
+
capabilities: [
|
|
82
|
+
{
|
|
83
|
+
name: "verify_token",
|
|
84
|
+
class: "read",
|
|
85
|
+
description: "Verify a session cookie or sk-tan-* API key. Returns { valid, userId, workspaceId, scopes, expiresAt } or { valid: false, reason }.",
|
|
86
|
+
parameters: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
token: { type: "string", minLength: 1 }
|
|
90
|
+
},
|
|
91
|
+
required: ["token"]
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "get_user",
|
|
96
|
+
class: "read",
|
|
97
|
+
description: "Read the user profile (id, email, name, image) for a verified userId.",
|
|
98
|
+
parameters: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: { userId: { type: "string", minLength: 1 } },
|
|
101
|
+
required: ["userId"]
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "list_workspaces",
|
|
106
|
+
class: "read",
|
|
107
|
+
description: "List workspaces (teams + the personal workspace) the user belongs to, with role + effective scopes.",
|
|
108
|
+
parameters: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: { userId: { type: "string", minLength: 1 } },
|
|
111
|
+
required: ["userId"]
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "switch_workspace",
|
|
116
|
+
class: "mutation",
|
|
117
|
+
description: "Resolve a workspace by id and return its effective scope set. Stateless on this adapter \u2014 caller persists the workspaceId in its own session store.",
|
|
118
|
+
parameters: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
userId: { type: "string", minLength: 1 },
|
|
122
|
+
workspaceId: { type: "string", minLength: 1 }
|
|
123
|
+
},
|
|
124
|
+
required: ["userId", "workspaceId"]
|
|
125
|
+
},
|
|
126
|
+
cas: "native-idempotency",
|
|
127
|
+
externalEffect: false
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "revoke_session",
|
|
131
|
+
class: "mutation",
|
|
132
|
+
description: "Revoke a session token or API key. Idempotent.",
|
|
133
|
+
parameters: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: { token: { type: "string", minLength: 1 } },
|
|
136
|
+
required: ["token"]
|
|
137
|
+
},
|
|
138
|
+
cas: "native-idempotency",
|
|
139
|
+
externalEffect: true
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
async executeRead(inv) {
|
|
144
|
+
if (inv.capabilityName === "verify_token") {
|
|
145
|
+
const token = readStringArg(inv.args, "token");
|
|
146
|
+
const result = await client.verifyToken(token);
|
|
147
|
+
return { data: result, fetchedAt: Date.now() };
|
|
148
|
+
}
|
|
149
|
+
if (inv.capabilityName === "get_user") {
|
|
150
|
+
const userId = readStringArg(inv.args, "userId");
|
|
151
|
+
const user = await client.getUser(userId);
|
|
152
|
+
return { data: user, fetchedAt: Date.now() };
|
|
153
|
+
}
|
|
154
|
+
if (inv.capabilityName === "list_workspaces") {
|
|
155
|
+
const userId = readStringArg(inv.args, "userId");
|
|
156
|
+
const workspaces = await client.listWorkspaces(userId);
|
|
157
|
+
return { data: { workspaces }, fetchedAt: Date.now() };
|
|
158
|
+
}
|
|
159
|
+
throw new Error(`tangle-id: unknown read capability ${inv.capabilityName}`);
|
|
160
|
+
},
|
|
161
|
+
async executeMutation(inv) {
|
|
162
|
+
if (inv.capabilityName === "switch_workspace") {
|
|
163
|
+
const userId = readStringArg(inv.args, "userId");
|
|
164
|
+
const workspaceId = readStringArg(inv.args, "workspaceId");
|
|
165
|
+
const result = await client.switchWorkspace(userId, workspaceId);
|
|
166
|
+
return {
|
|
167
|
+
status: "committed",
|
|
168
|
+
data: result,
|
|
169
|
+
committedAt: Date.now(),
|
|
170
|
+
idempotentReplay: false
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (inv.capabilityName === "revoke_session") {
|
|
174
|
+
const token = readStringArg(inv.args, "token");
|
|
175
|
+
await client.revokeSession(token);
|
|
176
|
+
return {
|
|
177
|
+
status: "committed",
|
|
178
|
+
data: { ok: true },
|
|
179
|
+
committedAt: Date.now(),
|
|
180
|
+
idempotentReplay: false
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
throw new Error(`tangle-id: unknown mutation capability ${inv.capabilityName}`);
|
|
184
|
+
},
|
|
185
|
+
async test(source) {
|
|
186
|
+
try {
|
|
187
|
+
const ok = await client.ping();
|
|
188
|
+
if (!ok) return { ok: false, reason: "id.tangle.tools /health returned non-200" };
|
|
189
|
+
return { ok: true };
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (err instanceof CredentialsExpired) {
|
|
192
|
+
return { ok: false, reason: "service token rejected \u2014 rotate TANGLE_SERVICE_TOKEN" };
|
|
193
|
+
}
|
|
194
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
return adapter;
|
|
199
|
+
}
|
|
200
|
+
function createTangleIdentityClient(opts = {}) {
|
|
201
|
+
const baseUrl = (opts.baseUrl ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\/+$/, "");
|
|
202
|
+
const serviceToken = opts.serviceToken;
|
|
203
|
+
const serviceName = opts.serviceName ?? "integrations";
|
|
204
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
205
|
+
const timeoutMs = opts.timeoutMs ?? PLATFORM_FETCH_TIMEOUT_MS;
|
|
206
|
+
function s2sHeaders() {
|
|
207
|
+
if (!serviceToken) {
|
|
208
|
+
throw new TangleIdentityUnreachableError(
|
|
209
|
+
"tangle-id: serviceToken is required for service-to-service calls (verify, get_user, list_workspaces, revoke)"
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
"content-type": "application/json",
|
|
214
|
+
authorization: `Bearer ${serviceToken}`,
|
|
215
|
+
"x-service-name": serviceName
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
async function jsonFetch(path, init) {
|
|
219
|
+
let res;
|
|
220
|
+
try {
|
|
221
|
+
res = await fetchImpl(`${baseUrl}${path}`, {
|
|
222
|
+
...init,
|
|
223
|
+
signal: init.signal ?? AbortSignal.timeout(timeoutMs)
|
|
224
|
+
});
|
|
225
|
+
} catch (err) {
|
|
226
|
+
throw new TangleIdentityUnreachableError(
|
|
227
|
+
`tangle-id: request to ${path} failed`,
|
|
228
|
+
{ cause: err }
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return res;
|
|
232
|
+
}
|
|
233
|
+
async function readErrorDetail(res) {
|
|
234
|
+
try {
|
|
235
|
+
const text = await res.text();
|
|
236
|
+
return text.trim().slice(0, 200);
|
|
237
|
+
} catch {
|
|
238
|
+
return "";
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function verifyApiKey(token) {
|
|
242
|
+
const res = await jsonFetch("/v1/keys/verify", {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: s2sHeaders(),
|
|
245
|
+
body: JSON.stringify({ key: token })
|
|
246
|
+
});
|
|
247
|
+
if (res.status === 401) {
|
|
248
|
+
return { valid: false, reason: "service_token_refused" };
|
|
249
|
+
}
|
|
250
|
+
if (!res.ok) {
|
|
251
|
+
throw new TangleIdentityUnreachableError(
|
|
252
|
+
`tangle-id: /v1/keys/verify returned ${res.status}: ${await readErrorDetail(res)}`,
|
|
253
|
+
{ status: res.status }
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const body = await res.json().catch(() => null);
|
|
257
|
+
if (!body || typeof body.valid !== "boolean") {
|
|
258
|
+
return { valid: false, reason: "malformed" };
|
|
259
|
+
}
|
|
260
|
+
if (!body.valid || !body.userId) {
|
|
261
|
+
return { valid: false, reason: "revoked" };
|
|
262
|
+
}
|
|
263
|
+
const scopes = Array.isArray(body.allowedModels) ? body.allowedModels.filter((value) => typeof value === "string") : [];
|
|
264
|
+
if (body.product) scopes.push(`product:${body.product}`);
|
|
265
|
+
const expiresAt = typeof body.expiresAt === "string" && body.expiresAt ? Date.parse(body.expiresAt) : void 0;
|
|
266
|
+
return {
|
|
267
|
+
valid: true,
|
|
268
|
+
kind: "api_key",
|
|
269
|
+
userId: body.userId,
|
|
270
|
+
workspaceId: body.ownerType === "team" && body.ownerId ? body.ownerId : body.userId,
|
|
271
|
+
ownerType: body.ownerType ?? "user",
|
|
272
|
+
scopes,
|
|
273
|
+
...Number.isFinite(expiresAt) ? { expiresAt } : {},
|
|
274
|
+
...body.keyId ? { credentialId: body.keyId } : {},
|
|
275
|
+
...body.product ? { product: body.product } : {}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
async function verifySession(token) {
|
|
279
|
+
const res = await jsonFetch("/api/auth/get-session", {
|
|
280
|
+
method: "GET",
|
|
281
|
+
headers: {
|
|
282
|
+
accept: "application/json",
|
|
283
|
+
authorization: `Bearer ${token}`
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
if (res.status === 401 || res.status === 403) {
|
|
287
|
+
return { valid: false, reason: "expired" };
|
|
288
|
+
}
|
|
289
|
+
if (!res.ok) {
|
|
290
|
+
throw new TangleIdentityUnreachableError(
|
|
291
|
+
`tangle-id: /api/auth/get-session returned ${res.status}: ${await readErrorDetail(res)}`,
|
|
292
|
+
{ status: res.status }
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
const body = await res.json().catch(() => null);
|
|
296
|
+
if (!body || !body.user || typeof body.user.id !== "string") {
|
|
297
|
+
return { valid: false, reason: "expired" };
|
|
298
|
+
}
|
|
299
|
+
const expiresAtRaw = body.session?.expiresAt;
|
|
300
|
+
const expiresAt = expiresAtRaw ? Date.parse(expiresAtRaw) : NaN;
|
|
301
|
+
return {
|
|
302
|
+
valid: true,
|
|
303
|
+
kind: "session",
|
|
304
|
+
userId: body.user.id,
|
|
305
|
+
workspaceId: body.session?.activeTeamId || body.user.id,
|
|
306
|
+
ownerType: body.session?.activeTeamId ? "team" : "user",
|
|
307
|
+
scopes: [],
|
|
308
|
+
...Number.isFinite(expiresAt) ? { expiresAt } : {},
|
|
309
|
+
...body.session?.id ? { credentialId: body.session.id } : {}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
async verifyToken(token) {
|
|
314
|
+
if (!token || typeof token !== "string") {
|
|
315
|
+
return { valid: false, reason: "malformed" };
|
|
316
|
+
}
|
|
317
|
+
if (token.startsWith(TANGLE_SERVICE_TOKEN_PREFIX)) {
|
|
318
|
+
return { valid: false, reason: "service_token_refused" };
|
|
319
|
+
}
|
|
320
|
+
if (token.startsWith(TANGLE_API_KEY_PREFIX)) {
|
|
321
|
+
return verifyApiKey(token);
|
|
322
|
+
}
|
|
323
|
+
return verifySession(token);
|
|
324
|
+
},
|
|
325
|
+
async getUser(userId) {
|
|
326
|
+
if (!userId) {
|
|
327
|
+
throw new TangleIdentityUnreachableError("tangle-id: getUser requires a non-empty userId");
|
|
328
|
+
}
|
|
329
|
+
const res = await jsonFetch(`/v1/users/${encodeURIComponent(userId)}`, {
|
|
330
|
+
method: "GET",
|
|
331
|
+
headers: s2sHeaders()
|
|
332
|
+
});
|
|
333
|
+
if (res.status === 404) {
|
|
334
|
+
throw new TangleIdentityUnreachableError(`tangle-id: user ${userId} not found`, { status: 404 });
|
|
335
|
+
}
|
|
336
|
+
if (!res.ok) {
|
|
337
|
+
throw new TangleIdentityUnreachableError(
|
|
338
|
+
`tangle-id: /v1/users/${userId} returned ${res.status}: ${await readErrorDetail(res)}`,
|
|
339
|
+
{ status: res.status }
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
const body = await res.json().catch(() => null);
|
|
343
|
+
const data = body?.data;
|
|
344
|
+
if (!data || typeof data.id !== "string") {
|
|
345
|
+
throw new TangleIdentityUnreachableError("tangle-id: /v1/users response had an invalid shape");
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
id: data.id,
|
|
349
|
+
...typeof data.email === "string" ? { email: data.email } : {},
|
|
350
|
+
...data.name !== void 0 ? { name: data.name } : {},
|
|
351
|
+
...data.image !== void 0 ? { image: data.image } : {}
|
|
352
|
+
};
|
|
353
|
+
},
|
|
354
|
+
async listWorkspaces(userId) {
|
|
355
|
+
const res = await jsonFetch("/v1/teams", {
|
|
356
|
+
method: "GET",
|
|
357
|
+
headers: { ...s2sHeaders(), "x-platform-user-id": userId }
|
|
358
|
+
});
|
|
359
|
+
if (!res.ok) {
|
|
360
|
+
throw new TangleIdentityUnreachableError(
|
|
361
|
+
`tangle-id: /v1/teams returned ${res.status}: ${await readErrorDetail(res)}`,
|
|
362
|
+
{ status: res.status }
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const body = await res.json().catch(() => null);
|
|
366
|
+
const rows = Array.isArray(body?.data) ? body.data : [];
|
|
367
|
+
const workspaces = [];
|
|
368
|
+
for (const row of rows) {
|
|
369
|
+
if (!row || typeof row.id !== "string" || typeof row.name !== "string") continue;
|
|
370
|
+
const role = row.role === "owner" || row.role === "admin" ? row.role : "member";
|
|
371
|
+
const scopes = Array.isArray(row.scopes) ? row.scopes.filter((value) => typeof value === "string") : [];
|
|
372
|
+
workspaces.push({
|
|
373
|
+
id: row.id,
|
|
374
|
+
name: row.name,
|
|
375
|
+
role,
|
|
376
|
+
isPersonal: Boolean(row.isPersonal) || row.id === userId,
|
|
377
|
+
scopes
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return workspaces;
|
|
381
|
+
},
|
|
382
|
+
async switchWorkspace(userId, workspaceId) {
|
|
383
|
+
const workspaces = await this.listWorkspaces(userId);
|
|
384
|
+
const match = workspaces.find((w) => w.id === workspaceId);
|
|
385
|
+
if (!match) {
|
|
386
|
+
throw new TangleIdentityUnreachableError(
|
|
387
|
+
`tangle-id: workspace ${workspaceId} not found for user ${userId}`,
|
|
388
|
+
{ status: 404 }
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return { ok: true, workspaceId: match.id, scopes: match.scopes };
|
|
392
|
+
},
|
|
393
|
+
async revokeSession(token) {
|
|
394
|
+
if (!token) return;
|
|
395
|
+
if (token.startsWith(TANGLE_SERVICE_TOKEN_PREFIX)) {
|
|
396
|
+
throw new TangleIdentityUnreachableError(
|
|
397
|
+
"tangle-id: refusing to revoke a service token \u2014 rotate it instead"
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
if (token.startsWith(TANGLE_API_KEY_PREFIX)) {
|
|
401
|
+
const v = await verifyApiKey(token);
|
|
402
|
+
if (!v.valid || !v.credentialId) return;
|
|
403
|
+
const res2 = await jsonFetch(`/v1/keys/${encodeURIComponent(v.credentialId)}`, {
|
|
404
|
+
method: "DELETE",
|
|
405
|
+
headers: s2sHeaders()
|
|
406
|
+
});
|
|
407
|
+
if (res2.status === 404) return;
|
|
408
|
+
if (!res2.ok) {
|
|
409
|
+
throw new TangleIdentityUnreachableError(
|
|
410
|
+
`tangle-id: DELETE /v1/keys/${v.credentialId} returned ${res2.status}: ${await readErrorDetail(res2)}`,
|
|
411
|
+
{ status: res2.status }
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const res = await jsonFetch("/api/auth/sign-out", {
|
|
417
|
+
method: "POST",
|
|
418
|
+
headers: {
|
|
419
|
+
"content-type": "application/json",
|
|
420
|
+
authorization: `Bearer ${token}`
|
|
421
|
+
},
|
|
422
|
+
body: "{}"
|
|
423
|
+
});
|
|
424
|
+
if (res.status >= 500) {
|
|
425
|
+
throw new TangleIdentityUnreachableError(
|
|
426
|
+
`tangle-id: /api/auth/sign-out returned ${res.status}: ${await readErrorDetail(res)}`,
|
|
427
|
+
{ status: res.status }
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
async ping() {
|
|
432
|
+
const res = await jsonFetch("/health", { method: "GET" });
|
|
433
|
+
return res.ok;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function readStringArg(args, key) {
|
|
438
|
+
const value = args[key];
|
|
439
|
+
if (typeof value !== "string" || !value) {
|
|
440
|
+
throw new Error(`tangle-id: missing required argument "${key}"`);
|
|
441
|
+
}
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export {
|
|
446
|
+
ResourceContention,
|
|
447
|
+
CredentialsExpired,
|
|
448
|
+
validateConnectorManifest,
|
|
449
|
+
assertValidConnectorManifest,
|
|
450
|
+
DEFAULT_TANGLE_PLATFORM_URL,
|
|
451
|
+
TANGLE_API_KEY_PREFIX,
|
|
452
|
+
TANGLE_SERVICE_TOKEN_PREFIX,
|
|
453
|
+
TangleIdentityUnreachableError,
|
|
454
|
+
tangleIdentity,
|
|
455
|
+
createTangleIdentityClient
|
|
456
|
+
};
|
|
457
|
+
//# sourceMappingURL=chunk-ATYHZXLL.js.map
|