@tangle-network/agent-integrations 0.28.0 → 0.30.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.
Files changed (46) hide show
  1. package/README.md +40 -0
  2. package/dist/bin/tangle-catalog-runtime.js +7 -6
  3. package/dist/bin/tangle-catalog-runtime.js.map +1 -1
  4. package/dist/catalog.d.ts +2 -2
  5. package/dist/catalog.js +11 -6
  6. package/dist/{chunk-SVQ4PHDZ.js → chunk-5ASL5XNX.js} +2 -2
  7. package/dist/{chunk-P24T3MLM.js → chunk-DACSERTI.js} +2 -2
  8. package/dist/chunk-FDZIQVK7.js +284 -0
  9. package/dist/chunk-FDZIQVK7.js.map +1 -0
  10. package/dist/{chunk-JU25UDN2.js → chunk-JOILC44P.js} +11 -5
  11. package/dist/chunk-JOILC44P.js.map +1 -0
  12. package/dist/{chunk-UWRYFPJW.js → chunk-M2RFFAMB.js} +559 -411
  13. package/dist/chunk-M2RFFAMB.js.map +1 -0
  14. package/dist/{chunk-4JQ754PA.js → chunk-VVC7U7W7.js} +28 -1
  15. package/dist/{chunk-4JQ754PA.js.map → chunk-VVC7U7W7.js.map} +1 -1
  16. package/dist/{chunk-ATYHZXLL.js → chunk-Y6O3MIBW.js} +1 -1
  17. package/dist/chunk-Y6O3MIBW.js.map +1 -0
  18. package/dist/connect/index.d.ts +1 -1
  19. package/dist/connect/index.js +2 -2
  20. package/dist/connectors/adapters/index.d.ts +2 -2
  21. package/dist/connectors/adapters/index.js +2 -2
  22. package/dist/connectors/index.d.ts +1 -1
  23. package/dist/connectors/index.js +2 -2
  24. package/dist/consumer.d.ts +8 -0
  25. package/dist/consumer.js +12 -0
  26. package/dist/consumer.js.map +1 -0
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +29 -11
  29. package/dist/middleware/index.d.ts +1 -1
  30. package/dist/middleware/index.js +2 -2
  31. package/dist/registry.d.ts +439 -92
  32. package/dist/registry.js +9 -6
  33. package/dist/runtime.d.ts +2 -2
  34. package/dist/runtime.js +7 -6
  35. package/dist/specs.d.ts +2 -2
  36. package/dist/specs.js +3 -1
  37. package/dist/tangle-catalog-runtime.d.ts +2 -2
  38. package/dist/tangle-catalog-runtime.js +7 -6
  39. package/dist/{tangle-id-CTU4kGId.d.ts → tangle-id-C6s2NT2r.d.ts} +7 -0
  40. package/docs/integration-execution-audit.md +1 -1
  41. package/package.json +23 -10
  42. package/dist/chunk-ATYHZXLL.js.map +0 -1
  43. package/dist/chunk-JU25UDN2.js.map +0 -1
  44. package/dist/chunk-UWRYFPJW.js.map +0 -1
  45. /package/dist/{chunk-SVQ4PHDZ.js.map → chunk-5ASL5XNX.js.map} +0 -0
  46. /package/dist/{chunk-P24T3MLM.js.map → chunk-DACSERTI.js.map} +0 -0
package/README.md CHANGED
@@ -288,6 +288,46 @@ For a full product checklist, see
288
288
  [External Product Integration](./docs/external-product-integration.md) and
289
289
  [Platform Control Plane Adoption](./docs/platform-control-plane.md).
290
290
 
291
+ ## Consuming a Hosted Hub
292
+
293
+ `createIntegrationRuntime` is for a product that *hosts* a hub — it owns the
294
+ stores, the vault, and OAuth. A product that instead *consumes* a hosted hub
295
+ (`id.tangle.tools`) from its backend talks to it over HTTP with
296
+ `createIntegrationHubClient`:
297
+
298
+ ```ts
299
+ import { createIntegrationHubClient } from '@tangle-network/agent-integrations/consumer'
300
+
301
+ const hub = createIntegrationHubClient({
302
+ product: 'blueprint-agent',
303
+ auth: {
304
+ mode: 'service',
305
+ serviceToken: process.env.SERVICE_TOKEN!,
306
+ serviceName: 'blueprint-agent',
307
+ },
308
+ })
309
+
310
+ // Is the user's GitHub connected? (resolve-manifest under the hood — the
311
+ // raw connection list is intentionally not service-token-reachable.)
312
+ const github = await hub.checkConnector({ userId, connectorId: 'github' })
313
+
314
+ // Mint a scoped, short-lived capability bundle for a sandbox process.
315
+ const { env } = await hub.mintCapabilityBundle({
316
+ userId,
317
+ subject: { type: 'sandbox', id: sandboxId },
318
+ manifestId: manifest.id,
319
+ })
320
+ ```
321
+
322
+ The client speaks the platform's `/v1/integrations/*` surface and supports
323
+ both auth modes the route layer accepts — a `svc_*` service token
324
+ (`mode: 'service'`, acting user in `X-Platform-User-Id`) or a per-user
325
+ `sk-tan-*` key (`mode: 'user-key'`). It exposes the four service-token-reachable
326
+ operations — `resolveManifest`, `createGrants` / `listGrants`,
327
+ `mintCapabilityBundle`, `runHealthchecks` — plus the `checkConnector`
328
+ convenience. Provider credentials never cross this client; bundles carry only
329
+ short-lived capability tokens.
330
+
291
331
  ## Product Hub Ownership
292
332
 
293
333
  Use a hosted hub when multiple apps intentionally share identity, billing,
@@ -4,15 +4,16 @@ import {
4
4
  buildTangleCatalogRuntimePackageManifest,
5
5
  renderTangleCatalogRuntimePnpmAddCommand,
6
6
  startTangleCatalogRuntimeNodeServer
7
- } from "../chunk-UWRYFPJW.js";
8
- import "../chunk-SVQ4PHDZ.js";
7
+ } from "../chunk-M2RFFAMB.js";
8
+ import "../chunk-DACSERTI.js";
9
+ import "../chunk-5ASL5XNX.js";
9
10
  import "../chunk-H4XYLS7T.js";
10
- import "../chunk-4JQ754PA.js";
11
+ import "../chunk-FDZIQVK7.js";
12
+ import "../chunk-VVC7U7W7.js";
11
13
  import "../chunk-376UBTNB.js";
12
- import "../chunk-JU25UDN2.js";
14
+ import "../chunk-JOILC44P.js";
13
15
  import "../chunk-2TW2QKGZ.js";
