@tangle-network/agent-integrations 0.25.7 → 0.27.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 +11 -2
  2. package/dist/bin/tangle-catalog-runtime.js +6 -2
  3. package/dist/bin/tangle-catalog-runtime.js.map +1 -1
  4. package/dist/catalog.d.ts +4 -1
  5. package/dist/catalog.js +6 -2
  6. package/dist/chunk-2TW2QKGZ.js +94 -0
  7. package/dist/chunk-2TW2QKGZ.js.map +1 -0
  8. package/dist/chunk-ATYHZXLL.js +457 -0
  9. package/dist/chunk-ATYHZXLL.js.map +1 -0
  10. package/dist/{chunk-A5I3EYU5.js → chunk-ICSBYCE2.js} +122 -1
  11. package/dist/chunk-ICSBYCE2.js.map +1 -0
  12. package/dist/{chunk-WC63AI4Q.js → chunk-JU25UDN2.js} +1252 -225
  13. package/dist/chunk-JU25UDN2.js.map +1 -0
  14. package/dist/chunk-P24T3MLM.js +106 -0
  15. package/dist/chunk-P24T3MLM.js.map +1 -0
  16. package/dist/chunk-SVQ4PHDZ.js +129 -0
  17. package/dist/chunk-SVQ4PHDZ.js.map +1 -0
  18. package/dist/connect/index.d.ts +112 -0
  19. package/dist/connect/index.js +14 -0
  20. package/dist/connect/index.js.map +1 -0
  21. package/dist/connectors/adapters/index.d.ts +593 -1
  22. package/dist/connectors/adapters/index.js +22 -1
  23. package/dist/connectors/index.d.ts +2 -1
  24. package/dist/connectors/index.js +32 -10
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.js +57 -11
  27. package/dist/middleware/index.d.ts +137 -0
  28. package/dist/middleware/index.js +14 -0
  29. package/dist/middleware/index.js.map +1 -0
  30. package/dist/registry.d.ts +165 -2
  31. package/dist/registry.js +6 -2
  32. package/dist/runtime.d.ts +4 -1
  33. package/dist/runtime.js +6 -2
  34. package/dist/specs.d.ts +4 -1
  35. package/dist/tangle-catalog-runtime.d.ts +4 -1
  36. package/dist/tangle-catalog-runtime.js +6 -2
  37. package/dist/tangle-id-CTU4kGId.d.ts +553 -0
  38. package/dist/webhooks/index.d.ts +193 -0
  39. package/dist/webhooks/index.js +285 -0
  40. package/dist/webhooks/index.js.map +1 -0
  41. package/examples/discover-capabilities.ts +46 -0
  42. package/examples/webhook-router.ts +56 -0
  43. package/package.json +25 -12
  44. package/dist/chunk-A5I3EYU5.js.map +0 -1
  45. package/dist/chunk-WC63AI4Q.js.map +0 -1
  46. package/dist/index-BQY5ry2s.d.ts +0 -808