14
- import "../chunk-P24T3MLM.js";
15
- import "../chunk-ATYHZXLL.js";
16
+ import "../chunk-Y6O3MIBW.js";
16
17
 
17
18
  // src/bin/tangle-catalog-runtime.ts
18
19
  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":";;;;;;;;;;;;;;;;;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":[]}
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,5 +1,5 @@
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 './tangle-id-CTU4kGId.js';
1
+ export { I as IntegrationCatalogView, a as IntegrationToolDefinition, b as IntegrationToolSearchFilters, c as IntegrationToolSearchResult, M as McpToolDefinition, d as buildIntegrationCatalogView, e as buildIntegrationToolCatalog, f as describeIntegrationTool, g as flattenIntegrationToolDefinition, i as integrationToolName, p as parseIntegrationToolName, s as searchIntegrationTools, t as toMcpTools } from './registry.js';
2
+ import './tangle-id-C6s2NT2r.js';
3
3
  import './errors-Bg3_rxnQ.js';
4
4
  import './connect/index.js';
5
5
  import './middleware/index.js';
package/dist/catalog.js CHANGED
@@ -1,22 +1,27 @@
1
1
  import {
2
2
  buildIntegrationCatalogView,
3
3
  buildIntegrationToolCatalog,
4
+ describeIntegrationTool,
5
+ flattenIntegrationToolDefinition,
4
6
  integrationToolName,
5
7
  parseIntegrationToolName,
6
8
  searchIntegrationTools,
7
9
  toMcpTools
8
- } from "./chunk-UWRYFPJW.js";
9
- import "./chunk-SVQ4PHDZ.js";
10
+ } from "./chunk-M2RFFAMB.js";
11
+ import "./chunk-DACSERTI.js";
12
+ import "./chunk-5ASL5XNX.js";
10
13
  import "./chunk-H4XYLS7T.js";
11
- import "./chunk-4JQ754PA.js";
14
+ import "./chunk-FDZIQVK7.js";
15
+ import "./chunk-VVC7U7W7.js";
12
16
  import "./chunk-376UBTNB.js";
13
- import "./chunk-JU25UDN2.js";
17
+ import "./chunk-JOILC44P.js";
14
18
  import "./chunk-2TW2QKGZ.js";
15
- import "./chunk-P24T3MLM.js";
16
- import "./chunk-ATYHZXLL.js";
19
+ import "./chunk-Y6O3MIBW.js";
17
20
  export {
18
21
  buildIntegrationCatalogView,
19
22
  buildIntegrationToolCatalog,
23
+ describeIntegrationTool,
24
+ flattenIntegrationToolDefinition,
20
25
  integrationToolName,
21
26
  parseIntegrationToolName,
22
27
  searchIntegrationTools,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TangleIdentityUnreachableError,
3
3
  createTangleIdentityClient
4
- } from "./chunk-ATYHZXLL.js";
4
+ } from "./chunk-Y6O3MIBW.js";
5
5
 
6
6
  // src/middleware/index.ts
7
7
  async function requireTangleAuth(request, opts = {}) {
@@ -126,4 +126,4 @@ export {
126
126
  honoTangleAuthMiddleware,
127
127
  expressTangleAuthMiddleware
128
128
  };
129
- //# sourceMappingURL=chunk-SVQ4PHDZ.js.map
129
+ //# sourceMappingURL=chunk-5ASL5XNX.js.map
@@ -2,7 +2,7 @@ import {
2
2
  DEFAULT_TANGLE_PLATFORM_URL,
3
3
  TangleIdentityUnreachableError,
4
4
  createTangleIdentityClient
5
- } from "./chunk-ATYHZXLL.js";
5
+ } from "./chunk-Y6O3MIBW.js";
6
6
 
7
7
  // src/connect/index.ts
8
8
  function startConnectFlow(opts, input) {
@@ -103,4 +103,4 @@ export {
103
103
  revokeConnectFlow,
104
104
  InMemoryConnectStateStore
105
105
  };
106
- //# sourceMappingURL=chunk-P24T3MLM.js.map
106
+ //# sourceMappingURL=chunk-DACSERTI.js.map
@@ -0,0 +1,284 @@
1
+ import {
2
+ DEFAULT_TANGLE_PLATFORM_URL
3
+ } from "./chunk-Y6O3MIBW.js";
4
+
5
+ // src/consumer.ts
6
+ var PLATFORM_USER_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
7
+ var DEFAULT_TIMEOUT_MS = 1e4;
8
+ var DEFAULT_MAX_ATTEMPTS = 2;
9
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
10
+ var IntegrationHubRequestError = class extends Error {
11
+ name = "IntegrationHubRequestError";
12
+ /** HTTP status, or 0 for a network-level failure. */
13
+ status;
14
+ /** Platform error code (`VALIDATION_ERROR`, `scope_missing`, …) or
15
+ * `network_error` / `http_error` when no structured code was returned. */
16
+ code;
17
+ /** `METHOD /path` the request targeted. */
18
+ endpoint;
19
+ /** True when the failure class is transient and a retry could succeed. */
20
+ retryable;
21
+ constructor(input) {
22
+ super(input.message);
23
+ this.status = input.status;
24
+ this.code = input.code;
25
+ this.endpoint = input.endpoint;
26
+ this.retryable = input.retryable;
27
+ }
28
+ };
29
+ var IntegrationHubClient = class {
30
+ endpoint;
31
+ product;
32
+ auth;
33
+ fetchImpl;
34
+ timeoutMs;
35
+ maxAttempts;
36
+ constructor(options) {
37
+ if (!options.product) {
38
+ throw new Error("IntegrationHubClient: product is required");
39
+ }
40
+ if (options.auth.mode === "service" && !options.auth.serviceToken) {
41
+ throw new Error("IntegrationHubClient: service auth requires a serviceToken");
42
+ }
43
+ if (options.auth.mode === "service" && !options.auth.serviceName) {
44
+ throw new Error("IntegrationHubClient: service auth requires a serviceName");
45
+ }
46
+ if (options.auth.mode === "user-key" && !options.auth.apiKey) {
47
+ throw new Error("IntegrationHubClient: user-key auth requires an apiKey");
48
+ }
49
+ this.endpoint = (options.endpoint ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\/+$/, "");
50
+ this.product = options.product;
51
+ this.auth = options.auth;
52
+ this.fetchImpl = options.fetchImpl ?? fetch;
53
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
54
+ this.maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
55
+ }
56
+ /**
57
+ * Resolve a manifest against a user's connections. The returned
58
+ * `ready` / `missing` split is the canonical way to ask "does this user
59
+ * have the connections this work needs" — the raw connection list is not
60
+ * reachable by a service token by design.
61
+ */
62
+ async resolveManifest(input) {
63
+ return this.request("POST", "/resolve-manifest", input.userId, {
64
+ product: input.product ?? this.product,
65
+ manifest: input.manifest,
66
+ ownerUserId: input.userId
67
+ });
68
+ }
69
+ /**
70
+ * Convenience over {@link resolveManifest} — probe a single connector and
71
+ * get back a boolean plus the satisfying connection. The Surface-A quest
72
+ * primitive ("is the user's GitHub linked?").
73
+ */
74
+ async checkConnector(input) {
75
+ const requirementId = input.connectorId;
76
+ const resolution = await this.resolveManifest({
77
+ userId: input.userId,
78
+ manifest: {
79
+ id: `connectivity-check:${input.connectorId}`,
80
+ requirements: [
81
+ {
82
+ id: requirementId,
83
+ connectorId: input.connectorId,
84
+ reason: `Connectivity check for ${input.connectorId}`,
85
+ mode: input.mode ?? "read",
86
+ ...input.requiredScopes ? { requiredScopes: input.requiredScopes } : {},
87
+ ...input.requiredActions ? { requiredActions: input.requiredActions } : {}
88
+ }
89
+ ]
90
+ }
91
+ });
92
+ const requirement = resolution.ready.find((r) => r.requirement.id === requirementId) ?? resolution.missing.find((r) => r.requirement.id === requirementId) ?? resolution.optionalMissing.find((r) => r.requirement.id === requirementId);
93
+ if (!requirement) {
94
+ throw new IntegrationHubRequestError({
95
+ status: 0,
96
+ code: "malformed_response",
97
+ message: `resolve-manifest returned no resolution for requirement ${requirementId}`,
98
+ endpoint: "POST /resolve-manifest",
99
+ retryable: false
100
+ });
101
+ }
102
+ return {
103
+ connected: requirement.status === "ready",
104
+ ...requirement.connection ? { connection: requirement.connection } : {},
105
+ resolution: requirement
106
+ };
107
+ }
108
+ /** Create grants for every satisfiable requirement of a manifest. The
109
+ * platform rejects the call if any non-optional requirement is missing a
110
+ * connection. */
111
+ async createGrants(input) {
112
+ const data = await this.request(
113
+ "POST",
114
+ "/grants",
115
+ input.userId,
116
+ {
117
+ grantee: input.grantee,
118
+ manifest: input.manifest,
119
+ ownerUserId: input.userId,
120
+ ...input.metadata ? { metadata: input.metadata } : {}
121
+ }
122
+ );
123
+ return data.grants;
124
+ }
125
+ /** List the acting user's grants, optionally filtered to one grantee. */
126
+ async listGrants(input) {
127
+ const query = input.grantee !== void 0 ? `?granteeType=${encodeURIComponent(input.grantee.type)}&granteeId=${encodeURIComponent(input.grantee.id)}` : "";
128
+ const data = await this.request(
129
+ "GET",
130
+ `/grants${query}`,
131
+ input.userId
132
+ );
133
+ return data.grants;
134
+ }
135
+ /** Mint a short-lived capability bundle for a sandbox / agent process.
136
+ * Provider credentials never leave the platform — the bundle carries only
137
+ * scoped, expiring capability tokens. */
138
+ async mintCapabilityBundle(input) {
139
+ if (!input.manifestId && !(input.grantIds && input.grantIds.length > 0)) {
140
+ throw new Error(
141
+ "IntegrationHubClient.mintCapabilityBundle: manifestId or a non-empty grantIds is required"
142
+ );
143
+ }
144
+ return this.request("POST", "/capabilities/bundle", input.userId, {
145
+ subject: input.subject,
146
+ ...input.manifestId ? { manifestId: input.manifestId } : {},
147
+ ...input.grantIds ? { grantIds: input.grantIds } : {},
148
+ ...input.grantee ? { grantee: input.grantee } : {},
149
+ ...input.ttlMs !== void 0 ? { ttlMs: input.ttlMs } : {}
150
+ });
151
+ }
152
+ /** Run live healthchecks across all of the acting user's connections. */
153
+ async runHealthchecks(input) {
154
+ const data = await this.request(
155
+ "POST",
156
+ "/healthchecks/run",
157
+ input.userId,
158
+ { ownerUserId: input.userId }
159
+ );
160
+ return data.healthchecks;
161
+ }
162
+ buildHeaders(userId, hasBody) {
163
+ const headers = new Headers({ accept: "application/json" });
164
+ if (hasBody) headers.set("content-type", "application/json");
165
+ if (this.auth.mode === "service") {
166
+ headers.set("authorization", `Bearer ${this.auth.serviceToken}`);
167
+ headers.set("x-service-name", this.auth.serviceName);
168
+ headers.set("x-platform-user-id", userId);
169
+ } else {
170
+ headers.set("authorization", `Bearer ${this.auth.apiKey}`);
171
+ }
172
+ return headers;
173
+ }
174
+ async request(method, path, userId, body) {
175
+ if (!PLATFORM_USER_ID_PATTERN.test(userId)) {
176
+ throw new IntegrationHubRequestError({
177
+ status: 0,
178
+ code: "invalid_user_id",
179
+ message: `userId ${JSON.stringify(userId)} is not a valid platform user id`,
180
+ endpoint: `${method} ${path}`,
181
+ retryable: false
182
+ });
183
+ }
184
+ const url = `${this.endpoint}/v1/integrations${path}`;
185
+ const endpointLabel = `${method} /v1/integrations${path}`;
186
+ const headers = this.buildHeaders(userId, body !== void 0);
187
+ const init = {
188
+ method,
189
+ headers,
190
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
191
+ };
192
+ let lastError;
193
+ for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
194
+ let response;
195
+ try {
196
+ response = await this.fetchImpl(url, {
197
+ ...init,
198
+ signal: AbortSignal.timeout(this.timeoutMs)
199
+ });
200
+ } catch (error) {
201
+ lastError = new IntegrationHubRequestError({
202
+ status: 0,
203
+ code: "network_error",
204
+ message: `${endpointLabel} failed: ${error instanceof Error ? error.message : String(error)}`,
205
+ endpoint: endpointLabel,
206
+ retryable: true
207
+ });
208
+ if (attempt < this.maxAttempts) {
209
+ await delay(attempt);
210
+ continue;
211
+ }
212
+ throw lastError;
213
+ }
214
+ const payload = await readJson(response);
215
+ if (response.ok && isSuccessEnvelope(payload)) {
216
+ return payload.data;
217
+ }
218
+ const retryable = RETRYABLE_STATUSES.has(response.status);
219
+ lastError = new IntegrationHubRequestError({
220
+ status: response.status,
221
+ code: errorCode(payload, response.status),
222
+ message: errorMessage(payload, endpointLabel, response.status),
223
+ endpoint: endpointLabel,
224
+ retryable
225
+ });
226
+ if (retryable && attempt < this.maxAttempts) {
227
+ await delay(attempt);
228
+ continue;
229
+ }
230
+ throw lastError;
231
+ }
232
+ throw lastError ?? new IntegrationHubRequestError({
233
+ status: 0,
234
+ code: "unknown",
235
+ message: `${endpointLabel} exhausted retries without a result`,
236
+ endpoint: endpointLabel,
237
+ retryable: false
238
+ });
239
+ }
240
+ };
241
+ function createIntegrationHubClient(options) {
242
+ return new IntegrationHubClient(options);
243
+ }
244
+ function isSuccessEnvelope(payload) {
245
+ return typeof payload === "object" && payload !== null && payload.success === true && "data" in payload;
246
+ }
247
+ async function readJson(response) {
248
+ const text = await response.text().catch(() => "");
249
+ if (!text) return void 0;
250
+ try {
251
+ return JSON.parse(text);
252
+ } catch {
253
+ return { __text: text };
254
+ }
255
+ }
256
+ function errorCode(payload, status) {
257
+ if (typeof payload === "object" && payload !== null) {
258
+ const error = payload.error;
259
+ if (error && typeof error.code === "string") return error.code;
260
+ }
261
+ return status === 0 ? "network_error" : "http_error";
262
+ }
263
+ function errorMessage(payload, endpointLabel, status) {
264
+ if (typeof payload === "object" && payload !== null) {
265
+ const record = payload;
266
+ if (record.error && typeof record.error.message === "string") {
267
+ return `${endpointLabel} \u2192 ${status}: ${record.error.message}`;
268
+ }
269
+ if (typeof record.__text === "string" && record.__text.length > 0) {
270
+ return `${endpointLabel} \u2192 ${status}: ${record.__text.slice(0, 300)}`;
271
+ }
272
+ }
273
+ return `${endpointLabel} \u2192 ${status}`;
274
+ }
275
+ function delay(attempt) {
276
+ return new Promise((resolve) => setTimeout(resolve, attempt * 200));
277
+ }
278
+
279
+ export {
280
+ IntegrationHubRequestError,
281
+ IntegrationHubClient,
282
+ createIntegrationHubClient
283
+ };
284
+ //# sourceMappingURL=chunk-FDZIQVK7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/consumer.ts"],"sourcesContent":["/**\n * @stable Integration Hub consumer client.\n *\n * The third client-shaped surface a product needs, alongside the two that\n * already ship:\n *\n * - `createTangleIntegrationsClient` (`client.ts`) — the *invoke* client.\n * Capability-token auth, runs INSIDE a sandbox / generated app, single\n * endpoint `/v1/integrations/invoke`.\n * - `startConnectFlow` / `finishConnectFlow` (`connect/index.ts`) — the\n * *user-consent* flow, mirrors `/cross-site/*`.\n * - **this** — the S2S *management* client. A product BACKEND (blueprint-\n * agent, sandbox, gtm-agent, tax-agent, legal-agent, evals, …) drives the\n * `/v1/integrations/{resolve-manifest,grants,capabilities/bundle,\n * healthchecks/run}` management surface on `id.tangle.tools` on behalf of\n * an identified user.\n *\n * Every consumer needs the identical client — the wire protocol, the\n * `{ success, data }` envelope, the auth header shape are all platform-owned.\n * Re-implementing a bespoke fetch loop per product forks the protocol and the\n * copies drift. This module is that shared implementation. It mirrors the\n * `connect/index.ts` design rule one-for-one: DO NOT invent the wire protocol\n * — speak exactly what `products/platform/api/src/routes/integrations.ts`\n * serves.\n *\n * Two auth modes — the route layer (`authMiddleware`) accepts either:\n *\n * - `service` — a `svc_*` service token + `X-Service-Name`. The acting\n * user travels in `X-Platform-User-Id`. The platform honors that header\n * only for service tokens whose `SERVICE_SCOPES` set contains\n * `impersonate:user`; a token without it is rejected (403). Reaches the\n * four management paths the platform allowlists for service tokens.\n * - `user-key` — a per-user `sk-tan-*` API key (minted via the connect\n * flow). The key identifies the user; no impersonation header. Reaches\n * every route the user themselves can.\n *\n * The capability-token `invoke` endpoint is intentionally NOT exposed here —\n * that is `createTangleIntegrationsClient`'s job and uses a different auth.\n */\n\nimport type { IntegrationActor, IntegrationConnection } from './index.js'\nimport type {\n IntegrationGrant,\n IntegrationManifest,\n IntegrationManifestResolution,\n IntegrationRequirementMode,\n IntegrationRequirementResolution,\n IntegrationSandboxBundle,\n} from './runtime.js'\nimport type { IntegrationHealthcheckResult } from './healthcheck.js'\nimport { DEFAULT_TANGLE_PLATFORM_URL } from './connectors/adapters/tangle-id.js'\n\n/** Matches the platform's `PLATFORM_USER_ID_PATTERN` (`auth.ts`). A user id\n * that fails this is rejected client-side before the request leaves. */\nconst PLATFORM_USER_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/\n\nconst DEFAULT_TIMEOUT_MS = 10_000\nconst DEFAULT_MAX_ATTEMPTS = 2\n/** HTTP statuses worth a retry — transient platform/edge failures only.\n * 4xx is deterministic and never retried. */\nconst RETRYABLE_STATUSES = new Set([502, 503, 504])\n\nexport type IntegrationHubAuth =\n | {\n mode: 'service'\n /** The `svc_*` token issued to this product. */\n serviceToken: string\n /** Registered service name — sent as `X-Service-Name`. Required\n * because one token may be shared across services, in which case the\n * platform demands the header to disambiguate. */\n serviceName: string\n }\n | {\n mode: 'user-key'\n /** A per-user `sk-tan-*` key bound to the acting user. */\n apiKey: string\n }\n\nexport interface IntegrationHubClientOptions {\n /** The product / consumer identifier (e.g. `blueprint-agent`). Sent as the\n * `product` field of resolve-manifest calls; recorded platform-side. */\n product: string\n /** Service-token or per-user-key auth. */\n auth: IntegrationHubAuth\n /** Platform base URL. Defaults to `https://id.tangle.tools`. */\n endpoint?: string\n /** Injected for tests. Defaults to the global `fetch`. */\n fetchImpl?: typeof fetch\n /** Per-request timeout in ms. Default 10_000. */\n timeoutMs?: number\n /** Max attempts on transient (network / 502 / 503 / 504) failures.\n * Default 2 — i.e. one retry. */\n maxAttempts?: number\n}\n\n/** Thrown for every non-2xx response and every transport failure. Carries the\n * HTTP status and the platform error code so callers can branch precisely\n * (`403` + `impersonate` → the service token lacks the scope; `409` /\n * `missing_connection` → prompt the user to connect). */\nexport class IntegrationHubRequestError extends Error {\n readonly name = 'IntegrationHubRequestError'\n /** HTTP status, or 0 for a network-level failure. */\n readonly status: number\n /** Platform error code (`VALIDATION_ERROR`, `scope_missing`, …) or\n * `network_error` / `http_error` when no structured code was returned. */\n readonly code: string\n /** `METHOD /path` the request targeted. */\n readonly endpoint: string\n /** True when the failure class is transient and a retry could succeed. */\n readonly retryable: boolean\n\n constructor(input: {\n status: number\n code: string\n message: string\n endpoint: string\n retryable: boolean\n }) {\n super(input.message)\n this.status = input.status\n this.code = input.code\n this.endpoint = input.endpoint\n this.retryable = input.retryable\n }\n}\n\nexport interface ResolveManifestInput {\n /** The acting user — the connection owner. */\n userId: string\n manifest: IntegrationManifest\n /** Overrides the client-level `product` for this call. */\n product?: string\n}\n\nexport interface CreateGrantsInput {\n /** The acting user — the connection owner. */\n userId: string\n /** Who the grant is FOR (the sandbox / agent / app that will invoke). */\n grantee: IntegrationActor\n manifest: IntegrationManifest\n metadata?: Record<string, unknown>\n}\n\nexport interface ListGrantsInput {\n /** The acting user — the connection owner. */\n userId: string\n /** Optional grantee filter; both fields travel together as query params. */\n grantee?: IntegrationActor\n}\n\nexport interface MintCapabilityBundleInput {\n /** The acting user — must own every connection behind the grants. */\n userId: string\n /** Who the capability bundle is issued TO (the sandbox / agent process). */\n subject: IntegrationActor\n /** Mint from every grant of a manifest … */\n manifestId?: string\n /** … or from an explicit grant id list. Exactly one of the two is required. */\n grantIds?: string[]\n grantee?: IntegrationActor\n /** Bundle TTL in ms. Platform clamps to [1s, 60m]; default 15m. */\n ttlMs?: number\n}\n\nexport interface CapabilityBundleResult {\n bundle: IntegrationSandboxBundle\n /** Bridge environment variables to inject into the sandbox process —\n * `buildIntegrationBridgeEnvironment(bundle)`, computed platform-side. */\n env: Record<string, string>\n}\n\nexport interface CheckConnectorInput {\n /** The acting user. */\n userId: string\n /** Connector to probe — `github`, `google-calendar`, `tangle-id`, … */\n connectorId: string\n /** Defaults to `read`. */\n mode?: IntegrationRequirementMode\n requiredScopes?: string[]\n requiredActions?: string[]\n}\n\nexport interface CheckConnectorResult {\n /** True when the user has an active connection satisfying the requirement. */\n connected: boolean\n /** The satisfying connection, present iff `connected`. */\n connection?: IntegrationConnection\n /** The full requirement resolution — status, missing scopes/actions, message. */\n resolution: IntegrationRequirementResolution\n}\n\n/**\n * S2S management client for the `id.tangle.tools` integration hub. One per\n * product; methods are stateless and safe to call concurrently.\n */\nexport class IntegrationHubClient {\n private readonly endpoint: string\n private readonly product: string\n private readonly auth: IntegrationHubAuth\n private readonly fetchImpl: typeof fetch\n private readonly timeoutMs: number\n private readonly maxAttempts: number\n\n constructor(options: IntegrationHubClientOptions) {\n if (!options.product) {\n throw new Error('IntegrationHubClient: product is required')\n }\n if (options.auth.mode === 'service' && !options.auth.serviceToken) {\n throw new Error('IntegrationHubClient: service auth requires a serviceToken')\n }\n if (options.auth.mode === 'service' && !options.auth.serviceName) {\n throw new Error('IntegrationHubClient: service auth requires a serviceName')\n }\n if (options.auth.mode === 'user-key' && !options.auth.apiKey) {\n throw new Error('IntegrationHubClient: user-key auth requires an apiKey')\n }\n this.endpoint = (options.endpoint ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\\/+$/, '')\n this.product = options.product\n this.auth = options.auth\n this.fetchImpl = options.fetchImpl ?? fetch\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)\n }\n\n /**\n * Resolve a manifest against a user's connections. The returned\n * `ready` / `missing` split is the canonical way to ask \"does this user\n * have the connections this work needs\" — the raw connection list is not\n * reachable by a service token by design.\n */\n async resolveManifest(input: ResolveManifestInput): Promise<IntegrationManifestResolution> {\n return this.request<IntegrationManifestResolution>('POST', '/resolve-manifest', input.userId, {\n product: input.product ?? this.product,\n manifest: input.manifest,\n ownerUserId: input.userId,\n })\n }\n\n /**\n * Convenience over {@link resolveManifest} — probe a single connector and\n * get back a boolean plus the satisfying connection. The Surface-A quest\n * primitive (\"is the user's GitHub linked?\").\n */\n async checkConnector(input: CheckConnectorInput): Promise<CheckConnectorResult> {\n const requirementId = input.connectorId\n const resolution = await this.resolveManifest({\n userId: input.userId,\n manifest: {\n id: `connectivity-check:${input.connectorId}`,\n requirements: [\n {\n id: requirementId,\n connectorId: input.connectorId,\n reason: `Connectivity check for ${input.connectorId}`,\n mode: input.mode ?? 'read',\n ...(input.requiredScopes ? { requiredScopes: input.requiredScopes } : {}),\n ...(input.requiredActions ? { requiredActions: input.requiredActions } : {}),\n },\n ],\n },\n })\n const requirement =\n resolution.ready.find((r) => r.requirement.id === requirementId) ??\n resolution.missing.find((r) => r.requirement.id === requirementId) ??\n resolution.optionalMissing.find((r) => r.requirement.id === requirementId)\n if (!requirement) {\n throw new IntegrationHubRequestError({\n status: 0,\n code: 'malformed_response',\n message: `resolve-manifest returned no resolution for requirement ${requirementId}`,\n endpoint: 'POST /resolve-manifest',\n retryable: false,\n })\n }\n return {\n connected: requirement.status === 'ready',\n ...(requirement.connection ? { connection: requirement.connection } : {}),\n resolution: requirement,\n }\n }\n\n /** Create grants for every satisfiable requirement of a manifest. The\n * platform rejects the call if any non-optional requirement is missing a\n * connection. */\n async createGrants(input: CreateGrantsInput): Promise<IntegrationGrant[]> {\n const data = await this.request<{ grants: IntegrationGrant[] }>(\n 'POST',\n '/grants',\n input.userId,\n {\n grantee: input.grantee,\n manifest: input.manifest,\n ownerUserId: input.userId,\n ...(input.metadata ? { metadata: input.metadata } : {}),\n },\n )\n return data.grants\n }\n\n /** List the acting user's grants, optionally filtered to one grantee. */\n async listGrants(input: ListGrantsInput): Promise<IntegrationGrant[]> {\n const query =\n input.grantee !== undefined\n ? `?granteeType=${encodeURIComponent(input.grantee.type)}&granteeId=${encodeURIComponent(input.grantee.id)}`\n : ''\n const data = await this.request<{ grants: IntegrationGrant[] }>(\n 'GET',\n `/grants${query}`,\n input.userId,\n )\n return data.grants\n }\n\n /** Mint a short-lived capability bundle for a sandbox / agent process.\n * Provider credentials never leave the platform — the bundle carries only\n * scoped, expiring capability tokens. */\n async mintCapabilityBundle(input: MintCapabilityBundleInput): Promise<CapabilityBundleResult> {\n if (!input.manifestId && !(input.grantIds && input.grantIds.length > 0)) {\n throw new Error(\n 'IntegrationHubClient.mintCapabilityBundle: manifestId or a non-empty grantIds is required',\n )\n }\n return this.request<CapabilityBundleResult>('POST', '/capabilities/bundle', input.userId, {\n subject: input.subject,\n ...(input.manifestId ? { manifestId: input.manifestId } : {}),\n ...(input.grantIds ? { grantIds: input.grantIds } : {}),\n ...(input.grantee ? { grantee: input.grantee } : {}),\n ...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),\n })\n }\n\n /** Run live healthchecks across all of the acting user's connections. */\n async runHealthchecks(input: { userId: string }): Promise<IntegrationHealthcheckResult[]> {\n const data = await this.request<{ healthchecks: IntegrationHealthcheckResult[] }>(\n 'POST',\n '/healthchecks/run',\n input.userId,\n { ownerUserId: input.userId },\n )\n return data.healthchecks\n }\n\n private buildHeaders(userId: string, hasBody: boolean): Headers {\n const headers = new Headers({ accept: 'application/json' })\n if (hasBody) headers.set('content-type', 'application/json')\n if (this.auth.mode === 'service') {\n headers.set('authorization', `Bearer ${this.auth.serviceToken}`)\n headers.set('x-service-name', this.auth.serviceName)\n headers.set('x-platform-user-id', userId)\n } else {\n headers.set('authorization', `Bearer ${this.auth.apiKey}`)\n }\n return headers\n }\n\n private async request<T>(\n method: 'GET' | 'POST',\n path: string,\n userId: string,\n body?: Record<string, unknown>,\n ): Promise<T> {\n if (!PLATFORM_USER_ID_PATTERN.test(userId)) {\n throw new IntegrationHubRequestError({\n status: 0,\n code: 'invalid_user_id',\n message: `userId ${JSON.stringify(userId)} is not a valid platform user id`,\n endpoint: `${method} ${path}`,\n retryable: false,\n })\n }\n const url = `${this.endpoint}/v1/integrations${path}`\n const endpointLabel = `${method} /v1/integrations${path}`\n const headers = this.buildHeaders(userId, body !== undefined)\n const init: RequestInit = {\n method,\n headers,\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n }\n\n let lastError: IntegrationHubRequestError | undefined\n for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n let response: Response\n try {\n response = await this.fetchImpl(url, {\n ...init,\n signal: AbortSignal.timeout(this.timeoutMs),\n })\n } catch (error) {\n lastError = new IntegrationHubRequestError({\n status: 0,\n code: 'network_error',\n message: `${endpointLabel} failed: ${error instanceof Error ? error.message : String(error)}`,\n endpoint: endpointLabel,\n retryable: true,\n })\n if (attempt < this.maxAttempts) {\n await delay(attempt)\n continue\n }\n throw lastError\n }\n\n const payload = await readJson(response)\n if (response.ok && isSuccessEnvelope(payload)) {\n return payload.data as T\n }\n\n const retryable = RETRYABLE_STATUSES.has(response.status)\n lastError = new IntegrationHubRequestError({\n status: response.status,\n code: errorCode(payload, response.status),\n message: errorMessage(payload, endpointLabel, response.status),\n endpoint: endpointLabel,\n retryable,\n })\n if (retryable && attempt < this.maxAttempts) {\n await delay(attempt)\n continue\n }\n throw lastError\n }\n // Unreachable — the loop always returns or throws — but satisfies the\n // type checker and fails loud if the invariant is ever broken.\n throw (\n lastError ??\n new IntegrationHubRequestError({\n status: 0,\n code: 'unknown',\n message: `${endpointLabel} exhausted retries without a result`,\n endpoint: endpointLabel,\n retryable: false,\n })\n )\n }\n}\n\nexport function createIntegrationHubClient(\n options: IntegrationHubClientOptions,\n): IntegrationHubClient {\n return new IntegrationHubClient(options)\n}\n\ninterface SuccessEnvelope {\n success: true\n data: unknown\n}\n\nfunction isSuccessEnvelope(payload: unknown): payload is SuccessEnvelope {\n return (\n typeof payload === 'object' &&\n payload !== null &&\n (payload as { success?: unknown }).success === true &&\n 'data' in payload\n )\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => '')\n if (!text) return undefined\n try {\n return JSON.parse(text)\n } catch {\n // Hono `HTTPException` (auth-middleware rejections) renders the message\n // as a plain-text body, not the `{ success, error }` envelope. Preserve\n // it so the error message stays actionable.\n return { __text: text }\n }\n}\n\nfunction errorCode(payload: unknown, status: number): string {\n if (typeof payload === 'object' && payload !== null) {\n const error = (payload as { error?: { code?: unknown } }).error\n if (error && typeof error.code === 'string') return error.code\n }\n return status === 0 ? 'network_error' : 'http_error'\n}\n\nfunction errorMessage(payload: unknown, endpointLabel: string, status: number): string {\n if (typeof payload === 'object' && payload !== null) {\n const record = payload as { error?: { message?: unknown }; __text?: unknown }\n if (record.error && typeof record.error.message === 'string') {\n return `${endpointLabel} → ${status}: ${record.error.message}`\n }\n if (typeof record.__text === 'string' && record.__text.length > 0) {\n return `${endpointLabel} → ${status}: ${record.__text.slice(0, 300)}`\n }\n }\n return `${endpointLabel} → ${status}`\n}\n\n/** Linear backoff — 200ms, 400ms, … — capped implicitly by `maxAttempts`. */\nfunction delay(attempt: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, attempt * 200))\n}\n"],"mappings":";;;;;AAsDA,IAAM,2BAA2B;AAEjC,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAG7B,IAAM,qBAAqB,oBAAI,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC;AAuC3C,IAAM,6BAAN,cAAyC,MAAM;AAAA,EAC3C,OAAO;AAAA;AAAA,EAEP;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,OAMT;AACD,UAAM,MAAM,OAAO;AACnB,SAAK,SAAS,MAAM;AACpB,SAAK,OAAO,MAAM;AAClB,SAAK,WAAW,MAAM;AACtB,SAAK,YAAY,MAAM;AAAA,EACzB;AACF;AAuEO,IAAM,uBAAN,MAA2B;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAAsC;AAChD,QAAI,CAAC,QAAQ,SAAS;AACpB,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC7D;AACA,QAAI,QAAQ,KAAK,SAAS,aAAa,CAAC,QAAQ,KAAK,cAAc;AACjE,YAAM,IAAI,MAAM,4DAA4D;AAAA,IAC9E;AACA,QAAI,QAAQ,KAAK,SAAS,aAAa,CAAC,QAAQ,KAAK,aAAa;AAChE,YAAM,IAAI,MAAM,2DAA2D;AAAA,IAC7E;AACA,QAAI,QAAQ,KAAK,SAAS,cAAc,CAAC,QAAQ,KAAK,QAAQ;AAC5D,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AACA,SAAK,YAAY,QAAQ,YAAY,6BAA6B,QAAQ,QAAQ,EAAE;AACpF,SAAK,UAAU,QAAQ;AACvB,SAAK,OAAO,QAAQ;AACpB,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,cAAc,KAAK,IAAI,GAAG,QAAQ,eAAe,oBAAoB;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,OAAqE;AACzF,WAAO,KAAK,QAAuC,QAAQ,qBAAqB,MAAM,QAAQ;AAAA,MAC5F,SAAS,MAAM,WAAW,KAAK;AAAA,MAC/B,UAAU,MAAM;AAAA,MAChB,aAAa,MAAM;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAe,OAA2D;AAC9E,UAAM,gBAAgB,MAAM;AAC5B,UAAM,aAAa,MAAM,KAAK,gBAAgB;AAAA,MAC5C,QAAQ,MAAM;AAAA,MACd,UAAU;AAAA,QACR,IAAI,sBAAsB,MAAM,WAAW;AAAA,QAC3C,cAAc;AAAA,UACZ;AAAA,YACE,IAAI;AAAA,YACJ,aAAa,MAAM;AAAA,YACnB,QAAQ,0BAA0B,MAAM,WAAW;AAAA,YACnD,MAAM,MAAM,QAAQ;AAAA,YACpB,GAAI,MAAM,iBAAiB,EAAE,gBAAgB,MAAM,eAAe,IAAI,CAAC;AAAA,YACvE,GAAI,MAAM,kBAAkB,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,UAC5E;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AACD,UAAM,cACJ,WAAW,MAAM,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,aAAa,KAC/D,WAAW,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,aAAa,KACjE,WAAW,gBAAgB,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,aAAa;AAC3E,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI,2BAA2B;AAAA,QACnC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS,2DAA2D,aAAa;AAAA,QACjF,UAAU;AAAA,QACV,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AACA,WAAO;AAAA,MACL,WAAW,YAAY,WAAW;AAAA,MAClC,GAAI,YAAY,aAAa,EAAE,YAAY,YAAY,WAAW,IAAI,CAAC;AAAA,MACvE,YAAY;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,OAAuD;AACxE,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,QACE,SAAS,MAAM;AAAA,QACf,UAAU,MAAM;AAAA,QAChB,aAAa,MAAM;AAAA,QACnB,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,MACvD;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,WAAW,OAAqD;AACpE,UAAM,QACJ,MAAM,YAAY,SACd,gBAAgB,mBAAmB,MAAM,QAAQ,IAAI,CAAC,cAAc,mBAAmB,MAAM,QAAQ,EAAE,CAAC,KACxG;AACN,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA,UAAU,KAAK;AAAA,MACf,MAAM;AAAA,IACR;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAqB,OAAmE;AAC5F,QAAI,CAAC,MAAM,cAAc,EAAE,MAAM,YAAY,MAAM,SAAS,SAAS,IAAI;AACvE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,QAAgC,QAAQ,wBAAwB,MAAM,QAAQ;AAAA,MACxF,SAAS,MAAM;AAAA,MACf,GAAI,MAAM,aAAa,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,MAC3D,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,MACrD,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,MAClD,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,IAC5D,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,gBAAgB,OAAoE;AACxF,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,EAAE,aAAa,MAAM,OAAO;AAAA,IAC9B;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,aAAa,QAAgB,SAA2B;AAC9D,UAAM,UAAU,IAAI,QAAQ,EAAE,QAAQ,mBAAmB,CAAC;AAC1D,QAAI,QAAS,SAAQ,IAAI,gBAAgB,kBAAkB;AAC3D,QAAI,KAAK,KAAK,SAAS,WAAW;AAChC,cAAQ,IAAI,iBAAiB,UAAU,KAAK,KAAK,YAAY,EAAE;AAC/D,cAAQ,IAAI,kBAAkB,KAAK,KAAK,WAAW;AACnD,cAAQ,IAAI,sBAAsB,MAAM;AAAA,IAC1C,OAAO;AACL,cAAQ,IAAI,iBAAiB,UAAU,KAAK,KAAK,MAAM,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,QACZ,QACA,MACA,QACA,MACY;AACZ,QAAI,CAAC,yBAAyB,KAAK,MAAM,GAAG;AAC1C,YAAM,IAAI,2BAA2B;AAAA,QACnC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS,UAAU,KAAK,UAAU,MAAM,CAAC;AAAA,QACzC,UAAU,GAAG,MAAM,IAAI,IAAI;AAAA,QAC3B,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AACA,UAAM,MAAM,GAAG,KAAK,QAAQ,mBAAmB,IAAI;AACnD,UAAM,gBAAgB,GAAG,MAAM,oBAAoB,IAAI;AACvD,UAAM,UAAU,KAAK,aAAa,QAAQ,SAAS,MAAS;AAC5D,UAAM,OAAoB;AAAA,MACxB;AAAA,MACA;AAAA,MACA,GAAI,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,IAC7D;AAEA,QAAI;AACJ,aAAS,UAAU,GAAG,WAAW,KAAK,aAAa,WAAW;AAC5D,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,KAAK,UAAU,KAAK;AAAA,UACnC,GAAG;AAAA,UACH,QAAQ,YAAY,QAAQ,KAAK,SAAS;AAAA,QAC5C,CAAC;AAAA,MACH,SAAS,OAAO;AACd,oBAAY,IAAI,2BAA2B;AAAA,UACzC,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS,GAAG,aAAa,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,UAC3F,UAAU;AAAA,UACV,WAAW;AAAA,QACb,CAAC;AACD,YAAI,UAAU,KAAK,aAAa;AAC9B,gBAAM,MAAM,OAAO;AACnB;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAEA,YAAM,UAAU,MAAM,SAAS,QAAQ;AACvC,UAAI,SAAS,MAAM,kBAAkB,OAAO,GAAG;AAC7C,eAAO,QAAQ;AAAA,MACjB;AAEA,YAAM,YAAY,mBAAmB,IAAI,SAAS,MAAM;AACxD,kBAAY,IAAI,2BAA2B;AAAA,QACzC,QAAQ,SAAS;AAAA,QACjB,MAAM,UAAU,SAAS,SAAS,MAAM;AAAA,QACxC,SAAS,aAAa,SAAS,eAAe,SAAS,MAAM;AAAA,QAC7D,UAAU;AAAA,QACV;AAAA,MACF,CAAC;AACD,UAAI,aAAa,UAAU,KAAK,aAAa;AAC3C,cAAM,MAAM,OAAO;AACnB;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAGA,UACE,aACA,IAAI,2BAA2B;AAAA,MAC7B,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS,GAAG,aAAa;AAAA,MACzB,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AAAA,EAEL;AACF;AAEO,SAAS,2BACd,SACsB;AACtB,SAAO,IAAI,qBAAqB,OAAO;AACzC;AAOA,SAAS,kBAAkB,SAA8C;AACvE,SACE,OAAO,YAAY,YACnB,YAAY,QACX,QAAkC,YAAY,QAC/C,UAAU;AAEd;AAEA,eAAe,SAAS,UAAsC;AAC5D,QAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AAIN,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AACF;AAEA,SAAS,UAAU,SAAkB,QAAwB;AAC3D,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,QAAS,QAA2C;AAC1D,QAAI,SAAS,OAAO,MAAM,SAAS,SAAU,QAAO,MAAM;AAAA,EAC5D;AACA,SAAO,WAAW,IAAI,kBAAkB;AAC1C;AAEA,SAAS,aAAa,SAAkB,eAAuB,QAAwB;AACrF,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,SAAS;AACf,QAAI,OAAO,SAAS,OAAO,OAAO,MAAM,YAAY,UAAU;AAC5D,aAAO,GAAG,aAAa,WAAM,MAAM,KAAK,OAAO,MAAM,OAAO;AAAA,IAC9D;AACA,QAAI,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,GAAG;AACjE,aAAO,GAAG,aAAa,WAAM,MAAM,KAAK,OAAO,OAAO,MAAM,GAAG,GAAG,CAAC;AAAA,IACrE;AAAA,EACF;AACA,SAAO,GAAG,aAAa,WAAM,MAAM;AACrC;AAGA,SAAS,MAAM,SAAgC;AAC7C,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,UAAU,GAAG,CAAC;AACpE;","names":[]}
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  CredentialsExpired,
8
8
  ResourceContention