@@ -0,0 +1,553 @@
1
+ /**
2
+ * Connector primitives — the contract a concrete first-party integration
3
+ * (Google Calendar, HubSpot, Stripe, ...) implements. Lower level than the
4
+ * hub-side `IntegrationProvider` interface from `../index.ts`: a single
5
+ * `IntegrationProvider` typically wraps several connectors (e.g., a
6
+ * "first-party" provider that lists all your shipped connectors as a
7
+ * single catalog).
8
+ *
9
+ * Layering:
10
+ *
11
+ * IntegrationHub — vendor-neutral facade (../index.ts)
12
+ * ↓
13
+ * IntegrationProvider — one per gateway or first-party provider
14
+ * ↓
15
+ * ConnectorAdapter (this file) — one per integration (Google Calendar, ...)
16
+ * ↓
17
+ * upstream HTTP API — vendor SDK / fetch / OAuth
18
+ *
19
+ * Three load-bearing decisions encoded here:
20
+ *
21
+ * 1. Capabilities are typed (`read` vs `mutation`). Every mutation MUST
22
+ * declare a CAS strategy. Conflict resolution is the SDK's job, not the
23
+ * connector's. `validateConnectorManifest()` rejects unsafe manifests
24
+ * before a connector is registered.
25
+ *
26
+ * 2. ConsistencyModel pins what the rest of the system can assume:
27
+ * authoritative → the source IS the truth (Calendar, payments)
28
+ * cache → we mirror with TTL and may serve stale (price list)
29
+ * advisory → informational only (FAQ doc)
30
+ * Agent planners can (and should) refuse to promise outcomes based on
31
+ * `cache`/`advisory` data without a live `authoritative` confirmation.
32
+ *
33
+ * 3. Capabilities surface to the calling agent's tool registry by
34
+ * transformation, not by hand-wiring. Adding a connector automatically
35
+ * expands the agent's toolbelt for that specific user without touching
36
+ * the prompt or runner.
37
+ */
38
+ /** Minimal JSON-schema shape used for capability arg validation. We
39
+ * intentionally don't pull `@types/json-schema` — most consumers already
40
+ * declare parameters as `Record<string, unknown>` and the
41
+ * shape is whatever the LLM SDK's structured-output expects. Keep the
42
+ * contract loose at the boundary; tighten via runtime zod where needed. */
43
+ type CapabilityParameterSchema = Record<string, unknown>;
44
+ /** What the rest of the system is allowed to assume about freshness. */
45
+ type ConsistencyModel = 'authoritative' | 'cache' | 'advisory';
46
+ /** Capability classes. `read` is safe to retry; `mutation` must go through
47
+ * MutationGuard (CAS + idempotency). `subscribe` is reserved for future
48
+ * push-driven sources (webhook callbacks) and is not yet wired. */
49
+ type CapabilityClass = 'read' | 'mutation' | 'subscribe';
50
+ /** Compare-and-swap strategy a mutation uses to detect conflicts. */
51
+ type CASStrategy =
52
+ /** Upstream returns an etag/sequence on read, accepts If-Match on write
53
+ * (Google Calendar, GitHub, GDocs revision_id). The connector returns
54
+ * 412 / Precondition Failed on conflict; the SDK maps to ResourceContention. */
55
+ 'etag-if-match'
56
+ /** Upstream guarantees exactly-once-per-key (Stripe, idempotent webhooks).
57
+ * The SDK passes the idempotency key through; no etag check. */
58
+ | 'native-idempotency'
59
+ /** No upstream concurrency control. Connector MUST do read-then-write
60
+ * and verify nothing changed in-between (best-effort). Suitable only
61
+ * for low-contention single-user resources; rejected for any
62
+ * consistencyModel='authoritative' write that may race. */
63
+ | 'optimistic-read-verify'
64
+ /** Source is not contended (e.g. logging, telemetry). Mutations are
65
+ * fire-and-forget. Marks the capability as not eligible for
66
+ * authoritative writes. */
67
+ | 'none';
68
+ interface CapabilityRead {
69
+ name: string;
70
+ class: 'read';
71
+ description: string;
72
+ /** JSON-schema for the tool args the agent passes when invoking. */
73
+ parameters: CapabilityParameterSchema;
74
+ /** Optional: declare which scopes (per the connector manifest) this
75
+ * capability requires. The capability is hidden from the agent's
76
+ * tool registry if the user's grant didn't include them. */
77
+ requiredScopes?: string[];
78
+ }
79
+ interface CapabilityMutation {
80
+ name: string;
81
+ class: 'mutation';
82
+ description: string;
83
+ parameters: CapabilityParameterSchema;
84
+ /** Mandatory: how does the connector guarantee at-most-once + conflict-detect? */
85
+ cas: CASStrategy;
86
+ /** True for capabilities that affect resources outside the calling user
87
+ * (e.g. booking against a shared calendar, charging a card). The agent's
88
+ * planner treats these specially: requires explicit caller confirmation
89
+ * before the call. */
90
+ externalEffect: boolean;
91
+ requiredScopes?: string[];
92
+ }
93
+ type Capability = CapabilityRead | CapabilityMutation;
94
+ /** OAuth2 scope catalog the user has granted us, plus arbitrary metadata
95
+ * the connector pinned at connect-time (calendar id, sheet id, webhook
96
+ * url, …). `metadata` MUST NOT contain secrets — those go in the
97
+ * encrypted credentials envelope. */
98
+ interface DataSourceMetadata {
99
+ scopes: string[];
100
+ [key: string]: unknown;
101
+ }
102
+ /** A connected, authenticated, ready-to-call data source for a project.
103
+ * Persistence shape mirrors the product's connection/source row but normalized — the
104
+ * encrypted credentials envelope is decrypted at hand-out time and held
105
+ * in memory only for the duration of the call. */
106
+ interface ResolvedDataSource {
107
+ id: string;
108
+ projectId: string;
109
+ publishedAgentId: string | null;
110
+ kind: string;
111
+ label: string;
112
+ consistencyModel: ConsistencyModel;
113
+ scopes: string[];
114
+ metadata: Record<string, unknown>;
115
+ /** Unwrapped credentials handed to the connector at call-time. Never
116
+ * persisted in this shape; never logged. */
117
+ credentials: ConnectorCredentials;
118
+ status: 'active' | 'revoked' | 'error';
119
+ }
120
+ /** Discriminated union of credential shapes. Connectors that need new
121
+ * shapes extend this union — `kind` is sealed via the tagged pattern so
122
+ * TypeScript catches an exhaustiveness gap at compile time. */
123
+ type ConnectorCredentials = {
124
+ kind: 'oauth2';
125
+ accessToken: string;
126
+ refreshToken?: string;
127
+ expiresAt?: number;
128
+ } | {
129
+ kind: 'api-key';
130
+ apiKey: string;
131
+ } | {
132
+ kind: 'custom';
133
+ values: Record<string, unknown>;
134
+ } | {
135
+ kind: 'hmac';
136
+ secret: string;
137
+ } | {
138
+ kind: 'none';
139
+ };
140
+ /** Result of a read capability invocation. */
141
+ interface CapabilityReadResult {
142
+ /** Free-form payload — the connector's data shape. The agent receives
143
+ * this as the tool result; planners consume it via JSON-shape contract
144
+ * declared in the capability's `parameters` (output schema). */
145
+ data: unknown;
146
+ /** Optional etag/sequence the caller can reuse for a subsequent CAS
147
+ * mutation. */
148
+ etag?: string;
149
+ /** When this read happened (UTC ms since epoch). */
150
+ fetchedAt: number;
151
+ }
152
+ /** Result of a mutation capability invocation. Either committed (with the
153
+ * resulting etag/sequence so the caller can chain mutations), or
154
+ * contended (the upstream rejected with a state mismatch — the agent
155
+ * should re-read and retry, or surface alternatives to the caller). */
156
+ type CapabilityMutationResult = {
157
+ status: 'committed';
158
+ data: unknown;
159
+ etagAfter?: string;
160
+ committedAt: number;
161
+ /** True iff this commit was returned from the idempotency store
162
+ * rather than executed against upstream. The caller can use this
163
+ * to suppress confirmation messages on retry. */
164
+ idempotentReplay: boolean;
165
+ } | {
166
+ status: 'conflict';
167
+ /** Best-effort alternative options the upstream surfaced (e.g.,
168
+ * next-available calendar slots after a booking conflict). */
169
+ alternatives: unknown[];
170
+ /** The current authoritative state, if the connector could re-read
171
+ * cheaply. */
172
+ currentState?: unknown;
173
+ message: string;
174
+ } | {
175
+ status: 'rate-limited';
176
+ /** Wall-clock ms the caller should wait before retrying. The SDK
177
+ * computes this from the bucket's refill schedule so the agent
178
+ * doesn't have to guess. */
179
+ retryAfterMs: number;
180
+ message: string;
181
+ };
182
+ /** Inputs the SDK passes into the connector's executeRead / executeMutation. */
183
+ interface ConnectorInvocation {
184
+ source: ResolvedDataSource;
185
+ capabilityName: string;
186
+ args: Record<string, unknown>;
187
+ /** Idempotency key the caller (or the SDK's defaulting policy) supplied.
188
+ * Always present at the connector boundary — the SDK manufactures one
189
+ * if the agent didn't pass one. */
190
+ idempotencyKey: string;
191
+ /** Optional caller-supplied etag the connector should send as If-Match. */
192
+ expectedEtag?: string;
193
+ /** Product/session id (if any) for forensic logging. */
194
+ callSessionId?: string;
195
+ }
196
+ /** A single inbound event extracted from a push payload. The webhook
197
+ * receiver persists one `InboundEvent` row per entry the connector returns. */
198
+ interface InboundEvent {
199
+ eventType: string;
200
+ providerEventId?: string;
201
+ payload: Record<string, unknown>;
202
+ }
203
+ /** Adapter response from an inbound-webhook dispatch. The receiver persists
204
+ * every `events[]` entry, then either honors the connector's `response`
205
+ * override (Slack `url_verification` echo, provider-specific 2xx body) or
206
+ * defaults to `{status: 200, body: {received: true, count: events.length}}`. */
207
+ interface EventHandlerResult {
208
+ events: InboundEvent[];
209
+ /** Optional: how to respond to the provider. Stripe wants 200 within
210
+ * 30s; Slack wants the challenge param echoed. */
211
+ response?: {
212
+ status: number;
213
+ body: unknown;
214
+ headers?: Record<string, string>;
215
+ };
216
+ }
217
+ /**
218
+ * Connector adapter — one per integration kind. Stateless. The SDK holds
219
+ * the persistence + crypto + mutation-guard concerns; the adapter only
220
+ * knows how to talk to its upstream.
221
+ */
222
+ interface ConnectorAdapter {
223
+ /** Manifest entry the registry uses to render UI + validate args. */
224
+ manifest: ConnectorManifest;
225
+ /** Read invocation. Required when manifest.capabilities contains reads.
226
+ * Should return whatever shape the capability declared
227
+ * in its parameters output schema. */
228
+ executeRead?(inv: ConnectorInvocation): Promise<CapabilityReadResult>;
229
+ /** Mutation invocation. Required when manifest.capabilities contains mutations.
230
+ * Throws ResourceContention on a CAS miss; throws
231
+ * any other Error for upstream failures. The MutationGuard wraps this
232
+ * with idempotency-key short-circuit + audit logging — adapters do
233
+ * NOT manage their own dedup. */
234
+ executeMutation?(inv: ConnectorInvocation): Promise<CapabilityMutationResult>;
235
+ /** Inbound webhook signature verifier. Called BEFORE handleInboundEvent.
236
+ * MUST use constant-time comparison (`crypto.timingSafeEqual`) for any
237
+ * HMAC check. The receiver returns 401 on `valid=false` without invoking
238
+ * handleInboundEvent. Optional: connectors that don't accept push events
239
+ * omit this method and the receiver returns 405 for the kind. */
240
+ verifySignature?(input: {
241
+ rawBody: string;
242
+ headers: Record<string, string | string[] | undefined>;
243
+ source: ResolvedDataSource;
244
+ }): {
245
+ valid: boolean;
246
+ reason?: string;
247
+ };
248
+ /** Inbound webhook dispatch. Called AFTER verifySignature passes. The
249
+ * adapter parses the provider payload and emits zero-or-more
250
+ * `InboundEvent` rows; the receiver persists them as one row each (modulo
251
+ * the (dataSourceId, providerEventId) dedup unique). The optional
252
+ * `response` overrides the receiver's default 200 (Slack `url_verification`
253
+ * needs to echo the challenge in the body to pass Slack's app-config check). */
254
+ handleInboundEvent?(input: {
255
+ source: ResolvedDataSource;
256
+ rawBody: string;
257
+ headers: Record<string, string | string[] | undefined>;
258
+ }): Promise<EventHandlerResult>;
259
+ /** OAuth callback handler — exchanges the auth code for tokens, returns
260
+ * the credentials envelope + scopes + metadata. Only present for
261
+ * oauth2-style adapters. */
262
+ exchangeOAuth?(input: {
263
+ code: string;
264
+ state: string;
265
+ codeVerifier: string;
266
+ redirectUri: string;
267
+ }): Promise<{
268
+ credentials: ConnectorCredentials;
269
+ scopes: string[];
270
+ metadata: Record<string, unknown>;
271
+ }>;
272
+ /** Refresh access token. Only required for oauth2 adapters with
273
+ * short-lived access tokens. */
274
+ refreshToken?(input: ConnectorCredentials): Promise<ConnectorCredentials>;
275
+ /** Health check — invoked when the user clicks "Test connection" in the
276
+ * UI. Should perform the cheapest possible read that proves the grant
277
+ * is still valid. Returns `{ok: false, reason}` rather than throwing
278
+ * for the common case (token expired, scope missing). */
279
+ test(source: ResolvedDataSource): Promise<{
280
+ ok: true;
281
+ } | {
282
+ ok: false;
283
+ reason: string;
284
+ }>;
285
+ }
286
+ /** Static manifest a connector module exports. Drives the UI catalog,
287
+ * scope display, capability discovery for the agent's tool registry. */
288
+ interface ConnectorManifest {
289
+ /** Stable kind id used as the foreign key in DataSource.kind. */
290
+ kind: string;
291
+ /** Human label shown in the UI catalog. */
292
+ displayName: string;
293
+ /** One-paragraph description shown next to the connect button. */
294
+ description: string;
295
+ /** Auth shape this connector requires. */
296
+ auth: AuthSpec;
297
+ /** Capability catalog — the agent's tool registry derives ToolDefinition
298
+ * entries from this list at request time. */
299
+ capabilities: Capability[];
300
+ /** ConsistencyModel default for this kind — overridable per DataSource
301
+ * if a particular instance is special (e.g., a user marks a sheet as
302
+ * `cache` because they refresh it nightly). */
303
+ defaultConsistencyModel: ConsistencyModel;
304
+ /** Connector category for UI grouping. */
305
+ category: 'calendar' | 'spreadsheet' | 'crm' | 'doc' | 'webhook' | 'storage' | 'comms' | 'commerce' | 'other';
306
+ /** Optional icon URL or named icon. */
307
+ icon?: string;
308
+ /** Optional per-kind rate-limit budget. The SDK enforces it inside
309
+ * `executeGuardedMutation` and the read path of `/invoke`. Omit to
310
+ * leave the connector unrestricted. */
311
+ rateLimit?: RateLimitSpec;
312
+ }
313
+ /** Token-bucket budget the SDK enforces against the connector's upstream.
314
+ * We meter on OUR side rather than letting the upstream reject so a
315
+ * chatty agent can't burn quota that's shared across customers (almost
316
+ * every OAuth client is). */
317
+ interface RateLimitSpec {
318
+ /** Max requests per window. */
319
+ requests: number;
320
+ /** Window in ms. */
321
+ windowMs: number;
322
+ /** Whether to apply across all DataSources sharing the same OAuth
323
+ * client (true; default), or per-DataSource (false). The former
324
+ * matches how upstreams meter (per-app), so almost always pick true. */
325
+ scope?: 'oauth-client' | 'data-source';
326
+ }
327
+ type AuthSpec = {
328
+ kind: 'oauth2';
329
+ /** Authorization endpoint URL. */
330
+ authorizationUrl: string;
331
+ /** Token endpoint URL. */
332
+ tokenUrl: string;
333
+ /** Scopes requested in the authorization grant. The user UI shows
334
+ * these so the customer knows what's being shared. */
335
+ scopes: string[];
336
+ /** Whether the connector supports incremental authorization (Google
337
+ * does; many don't). */
338
+ incremental?: boolean;
339
+ /** Env-var name holding the OAuth client_id. */
340
+ clientIdEnv: string;
341
+ /** Env-var name holding the OAuth client_secret. */
342
+ clientSecretEnv: string;
343
+ /** Optional extra params attached to the authorization URL (e.g.,
344
+ * Google's `access_type=offline&prompt=consent` to obtain refresh
345
+ * tokens). */
346
+ extraAuthParams?: Record<string, string>;
347
+ } | {
348
+ kind: 'api-key';
349
+ /** UI hint shown when collecting the key. */
350
+ hint: string;
351
+ } | {
352
+ kind: 'hmac';
353
+ } | {
354
+ kind: 'none';
355
+ };
356
+ /** Thrown by `executeMutation` when upstream rejects on CAS — caught and
357
+ * rewrapped by MutationGuard. */
358
+ declare class ResourceContention extends Error {
359
+ readonly alternatives: unknown[];
360
+ readonly currentState?: unknown | undefined;
361
+ readonly name = "ResourceContention";
362
+ constructor(message: string, alternatives?: unknown[], currentState?: unknown | undefined);
363
+ }
364
+ /** Thrown when the connector finds the user's grant has been revoked or
365
+ * the access token is no longer valid AND refresh failed. Surfaces to
366
+ * the UI as "Reconnect required". */
367
+ declare class CredentialsExpired extends Error {
368
+ readonly dataSourceId: string;
369
+ readonly name = "CredentialsExpired";
370
+ constructor(message: string, dataSourceId: string);
371
+ }
372
+ interface ConnectorManifestValidationIssue {
373
+ path: string;
374
+ message: string;
375
+ }
376
+ interface ConnectorManifestValidationResult {
377
+ ok: boolean;
378
+ issues: ConnectorManifestValidationIssue[];
379
+ }
380
+ /** Validate the static connector manifest before a provider registers it.
381
+ * This catches the expensive mistakes early: duplicate capability names,
382
+ * mutation capabilities without CAS, authoritative fire-and-forget writes,
383
+ * and invalid rate-limit specs. */
384
+ declare function validateConnectorManifest(manifest: ConnectorManifest): ConnectorManifestValidationResult;
385
+ declare function assertValidConnectorManifest(manifest: ConnectorManifest): void;
386
+
387
+ /**
388
+ * @stable Tangle Identity — `id.tangle.tools` connector + verifier.
389
+ *
390
+ * This is the *identity* substrate every Tangle product (legal, tax, gtm,
391
+ * creative, agent-builder, sandbox, evals, …) sits on. The shape mirrors
392
+ * what `tcloud` and the sandbox `PlatformClient` already implement against
393
+ * the platform repo at `agent-dev-container-collab-m1/products/platform`,
394
+ * so consumers can switch from a hand-rolled fetch loop to this adapter
395
+ * without changing the wire protocol.
396
+ *
397
+ * What it covers, end-to-end:
398
+ *
399
+ * verify_token({ token })
400
+ * → { kind: 'api_key' | 'session', valid, userId?, workspaceId?, scopes, expiresAt? }
401
+ * Verifies a single credential. Two token shapes are recognized:
402
+ * - `sk-tan-*` API keys — POST /v1/keys/verify with the service token.
403
+ * Returns `userId`, `keyId`, `product`, granted scopes (`allowedModels`
404
+ * + product flag), and budget metadata.
405
+ * - Better Auth session cookies / Bearer session tokens — GET
406
+ * /api/auth/get-session with the credential forwarded as-is.
407
+ * Returns the user row.
408
+ * Wrong-issuer / tampered / expired all surface as `{ valid: false }`
409
+ * with a stable `reason`. Never throws on bad-token; only throws when
410
+ * id.tangle.tools itself is unreachable or returns a 5xx (lets callers
411
+ * fail closed without confusing token failures with platform failures).
412
+ *
413
+ * get_user({ userId })
414
+ * → { id, email, name?, image? }
415
+ * Read-only profile lookup. Service-token authenticated. Used by
416
+ * `requireTangleAuth` middleware to hydrate the request context when
417
+ * a downstream wants the user's email without re-verifying.
418
+ *
419
+ * list_workspaces({ userId })
420
+ * → { workspaces: [{ id, name, role, isPersonal }] }
421
+ * A workspace = a Tangle team. Personal workspace is rendered with
422
+ * `isPersonal: true` (own id === userId on the platform schema).
423
+ * Mirrors `GET /v1/teams` on the platform side.
424
+ *
425
+ * switch_workspace({ workspaceId })
426
+ * → { ok: true, workspaceId, scopes }
427
+ * Stateless on this adapter — the caller persists the workspaceId in
428
+ * its own session. The connector returns the workspace's effective
429
+ * scope set so the caller can immediately filter capability discovery
430
+ * against the new workspace's grant matrix.
431
+ *
432
+ * revoke_session({ token })
433
+ * → { ok: true }
434
+ * For session tokens: POST /api/auth/sign-out. For API keys: DELETE
435
+ * /v1/keys/{id}. The adapter detects the kind from the prefix.
436
+ *
437
+ * Auth:
438
+ * - **Service token** (`Bearer svc_*`) is required for `verify_token` of
439
+ * `sk-tan-*` keys, `get_user`, `list_workspaces`, and `revoke_session`
440
+ * of API keys.
441
+ * - **Session cookie / Bearer session** is forwarded as-is for session
442
+ * verification and session revocation.
443
+ *
444
+ * The adapter is stateless. Caller resolves `serviceToken` + `baseUrl` from
445
+ * env (`TANGLE_PLATFORM_URL`, `TANGLE_SERVICE_TOKEN`) and passes them at
446
+ * construction. The adapter never reads from `process.env` itself — this
447
+ * keeps it CF Worker compatible (no Node-only env semantics) and lets
448
+ * tests inject a fake fetch + service token in one place.
449
+ */
450
+
451
+ /** Default platform URL (matches `DEFAULT_PLATFORM_URL` in tcloud). */
452
+ declare const DEFAULT_TANGLE_PLATFORM_URL = "https://id.tangle.tools";
453
+ /** API-key prefix the platform issues. Used to disambiguate token kind
454
+ * without a round-trip. */
455
+ declare const TANGLE_API_KEY_PREFIX = "sk-tan-";
456
+ /** Service-token prefix. Mirrored from the platform's middleware so we
457
+ * can refuse to forward service tokens through the user-session path. */
458
+ declare const TANGLE_SERVICE_TOKEN_PREFIX = "svc_";
459
+ interface TangleIdentityOptions {
460
+ /** Base URL of the id.tangle.tools deployment (no trailing slash). */
461
+ baseUrl?: string;
462
+ /**
463
+ * Service token (`svc_*`) used for S2S calls (verify, provision, etc.).
464
+ * Required for API-key verification and the user/workspace read paths.
465
+ * Omit only for session-only flows on a deployment that exposes those
466
+ * routes unauthenticated (rare; never in production).
467
+ */
468
+ serviceToken?: string;
469
+ /** Service identity claimed in the `X-Service-Name` header. */
470
+ serviceName?: string;
471
+ /** Injected fetch — defaults to global. Tests pass a vi mock. */
472
+ fetchImpl?: typeof fetch;
473
+ /** Per-call timeout override (default {@link PLATFORM_FETCH_TIMEOUT_MS}). */
474
+ timeoutMs?: number;
475
+ }
476
+ /** Stable result of a token verification. `valid: false` is returned for
477
+ * every recognizable bad-token shape (expired, tampered, wrong issuer,
478
+ * unknown kind); only true platform unreachability throws. */
479
+ type TangleTokenVerifyResult = {
480
+ valid: true;
481
+ kind: 'api_key' | 'session';
482
+ userId: string;
483
+ /** Active workspace at the moment of issue, if the credential is
484
+ * workspace-scoped (team-owned API key). Personal credentials
485
+ * return the user's personal workspace (== `userId`). */
486
+ workspaceId: string;
487
+ scopes: string[];
488
+ /** Wall-clock ms epoch when the credential expires. Undefined for
489
+ * non-expiring credentials (most session cookies are sliding). */
490
+ expiresAt?: number;
491
+ /** Stable id of the credential row, when known (key.id for API
492
+ * keys, session.id for sessions). Useful for revoke + audit. */
493
+ credentialId?: string;
494
+ /** Product the credential is scoped to, when known. */
495
+ product?: string;
496
+ /** Owner shape — `user` for personal credentials, `team` for
497
+ * team-owned API keys. Always matches the workspace's owner type. */
498
+ ownerType: 'user' | 'team';
499
+ } | {
500
+ valid: false;
501
+ /** Stable reason code: `tampered`, `expired`, `revoked`,
502
+ * `wrong_issuer`, `unknown_kind`, `service_token_refused`. */
503
+ reason: TangleTokenVerifyFailure;
504
+ };
505
+ type TangleTokenVerifyFailure = 'tampered' | 'expired' | 'revoked' | 'wrong_issuer' | 'unknown_kind' | 'service_token_refused' | 'malformed';
506
+ interface TangleUserSummary {
507
+ id: string;
508
+ email?: string;
509
+ name?: string | null;
510
+ image?: string | null;
511
+ }
512
+ interface TangleWorkspaceSummary {
513
+ id: string;
514
+ name: string;
515
+ role: 'owner' | 'admin' | 'member';
516
+ isPersonal: boolean;
517
+ /** Effective scope set for the calling user inside this workspace.
518
+ * Sourced from the team's plan + per-product policy on the platform. */
519
+ scopes: string[];
520
+ }
521
+ /** Thrown when id.tangle.tools is unreachable or returns 5xx. NOT thrown
522
+ * for bad-token responses — those round-trip as `{ valid: false }`. */
523
+ declare class TangleIdentityUnreachableError extends Error {
524
+ readonly name = "TangleIdentityUnreachableError";
525
+ readonly status?: number;
526
+ constructor(message: string, opts?: {
527
+ status?: number;
528
+ cause?: unknown;
529
+ });
530
+ }
531
+ /** Build a `ConnectorAdapter` exposing id.tangle.tools as a first-party
532
+ * integration. The adapter participates in the standard discovery /
533
+ * capability gating loop, so a product can list identity ops alongside
534
+ * Gmail / Stripe / etc. in the same tool registry. */
535
+ declare function tangleIdentity(opts?: TangleIdentityOptions): ConnectorAdapter;
536
+ /** Low-level HTTP client used by the adapter. Exported so consumers
537
+ * (middleware, connect routes, custom apps) can hit id.tangle.tools
538
+ * without going through the connector pipeline. */
539
+ interface TangleIdentityClient {
540
+ verifyToken(token: string): Promise<TangleTokenVerifyResult>;
541
+ getUser(userId: string): Promise<TangleUserSummary>;
542
+ listWorkspaces(userId: string): Promise<TangleWorkspaceSummary[]>;
543
+ switchWorkspace(userId: string, workspaceId: string): Promise<{
544
+ ok: true;
545
+ workspaceId: string;
546
+ scopes: string[];
547
+ }>;
548
+ revokeSession(token: string): Promise<void>;
549
+ ping(): Promise<boolean>;
550
+ }
551
+ declare function createTangleIdentityClient(opts?: TangleIdentityOptions): TangleIdentityClient;
552
+
553
+ export { type AuthSpec as A, assertValidConnectorManifest as B, type ConnectorAdapter as C, DEFAULT_TANGLE_PLATFORM_URL as D, type EventHandlerResult as E, createTangleIdentityClient as F, tangleIdentity as G, validateConnectorManifest as H, type InboundEvent as I, type ResolvedDataSource as R, type TangleIdentityOptions as T, type TangleUserSummary as a, type TangleIdentityClient as b, type TangleTokenVerifyFailure as c, type ConnectorCredentials as d, type CASStrategy as e, type Capability as f, type CapabilityClass as g, type CapabilityMutation as h, type CapabilityMutationResult as i, type CapabilityParameterSchema as j, type CapabilityRead as k, type CapabilityReadResult as l, type ConnectorInvocation as m, type ConnectorManifest as n, type ConnectorManifestValidationIssue as o, type ConnectorManifestValidationResult as p, type ConsistencyModel as q, CredentialsExpired as r, type DataSourceMetadata as s, type RateLimitSpec as t, ResourceContention as u, TANGLE_API_KEY_PREFIX as v, TANGLE_SERVICE_TOKEN_PREFIX as w, TangleIdentityUnreachableError as x, type TangleTokenVerifyResult as y, type TangleWorkspaceSummary as z };