9
- } from "./chunk-ATYHZXLL.js";
9
+ } from "./chunk-Y6O3MIBW.js";
10
10
 
11
11
  // src/connectors/oauth.ts
12
12
  import { createHash, randomBytes } from "crypto";
@@ -1147,13 +1147,13 @@ function gmail(opts) {
1147
1147
  ]
1148
1148
  },
1149
1149
  async executeRead(inv) {
1150
- const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1150
+ const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret, inv.onCredentialsRotated);
1151
1151
  if (inv.capabilityName === "list_messages") return listMessages(inv, accessToken, timeoutMs);
1152
1152
  if (inv.capabilityName === "read_message") return readMessage(inv, accessToken, timeoutMs);
1153
1153
  throw new Error(`gmail: unknown read capability ${inv.capabilityName}`);
1154
1154
  },
1155
1155
  async executeMutation(inv) {
1156
- const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1156
+ const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret, inv.onCredentialsRotated);
1157
1157
  if (inv.capabilityName === "send_reply") return sendReply(inv, accessToken, timeoutMs);
1158
1158
  if (inv.capabilityName === "watch_label") return watchLabel(inv, accessToken, timeoutMs);
1159
1159
  throw new Error(`gmail: unknown mutation capability ${inv.capabilityName}`);
@@ -1426,7 +1426,7 @@ async function watchLabel(inv, accessToken, timeoutMs) {
1426
1426
  idempotentReplay: false
1427
1427
  };
1428
1428
  }
1429
- async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
1429
+ async function ensureFreshAccessToken4(creds, clientId, clientSecret, onCredentialsRotated) {
1430
1430
  if (creds.kind !== "oauth2") {
1431
1431
  throw new Error("gmail: expected oauth2 credentials");
1432
1432
  }
@@ -1445,6 +1445,12 @@ async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
1445
1445
  creds.accessToken = refreshed.accessToken;
1446
1446
  creds.expiresAt = refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0;
1447
1447
  if (refreshed.refreshToken) creds.refreshToken = refreshed.refreshToken;
1448
+ onCredentialsRotated?.({
1449
+ kind: "oauth2",
1450
+ accessToken: creds.accessToken,
1451
+ refreshToken: creds.refreshToken,
1452
+ expiresAt: creds.expiresAt
1453
+ });
1448
1454
  return creds.accessToken;
1449
1455
  }
1450
1456
 
@@ -4257,4 +4263,4 @@ export {
4257
4263
  asanaConnector,
4258
4264
  salesforceConnector
4259
4265
  };
4260
- //# sourceMappingURL=chunk-JU25UDN2.js.map
4266
+ //# sourceMappingURL=chunk-JOILC44P.js.map