@tangle-network/agent-integrations 0.53.51 → 0.53.52

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 (49) hide show
  1. package/dist/catalog.d.ts +2 -2
  2. package/dist/catalog.js +8 -8
  3. package/dist/{chunk-W2FFWDJ7.js → chunk-ABWBYMCF.js} +2 -2
  4. package/dist/{chunk-VV73V64K.js → chunk-CONRYVPK.js} +4 -2
  5. package/dist/chunk-CONRYVPK.js.map +1 -0
  6. package/dist/{chunk-R4KVF4KT.js → chunk-CT3J7ZB6.js} +34 -6
  7. package/dist/chunk-CT3J7ZB6.js.map +1 -0
  8. package/dist/{chunk-P4YUWB77.js → chunk-DCSQH4O6.js} +41 -13
  9. package/dist/chunk-DCSQH4O6.js.map +1 -0
  10. package/dist/{chunk-CJ6SZNMU.js → chunk-E2QYXDJQ.js} +3 -2
  11. package/dist/{chunk-CJ6SZNMU.js.map → chunk-E2QYXDJQ.js.map} +1 -1
  12. package/dist/{chunk-LIY4A33C.js → chunk-PIEBVPSW.js} +2 -2
  13. package/dist/{chunk-RBCF44OD.js → chunk-PIF635BP.js} +2 -2
  14. package/dist/{chunk-BYTP5QJQ.js → chunk-QL6CRJ3E.js} +27 -10
  15. package/dist/{chunk-BYTP5QJQ.js.map → chunk-QL6CRJ3E.js.map} +1 -1
  16. package/dist/connect/index.d.ts +2 -2
  17. package/dist/connect/index.js +2 -2
  18. package/dist/connectors/adapters/index.d.ts +10 -12
  19. package/dist/connectors/adapters/index.js +2 -2
  20. package/dist/connectors/index.d.ts +4 -3
  21. package/dist/connectors/index.js +3 -3
  22. package/dist/{consumer-48BUPeUQ.d.ts → consumer-BbQWkIzU.d.ts} +1 -1
  23. package/dist/consumer.d.ts +3 -3
  24. package/dist/consumer.js +2 -2
  25. package/dist/{core-types-BR9Gv6hE.d.ts → core-types-A-xIaTzb.d.ts} +1 -1
  26. package/dist/coverage-catalog.d.ts +2 -2
  27. package/dist/{importers-0vMRe5jJ.d.ts → importers-BMigcDVf.d.ts} +1 -1
  28. package/dist/index.d.ts +8 -8
  29. package/dist/index.js +8 -8
  30. package/dist/mcp.d.ts +3 -3
  31. package/dist/mcp.js +8 -8
  32. package/dist/middleware/index.d.ts +2 -2
  33. package/dist/middleware/index.js +2 -2
  34. package/dist/registry.d.ts +2 -2
  35. package/dist/registry.js +8 -8
  36. package/dist/runtime.d.ts +2 -2
  37. package/dist/runtime.js +8 -8
  38. package/dist/specs.d.ts +6 -4
  39. package/dist/specs.js +4 -4
  40. package/dist/{tangle-id-BRtXszIF.d.ts → tangle-id-gFOGXe4Y.d.ts} +1 -1
  41. package/dist/{types-Hy58Fb8T.d.ts → types-DpKEBiX6.d.ts} +8 -6
  42. package/docs/integration-execution-matrix.json +29 -8
  43. package/package.json +1 -1
  44. package/dist/chunk-P4YUWB77.js.map +0 -1
  45. package/dist/chunk-R4KVF4KT.js.map +0 -1
  46. package/dist/chunk-VV73V64K.js.map +0 -1
  47. /package/dist/{chunk-W2FFWDJ7.js.map → chunk-ABWBYMCF.js.map} +0 -0
  48. /package/dist/{chunk-LIY4A33C.js.map → chunk-PIEBVPSW.js.map} +0 -0
  49. /package/dist/{chunk-RBCF44OD.js.map → chunk-PIF635BP.js.map} +0 -0
@@ -111,17 +111,12 @@ function validateOAuth2AuthSpec(auth, issues, path) {
111
111
  });
112
112
  }
113
113
  for (const [key, spec] of Object.entries(auth.urlTemplateMetadata ?? {})) {
114
- if (spec.kind !== "base-url") {
114
+ if (spec.kind !== "base-url" && spec.kind !== "path-segment") {
115
115
  issues.push({
116
116
  path: `${path}.urlTemplateMetadata.${key}.kind`,
117
- message: "OAuth URL metadata kind must be base-url"
118
- });
119
- }
120
- if (!spec.requirePublicHttps && !spec.allowedBaseUrls?.length && !spec.allowedBaseUrlSuffixes?.length) {
121
- issues.push({
122
- path: `${path}.urlTemplateMetadata.${key}`,
123
- message: "OAuth base URL metadata requires an allowlist or public HTTPS policy"
117
+ message: "OAuth URL metadata kind must be base-url or path-segment"
124
118
  });
119
+ continue;
125
120
  }
126
121
  const placeholder = `{${key}}`;
127
122
  if (!auth.authorizationUrl?.includes(placeholder) && !auth.tokenUrl.includes(placeholder)) {
@@ -130,13 +125,30 @@ function validateOAuth2AuthSpec(auth, issues, path) {
130
125
  message: `OAuth URL metadata is unused; add ${placeholder} to an OAuth endpoint`
131
126
  });
132
127
  }
133
- for (const suffix of spec.allowedBaseUrlSuffixes ?? []) {
134
- if (!/^\.[a-z0-9.-]+$/i.test(suffix)) {
128
+ if (spec.kind === "base-url") {
129
+ if (!spec.requirePublicHttps && !spec.allowedBaseUrls?.length && !spec.allowedBaseUrlSuffixes?.length) {
135
130
  issues.push({
136
- path: `${path}.urlTemplateMetadata.${key}.allowedBaseUrlSuffixes`,
137
- message: "OAuth base URL hostname suffixes must start with a dot"
131
+ path: `${path}.urlTemplateMetadata.${key}`,
132
+ message: "OAuth base URL metadata requires an allowlist or public HTTPS policy"
138
133
  });
139
134
  }
135
+ for (const suffix of spec.allowedBaseUrlSuffixes ?? []) {
136
+ if (!/^\.[a-z0-9.-]+$/i.test(suffix)) {
137
+ issues.push({
138
+ path: `${path}.urlTemplateMetadata.${key}.allowedBaseUrlSuffixes`,
139
+ message: "OAuth base URL hostname suffixes must start with a dot"
140
+ });
141
+ }
142
+ }
143
+ } else {
144
+ for (const endpoint of [auth.authorizationUrl, auth.tokenUrl]) {
145
+ if (endpoint?.includes(placeholder) && !isOAuthPathSegmentPlaceholder(endpoint, placeholder)) {
146
+ issues.push({
147
+ path: `${path}.urlTemplateMetadata.${key}`,
148
+ message: `OAuth path metadata ${placeholder} must occupy a complete URL path segment`
149
+ });
150
+ }
151
+ }
140
152
  }
141
153
  }
142
154
  if (auth.tokenClientAuthMethod !== void 0 && auth.tokenClientAuthMethod !== "none" && auth.tokenClientAuthMethod !== "client_secret_post" && auth.tokenClientAuthMethod !== "client_secret_basic") {
@@ -160,6 +172,22 @@ function validateOAuth2AuthSpec(auth, issues, path) {
160
172
  }
161
173
  }
162
174
  }
175
+ function isOAuthPathSegmentPlaceholder(template, placeholder) {
176
+ const schemeIndex = template.indexOf("://");
177
+ const rootEnd = schemeIndex >= 0 ? schemeIndex + 3 : template.startsWith("{") ? template.indexOf("}") + 1 : 0;
178
+ const pathStart = template.indexOf("/", rootEnd);
179
+ const queryStart = template.search(/[?#]/);
180
+ let cursor = 0;
181
+ while (true) {
182
+ const index = template.indexOf(placeholder, cursor);
183
+ if (index < 0) return true;
184
+ const end = index + placeholder.length;
185
+ if (pathStart < 0 || index < pathStart || queryStart >= 0 && index >= queryStart || template[index - 1] !== "/" || end < template.length && template[end] !== "/" && template[end] !== "?" && template[end] !== "#") {
186
+ return false;
187
+ }
188
+ cursor = end;
189
+ }
190
+ }
163
191
  function assertValidConnectorManifest(manifest) {
164
192
  const result = validateConnectorManifest(manifest);
165
193
  if (!result.ok) {
@@ -832,4 +860,4 @@ export {
832
860
  tangleIdentity,
833
861
  createTangleIdentityClient
834
862
  };
835
- //# sourceMappingURL=chunk-P4YUWB77.js.map
863
+ //# sourceMappingURL=chunk-DCSQH4O6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connectors/types.ts","../src/connectors/adapters/tangle-id.ts"],"sourcesContent":["/**\n * Connector primitives — the contract a concrete first-party integration\n * (Google Calendar, HubSpot, Stripe, ...) implements. Lower level than the\n * hub-side `IntegrationProvider` interface from `../index.ts`: a single\n * `IntegrationProvider` typically wraps several connectors (e.g., a\n * \"first-party\" provider that lists all your shipped connectors as a\n * single catalog).\n *\n * Layering:\n *\n * IntegrationHub — vendor-neutral facade (../index.ts)\n * ↓\n * IntegrationProvider — one per gateway or first-party provider\n * ↓\n * ConnectorAdapter (this file) — one per integration (Google Calendar, ...)\n * ↓\n * upstream HTTP API — vendor SDK / fetch / OAuth\n *\n * Three load-bearing decisions encoded here:\n *\n * 1. Capabilities are typed (`read` vs `mutation`). Every mutation MUST\n * declare a CAS strategy. Conflict resolution is the SDK's job, not the\n * connector's. `validateConnectorManifest()` rejects unsafe manifests\n * before a connector is registered.\n *\n * 2. ConsistencyModel pins what the rest of the system can assume:\n * authoritative → the source IS the truth (Calendar, payments)\n * cache → we mirror with TTL and may serve stale (price list)\n * advisory → informational only (FAQ doc)\n * Agent planners can (and should) refuse to promise outcomes based on\n * `cache`/`advisory` data without a live `authoritative` confirmation.\n *\n * 3. Capabilities surface to the calling agent's tool registry by\n * transformation, not by hand-wiring. Adding a connector automatically\n * expands the agent's toolbelt for that specific user without touching\n * the prompt or runner.\n */\n\n/** Minimal JSON-schema shape used for capability arg validation. We\n * intentionally don't pull `@types/json-schema` — most consumers already\n * declare parameters as `Record<string, unknown>` and the\n * shape is whatever the LLM SDK's structured-output expects. Keep the\n * contract loose at the boundary; tighten via runtime zod where needed. */\nexport type CapabilityParameterSchema = Record<string, unknown>\n\n/** What the rest of the system is allowed to assume about freshness. */\nexport type ConsistencyModel = 'authoritative' | 'cache' | 'advisory'\n\n/** Capability classes. `read` is safe to retry; `mutation` must go through\n * MutationGuard (CAS + idempotency). `subscribe` is reserved for future\n * push-driven sources (webhook callbacks) and is not yet wired. */\nexport type CapabilityClass = 'read' | 'mutation' | 'subscribe'\n\n/** Compare-and-swap strategy a mutation uses to detect conflicts. */\nexport type CASStrategy =\n /** Upstream returns an etag/sequence on read, accepts If-Match on write\n * (Google Calendar, GitHub, GDocs revision_id). The connector returns\n * 412 / Precondition Failed on conflict; the SDK maps to ResourceContention. */\n | 'etag-if-match'\n /** Upstream guarantees exactly-once-per-key (Stripe, idempotent webhooks).\n * The SDK passes the idempotency key through; no etag check. */\n | 'native-idempotency'\n /** No upstream concurrency control. Connector MUST do read-then-write\n * and verify nothing changed in-between (best-effort). Suitable only\n * for low-contention single-user resources; rejected for any\n * consistencyModel='authoritative' write that may race. */\n | 'optimistic-read-verify'\n /** Source is not contended (e.g. logging, telemetry). Mutations are\n * fire-and-forget. Marks the capability as not eligible for\n * authoritative writes. */\n | 'none'\n\nexport interface CapabilityRead {\n name: string\n class: 'read'\n description: string\n /** JSON-schema for the tool args the agent passes when invoking. */\n parameters: CapabilityParameterSchema\n /** Optional: declare which scopes (per the connector manifest) this\n * capability requires. The capability is hidden from the agent's\n * tool registry if the user's grant didn't include them. */\n requiredScopes?: string[]\n}\n\nexport interface CapabilityMutation {\n name: string\n class: 'mutation'\n description: string\n parameters: CapabilityParameterSchema\n /** Mandatory: how does the connector guarantee at-most-once + conflict-detect? */\n cas: CASStrategy\n /** True for capabilities that affect resources outside the calling user\n * (e.g. booking against a shared calendar, charging a card). The agent's\n * planner treats these specially: requires explicit caller confirmation\n * before the call. */\n externalEffect: boolean\n requiredScopes?: string[]\n}\n\nexport type Capability = CapabilityRead | CapabilityMutation\n\n/** OAuth2 scope catalog the user has granted us, plus arbitrary metadata\n * the connector pinned at connect-time (calendar id, sheet id, webhook\n * url, …). `metadata` MUST NOT contain secrets — those go in the\n * encrypted credentials envelope. */\nexport interface DataSourceMetadata {\n scopes: string[]\n [key: string]: unknown\n}\n\n/** A connected, authenticated, ready-to-call data source for a project.\n * Persistence shape mirrors the product's connection/source row but normalized — the\n * encrypted credentials envelope is decrypted at hand-out time and held\n * in memory only for the duration of the call. */\nexport interface ResolvedDataSource {\n id: string\n projectId: string\n publishedAgentId: string | null\n kind: string\n label: string\n consistencyModel: ConsistencyModel\n scopes: string[]\n metadata: Record<string, unknown>\n /** Unwrapped credentials handed to the connector at call-time. Never\n * persisted in this shape; never logged. */\n credentials: ConnectorCredentials\n status: 'active' | 'revoked' | 'error'\n}\n\n/** Discriminated union of credential shapes. Connectors that need new\n * shapes extend this union — `kind` is sealed via the tagged pattern so\n * TypeScript catches an exhaustiveness gap at compile time. */\nexport type ConnectorCredentials =\n | { kind: 'oauth2'; accessToken: string; refreshToken?: string; expiresAt?: number }\n | { kind: 'api-key'; apiKey: string }\n | { kind: 'custom'; values: Record<string, unknown> }\n | { kind: 'hmac'; secret: string }\n | { kind: 'none' }\n\n/** Result of a read capability invocation. */\nexport interface CapabilityReadResult {\n /** Free-form payload — the connector's data shape. The agent receives\n * this as the tool result; planners consume it via JSON-shape contract\n * declared in the capability's `parameters` (output schema). */\n data: unknown\n /** Optional etag/sequence the caller can reuse for a subsequent CAS\n * mutation. */\n etag?: string\n /** When this read happened (UTC ms since epoch). */\n fetchedAt: number\n}\n\n/** Result of a mutation capability invocation. Either committed (with the\n * resulting etag/sequence so the caller can chain mutations), or\n * contended (the upstream rejected with a state mismatch — the agent\n * should re-read and retry, or surface alternatives to the caller). */\nexport type CapabilityMutationResult =\n | {\n status: 'committed'\n data: unknown\n etagAfter?: string\n committedAt: number\n /** True iff this commit was returned from the idempotency store\n * rather than executed against upstream. The caller can use this\n * to suppress confirmation messages on retry. */\n idempotentReplay: boolean\n }\n | {\n status: 'conflict'\n /** Best-effort alternative options the upstream surfaced (e.g.,\n * next-available calendar slots after a booking conflict). */\n alternatives: unknown[]\n /** The current authoritative state, if the connector could re-read\n * cheaply. */\n currentState?: unknown\n message: string\n }\n | {\n status: 'rate-limited'\n /** Wall-clock ms the caller should wait before retrying. The SDK\n * computes this from the bucket's refill schedule so the agent\n * doesn't have to guess. */\n retryAfterMs: number\n message: string\n }\n\n/** Inputs the SDK passes into the connector's executeRead / executeMutation. */\nexport interface ConnectorInvocation {\n source: ResolvedDataSource\n capabilityName: string\n args: Record<string, unknown>\n /** Idempotency key the caller (or the SDK's defaulting policy) supplied.\n * Always present at the connector boundary — the SDK manufactures one\n * if the agent didn't pass one. */\n idempotencyKey: string\n /** Optional caller-supplied etag the connector should send as If-Match. */\n expectedEtag?: string\n /** Product/session id (if any) for forensic logging. */\n callSessionId?: string\n /** Fired by the adapter when it rotates the credentials mid-call (e.g. an\n * OAuth access token refreshed on expiry). The host re-encrypts +\n * persists the rotated envelope so the next expiry does not force a\n * reconnect. Adapters MUST invoke this with the FULL rotated envelope,\n * not a partial — the previous refresh token is preserved by the caller's\n * refresh logic, not inferred here. */\n onCredentialsRotated?: (\n credentials: ConnectorCredentials,\n ) => Promise<void> | void\n}\n\n/** A single inbound event extracted from a push payload. The webhook\n * receiver persists one `InboundEvent` row per entry the connector returns. */\nexport interface InboundEvent {\n eventType: string\n providerEventId?: string\n payload: Record<string, unknown>\n}\n\n/** Adapter response from an inbound-webhook dispatch. The receiver persists\n * every `events[]` entry, then either honors the connector's `response`\n * override (Slack `url_verification` echo, provider-specific 2xx body) or\n * defaults to `{status: 200, body: {received: true, count: events.length}}`. */\nexport interface EventHandlerResult {\n events: InboundEvent[]\n /** Optional: how to respond to the provider. Stripe wants 200 within\n * 30s; Slack wants the challenge param echoed. */\n response?: { status: number; body: unknown; headers?: Record<string, string> }\n}\n\n/**\n * Connector adapter — one per integration kind. Stateless. The SDK holds\n * the persistence + crypto + mutation-guard concerns; the adapter only\n * knows how to talk to its upstream.\n */\nexport interface ConnectorAdapter {\n /** Manifest entry the registry uses to render UI + validate args. */\n manifest: ConnectorManifest\n /**\n * True when this adapter only receives provider events and must not appear\n * in public connector or action discovery. Hosts can still register it\n * explicitly for their inbound route, which preserves its stable kind id.\n */\n inboundOnly?: boolean\n /** Read invocation. Required when manifest.capabilities contains reads.\n * Should return whatever shape the capability declared\n * in its parameters output schema. */\n executeRead?(inv: ConnectorInvocation): Promise<CapabilityReadResult>\n /** Mutation invocation. Required when manifest.capabilities contains mutations.\n * Throws ResourceContention on a CAS miss; throws\n * any other Error for upstream failures. The MutationGuard wraps this\n * with idempotency-key short-circuit + audit logging — adapters do\n * NOT manage their own dedup. */\n executeMutation?(inv: ConnectorInvocation): Promise<CapabilityMutationResult>\n /** Inbound webhook signature verifier. Called BEFORE handleInboundEvent.\n * MUST use constant-time comparison (`crypto.timingSafeEqual`) for any\n * HMAC check. The receiver returns 401 on `valid=false` without invoking\n * handleInboundEvent. Optional: connectors that don't accept push events\n * omit this method and the receiver returns 405 for the kind. */\n verifySignature?(input: {\n rawBody: string\n headers: Record<string, string | string[] | undefined>\n source: ResolvedDataSource\n }): { valid: boolean; reason?: string }\n /** Inbound webhook dispatch. Called AFTER verifySignature passes. The\n * adapter parses the provider payload and emits zero-or-more\n * `InboundEvent` rows; the receiver persists them as one row each (modulo\n * the (dataSourceId, providerEventId) dedup unique). The optional\n * `response` overrides the receiver's default 200 (Slack `url_verification`\n * needs to echo the challenge in the body to pass Slack's app-config check). */\n handleInboundEvent?(input: {\n source: ResolvedDataSource\n rawBody: string\n headers: Record<string, string | string[] | undefined>\n }): Promise<EventHandlerResult>\n /** OAuth callback handler — exchanges the auth code for tokens, returns\n * the credentials envelope + scopes + metadata. Only present for\n * oauth2-style adapters. */\n exchangeOAuth?(input: {\n code: string\n state: string\n /** Required unless this connector declares PKCE as unsupported. */\n codeVerifier?: string\n redirectUri: string\n }): Promise<{\n credentials: ConnectorCredentials\n scopes: string[]\n metadata: Record<string, unknown>\n }>\n /** Refresh access token. Only required for oauth2 adapters with\n * short-lived access tokens. */\n refreshToken?(input: ConnectorCredentials): Promise<ConnectorCredentials>\n /** Health check — invoked when the user clicks \"Test connection\" in the\n * UI. Should perform the cheapest possible read that proves the grant\n * is still valid. Returns `{ok: false, reason}` rather than throwing\n * for the common case (token expired, scope missing). */\n test(\n source: ResolvedDataSource,\n /** Optional persistence sink for credentials rotated by the health check. */\n onCredentialsRotated?: (\n credentials: ConnectorCredentials,\n ) => Promise<void> | void,\n ): Promise<{ ok: true } | { ok: false; reason: string }>\n}\n\n/** Static manifest a connector module exports. Drives the UI catalog,\n * scope display, capability discovery for the agent's tool registry. */\nexport interface ConnectorManifest {\n /** Stable kind id used as the foreign key in DataSource.kind. */\n kind: string\n /** Human label shown in the UI catalog. */\n displayName: string\n /** One-paragraph description shown next to the connect button. */\n description: string\n /** Auth shape this connector requires. */\n auth: AuthSpec\n /** Capability catalog — the agent's tool registry derives ToolDefinition\n * entries from this list at request time. */\n capabilities: Capability[]\n /** ConsistencyModel default for this kind — overridable per DataSource\n * if a particular instance is special (e.g., a user marks a sheet as\n * `cache` because they refresh it nightly). */\n defaultConsistencyModel: ConsistencyModel\n /** Connector category for UI grouping. */\n category:\n | 'calendar'\n | 'spreadsheet'\n | 'crm'\n | 'doc'\n | 'webhook'\n | 'storage'\n | 'comms'\n | 'commerce'\n | 'database'\n | 'sales-intelligence'\n | 'market-intelligence'\n | 'hr'\n | 'other'\n /** Optional icon URL or named icon. */\n icon?: string\n /** Optional per-kind rate-limit budget. The SDK enforces it inside\n * `executeGuardedMutation` and the read path of `/invoke`. Omit to\n * leave the connector unrestricted. */\n rateLimit?: RateLimitSpec\n}\n\n/** Token-bucket budget the SDK enforces against the connector's upstream.\n * We meter on OUR side rather than letting the upstream reject so a\n * chatty agent can't burn quota that's shared across customers (almost\n * every OAuth client is). */\nexport interface RateLimitSpec {\n /** Max requests per window. */\n requests: number\n /** Window in ms. */\n windowMs: number\n /** Whether to apply across all DataSources sharing the same OAuth\n * client (true; default), or per-DataSource (false). The former\n * matches how upstreams meter (per-app), so almost always pick true. */\n scope?: 'oauth-client' | 'data-source'\n}\n\n/** Source mapping for `OAuth2AuthSpec.tokenMetadata`. A bare string names the\n * token-response field to capture (capture-if-present). The object form marks\n * the field `required` so a missing/empty value fails the token exchange loud\n * rather than silently capturing nothing. */\nexport type TokenMetadataSource = string | { field: string; required?: boolean }\n\n/** How an OAuth token endpoint authenticates this client.\n *\n * Public clients use `none` and send only the client id. OAuth providers\n * otherwise default to `client_secret_post`, which sends both values in the\n * form body. `client_secret_basic` sends an HTTP Basic header instead. */\nexport type OAuth2TokenClientAuthMethod =\n | 'none'\n | 'client_secret_post'\n | 'client_secret_basic'\n\nexport type OAuth2UrlTemplateMetadataSpec =\n | {\n /** A complete HTTPS provider base URL, substituted for a URL template\n * placeholder such as `{accountUrl}`. */\n kind: 'base-url'\n /** Exact provider roots accepted for finite regional endpoint sets. */\n allowedBaseUrls?: readonly string[]\n /** HTTPS hostname suffixes accepted for tenant-specific provider roots. */\n allowedBaseUrlSuffixes?: readonly string[]\n /** Permit a caller-selected public HTTPS root for self-hosted providers. */\n requirePublicHttps?: boolean\n }\n | {\n /** One RFC 3986 unreserved path segment. The resolver encodes it and\n * rejects separators, dot segments, query data, and fragments. */\n kind: 'path-segment'\n }\n\ntype OAuth2AuthSpec = {\n kind: 'oauth2'\n /** OAuth2 grant type. Defaults to `'authorization_code'` (the interactive\n * 3-legged flow). Set `'client_credentials'` for machine-to-machine\n * providers (e.g. Paychex) that issue tokens directly from client\n * id/secret with no user-facing authorization step — `authorizationUrl`\n * is then omitted. */\n grantType?: 'authorization_code' | 'client_credentials'\n /** Authorization endpoint URL. Required for the `authorization_code`\n * grant; omitted for `client_credentials` (there is no authorize step). */\n authorizationUrl?: string\n /** Token endpoint URL. */\n tokenUrl: string\n /** Scopes requested in the authorization grant. The user UI shows\n * these so the customer knows what's being shared. */\n scopes: string[]\n /** Separator used when serializing multiple scopes into the authorization\n * URL. OAuth defaults to spaces; Zoho requires comma-delimited scopes. */\n scopeSeparator?: ' ' | ','\n /** Provider PKCE posture. Defaults to the host flow policy when omitted. */\n pkce?: 'required' | 'supported' | 'unsupported'\n /** Query parameter that receives the OAuth client id on the authorization\n * URL. Defaults to `client_id`; TikTok calls this value `client_key`. */\n authorizationClientIdParam?: string\n /** Whether the connector supports incremental authorization (Google\n * does; many don't). */\n incremental?: boolean\n /** Env-var name holding the OAuth client_id. */\n clientIdEnv: string\n /** Env-var name holding the OAuth client_secret. Omit only for public\n * clients whose tokenClientAuthMethod is `none`. */\n clientSecretEnv?: string\n /** Form parameter that receives the client id during token exchange.\n * Defaults to `client_id`; TikTok calls this value `client_key`. */\n tokenClientIdParam?: string\n /** Form parameter that receives the client secret during token exchange.\n * Defaults to `client_secret`. */\n tokenClientSecretParam?: string\n /** OAuth client authentication for the authorization-code token exchange.\n * Defaults to `client_secret_post` for backward compatibility. */\n tokenClientAuthMethod?: OAuth2TokenClientAuthMethod\n /** Optional extra params attached to the authorization URL (e.g.,\n * Google's `access_type=offline&prompt=consent` to obtain refresh\n * tokens). */\n extraAuthParams?: Record<string, string>\n /** Rules for metadata placeholders that represent provider roots or path\n * segments. Placeholders without a rule remain single DNS labels. */\n urlTemplateMetadata?: Readonly<Record<string, OAuth2UrlTemplateMetadataSpec>>\n /** Non-secret headers sent to the token endpoint. Use this for provider\n * identification headers such as Reddit's required User-Agent. The runtime\n * always owns Authorization and Content-Type. */\n tokenRequestHeaders?: Record<string, string>\n /** Whether to send the `scope` query param on the authorization URL.\n * Defaults to true. Set false for providers that reject a per-request\n * scope and pin scopes app-side (e.g. HelloSign/Dropbox Sign). */\n sendScopeParam?: boolean\n /** Declarative capture of provider-specific fields from the OAuth token\n * response into the connection's `metadata`, keyed by the connection-metadata\n * key to write → the token-response field to read. The counterpart to\n * `baseUrl.metadataKey`: providers like Gong return a per-customer host\n * (`api_base_url_for_customer`) at token exchange that every later call must\n * target, but the standard token parser keeps only the canonical OAuth\n * fields and drops the rest. Declaring the mapping here makes `completeAuth`\n * persist the value into `metadata` so `baseUrl` resolution finds it. Mark a\n * field `required` to fail the exchange loud when it is absent (surfacing the\n * break at connect, not first call). Optional and backward-compatible —\n * connectors that omit it are unaffected. */\n tokenMetadata?: Record<string, TokenMetadataSource>\n}\ntype ApiKeyAuthSpec = {\n kind: 'api-key'\n /** UI hint shown when collecting the key. */\n hint: string\n}\ntype SingleAuthSpec = OAuth2AuthSpec | ApiKeyAuthSpec | { kind: 'hmac' } | { kind: 'none' }\n\nexport type AuthSpec =\n | SingleAuthSpec\n | {\n kind: 'one_of'\n /** Preferred auth kind for setup UI and OAuth-connect flows. */\n preferred: 'oauth2' | 'api-key'\n /** Explicit supported auth modes. Do not infer fallback modes. */\n options: readonly [SingleAuthSpec, SingleAuthSpec, ...SingleAuthSpec[]]\n }\n\n/** Thrown by `executeMutation` when upstream rejects on CAS — caught and\n * rewrapped by MutationGuard. */\nexport class ResourceContention extends Error {\n override readonly name = 'ResourceContention'\n constructor(\n message: string,\n public readonly alternatives: unknown[] = [],\n public readonly currentState?: unknown,\n ) {\n super(message)\n }\n}\n\n/** Structured HTTP failure detail attached to the typed provider errors below.\n * The platform classifier reads `status`/`reason` off the thrown instance to\n * route a 403 by WHY it failed (credential vs config/permission vs quota)\n * instead of regexing a flattened message. */\nexport interface ProviderHttpErrorInit {\n /** Upstream HTTP status (e.g. 401, 403, 429). */\n status: number\n /** Provider-specific machine reason (e.g. Google's `accessNotConfigured`,\n * `dailyLimitExceeded`, `invalid_grant`) — the actionable signal. */\n reason?: string\n /** Parsed upstream error body, kept for diagnostics/audit. */\n body?: unknown\n}\n\n/** Thrown when the connector finds the user's grant has been revoked or the\n * access token is no longer valid AND refresh failed — reconnecting fixes it.\n * Surfaces to the UI as \"Reconnect required\". Carries optional structured\n * `{status, reason, body}` so the platform can confirm a 401/403 really is a\n * credential reason before flipping the connection to reconnect_required. */\nexport class CredentialsExpired extends Error {\n override readonly name = 'CredentialsExpired'\n readonly status?: number\n readonly reason?: string\n readonly body?: unknown\n constructor(\n message: string,\n public readonly dataSourceId: string,\n init?: ProviderHttpErrorInit,\n ) {\n super(message)\n this.status = init?.status\n this.reason = init?.reason\n this.body = init?.body\n }\n}\n\n/** Thrown when the provider rejects the call for a configuration or permission\n * reason that reconnecting the credential CANNOT fix — the API isn't enabled\n * for the project (`accessNotConfigured`), a required scope is missing\n * (`insufficientPermissions`), or the resource is forbidden\n * (`PERMISSION_DENIED`). Distinct from CredentialsExpired so the platform\n * surfaces \"fix the project/scope\" instead of sending the user into an\n * unresolvable reconnect loop. */\nexport class ProviderConfigError extends Error {\n override readonly name = 'ProviderConfigError'\n readonly status: number\n readonly reason?: string\n readonly body?: unknown\n constructor(\n message: string,\n public readonly dataSourceId: string,\n init: ProviderHttpErrorInit,\n ) {\n super(message)\n this.status = init.status\n this.reason = init.reason\n this.body = init.body\n }\n}\n\nexport interface ProviderRateLimitedInit extends ProviderHttpErrorInit {\n /** Wall-clock ms the caller should wait before retrying — resolved from the\n * provider's `Retry-After` when one was sent. */\n retryAfterMs?: number\n}\n\n/** Thrown when the provider throttles the call (quota / rate limit). Gmail and\n * Calendar surface daily/per-user limits as HTTP 403 with a quota `reason`\n * (`rateLimitExceeded`/`dailyLimitExceeded`/`userRateLimitExceeded`), NOT 429\n * — so it is classified by reason, not status, and must never read as a\n * credential failure. The message includes \"rate limit\" so a platform that\n * only inspects the message still routes it to rate-limited. */\nexport class ProviderRateLimited extends Error {\n override readonly name = 'ProviderRateLimited'\n readonly status: number\n readonly reason?: string\n readonly body?: unknown\n readonly retryAfterMs?: number\n constructor(\n message: string,\n public readonly dataSourceId: string,\n init: ProviderRateLimitedInit,\n ) {\n super(message)\n this.status = init.status\n this.reason = init.reason\n this.body = init.body\n this.retryAfterMs = init.retryAfterMs\n }\n}\n\nexport interface ConnectorManifestValidationIssue {\n path: string\n message: string\n}\n\nexport interface ConnectorManifestValidationResult {\n ok: boolean\n issues: ConnectorManifestValidationIssue[]\n}\n\n/** Validate the static connector manifest before a provider registers it.\n * This catches the expensive mistakes early: duplicate capability names,\n * mutation capabilities without CAS, authoritative fire-and-forget writes,\n * and invalid rate-limit specs. */\nexport function validateConnectorManifest(manifest: ConnectorManifest): ConnectorManifestValidationResult {\n const issues: ConnectorManifestValidationIssue[] = []\n if (!manifest.kind.trim()) issues.push({ path: 'kind', message: 'kind is required' })\n if (!manifest.displayName.trim()) issues.push({ path: 'displayName', message: 'displayName is required' })\n validateAuthSpec(manifest.auth, issues)\n const seen = new Set<string>()\n for (const [index, capability] of manifest.capabilities.entries()) {\n const path = `capabilities[${index}]`\n if (!capability.name.trim()) issues.push({ path: `${path}.name`, message: 'capability name is required' })\n if (seen.has(capability.name)) issues.push({ path: `${path}.name`, message: `duplicate capability name: ${capability.name}` })\n seen.add(capability.name)\n if (capability.class === 'mutation') {\n if (!capability.cas) issues.push({ path: `${path}.cas`, message: 'mutation capability must declare a CAS strategy' })\n if (manifest.defaultConsistencyModel === 'authoritative' && capability.cas === 'none') {\n issues.push({ path: `${path}.cas`, message: 'authoritative mutations cannot use cas=\"none\"' })\n }\n }\n }\n if (manifest.rateLimit) {\n if (!Number.isFinite(manifest.rateLimit.requests) || manifest.rateLimit.requests <= 0) {\n issues.push({ path: 'rateLimit.requests', message: 'rateLimit.requests must be positive' })\n }\n if (!Number.isFinite(manifest.rateLimit.windowMs) || manifest.rateLimit.windowMs <= 0) {\n issues.push({ path: 'rateLimit.windowMs', message: 'rateLimit.windowMs must be positive' })\n }\n }\n return { ok: issues.length === 0, issues }\n}\n\nfunction validateAuthSpec(auth: AuthSpec, issues: ConnectorManifestValidationIssue[]): void {\n if (auth.kind === 'oauth2') {\n validateOAuth2AuthSpec(auth, issues, 'auth')\n return\n }\n if (auth.kind !== 'one_of') return\n if (auth.options.length < 2) {\n issues.push({ path: 'auth.options', message: 'one_of auth must declare at least two options' })\n }\n const optionKinds = new Set(auth.options.map((option) => option.kind))\n if (!optionKinds.has(auth.preferred)) {\n issues.push({ path: 'auth.preferred', message: 'one_of preferred auth must match an option kind' })\n }\n for (const [index, option] of auth.options.entries()) {\n if (option.kind === 'oauth2') {\n validateOAuth2AuthSpec(option, issues, `auth.options[${index}]`)\n }\n }\n}\n\nfunction validateOAuth2AuthSpec(\n auth: OAuth2AuthSpec,\n issues: ConnectorManifestValidationIssue[],\n path: string,\n): void {\n if ((auth.grantType ?? 'authorization_code') === 'authorization_code' && !auth.authorizationUrl?.trim()) {\n issues.push({ path: `${path}.authorizationUrl`, message: 'authorization_code grant requires authorizationUrl' })\n }\n if (\n auth.pkce !== undefined &&\n auth.pkce !== 'required' &&\n auth.pkce !== 'supported' &&\n auth.pkce !== 'unsupported'\n ) {\n issues.push({\n path: `${path}.pkce`,\n message: 'pkce must be required, supported, or unsupported',\n })\n }\n for (const [key, spec] of Object.entries(auth.urlTemplateMetadata ?? {})) {\n if (spec.kind !== 'base-url' && spec.kind !== 'path-segment') {\n issues.push({\n path: `${path}.urlTemplateMetadata.${key}.kind`,\n message: 'OAuth URL metadata kind must be base-url or path-segment',\n })\n continue\n }\n const placeholder = `{${key}}`\n if (!auth.authorizationUrl?.includes(placeholder) && !auth.tokenUrl.includes(placeholder)) {\n issues.push({\n path: `${path}.urlTemplateMetadata.${key}`,\n message: `OAuth URL metadata is unused; add ${placeholder} to an OAuth endpoint`,\n })\n }\n if (spec.kind === 'base-url') {\n if (\n !spec.requirePublicHttps &&\n !spec.allowedBaseUrls?.length &&\n !spec.allowedBaseUrlSuffixes?.length\n ) {\n issues.push({\n path: `${path}.urlTemplateMetadata.${key}`,\n message: 'OAuth base URL metadata requires an allowlist or public HTTPS policy',\n })\n }\n for (const suffix of spec.allowedBaseUrlSuffixes ?? []) {\n if (!/^\\.[a-z0-9.-]+$/i.test(suffix)) {\n issues.push({\n path: `${path}.urlTemplateMetadata.${key}.allowedBaseUrlSuffixes`,\n message: 'OAuth base URL hostname suffixes must start with a dot',\n })\n }\n }\n } else {\n for (const endpoint of [auth.authorizationUrl, auth.tokenUrl]) {\n if (\n endpoint?.includes(placeholder) &&\n !isOAuthPathSegmentPlaceholder(endpoint, placeholder)\n ) {\n issues.push({\n path: `${path}.urlTemplateMetadata.${key}`,\n message: `OAuth path metadata ${placeholder} must occupy a complete URL path segment`,\n })\n }\n }\n }\n }\n if (\n auth.tokenClientAuthMethod !== undefined &&\n auth.tokenClientAuthMethod !== 'none' &&\n auth.tokenClientAuthMethod !== 'client_secret_post' &&\n auth.tokenClientAuthMethod !== 'client_secret_basic'\n ) {\n issues.push({\n path: `${path}.tokenClientAuthMethod`,\n message: 'tokenClientAuthMethod must be none, client_secret_post, or client_secret_basic',\n })\n }\n if (\n (auth.tokenClientAuthMethod ?? 'client_secret_post') !== 'none' &&\n !auth.clientSecretEnv?.trim()\n ) {\n issues.push({\n path: `${path}.clientSecretEnv`,\n message: 'confidential OAuth clients require clientSecretEnv',\n })\n }\n for (const header of Object.keys(auth.tokenRequestHeaders ?? {})) {\n if (header.toLowerCase() === 'authorization' || header.toLowerCase() === 'content-type') {\n issues.push({\n path: `${path}.tokenRequestHeaders.${header}`,\n message: 'tokenRequestHeaders cannot override Authorization or Content-Type',\n })\n }\n }\n}\n\nfunction isOAuthPathSegmentPlaceholder(template: string, placeholder: string): boolean {\n const schemeIndex = template.indexOf('://')\n const rootEnd = schemeIndex >= 0\n ? schemeIndex + 3\n : template.startsWith('{')\n ? template.indexOf('}') + 1\n : 0\n const pathStart = template.indexOf('/', rootEnd)\n const queryStart = template.search(/[?#]/)\n let cursor = 0\n while (true) {\n const index = template.indexOf(placeholder, cursor)\n if (index < 0) return true\n const end = index + placeholder.length\n if (\n pathStart < 0 ||\n index < pathStart ||\n (queryStart >= 0 && index >= queryStart) ||\n template[index - 1] !== '/' ||\n (end < template.length && template[end] !== '/' && template[end] !== '?' && template[end] !== '#')\n ) {\n return false\n }\n cursor = end\n }\n}\n\nexport function assertValidConnectorManifest(manifest: ConnectorManifest): void {\n const result = validateConnectorManifest(manifest)\n if (!result.ok) {\n throw new Error(`Invalid connector manifest ${manifest.kind || '<unknown>'}: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join('; ')}`)\n }\n}\n","/**\n * @stable Tangle Identity — `id.tangle.tools` connector + verifier.\n *\n * This is the *identity* substrate every Tangle product (legal, tax, gtm,\n * creative, agent-builder, sandbox, evals, …) sits on. The shape mirrors\n * what `tcloud` and the sandbox `PlatformClient` already implement against\n * the platform repo at `agent-dev-container-collab-m1/products/platform`,\n * so consumers can switch from a hand-rolled fetch loop to this adapter\n * without changing the wire protocol.\n *\n * What it covers, end-to-end:\n *\n * verify_token({ token })\n * → { kind: 'api_key' | 'session', valid, userId?, workspaceId?, scopes, expiresAt? }\n * Verifies a single credential. Two token shapes are recognized:\n * - `sk-tan-*` API keys — POST /v1/keys/verify with the service token.\n * Returns `userId`, `keyId`, `product`, granted scopes (`allowedModels`\n * + product flag), and budget metadata.\n * - Better Auth session cookies / Bearer session tokens — GET\n * /api/auth/get-session with the credential forwarded as-is.\n * Returns the user row.\n * Wrong-issuer / tampered / expired all surface as `{ valid: false }`\n * with a stable `reason`. Never throws on bad-token; only throws when\n * id.tangle.tools itself is unreachable or returns a 5xx (lets callers\n * fail closed without confusing token failures with platform failures).\n *\n * get_user({ userId })\n * → { id, email, name?, image? }\n * Read-only profile lookup. Service-token authenticated. Used by\n * `requireTangleAuth` middleware to hydrate the request context when\n * a downstream wants the user's email without re-verifying.\n *\n * list_workspaces({ userId })\n * → { workspaces: [{ id, name, role, isPersonal }] }\n * A workspace = a Tangle team. Personal workspace is rendered with\n * `isPersonal: true` (own id === userId on the platform schema).\n * Mirrors `GET /v1/teams` on the platform side.\n *\n * switch_workspace({ workspaceId })\n * → { ok: true, workspaceId, scopes }\n * Stateless on this adapter — the caller persists the workspaceId in\n * its own session. The connector returns the workspace's effective\n * scope set so the caller can immediately filter capability discovery\n * against the new workspace's grant matrix.\n *\n * revoke_session({ token })\n * → { ok: true }\n * For session tokens: POST /api/auth/sign-out. For API keys: DELETE\n * /v1/keys/{id}. The adapter detects the kind from the prefix.\n *\n * Auth:\n * - **Service token** (`Bearer svc_*`) is required for `verify_token` of\n * `sk-tan-*` keys, `get_user`, `list_workspaces`, and `revoke_session`\n * of API keys.\n * - **Session cookie / Bearer session** is forwarded as-is for session\n * verification and session revocation.\n *\n * The adapter is stateless. Caller resolves `serviceToken` + `baseUrl` from\n * env (`TANGLE_PLATFORM_URL`, `TANGLE_SERVICE_TOKEN`) and passes them at\n * construction. The adapter never reads from `process.env` itself — this\n * keeps it CF Worker compatible (no Node-only env semantics) and lets\n * tests inject a fake fetch + service token in one place.\n */\n\nimport {\n type CapabilityMutationResult,\n type CapabilityReadResult,\n type ConnectorAdapter,\n type ConnectorInvocation,\n CredentialsExpired,\n} from '../types.js'\n\n/** Default platform URL (matches `DEFAULT_PLATFORM_URL` in tcloud). */\nexport const DEFAULT_TANGLE_PLATFORM_URL = 'https://id.tangle.tools'\n\n/** Hard timeout for every outbound platform call. Matches sandbox's\n * `PLATFORM_FETCH_TIMEOUT_MS` so a slow id.tangle.tools doesn't tie up\n * the consumer's request handler. */\nconst PLATFORM_FETCH_TIMEOUT_MS = 5_000\n\n/** API-key prefix the platform issues. Used to disambiguate token kind\n * without a round-trip. */\nexport const TANGLE_API_KEY_PREFIX = 'sk-tan-'\n\n/** Service-token prefix. Mirrored from the platform's middleware so we\n * can refuse to forward service tokens through the user-session path. */\nexport const TANGLE_SERVICE_TOKEN_PREFIX = 'svc_'\n\nexport interface TangleIdentityOptions {\n /** Base URL of the id.tangle.tools deployment (no trailing slash). */\n baseUrl?: string\n /**\n * Service token (`svc_*`) used for S2S calls (verify, provision, etc.).\n * Required for API-key verification and the user/workspace read paths.\n * Omit only for session-only flows on a deployment that exposes those\n * routes unauthenticated (rare; never in production).\n */\n serviceToken?: string\n /** Service identity claimed in the `X-Service-Name` header. */\n serviceName?: string\n /** Injected fetch — defaults to global. Tests pass a vi mock. */\n fetchImpl?: typeof fetch\n /** Per-call timeout override (default {@link PLATFORM_FETCH_TIMEOUT_MS}). */\n timeoutMs?: number\n}\n\n/** Stable result of a token verification. `valid: false` is returned for\n * every recognizable bad-token shape (expired, tampered, wrong issuer,\n * unknown kind); only true platform unreachability throws. */\nexport type TangleTokenVerifyResult =\n | {\n valid: true\n kind: 'api_key' | 'session'\n userId: string\n /** Active workspace at the moment of issue, if the credential is\n * workspace-scoped (team-owned API key). Personal credentials\n * return the user's personal workspace (== `userId`). */\n workspaceId: string\n scopes: string[]\n /** Wall-clock ms epoch when the credential expires. Undefined for\n * non-expiring credentials (most session cookies are sliding). */\n expiresAt?: number\n /** Stable id of the credential row, when known (key.id for API\n * keys, session.id for sessions). Useful for revoke + audit. */\n credentialId?: string\n /** Product the credential is scoped to, when known. */\n product?: string\n /** Owner shape — `user` for personal credentials, `team` for\n * team-owned API keys. Always matches the workspace's owner type. */\n ownerType: 'user' | 'team'\n }\n | {\n valid: false\n /** Stable reason code: `tampered`, `expired`, `revoked`,\n * `wrong_issuer`, `unknown_kind`, `service_token_refused`. */\n reason: TangleTokenVerifyFailure\n }\n\nexport type TangleTokenVerifyFailure =\n | 'tampered'\n | 'expired'\n | 'revoked'\n | 'wrong_issuer'\n | 'unknown_kind'\n | 'service_token_refused'\n | 'malformed'\n\nexport interface TangleUserSummary {\n id: string\n email?: string\n name?: string | null\n image?: string | null\n}\n\nexport interface TangleWorkspaceSummary {\n id: string\n name: string\n role: 'owner' | 'admin' | 'member'\n isPersonal: boolean\n /** Effective scope set for the calling user inside this workspace.\n * Sourced from the team's plan + per-product policy on the platform. */\n scopes: string[]\n}\n\n/** Thrown when id.tangle.tools is unreachable or returns 5xx. NOT thrown\n * for bad-token responses — those round-trip as `{ valid: false }`. */\nexport class TangleIdentityUnreachableError extends Error {\n override readonly name = 'TangleIdentityUnreachableError'\n readonly status?: number\n constructor(message: string, opts?: { status?: number; cause?: unknown }) {\n super(message, opts)\n this.status = opts?.status\n }\n}\n\n/** Build a `ConnectorAdapter` exposing id.tangle.tools as a first-party\n * integration. The adapter participates in the standard discovery /\n * capability gating loop, so a product can list identity ops alongside\n * Gmail / Stripe / etc. in the same tool registry. */\nexport function tangleIdentity(opts: TangleIdentityOptions = {}): ConnectorAdapter {\n const client = createTangleIdentityClient(opts)\n const adapter: ConnectorAdapter = {\n manifest: {\n kind: 'tangle-id',\n displayName: 'Tangle Identity',\n description:\n '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.',\n auth: { kind: 'api-key', hint: 'Tangle platform service token (svc_*).' },\n category: 'other',\n defaultConsistencyModel: 'authoritative',\n // 50 req/s per service token matches the platform's documented\n // S2S budget. We meter on our side so a chatty product doesn't\n // burn the shared service-token quota and block the rest.\n rateLimit: { requests: 50, windowMs: 1_000, scope: 'oauth-client' },\n capabilities: [\n {\n name: 'verify_token',\n class: 'read',\n description:\n 'Verify a session cookie or sk-tan-* API key. Returns { valid, userId, workspaceId, scopes, expiresAt } or { valid: false, reason }.',\n parameters: {\n type: 'object',\n properties: {\n token: { type: 'string', minLength: 1 },\n },\n required: ['token'],\n },\n },\n {\n name: 'get_user',\n class: 'read',\n description: 'Read the user profile (id, email, name, image) for a verified userId.',\n parameters: {\n type: 'object',\n properties: { userId: { type: 'string', minLength: 1 } },\n required: ['userId'],\n },\n },\n {\n name: 'list_workspaces',\n class: 'read',\n description: 'List workspaces (teams + the personal workspace) the user belongs to, with role + effective scopes.',\n parameters: {\n type: 'object',\n properties: { userId: { type: 'string', minLength: 1 } },\n required: ['userId'],\n },\n },\n {\n name: 'switch_workspace',\n class: 'mutation',\n description:\n 'Resolve a workspace by id and return its effective scope set. Stateless on this adapter — caller persists the workspaceId in its own session store.',\n parameters: {\n type: 'object',\n properties: {\n userId: { type: 'string', minLength: 1 },\n workspaceId: { type: 'string', minLength: 1 },\n },\n required: ['userId', 'workspaceId'],\n },\n cas: 'native-idempotency',\n externalEffect: false,\n },\n {\n name: 'revoke_session',\n class: 'mutation',\n description: 'Revoke a session token or API key. Idempotent.',\n parameters: {\n type: 'object',\n properties: { token: { type: 'string', minLength: 1 } },\n required: ['token'],\n },\n cas: 'native-idempotency',\n externalEffect: true,\n },\n {\n name: 'workspaces.create',\n class: 'mutation',\n description:\n 'Create a new workspace (team) owned by the calling user. Idempotent by display name + owner — re-issuing with the same name returns the existing workspace.',\n parameters: {\n type: 'object',\n properties: {\n userId: { type: 'string', minLength: 1 },\n name: { type: 'string', minLength: 1 },\n slug: { type: 'string', minLength: 1 },\n },\n required: ['userId', 'name'],\n },\n cas: 'native-idempotency',\n externalEffect: true,\n },\n {\n name: 'workspaces.delete',\n class: 'mutation',\n description:\n 'Delete a workspace. Refuses to delete a user personal workspace. Idempotent: 404 is treated as a no-op.',\n parameters: {\n type: 'object',\n properties: { workspaceId: { type: 'string', minLength: 1 } },\n required: ['workspaceId'],\n },\n cas: 'native-idempotency',\n externalEffect: true,\n },\n {\n name: 'members.invite',\n class: 'mutation',\n description:\n 'Invite a member to a workspace by email. Idempotent: re-inviting the same email returns the existing invitation.',\n parameters: {\n type: 'object',\n properties: {\n workspaceId: { type: 'string', minLength: 1 },\n email: { type: 'string', minLength: 3 },\n role: { type: 'string', enum: ['owner', 'admin', 'member'] },\n },\n required: ['workspaceId', 'email'],\n },\n cas: 'native-idempotency',\n externalEffect: true,\n },\n {\n name: 'members.remove',\n class: 'mutation',\n description:\n 'Remove a member from a workspace by userId. Idempotent: 404 is treated as a no-op.',\n parameters: {\n type: 'object',\n properties: {\n workspaceId: { type: 'string', minLength: 1 },\n userId: { type: 'string', minLength: 1 },\n },\n required: ['workspaceId', 'userId'],\n },\n cas: 'native-idempotency',\n externalEffect: true,\n },\n ],\n },\n\n async executeRead(inv: ConnectorInvocation): Promise<CapabilityReadResult> {\n if (inv.capabilityName === 'verify_token') {\n const token = readStringArg(inv.args, 'token')\n const result = await client.verifyToken(token)\n return { data: result, fetchedAt: Date.now() }\n }\n if (inv.capabilityName === 'get_user') {\n const userId = readStringArg(inv.args, 'userId')\n const user = await client.getUser(userId)\n return { data: user, fetchedAt: Date.now() }\n }\n if (inv.capabilityName === 'list_workspaces') {\n const userId = readStringArg(inv.args, 'userId')\n const workspaces = await client.listWorkspaces(userId)\n return { data: { workspaces }, fetchedAt: Date.now() }\n }\n throw new Error(`tangle-id: unknown read capability ${inv.capabilityName}`)\n },\n\n async executeMutation(inv: ConnectorInvocation): Promise<CapabilityMutationResult> {\n if (inv.capabilityName === 'switch_workspace') {\n const userId = readStringArg(inv.args, 'userId')\n const workspaceId = readStringArg(inv.args, 'workspaceId')\n const result = await client.switchWorkspace(userId, workspaceId)\n return {\n status: 'committed',\n data: result,\n committedAt: Date.now(),\n idempotentReplay: false,\n }\n }\n if (inv.capabilityName === 'revoke_session') {\n const token = readStringArg(inv.args, 'token')\n await client.revokeSession(token)\n return {\n status: 'committed',\n data: { ok: true },\n committedAt: Date.now(),\n idempotentReplay: false,\n }\n }\n if (inv.capabilityName === 'workspaces.create') {\n const userId = readStringArg(inv.args, 'userId')\n const name = readStringArg(inv.args, 'name')\n const slug = readOptionalStringArg(inv.args, 'slug')\n const workspace = await client.createWorkspace(userId, { name, slug })\n return {\n status: 'committed',\n data: workspace,\n committedAt: Date.now(),\n idempotentReplay: false,\n }\n }\n if (inv.capabilityName === 'workspaces.delete') {\n const workspaceId = readStringArg(inv.args, 'workspaceId')\n await client.deleteWorkspace(workspaceId)\n return {\n status: 'committed',\n data: { ok: true, workspaceId },\n committedAt: Date.now(),\n idempotentReplay: false,\n }\n }\n if (inv.capabilityName === 'members.invite') {\n const workspaceId = readStringArg(inv.args, 'workspaceId')\n const email = readStringArg(inv.args, 'email')\n const role = readOptionalRole(inv.args, 'role')\n const invitation = await client.inviteMember(workspaceId, email, role)\n return {\n status: 'committed',\n data: invitation,\n committedAt: Date.now(),\n idempotentReplay: false,\n }\n }\n if (inv.capabilityName === 'members.remove') {\n const workspaceId = readStringArg(inv.args, 'workspaceId')\n const userId = readStringArg(inv.args, 'userId')\n await client.removeMember(workspaceId, userId)\n return {\n status: 'committed',\n data: { ok: true, workspaceId, userId },\n committedAt: Date.now(),\n idempotentReplay: false,\n }\n }\n throw new Error(`tangle-id: unknown mutation capability ${inv.capabilityName}`)\n },\n\n async test(source) {\n try {\n // Cheapest probe: hit the platform's /health endpoint. Doesn't\n // burn the service-token quota and proves the deployment is up.\n const ok = await client.ping()\n if (!ok) return { ok: false, reason: 'id.tangle.tools /health returned non-200' }\n return { ok: true }\n } catch (err) {\n if (err instanceof CredentialsExpired) {\n return { ok: false, reason: 'service token rejected — rotate TANGLE_SERVICE_TOKEN' }\n }\n return { ok: false, reason: err instanceof Error ? err.message : String(err) }\n }\n },\n }\n return adapter\n}\n\n/** Low-level HTTP client used by the adapter. Exported so consumers\n * (middleware, connect routes, custom apps) can hit id.tangle.tools\n * without going through the connector pipeline. */\nexport interface TangleIdentityClient {\n verifyToken(token: string): Promise<TangleTokenVerifyResult>\n getUser(userId: string): Promise<TangleUserSummary>\n listWorkspaces(userId: string): Promise<TangleWorkspaceSummary[]>\n switchWorkspace(\n userId: string,\n workspaceId: string,\n ): Promise<{ ok: true; workspaceId: string; scopes: string[] }>\n revokeSession(token: string): Promise<void>\n /** Create a new workspace owned by `userId`. The platform's\n * `POST /v1/teams` returns the persisted row. Idempotent on\n * `(ownerId, name)` upstream; conflicts are unwrapped to the\n * existing row, not surfaced as errors. */\n createWorkspace(\n userId: string,\n spec: { name: string; slug?: string },\n ): Promise<TangleWorkspaceSummary>\n /** Delete a workspace. Refuses to delete the user's personal\n * workspace (the platform-side `/v1/teams/{id}` returns 409). */\n deleteWorkspace(workspaceId: string): Promise<void>\n /** Invite a member to a workspace by email. Idempotent: re-issuing\n * the same invite returns the existing pending invitation row. */\n inviteMember(\n workspaceId: string,\n email: string,\n role?: TangleWorkspaceSummary['role'],\n ): Promise<TangleInvitationSummary>\n /** Remove a member from a workspace by `userId`. 404 is a no-op. */\n removeMember(workspaceId: string, userId: string): Promise<void>\n ping(): Promise<boolean>\n}\n\nexport interface TangleInvitationSummary {\n id: string\n workspaceId: string\n email: string\n role: TangleWorkspaceSummary['role']\n status: 'pending' | 'accepted' | 'revoked'\n}\n\nexport function createTangleIdentityClient(opts: TangleIdentityOptions = {}): TangleIdentityClient {\n const baseUrl = (opts.baseUrl ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\\/+$/, '')\n const serviceToken = opts.serviceToken\n const serviceName = opts.serviceName ?? 'integrations'\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? PLATFORM_FETCH_TIMEOUT_MS\n\n function s2sHeaders(): Record<string, string> {\n if (!serviceToken) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: serviceToken is required for service-to-service calls (verify, get_user, list_workspaces, revoke)',\n )\n }\n return {\n 'content-type': 'application/json',\n authorization: `Bearer ${serviceToken}`,\n 'x-service-name': serviceName,\n }\n }\n\n async function jsonFetch(path: string, init: RequestInit): Promise<Response> {\n let res: Response\n try {\n res = await fetchImpl(`${baseUrl}${path}`, {\n ...init,\n signal: init.signal ?? AbortSignal.timeout(timeoutMs),\n })\n } catch (err) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: request to ${path} failed`,\n { cause: err },\n )\n }\n return res\n }\n\n async function readErrorDetail(res: Response): Promise<string> {\n try {\n const text = await res.text()\n return text.trim().slice(0, 200)\n } catch {\n return ''\n }\n }\n\n async function verifyApiKey(token: string): Promise<TangleTokenVerifyResult> {\n const res = await jsonFetch('/v1/keys/verify', {\n method: 'POST',\n headers: s2sHeaders(),\n body: JSON.stringify({ key: token }),\n })\n if (res.status === 401) {\n // Service token rejected — distinct from \"token is bad\". The\n // platform middleware refuses our svc_* credential. Surface that\n // as `service_token_refused` so the caller can rotate.\n return { valid: false, reason: 'service_token_refused' }\n }\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: /v1/keys/verify returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n const body = (await res.json().catch(() => null)) as\n | {\n valid?: boolean\n userId?: string\n ownerId?: string\n ownerType?: 'user' | 'team'\n keyId?: string\n product?: string\n allowedModels?: unknown\n expiresAt?: string\n }\n | null\n if (!body || typeof body.valid !== 'boolean') {\n return { valid: false, reason: 'malformed' }\n }\n if (!body.valid || !body.userId) {\n return { valid: false, reason: 'revoked' }\n }\n const scopes = Array.isArray(body.allowedModels)\n ? body.allowedModels.filter((value): value is string => typeof value === 'string')\n : []\n if (body.product) scopes.push(`product:${body.product}`)\n const expiresAt =\n typeof body.expiresAt === 'string' && body.expiresAt\n ? Date.parse(body.expiresAt)\n : undefined\n return {\n valid: true,\n kind: 'api_key',\n userId: body.userId,\n workspaceId: body.ownerType === 'team' && body.ownerId ? body.ownerId : body.userId,\n ownerType: body.ownerType ?? 'user',\n scopes,\n ...(Number.isFinite(expiresAt) ? { expiresAt: expiresAt as number } : {}),\n ...(body.keyId ? { credentialId: body.keyId } : {}),\n ...(body.product ? { product: body.product } : {}),\n }\n }\n\n async function verifySession(token: string): Promise<TangleTokenVerifyResult> {\n // Better Auth's get-session endpoint accepts either:\n // - Cookie header `better-auth.session_token=<jwt>` (browser flow)\n // - Bearer token (mobile / CLI flow), which Better Auth's bearer\n // plugin extracts and resolves to a session row.\n // We always send Bearer; the platform's auth handler treats absent\n // cookie + present Bearer identically.\n const res = await jsonFetch('/api/auth/get-session', {\n method: 'GET',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${token}`,\n },\n })\n if (res.status === 401 || res.status === 403) {\n return { valid: false, reason: 'expired' }\n }\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: /api/auth/get-session returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n const body = (await res.json().catch(() => null)) as\n | {\n user?: { id?: string; email?: string }\n session?: { id?: string; expiresAt?: string; activeTeamId?: string | null }\n }\n | null\n if (!body || !body.user || typeof body.user.id !== 'string') {\n return { valid: false, reason: 'expired' }\n }\n const expiresAtRaw = body.session?.expiresAt\n const expiresAt = expiresAtRaw ? Date.parse(expiresAtRaw) : NaN\n return {\n valid: true,\n kind: 'session',\n userId: body.user.id,\n workspaceId: body.session?.activeTeamId || body.user.id,\n ownerType: body.session?.activeTeamId ? 'team' : 'user',\n scopes: [],\n ...(Number.isFinite(expiresAt) ? { expiresAt } : {}),\n ...(body.session?.id ? { credentialId: body.session.id } : {}),\n }\n }\n\n return {\n async verifyToken(token: string): Promise<TangleTokenVerifyResult> {\n if (!token || typeof token !== 'string') {\n return { valid: false, reason: 'malformed' }\n }\n if (token.startsWith(TANGLE_SERVICE_TOKEN_PREFIX)) {\n // Service tokens MUST NOT be accepted as user identity. The\n // platform middleware sets `authMethod: 'service_token'` and\n // refuses every user-bound route on that path; mirror the\n // refusal here so a misconfigured product can't accidentally\n // treat an svc_* as the calling user.\n return { valid: false, reason: 'service_token_refused' }\n }\n if (token.startsWith(TANGLE_API_KEY_PREFIX)) {\n return verifyApiKey(token)\n }\n // Anything else — treat as a session bearer (Better Auth-emitted\n // JWTs are opaque to us). Wrong-issuer / random-string lands\n // on the 401 path inside `verifySession` and round-trips as\n // `expired`. We do not pre-validate JWT signatures — only the\n // platform holds the signing secret.\n return verifySession(token)\n },\n\n async getUser(userId: string): Promise<TangleUserSummary> {\n if (!userId) {\n throw new TangleIdentityUnreachableError('tangle-id: getUser requires a non-empty userId')\n }\n const res = await jsonFetch(`/v1/users/${encodeURIComponent(userId)}`, {\n method: 'GET',\n headers: s2sHeaders(),\n })\n if (res.status === 404) {\n throw new TangleIdentityUnreachableError(`tangle-id: user ${userId} not found`, { status: 404 })\n }\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: /v1/users/${userId} returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n const body = (await res.json().catch(() => null)) as\n | { success?: boolean; data?: { id?: string; email?: string; name?: string | null; image?: string | null } }\n | null\n const data = body?.data\n if (!data || typeof data.id !== 'string') {\n throw new TangleIdentityUnreachableError('tangle-id: /v1/users response had an invalid shape')\n }\n return {\n id: data.id,\n ...(typeof data.email === 'string' ? { email: data.email } : {}),\n ...(data.name !== undefined ? { name: data.name } : {}),\n ...(data.image !== undefined ? { image: data.image } : {}),\n }\n },\n\n async listWorkspaces(userId: string): Promise<TangleWorkspaceSummary[]> {\n const res = await jsonFetch('/v1/teams', {\n method: 'GET',\n headers: { ...s2sHeaders(), 'x-platform-user-id': userId },\n })\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: /v1/teams returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n const body = (await res.json().catch(() => null)) as\n | {\n success?: boolean\n data?: Array<{\n id?: string\n name?: string\n role?: string\n isPersonal?: boolean\n scopes?: unknown\n }>\n }\n | null\n const rows = Array.isArray(body?.data) ? body!.data : []\n const workspaces: TangleWorkspaceSummary[] = []\n for (const row of rows) {\n if (!row || typeof row.id !== 'string' || typeof row.name !== 'string') continue\n const role: TangleWorkspaceSummary['role'] =\n row.role === 'owner' || row.role === 'admin' ? row.role : 'member'\n const scopes = Array.isArray(row.scopes)\n ? row.scopes.filter((value): value is string => typeof value === 'string')\n : []\n workspaces.push({\n id: row.id,\n name: row.name,\n role,\n isPersonal: Boolean(row.isPersonal) || row.id === userId,\n scopes,\n })\n }\n return workspaces\n },\n\n async switchWorkspace(userId, workspaceId) {\n const workspaces = await this.listWorkspaces(userId)\n const match = workspaces.find((w) => w.id === workspaceId)\n if (!match) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: workspace ${workspaceId} not found for user ${userId}`,\n { status: 404 },\n )\n }\n return { ok: true, workspaceId: match.id, scopes: match.scopes }\n },\n\n async revokeSession(token: string): Promise<void> {\n if (!token) return\n if (token.startsWith(TANGLE_SERVICE_TOKEN_PREFIX)) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: refusing to revoke a service token — rotate it instead',\n )\n }\n if (token.startsWith(TANGLE_API_KEY_PREFIX)) {\n // We don't know the key id until we verify; do that first so\n // revoke is keyed by id (the only thing the platform's DELETE\n // /v1/keys/{id} accepts). Bad-key responses are no-ops.\n const v = await verifyApiKey(token)\n if (!v.valid || !v.credentialId) return\n const res = await jsonFetch(`/v1/keys/${encodeURIComponent(v.credentialId)}`, {\n method: 'DELETE',\n headers: s2sHeaders(),\n })\n if (res.status === 404) return\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: DELETE /v1/keys/${v.credentialId} returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n return\n }\n // Session token — Better Auth's sign-out endpoint accepts the\n // session via Bearer + Cookie. We forward the credential as-is.\n const res = await jsonFetch('/api/auth/sign-out', {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${token}`,\n },\n body: '{}',\n })\n // Better Auth returns 200 on a fresh sign-out and 200/401 on a\n // stale one; either way the session is no longer usable.\n if (res.status >= 500) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: /api/auth/sign-out returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n },\n\n async createWorkspace(userId, spec) {\n if (!userId) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: createWorkspace requires a non-empty userId',\n )\n }\n const res = await jsonFetch('/v1/teams', {\n method: 'POST',\n headers: { ...s2sHeaders(), 'x-platform-user-id': userId },\n body: JSON.stringify({\n name: spec.name,\n ...(spec.slug ? { slug: spec.slug } : {}),\n }),\n })\n if (res.status === 401) {\n throw new CredentialsExpired(\n 'tangle-id: service token rejected on POST /v1/teams',\n 'tangle-id',\n )\n }\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: POST /v1/teams returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n const body = (await res.json().catch(() => null)) as\n | {\n success?: boolean\n data?: { id?: string; name?: string; role?: string; isPersonal?: boolean; scopes?: unknown }\n }\n | null\n const data = body?.data\n if (!data || typeof data.id !== 'string' || typeof data.name !== 'string') {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: POST /v1/teams response had an invalid shape',\n )\n }\n const role: TangleWorkspaceSummary['role'] =\n data.role === 'owner' || data.role === 'admin' ? data.role : 'owner'\n const scopes = Array.isArray(data.scopes)\n ? data.scopes.filter((value): value is string => typeof value === 'string')\n : []\n return {\n id: data.id,\n name: data.name,\n role,\n isPersonal: Boolean(data.isPersonal) || data.id === userId,\n scopes,\n }\n },\n\n async deleteWorkspace(workspaceId) {\n if (!workspaceId) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: deleteWorkspace requires a non-empty workspaceId',\n )\n }\n const res = await jsonFetch(`/v1/teams/${encodeURIComponent(workspaceId)}`, {\n method: 'DELETE',\n headers: s2sHeaders(),\n })\n if (res.status === 401) {\n throw new CredentialsExpired(\n 'tangle-id: service token rejected on DELETE /v1/teams',\n 'tangle-id',\n )\n }\n if (res.status === 404) return\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: DELETE /v1/teams/${workspaceId} returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n },\n\n async inviteMember(workspaceId, email, role) {\n if (!workspaceId) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: inviteMember requires a non-empty workspaceId',\n )\n }\n if (!email) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: inviteMember requires a non-empty email',\n )\n }\n const res = await jsonFetch(\n `/v1/teams/${encodeURIComponent(workspaceId)}/invitations`,\n {\n method: 'POST',\n headers: s2sHeaders(),\n body: JSON.stringify({\n email,\n ...(role ? { role } : {}),\n }),\n },\n )\n if (res.status === 401) {\n throw new CredentialsExpired(\n 'tangle-id: service token rejected on POST invitation',\n 'tangle-id',\n )\n }\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: POST /v1/teams/${workspaceId}/invitations returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n const body = (await res.json().catch(() => null)) as\n | {\n success?: boolean\n data?: {\n id?: string\n workspaceId?: string\n teamId?: string\n email?: string\n role?: string\n status?: string\n }\n }\n | null\n const data = body?.data\n if (!data || typeof data.id !== 'string') {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: POST invitation response had an invalid shape',\n )\n }\n const resolvedRole: TangleWorkspaceSummary['role'] =\n data.role === 'owner' || data.role === 'admin' ? data.role : 'member'\n const status: TangleInvitationSummary['status'] =\n data.status === 'accepted' || data.status === 'revoked' ? data.status : 'pending'\n return {\n id: data.id,\n workspaceId: data.workspaceId ?? data.teamId ?? workspaceId,\n email: data.email ?? email,\n role: resolvedRole,\n status,\n }\n },\n\n async removeMember(workspaceId, userId) {\n if (!workspaceId || !userId) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: removeMember requires non-empty workspaceId and userId',\n )\n }\n const res = await jsonFetch(\n `/v1/teams/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(userId)}`,\n {\n method: 'DELETE',\n headers: s2sHeaders(),\n },\n )\n if (res.status === 401) {\n throw new CredentialsExpired(\n 'tangle-id: service token rejected on DELETE member',\n 'tangle-id',\n )\n }\n if (res.status === 404) return\n if (!res.ok) {\n throw new TangleIdentityUnreachableError(\n `tangle-id: DELETE /v1/teams/${workspaceId}/members/${userId} returned ${res.status}: ${await readErrorDetail(res)}`,\n { status: res.status },\n )\n }\n },\n\n async ping(): Promise<boolean> {\n const res = await jsonFetch('/health', { method: 'GET' })\n return res.ok\n },\n }\n}\n\nfunction readStringArg(args: Record<string, unknown>, key: string): string {\n const value = args[key]\n if (typeof value !== 'string' || !value) {\n throw new Error(`tangle-id: missing required argument \"${key}\"`)\n }\n return value\n}\n\nfunction readOptionalStringArg(args: Record<string, unknown>, key: string): string | undefined {\n const value = args[key]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string' || !value) return undefined\n return value\n}\n\nfunction readOptionalRole(\n args: Record<string, unknown>,\n key: string,\n): TangleWorkspaceSummary['role'] | undefined {\n const value = args[key]\n if (value === 'owner' || value === 'admin' || value === 'member') return value\n return undefined\n}\n"],"mappings":";AAkeO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAE5C,YACE,SACgB,eAA0B,CAAC,GAC3B,cAChB;AACA,UAAM,OAAO;AAHG;AACA;AAAA,EAGlB;AAAA,EAJkB;AAAA,EACA;AAAA,EAJA,OAAO;AAQ3B;AAqBO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAK5C,YACE,SACgB,cAChB,MACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,SAAS,MAAM;AACpB,SAAK,SAAS,MAAM;AACpB,SAAK,OAAO,MAAM;AAAA,EACpB;AAAA,EAPkB;AAAA,EANA,OAAO;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAWX;AASO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAK7C,YACE,SACgB,cAChB,MACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,SAAS,KAAK;AACnB,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK;AAAA,EACnB;AAAA,EAPkB;AAAA,EANA,OAAO;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAWX;AAcO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAM7C,YACE,SACgB,cAChB,MACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,SAAS,KAAK;AACnB,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK;AACjB,SAAK,eAAe,KAAK;AAAA,EAC3B;AAAA,EARkB;AAAA,EAPA,OAAO;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAYX;AAgBO,SAAS,0BAA0B,UAAgE;AACxG,QAAM,SAA6C,CAAC;AACpD,MAAI,CAAC,SAAS,KAAK,KAAK,EAAG,QAAO,KAAK,EAAE,MAAM,QAAQ,SAAS,mBAAmB,CAAC;AACpF,MAAI,CAAC,SAAS,YAAY,KAAK,EAAG,QAAO,KAAK,EAAE,MAAM,eAAe,SAAS,0BAA0B,CAAC;AACzG,mBAAiB,SAAS,MAAM,MAAM;AACtC,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,CAAC,OAAO,UAAU,KAAK,SAAS,aAAa,QAAQ,GAAG;AACjE,UAAM,OAAO,gBAAgB,KAAK;AAClC,QAAI,CAAC,WAAW,KAAK,KAAK,EAAG,QAAO,KAAK,EAAE,MAAM,GAAG,IAAI,SAAS,SAAS,8BAA8B,CAAC;AACzG,QAAI,KAAK,IAAI,WAAW,IAAI,EAAG,QAAO,KAAK,EAAE,MAAM,GAAG,IAAI,SAAS,SAAS,8BAA8B,WAAW,IAAI,GAAG,CAAC;AAC7H,SAAK,IAAI,WAAW,IAAI;AACxB,QAAI,WAAW,UAAU,YAAY;AACnC,UAAI,CAAC,WAAW,IAAK,QAAO,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,SAAS,kDAAkD,CAAC;AACpH,UAAI,SAAS,4BAA4B,mBAAmB,WAAW,QAAQ,QAAQ;AACrF,eAAO,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,SAAS,gDAAgD,CAAC;AAAA,MAC/F;AAAA,IACF;AAAA,EACF;AACA,MAAI,SAAS,WAAW;AACtB,QAAI,CAAC,OAAO,SAAS,SAAS,UAAU,QAAQ,KAAK,SAAS,UAAU,YAAY,GAAG;AACrF,aAAO,KAAK,EAAE,MAAM,sBAAsB,SAAS,sCAAsC,CAAC;AAAA,IAC5F;AACA,QAAI,CAAC,OAAO,SAAS,SAAS,UAAU,QAAQ,KAAK,SAAS,UAAU,YAAY,GAAG;AACrF,aAAO,KAAK,EAAE,MAAM,sBAAsB,SAAS,sCAAsC,CAAC;AAAA,IAC5F;AAAA,EACF;AACA,SAAO,EAAE,IAAI,OAAO,WAAW,GAAG,OAAO;AAC3C;AAEA,SAAS,iBAAiB,MAAgB,QAAkD;AAC1F,MAAI,KAAK,SAAS,UAAU;AAC1B,2BAAuB,MAAM,QAAQ,MAAM;AAC3C;AAAA,EACF;AACA,MAAI,KAAK,SAAS,SAAU;AAC5B,MAAI,KAAK,QAAQ,SAAS,GAAG;AAC3B,WAAO,KAAK,EAAE,MAAM,gBAAgB,SAAS,gDAAgD,CAAC;AAAA,EAChG;AACA,QAAM,cAAc,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC,WAAW,OAAO,IAAI,CAAC;AACrE,MAAI,CAAC,YAAY,IAAI,KAAK,SAAS,GAAG;AACpC,WAAO,KAAK,EAAE,MAAM,kBAAkB,SAAS,kDAAkD,CAAC;AAAA,EACpG;AACA,aAAW,CAAC,OAAO,MAAM,KAAK,KAAK,QAAQ,QAAQ,GAAG;AACpD,QAAI,OAAO,SAAS,UAAU;AAC5B,6BAAuB,QAAQ,QAAQ,gBAAgB,KAAK,GAAG;AAAA,IACjE;AAAA,EACF;AACF;AAEA,SAAS,uBACP,MACA,QACA,MACM;AACN,OAAK,KAAK,aAAa,0BAA0B,wBAAwB,CAAC,KAAK,kBAAkB,KAAK,GAAG;AACvG,WAAO,KAAK,EAAE,MAAM,GAAG,IAAI,qBAAqB,SAAS,qDAAqD,CAAC;AAAA,EACjH;AACA,MACE,KAAK,SAAS,UACd,KAAK,SAAS,cACd,KAAK,SAAS,eACd,KAAK,SAAS,eACd;AACA,WAAO,KAAK;AAAA,MACV,MAAM,GAAG,IAAI;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACA,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,uBAAuB,CAAC,CAAC,GAAG;AACxE,QAAI,KAAK,SAAS,cAAc,KAAK,SAAS,gBAAgB;AAC5D,aAAO,KAAK;AAAA,QACV,MAAM,GAAG,IAAI,wBAAwB,GAAG;AAAA,QACxC,SAAS;AAAA,MACX,CAAC;AACD;AAAA,IACF;AACA,UAAM,cAAc,IAAI,GAAG;AAC3B,QAAI,CAAC,KAAK,kBAAkB,SAAS,WAAW,KAAK,CAAC,KAAK,SAAS,SAAS,WAAW,GAAG;AACzF,aAAO,KAAK;AAAA,QACV,MAAM,GAAG,IAAI,wBAAwB,GAAG;AAAA,QACxC,SAAS,qCAAqC,WAAW;AAAA,MAC3D,CAAC;AAAA,IACH;AACA,QAAI,KAAK,SAAS,YAAY;AAC5B,UACE,CAAC,KAAK,sBACN,CAAC,KAAK,iBAAiB,UACvB,CAAC,KAAK,wBAAwB,QAC9B;AACA,eAAO,KAAK;AAAA,UACV,MAAM,GAAG,IAAI,wBAAwB,GAAG;AAAA,UACxC,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AACA,iBAAW,UAAU,KAAK,0BAA0B,CAAC,GAAG;AACtD,YAAI,CAAC,mBAAmB,KAAK,MAAM,GAAG;AACpC,iBAAO,KAAK;AAAA,YACV,MAAM,GAAG,IAAI,wBAAwB,GAAG;AAAA,YACxC,SAAS;AAAA,UACX,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,YAAY,CAAC,KAAK,kBAAkB,KAAK,QAAQ,GAAG;AAC7D,YACE,UAAU,SAAS,WAAW,KAC9B,CAAC,8BAA8B,UAAU,WAAW,GACpD;AACA,iBAAO,KAAK;AAAA,YACV,MAAM,GAAG,IAAI,wBAAwB,GAAG;AAAA,YACxC,SAAS,uBAAuB,WAAW;AAAA,UAC7C,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,MACE,KAAK,0BAA0B,UAC/B,KAAK,0BAA0B,UAC/B,KAAK,0BAA0B,wBAC/B,KAAK,0BAA0B,uBAC/B;AACA,WAAO,KAAK;AAAA,MACV,MAAM,GAAG,IAAI;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACA,OACG,KAAK,yBAAyB,0BAA0B,UACzD,CAAC,KAAK,iBAAiB,KAAK,GAC5B;AACA,WAAO,KAAK;AAAA,MACV,MAAM,GAAG,IAAI;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACA,aAAW,UAAU,OAAO,KAAK,KAAK,uBAAuB,CAAC,CAAC,GAAG;AAChE,QAAI,OAAO,YAAY,MAAM,mBAAmB,OAAO,YAAY,MAAM,gBAAgB;AACvF,aAAO,KAAK;AAAA,QACV,MAAM,GAAG,IAAI,wBAAwB,MAAM;AAAA,QAC3C,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,8BAA8B,UAAkB,aAA8B;AACrF,QAAM,cAAc,SAAS,QAAQ,KAAK;AAC1C,QAAM,UAAU,eAAe,IAC3B,cAAc,IACd,SAAS,WAAW,GAAG,IACrB,SAAS,QAAQ,GAAG,IAAI,IACxB;AACN,QAAM,YAAY,SAAS,QAAQ,KAAK,OAAO;AAC/C,QAAM,aAAa,SAAS,OAAO,MAAM;AACzC,MAAI,SAAS;AACb,SAAO,MAAM;AACX,UAAM,QAAQ,SAAS,QAAQ,aAAa,MAAM;AAClD,QAAI,QAAQ,EAAG,QAAO;AACtB,UAAM,MAAM,QAAQ,YAAY;AAChC,QACE,YAAY,KACZ,QAAQ,aACP,cAAc,KAAK,SAAS,cAC7B,SAAS,QAAQ,CAAC,MAAM,OACvB,MAAM,SAAS,UAAU,SAAS,GAAG,MAAM,OAAO,SAAS,GAAG,MAAM,OAAO,SAAS,GAAG,MAAM,KAC9F;AACA,aAAO;AAAA,IACT;AACA,aAAS;AAAA,EACX;AACF;AAEO,SAAS,6BAA6B,UAAmC;AAC9E,QAAM,SAAS,0BAA0B,QAAQ;AACjD,MAAI,CAAC,OAAO,IAAI;AACd,UAAM,IAAI,MAAM,8BAA8B,SAAS,QAAQ,WAAW,KAAK,OAAO,OAAO,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO,EAAE,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EAC7J;AACF;;;AC/rBO,IAAM,8BAA8B;AAK3C,IAAM,4BAA4B;AAI3B,IAAM,wBAAwB;AAI9B,IAAM,8BAA8B;AAgFpC,IAAM,iCAAN,cAA6C,MAAM;AAAA,EACtC,OAAO;AAAA,EAChB;AAAA,EACT,YAAY,SAAiB,MAA6C;AACxE,UAAM,SAAS,IAAI;AACnB,SAAK,SAAS,MAAM;AAAA,EACtB;AACF;AAMO,SAAS,eAAe,OAA8B,CAAC,GAAqB;AACjF,QAAM,SAAS,2BAA2B,IAAI;AAC9C,QAAM,UAA4B;AAAA,IAChC,UAAU;AAAA,MACR,MAAM;AAAA,MACN,aAAa;AAAA,MACb,aACE;AAAA,MACF,MAAM,EAAE,MAAM,WAAW,MAAM,yCAAyC;AAAA,MACxE,UAAU;AAAA,MACV,yBAAyB;AAAA;AAAA;AAAA;AAAA,MAIzB,WAAW,EAAE,UAAU,IAAI,UAAU,KAAO,OAAO,eAAe;AAAA,MAClE,cAAc;AAAA,QACZ;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aACE;AAAA,UACF,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY;AAAA,cACV,OAAO,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,YACxC;AAAA,YACA,UAAU,CAAC,OAAO;AAAA,UACpB;AAAA,QACF;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aAAa;AAAA,UACb,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,WAAW,EAAE,EAAE;AAAA,YACvD,UAAU,CAAC,QAAQ;AAAA,UACrB;AAAA,QACF;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aAAa;AAAA,UACb,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,WAAW,EAAE,EAAE;AAAA,YACvD,UAAU,CAAC,QAAQ;AAAA,UACrB;AAAA,QACF;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aACE;AAAA,UACF,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY;AAAA,cACV,QAAQ,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,cACvC,aAAa,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,YAC9C;AAAA,YACA,UAAU,CAAC,UAAU,aAAa;AAAA,UACpC;AAAA,UACA,KAAK;AAAA,UACL,gBAAgB;AAAA,QAClB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aAAa;AAAA,UACb,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,WAAW,EAAE,EAAE;AAAA,YACtD,UAAU,CAAC,OAAO;AAAA,UACpB;AAAA,UACA,KAAK;AAAA,UACL,gBAAgB;AAAA,QAClB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aACE;AAAA,UACF,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY;AAAA,cACV,QAAQ,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,cACvC,MAAM,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,cACrC,MAAM,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,YACvC;AAAA,YACA,UAAU,CAAC,UAAU,MAAM;AAAA,UAC7B;AAAA,UACA,KAAK;AAAA,UACL,gBAAgB;AAAA,QAClB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aACE;AAAA,UACF,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,WAAW,EAAE,EAAE;AAAA,YAC5D,UAAU,CAAC,aAAa;AAAA,UAC1B;AAAA,UACA,KAAK;AAAA,UACL,gBAAgB;AAAA,QAClB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aACE;AAAA,UACF,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY;AAAA,cACV,aAAa,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,cAC5C,OAAO,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,cACtC,MAAM,EAAE,MAAM,UAAU,MAAM,CAAC,SAAS,SAAS,QAAQ,EAAE;AAAA,YAC7D;AAAA,YACA,UAAU,CAAC,eAAe,OAAO;AAAA,UACnC;AAAA,UACA,KAAK;AAAA,UACL,gBAAgB;AAAA,QAClB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aACE;AAAA,UACF,YAAY;AAAA,YACV,MAAM;AAAA,YACN,YAAY;AAAA,cACV,aAAa,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,cAC5C,QAAQ,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,YACzC;AAAA,YACA,UAAU,CAAC,eAAe,QAAQ;AAAA,UACpC;AAAA,UACA,KAAK;AAAA,UACL,gBAAgB;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,YAAY,KAAyD;AACzE,UAAI,IAAI,mBAAmB,gBAAgB;AACzC,cAAM,QAAQ,cAAc,IAAI,MAAM,OAAO;AAC7C,cAAM,SAAS,MAAM,OAAO,YAAY,KAAK;AAC7C,eAAO,EAAE,MAAM,QAAQ,WAAW,KAAK,IAAI,EAAE;AAAA,MAC/C;AACA,UAAI,IAAI,mBAAmB,YAAY;AACrC,cAAM,SAAS,cAAc,IAAI,MAAM,QAAQ;AAC/C,cAAM,OAAO,MAAM,OAAO,QAAQ,MAAM;AACxC,eAAO,EAAE,MAAM,MAAM,WAAW,KAAK,IAAI,EAAE;AAAA,MAC7C;AACA,UAAI,IAAI,mBAAmB,mBAAmB;AAC5C,cAAM,SAAS,cAAc,IAAI,MAAM,QAAQ;AAC/C,cAAM,aAAa,MAAM,OAAO,eAAe,MAAM;AACrD,eAAO,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW,KAAK,IAAI,EAAE;AAAA,MACvD;AACA,YAAM,IAAI,MAAM,sCAAsC,IAAI,cAAc,EAAE;AAAA,IAC5E;AAAA,IAEA,MAAM,gBAAgB,KAA6D;AACjF,UAAI,IAAI,mBAAmB,oBAAoB;AAC7C,cAAM,SAAS,cAAc,IAAI,MAAM,QAAQ;AAC/C,cAAM,cAAc,cAAc,IAAI,MAAM,aAAa;AACzD,cAAM,SAAS,MAAM,OAAO,gBAAgB,QAAQ,WAAW;AAC/D,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,aAAa,KAAK,IAAI;AAAA,UACtB,kBAAkB;AAAA,QACpB;AAAA,MACF;AACA,UAAI,IAAI,mBAAmB,kBAAkB;AAC3C,cAAM,QAAQ,cAAc,IAAI,MAAM,OAAO;AAC7C,cAAM,OAAO,cAAc,KAAK;AAChC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,MAAM,EAAE,IAAI,KAAK;AAAA,UACjB,aAAa,KAAK,IAAI;AAAA,UACtB,kBAAkB;AAAA,QACpB;AAAA,MACF;AACA,UAAI,IAAI,mBAAmB,qBAAqB;AAC9C,cAAM,SAAS,cAAc,IAAI,MAAM,QAAQ;AAC/C,cAAM,OAAO,cAAc,IAAI,MAAM,MAAM;AAC3C,cAAM,OAAO,sBAAsB,IAAI,MAAM,MAAM;AACnD,cAAM,YAAY,MAAM,OAAO,gBAAgB,QAAQ,EAAE,MAAM,KAAK,CAAC;AACrE,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,aAAa,KAAK,IAAI;AAAA,UACtB,kBAAkB;AAAA,QACpB;AAAA,MACF;AACA,UAAI,IAAI,mBAAmB,qBAAqB;AAC9C,cAAM,cAAc,cAAc,IAAI,MAAM,aAAa;AACzD,cAAM,OAAO,gBAAgB,WAAW;AACxC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,MAAM,EAAE,IAAI,MAAM,YAAY;AAAA,UAC9B,aAAa,KAAK,IAAI;AAAA,UACtB,kBAAkB;AAAA,QACpB;AAAA,MACF;AACA,UAAI,IAAI,mBAAmB,kBAAkB;AAC3C,cAAM,cAAc,cAAc,IAAI,MAAM,aAAa;AACzD,cAAM,QAAQ,cAAc,IAAI,MAAM,OAAO;AAC7C,cAAM,OAAO,iBAAiB,IAAI,MAAM,MAAM;AAC9C,cAAM,aAAa,MAAM,OAAO,aAAa,aAAa,OAAO,IAAI;AACrE,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,aAAa,KAAK,IAAI;AAAA,UACtB,kBAAkB;AAAA,QACpB;AAAA,MACF;AACA,UAAI,IAAI,mBAAmB,kBAAkB;AAC3C,cAAM,cAAc,cAAc,IAAI,MAAM,aAAa;AACzD,cAAM,SAAS,cAAc,IAAI,MAAM,QAAQ;AAC/C,cAAM,OAAO,aAAa,aAAa,MAAM;AAC7C,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,MAAM,EAAE,IAAI,MAAM,aAAa,OAAO;AAAA,UACtC,aAAa,KAAK,IAAI;AAAA,UACtB,kBAAkB;AAAA,QACpB;AAAA,MACF;AACA,YAAM,IAAI,MAAM,0CAA0C,IAAI,cAAc,EAAE;AAAA,IAChF;AAAA,IAEA,MAAM,KAAK,QAAQ;AACjB,UAAI;AAGF,cAAM,KAAK,MAAM,OAAO,KAAK;AAC7B,YAAI,CAAC,GAAI,QAAO,EAAE,IAAI,OAAO,QAAQ,2CAA2C;AAChF,eAAO,EAAE,IAAI,KAAK;AAAA,MACpB,SAAS,KAAK;AACZ,YAAI,eAAe,oBAAoB;AACrC,iBAAO,EAAE,IAAI,OAAO,QAAQ,4DAAuD;AAAA,QACrF;AACA,eAAO,EAAE,IAAI,OAAO,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAC/E;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AA6CO,SAAS,2BAA2B,OAA8B,CAAC,GAAyB;AACjG,QAAM,WAAW,KAAK,WAAW,6BAA6B,QAAQ,QAAQ,EAAE;AAChF,QAAM,eAAe,KAAK;AAC1B,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,YAAY,KAAK,aAAa;AAEpC,WAAS,aAAqC;AAC5C,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,MACL,gBAAgB;AAAA,MAChB,eAAe,UAAU,YAAY;AAAA,MACrC,kBAAkB;AAAA,IACpB;AAAA,EACF;AAEA,iBAAe,UAAU,MAAc,MAAsC;AAC3E,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,IAAI;AAAA,QACzC,GAAG;AAAA,QACH,QAAQ,KAAK,UAAU,YAAY,QAAQ,SAAS;AAAA,MACtD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,yBAAyB,IAAI;AAAA,QAC7B,EAAE,OAAO,IAAI;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,gBAAgB,KAAgC;AAC7D,QAAI;AACF,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,aAAO,KAAK,KAAK,EAAE,MAAM,GAAG,GAAG;AAAA,IACjC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,iBAAe,aAAa,OAAiD;AAC3E,UAAM,MAAM,MAAM,UAAU,mBAAmB;AAAA,MAC7C,QAAQ;AAAA,MACR,SAAS,WAAW;AAAA,MACpB,MAAM,KAAK,UAAU,EAAE,KAAK,MAAM,CAAC;AAAA,IACrC,CAAC;AACD,QAAI,IAAI,WAAW,KAAK;AAItB,aAAO,EAAE,OAAO,OAAO,QAAQ,wBAAwB;AAAA,IACzD;AACA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,uCAAuC,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,QAChF,EAAE,QAAQ,IAAI,OAAO;AAAA,MACvB;AAAA,IACF;AACA,UAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAY/C,QAAI,CAAC,QAAQ,OAAO,KAAK,UAAU,WAAW;AAC5C,aAAO,EAAE,OAAO,OAAO,QAAQ,YAAY;AAAA,IAC7C;AACA,QAAI,CAAC,KAAK,SAAS,CAAC,KAAK,QAAQ;AAC/B,aAAO,EAAE,OAAO,OAAO,QAAQ,UAAU;AAAA,IAC3C;AACA,UAAM,SAAS,MAAM,QAAQ,KAAK,aAAa,IAC3C,KAAK,cAAc,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ,IAC/E,CAAC;AACL,QAAI,KAAK,QAAS,QAAO,KAAK,WAAW,KAAK,OAAO,EAAE;AACvD,UAAM,YACJ,OAAO,KAAK,cAAc,YAAY,KAAK,YACvC,KAAK,MAAM,KAAK,SAAS,IACzB;AACN,WAAO;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,QAAQ,KAAK;AAAA,MACb,aAAa,KAAK,cAAc,UAAU,KAAK,UAAU,KAAK,UAAU,KAAK;AAAA,MAC7E,WAAW,KAAK,aAAa;AAAA,MAC7B;AAAA,MACA,GAAI,OAAO,SAAS,SAAS,IAAI,EAAE,UAA+B,IAAI,CAAC;AAAA,MACvE,GAAI,KAAK,QAAQ,EAAE,cAAc,KAAK,MAAM,IAAI,CAAC;AAAA,MACjD,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IAClD;AAAA,EACF;AAEA,iBAAe,cAAc,OAAiD;AAO5E,UAAM,MAAM,MAAM,UAAU,yBAAyB;AAAA,MACnD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,QAAQ;AAAA,QACR,eAAe,UAAU,KAAK;AAAA,MAChC;AAAA,IACF,CAAC;AACD,QAAI,IAAI,WAAW,OAAO,IAAI,WAAW,KAAK;AAC5C,aAAO,EAAE,OAAO,OAAO,QAAQ,UAAU;AAAA,IAC3C;AACA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,6CAA6C,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,QACtF,EAAE,QAAQ,IAAI,OAAO;AAAA,MACvB;AAAA,IACF;AACA,UAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAM/C,QAAI,CAAC,QAAQ,CAAC,KAAK,QAAQ,OAAO,KAAK,KAAK,OAAO,UAAU;AAC3D,aAAO,EAAE,OAAO,OAAO,QAAQ,UAAU;AAAA,IAC3C;AACA,UAAM,eAAe,KAAK,SAAS;AACnC,UAAM,YAAY,eAAe,KAAK,MAAM,YAAY,IAAI;AAC5D,WAAO;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,QAAQ,KAAK,KAAK;AAAA,MAClB,aAAa,KAAK,SAAS,gBAAgB,KAAK,KAAK;AAAA,MACrD,WAAW,KAAK,SAAS,eAAe,SAAS;AAAA,MACjD,QAAQ,CAAC;AAAA,MACT,GAAI,OAAO,SAAS,SAAS,IAAI,EAAE,UAAU,IAAI,CAAC;AAAA,MAClD,GAAI,KAAK,SAAS,KAAK,EAAE,cAAc,KAAK,QAAQ,GAAG,IAAI,CAAC;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,YAAY,OAAiD;AACjE,UAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,eAAO,EAAE,OAAO,OAAO,QAAQ,YAAY;AAAA,MAC7C;AACA,UAAI,MAAM,WAAW,2BAA2B,GAAG;AAMjD,eAAO,EAAE,OAAO,OAAO,QAAQ,wBAAwB;AAAA,MACzD;AACA,UAAI,MAAM,WAAW,qBAAqB,GAAG;AAC3C,eAAO,aAAa,KAAK;AAAA,MAC3B;AAMA,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,IAEA,MAAM,QAAQ,QAA4C;AACxD,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,+BAA+B,gDAAgD;AAAA,MAC3F;AACA,YAAM,MAAM,MAAM,UAAU,aAAa,mBAAmB,MAAM,CAAC,IAAI;AAAA,QACrE,QAAQ;AAAA,QACR,SAAS,WAAW;AAAA,MACtB,CAAC;AACD,UAAI,IAAI,WAAW,KAAK;AACtB,cAAM,IAAI,+BAA+B,mBAAmB,MAAM,cAAc,EAAE,QAAQ,IAAI,CAAC;AAAA,MACjG;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,wBAAwB,MAAM,aAAa,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,UACpF,EAAE,QAAQ,IAAI,OAAO;AAAA,QACvB;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAG/C,YAAM,OAAO,MAAM;AACnB,UAAI,CAAC,QAAQ,OAAO,KAAK,OAAO,UAAU;AACxC,cAAM,IAAI,+BAA+B,oDAAoD;AAAA,MAC/F;AACA,aAAO;AAAA,QACL,IAAI,KAAK;AAAA,QACT,GAAI,OAAO,KAAK,UAAU,WAAW,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAC9D,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QACrD,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,IAEA,MAAM,eAAe,QAAmD;AACtE,YAAM,MAAM,MAAM,UAAU,aAAa;AAAA,QACvC,QAAQ;AAAA,QACR,SAAS,EAAE,GAAG,WAAW,GAAG,sBAAsB,OAAO;AAAA,MAC3D,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,iCAAiC,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,UAC1E,EAAE,QAAQ,IAAI,OAAO;AAAA,QACvB;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAY/C,YAAM,OAAO,MAAM,QAAQ,MAAM,IAAI,IAAI,KAAM,OAAO,CAAC;AACvD,YAAM,aAAuC,CAAC;AAC9C,iBAAW,OAAO,MAAM;AACtB,YAAI,CAAC,OAAO,OAAO,IAAI,OAAO,YAAY,OAAO,IAAI,SAAS,SAAU;AACxE,cAAM,OACJ,IAAI,SAAS,WAAW,IAAI,SAAS,UAAU,IAAI,OAAO;AAC5D,cAAM,SAAS,MAAM,QAAQ,IAAI,MAAM,IACnC,IAAI,OAAO,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ,IACvE,CAAC;AACL,mBAAW,KAAK;AAAA,UACd,IAAI,IAAI;AAAA,UACR,MAAM,IAAI;AAAA,UACV;AAAA,UACA,YAAY,QAAQ,IAAI,UAAU,KAAK,IAAI,OAAO;AAAA,UAClD;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,gBAAgB,QAAQ,aAAa;AACzC,YAAM,aAAa,MAAM,KAAK,eAAe,MAAM;AACnD,YAAM,QAAQ,WAAW,KAAK,CAAC,MAAM,EAAE,OAAO,WAAW;AACzD,UAAI,CAAC,OAAO;AACV,cAAM,IAAI;AAAA,UACR,wBAAwB,WAAW,uBAAuB,MAAM;AAAA,UAChE,EAAE,QAAQ,IAAI;AAAA,QAChB;AAAA,MACF;AACA,aAAO,EAAE,IAAI,MAAM,aAAa,MAAM,IAAI,QAAQ,MAAM,OAAO;AAAA,IACjE;AAAA,IAEA,MAAM,cAAc,OAA8B;AAChD,UAAI,CAAC,MAAO;AACZ,UAAI,MAAM,WAAW,2BAA2B,GAAG;AACjD,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,MAAM,WAAW,qBAAqB,GAAG;AAI3C,cAAM,IAAI,MAAM,aAAa,KAAK;AAClC,YAAI,CAAC,EAAE,SAAS,CAAC,EAAE,aAAc;AACjC,cAAMA,OAAM,MAAM,UAAU,YAAY,mBAAmB,EAAE,YAAY,CAAC,IAAI;AAAA,UAC5E,QAAQ;AAAA,UACR,SAAS,WAAW;AAAA,QACtB,CAAC;AACD,YAAIA,KAAI,WAAW,IAAK;AACxB,YAAI,CAACA,KAAI,IAAI;AACX,gBAAM,IAAI;AAAA,YACR,8BAA8B,EAAE,YAAY,aAAaA,KAAI,MAAM,KAAK,MAAM,gBAAgBA,IAAG,CAAC;AAAA,YAClG,EAAE,QAAQA,KAAI,OAAO;AAAA,UACvB;AAAA,QACF;AACA;AAAA,MACF;AAGA,YAAM,MAAM,MAAM,UAAU,sBAAsB;AAAA,QAChD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,eAAe,UAAU,KAAK;AAAA,QAChC;AAAA,QACA,MAAM;AAAA,MACR,CAAC;AAGD,UAAI,IAAI,UAAU,KAAK;AACrB,cAAM,IAAI;AAAA,UACR,0CAA0C,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,UACnF,EAAE,QAAQ,IAAI,OAAO;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,gBAAgB,QAAQ,MAAM;AAClC,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAM,MAAM,UAAU,aAAa;AAAA,QACvC,QAAQ;AAAA,QACR,SAAS,EAAE,GAAG,WAAW,GAAG,sBAAsB,OAAO;AAAA,QACzD,MAAM,KAAK,UAAU;AAAA,UACnB,MAAM,KAAK;AAAA,UACX,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QACzC,CAAC;AAAA,MACH,CAAC;AACD,UAAI,IAAI,WAAW,KAAK;AACtB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,sCAAsC,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,UAC/E,EAAE,QAAQ,IAAI,OAAO;AAAA,QACvB;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAM/C,YAAM,OAAO,MAAM;AACnB,UAAI,CAAC,QAAQ,OAAO,KAAK,OAAO,YAAY,OAAO,KAAK,SAAS,UAAU;AACzE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,OACJ,KAAK,SAAS,WAAW,KAAK,SAAS,UAAU,KAAK,OAAO;AAC/D,YAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,IACpC,KAAK,OAAO,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ,IACxE,CAAC;AACL,aAAO;AAAA,QACL,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,QACX;AAAA,QACA,YAAY,QAAQ,KAAK,UAAU,KAAK,KAAK,OAAO;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,gBAAgB,aAAa;AACjC,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAM,MAAM,UAAU,aAAa,mBAAmB,WAAW,CAAC,IAAI;AAAA,QAC1E,QAAQ;AAAA,QACR,SAAS,WAAW;AAAA,MACtB,CAAC;AACD,UAAI,IAAI,WAAW,KAAK;AACtB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,UAAI,IAAI,WAAW,IAAK;AACxB,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,+BAA+B,WAAW,aAAa,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,UAChG,EAAE,QAAQ,IAAI,OAAO;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,aAAa,aAAa,OAAO,MAAM;AAC3C,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,OAAO;AACV,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAM,MAAM;AAAA,QAChB,aAAa,mBAAmB,WAAW,CAAC;AAAA,QAC5C;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,WAAW;AAAA,UACpB,MAAM,KAAK,UAAU;AAAA,YACnB;AAAA,YACA,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,UACzB,CAAC;AAAA,QACH;AAAA,MACF;AACA,UAAI,IAAI,WAAW,KAAK;AACtB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,6BAA6B,WAAW,yBAAyB,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,UAC1G,EAAE,QAAQ,IAAI,OAAO;AAAA,QACvB;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAa/C,YAAM,OAAO,MAAM;AACnB,UAAI,CAAC,QAAQ,OAAO,KAAK,OAAO,UAAU;AACxC,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,eACJ,KAAK,SAAS,WAAW,KAAK,SAAS,UAAU,KAAK,OAAO;AAC/D,YAAM,SACJ,KAAK,WAAW,cAAc,KAAK,WAAW,YAAY,KAAK,SAAS;AAC1E,aAAO;AAAA,QACL,IAAI,KAAK;AAAA,QACT,aAAa,KAAK,eAAe,KAAK,UAAU;AAAA,QAChD,OAAO,KAAK,SAAS;AAAA,QACrB,MAAM;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,aAAa,aAAa,QAAQ;AACtC,UAAI,CAAC,eAAe,CAAC,QAAQ;AAC3B,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAM,MAAM;AAAA,QAChB,aAAa,mBAAmB,WAAW,CAAC,YAAY,mBAAmB,MAAM,CAAC;AAAA,QAClF;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,WAAW;AAAA,QACtB;AAAA,MACF;AACA,UAAI,IAAI,WAAW,KAAK;AACtB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,UAAI,IAAI,WAAW,IAAK;AACxB,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,+BAA+B,WAAW,YAAY,MAAM,aAAa,IAAI,MAAM,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,UAClH,EAAE,QAAQ,IAAI,OAAO;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,OAAyB;AAC7B,YAAM,MAAM,MAAM,UAAU,WAAW,EAAE,QAAQ,MAAM,CAAC;AACxD,aAAO,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEA,SAAS,cAAc,MAA+B,KAAqB;AACzE,QAAM,QAAQ,KAAK,GAAG;AACtB,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO;AACvC,UAAM,IAAI,MAAM,yCAAyC,GAAG,GAAG;AAAA,EACjE;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,MAA+B,KAAiC;AAC7F,QAAM,QAAQ,KAAK,GAAG;AACtB,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,CAAC,MAAO,QAAO;AAChD,SAAO;AACT;AAEA,SAAS,iBACP,MACA,KAC4C;AAC5C,QAAM,QAAQ,KAAK,GAAG;AACtB,MAAI,UAAU,WAAW,UAAU,WAAW,UAAU,SAAU,QAAO;AACzE,SAAO;AACT;","names":["res"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CONNECTOR_ADAPTER_FACTORIES,
3
3
  adapters_exports
4
- } from "./chunk-BYTP5QJQ.js";
4
+ } from "./chunk-QL6CRJ3E.js";
5
5
 
6
6
  // src/connectors/bundled-manifests.ts
7
7
  var MANIFEST_PROBE_PREFIX = "tangle-adapter-metadata-";
@@ -115,6 +115,7 @@ function bundledOAuth2AuthContract(manifest) {
115
115
  tokenClientIdParam: candidate.tokenClientIdParam ?? "client_id",
116
116
  tokenClientSecretParam: candidate.tokenClientSecretParam ?? "client_secret",
117
117
  tokenClientAuthMethod: candidate.tokenClientAuthMethod ?? "client_secret_post",
118
+ sendScopeParam: candidate.sendScopeParam,
118
119
  extraAuthParams: candidate.extraAuthParams,
119
120
  tokenRequestHeaders: candidate.tokenRequestHeaders,
120
121
  urlTemplateMetadata: candidate.urlTemplateMetadata
@@ -143,4 +144,4 @@ export {
143
144
  bundledOAuth2AuthContract,
144
145
  bundledOAuth2Auth
145
146
  };
146
- //# sourceMappingURL=chunk-CJ6SZNMU.js.map
147
+ //# sourceMappingURL=chunk-E2QYXDJQ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/connectors/bundled-manifests.ts"],"sourcesContent":["import {\n CONNECTOR_ADAPTER_FACTORIES,\n type ConnectorAdapterFactoryDefinition,\n} from './adapters/factories.js'\nimport * as bundledAdapters from './adapters/index.js'\nimport type {\n ConnectorAdapter,\n ConnectorManifest,\n OAuth2UrlTemplateMetadataSpec,\n} from './types.js'\n\n/**\n * The bundled adapter registry — the single source of truth for \"which\n * connectors does this package actually implement, and how do they really\n * authenticate\".\n *\n * This exists because the answer was previously maintained by hand in two\n * places that drifted apart: `specs/registry.ts` carried an 18-name\n * `EXECUTABLE_KINDS` set while the package shipped 530 adapters, and the\n * platform kept its own private `collectBundledAdapters()` copy. A catalog\n * that describes connectors must derive from the connectors, never restate\n * them — restating is what let QuickBooks and Xero advertise a\n * `https://example.invalid/...` authorization URL behind a live Connect\n * button.\n *\n * Manifests are static data. Factory-style adapters need client credentials\n * to *execute*, but their manifest (auth endpoints, scopes, capability list)\n * is fixed at definition time, so we instantiate them with clearly-marked\n * placeholder options purely to read that static shape. The placeholders are\n * never used to make a request — {@link listBundledAdapterManifests} returns\n * manifests, not adapters.\n */\n\n/** Placeholder marker for factory options resolved only to read a manifest. */\nconst MANIFEST_PROBE_PREFIX = 'tangle-adapter-metadata-'\n\nfunction isConnectorAdapter(value: unknown): value is ConnectorAdapter {\n if (!value || typeof value !== 'object') return false\n const manifest = (value as { manifest?: unknown }).manifest\n if (!manifest || typeof manifest !== 'object') return false\n return typeof (manifest as { kind?: unknown }).kind === 'string'\n}\n\nfunction manifestFromFactory(\n definition: ConnectorAdapterFactoryDefinition,\n): ConnectorManifest | undefined {\n const probeOptions = Object.fromEntries(\n Object.keys(definition.envMap).map((name) => [name, `${MANIFEST_PROBE_PREFIX}${name}`]),\n )\n try {\n return definition.factory(probeOptions).manifest\n } catch {\n // A factory that cannot be constructed from placeholder options cannot\n // contribute a manifest. Skipping is correct: it means the adapter is not\n // describable without real credentials, so the catalog must not claim it.\n return undefined\n }\n}\n\nlet cachedManifests: ConnectorManifest[] | undefined\nlet cachedByKind: Map<string, ConnectorManifest> | undefined\n\n/**\n * Direct adapter instances that public catalog and action hosts may register.\n *\n * Factory adapters are intentionally absent: their construction requires\n * product-owned OAuth configuration. Inbound-only adapters remain available\n * as named exports for webhook hosts, but never compete with action adapters\n * for a public connector kind.\n */\nexport function listBundledConnectorAdapters(): ConnectorAdapter[] {\n const byKind = new Map<string, ConnectorAdapter>()\n const exportByKind = new Map<string, string>()\n for (const [exportName, value] of Object.entries(bundledAdapters)) {\n if (!isConnectorAdapter(value) || value.inboundOnly) continue\n const kind = value.manifest.kind\n const existingExport = exportByKind.get(kind)\n if (existingExport) {\n throw new Error(\n `Duplicate public connector adapter kind \"${kind}\" exported by \"${existingExport}\" and \"${exportName}\".`,\n )\n }\n exportByKind.set(kind, exportName)\n byKind.set(kind, value)\n }\n return [...byKind.values()]\n}\n\nfunction buildRegistry(): Map<string, ConnectorManifest> {\n const byKind = new Map<string, ConnectorManifest>()\n for (const adapter of listBundledConnectorAdapters()) {\n byKind.set(adapter.manifest.kind, adapter.manifest)\n }\n for (const definition of CONNECTOR_ADAPTER_FACTORIES) {\n if (byKind.has(definition.kind)) continue\n const manifest = manifestFromFactory(definition)\n if (manifest) byKind.set(manifest.kind, manifest)\n }\n return byKind\n}\n\nfunction registry(): Map<string, ConnectorManifest> {\n if (!cachedByKind) cachedByKind = buildRegistry()\n return cachedByKind\n}\n\n/** Every connector manifest this package bundles, sorted by kind. */\nexport function listBundledAdapterManifests(): ConnectorManifest[] {\n if (!cachedManifests) {\n cachedManifests = [...registry().values()].sort((a, b) => a.kind.localeCompare(b.kind))\n }\n return cachedManifests\n}\n\n/** The bundled manifest for `kind`, or undefined when nothing implements it. */\nexport function getBundledAdapterManifest(kind: string): ConnectorManifest | undefined {\n return registry().get(kind)\n}\n\n/** Whether this package ships a real, executable adapter for `kind`. */\nexport function hasBundledAdapter(kind: string): boolean {\n return registry().has(kind)\n}\n\n/** Kinds this package implements. */\nexport function listBundledAdapterKinds(): string[] {\n return listBundledAdapterManifests().map((manifest) => manifest.kind)\n}\n\n/**\n * How a bundled connector really authenticates.\n *\n * `oauth2_client_credentials` is called out separately because it IS a\n * connectable mode but has no user-facing authorize step — a caller that\n * treats \"oauth2 without an authorizationUrl\" as broken would wrongly condemn\n * these machine-to-machine providers.\n */\nexport type BundledAuthMode =\n | 'oauth2'\n | 'oauth2_client_credentials'\n | 'api_key'\n | 'hmac'\n | 'none'\n\n/** The auth mode a connect flow would actually drive for this manifest,\n * resolving `one_of` through its declared `preferred` mode. */\nexport function bundledAuthMode(manifest: ConnectorManifest): BundledAuthMode | undefined {\n const auth = manifest.auth\n const effective =\n auth.kind === 'one_of'\n ? (auth.options.find((option) => option.kind === auth.preferred) ?? auth.options[0])\n : auth\n switch (effective.kind) {\n case 'oauth2':\n return effective.grantType === 'client_credentials' ? 'oauth2_client_credentials' : 'oauth2'\n case 'api-key':\n return 'api_key'\n case 'hmac':\n return 'hmac'\n case 'none':\n return 'none'\n default:\n return undefined\n }\n}\n\n/** The setup hint an api-key connector shows when collecting its credential. */\nexport function bundledApiKeyHint(manifest: ConnectorManifest): string | undefined {\n const auth = manifest.auth\n const candidates = auth.kind === 'one_of' ? auth.options : [auth]\n for (const candidate of candidates) {\n if (candidate.kind === 'api-key') return candidate.hint\n }\n return undefined\n}\n\nexport interface BundledOAuth2AuthContract {\n grantType: 'authorization_code' | 'client_credentials'\n authorizationUrl?: string\n tokenUrl: string\n scopes: string[]\n clientIdEnv: string\n clientSecretEnv?: string\n scopeSeparator: ' ' | ','\n pkce?: 'required' | 'supported' | 'unsupported'\n authorizationClientIdParam: string\n tokenClientIdParam: string\n tokenClientSecretParam: string\n tokenClientAuthMethod: 'none' | 'client_secret_post' | 'client_secret_basic'\n extraAuthParams?: Record<string, string>\n tokenRequestHeaders?: Record<string, string>\n urlTemplateMetadata?: Readonly<Record<string, OAuth2UrlTemplateMetadataSpec>>\n}\n\n/** The selected OAuth2 contract for a manifest, including non-redirect\n * client-credentials providers and validated provider-root templates. */\nexport function bundledOAuth2AuthContract(\n manifest: ConnectorManifest,\n): BundledOAuth2AuthContract | undefined {\n const auth = manifest.auth\n const candidates =\n auth.kind === 'one_of'\n ? auth.options.filter((option) => option.kind === auth.preferred)\n : [auth]\n for (const candidate of candidates) {\n if (candidate.kind !== 'oauth2') continue\n const grantType = candidate.grantType ?? 'authorization_code'\n if (grantType === 'authorization_code' && !candidate.authorizationUrl) continue\n return {\n grantType,\n authorizationUrl: candidate.authorizationUrl,\n tokenUrl: candidate.tokenUrl,\n scopes: candidate.scopes,\n clientIdEnv: candidate.clientIdEnv,\n clientSecretEnv: candidate.clientSecretEnv,\n scopeSeparator: candidate.scopeSeparator ?? ' ',\n pkce: candidate.pkce,\n authorizationClientIdParam: candidate.authorizationClientIdParam ?? 'client_id',\n tokenClientIdParam: candidate.tokenClientIdParam ?? 'client_id',\n tokenClientSecretParam: candidate.tokenClientSecretParam ?? 'client_secret',\n tokenClientAuthMethod: candidate.tokenClientAuthMethod ?? 'client_secret_post',\n extraAuthParams: candidate.extraAuthParams,\n tokenRequestHeaders: candidate.tokenRequestHeaders,\n urlTemplateMetadata: candidate.urlTemplateMetadata,\n }\n }\n return undefined\n}\n\n/** The authorization-code OAuth2 branch of a manifest's auth spec, if it has\n * one. `one_of` manifests are resolved through their `preferred` mode, which\n * is the mode a connect flow would actually drive. Returns undefined for\n * api-key/hmac/none connectors and for `client_credentials` OAuth (which has\n * no user-facing authorize step). */\nexport function bundledOAuth2Auth(manifest: ConnectorManifest):\n | (BundledOAuth2AuthContract & {\n grantType: 'authorization_code'\n authorizationUrl: string\n })\n | undefined {\n const contract = bundledOAuth2AuthContract(manifest)\n if (contract?.grantType !== 'authorization_code' || !contract.authorizationUrl) return undefined\n return {\n ...contract,\n grantType: 'authorization_code',\n authorizationUrl: contract.authorizationUrl,\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/connectors/bundled-manifests.ts"],"sourcesContent":["import {\n CONNECTOR_ADAPTER_FACTORIES,\n type ConnectorAdapterFactoryDefinition,\n} from './adapters/factories.js'\nimport * as bundledAdapters from './adapters/index.js'\nimport type {\n ConnectorAdapter,\n ConnectorManifest,\n OAuth2UrlTemplateMetadataSpec,\n} from './types.js'\n\n/**\n * The bundled adapter registry — the single source of truth for \"which\n * connectors does this package actually implement, and how do they really\n * authenticate\".\n *\n * This exists because the answer was previously maintained by hand in two\n * places that drifted apart: `specs/registry.ts` carried an 18-name\n * `EXECUTABLE_KINDS` set while the package shipped 530 adapters, and the\n * platform kept its own private `collectBundledAdapters()` copy. A catalog\n * that describes connectors must derive from the connectors, never restate\n * them — restating is what let QuickBooks and Xero advertise a\n * `https://example.invalid/...` authorization URL behind a live Connect\n * button.\n *\n * Manifests are static data. Factory-style adapters need client credentials\n * to *execute*, but their manifest (auth endpoints, scopes, capability list)\n * is fixed at definition time, so we instantiate them with clearly-marked\n * placeholder options purely to read that static shape. The placeholders are\n * never used to make a request — {@link listBundledAdapterManifests} returns\n * manifests, not adapters.\n */\n\n/** Placeholder marker for factory options resolved only to read a manifest. */\nconst MANIFEST_PROBE_PREFIX = 'tangle-adapter-metadata-'\n\nfunction isConnectorAdapter(value: unknown): value is ConnectorAdapter {\n if (!value || typeof value !== 'object') return false\n const manifest = (value as { manifest?: unknown }).manifest\n if (!manifest || typeof manifest !== 'object') return false\n return typeof (manifest as { kind?: unknown }).kind === 'string'\n}\n\nfunction manifestFromFactory(\n definition: ConnectorAdapterFactoryDefinition,\n): ConnectorManifest | undefined {\n const probeOptions = Object.fromEntries(\n Object.keys(definition.envMap).map((name) => [name, `${MANIFEST_PROBE_PREFIX}${name}`]),\n )\n try {\n return definition.factory(probeOptions).manifest\n } catch {\n // A factory that cannot be constructed from placeholder options cannot\n // contribute a manifest. Skipping is correct: it means the adapter is not\n // describable without real credentials, so the catalog must not claim it.\n return undefined\n }\n}\n\nlet cachedManifests: ConnectorManifest[] | undefined\nlet cachedByKind: Map<string, ConnectorManifest> | undefined\n\n/**\n * Direct adapter instances that public catalog and action hosts may register.\n *\n * Factory adapters are intentionally absent: their construction requires\n * product-owned OAuth configuration. Inbound-only adapters remain available\n * as named exports for webhook hosts, but never compete with action adapters\n * for a public connector kind.\n */\nexport function listBundledConnectorAdapters(): ConnectorAdapter[] {\n const byKind = new Map<string, ConnectorAdapter>()\n const exportByKind = new Map<string, string>()\n for (const [exportName, value] of Object.entries(bundledAdapters)) {\n if (!isConnectorAdapter(value) || value.inboundOnly) continue\n const kind = value.manifest.kind\n const existingExport = exportByKind.get(kind)\n if (existingExport) {\n throw new Error(\n `Duplicate public connector adapter kind \"${kind}\" exported by \"${existingExport}\" and \"${exportName}\".`,\n )\n }\n exportByKind.set(kind, exportName)\n byKind.set(kind, value)\n }\n return [...byKind.values()]\n}\n\nfunction buildRegistry(): Map<string, ConnectorManifest> {\n const byKind = new Map<string, ConnectorManifest>()\n for (const adapter of listBundledConnectorAdapters()) {\n byKind.set(adapter.manifest.kind, adapter.manifest)\n }\n for (const definition of CONNECTOR_ADAPTER_FACTORIES) {\n if (byKind.has(definition.kind)) continue\n const manifest = manifestFromFactory(definition)\n if (manifest) byKind.set(manifest.kind, manifest)\n }\n return byKind\n}\n\nfunction registry(): Map<string, ConnectorManifest> {\n if (!cachedByKind) cachedByKind = buildRegistry()\n return cachedByKind\n}\n\n/** Every connector manifest this package bundles, sorted by kind. */\nexport function listBundledAdapterManifests(): ConnectorManifest[] {\n if (!cachedManifests) {\n cachedManifests = [...registry().values()].sort((a, b) => a.kind.localeCompare(b.kind))\n }\n return cachedManifests\n}\n\n/** The bundled manifest for `kind`, or undefined when nothing implements it. */\nexport function getBundledAdapterManifest(kind: string): ConnectorManifest | undefined {\n return registry().get(kind)\n}\n\n/** Whether this package ships a real, executable adapter for `kind`. */\nexport function hasBundledAdapter(kind: string): boolean {\n return registry().has(kind)\n}\n\n/** Kinds this package implements. */\nexport function listBundledAdapterKinds(): string[] {\n return listBundledAdapterManifests().map((manifest) => manifest.kind)\n}\n\n/**\n * How a bundled connector really authenticates.\n *\n * `oauth2_client_credentials` is called out separately because it IS a\n * connectable mode but has no user-facing authorize step — a caller that\n * treats \"oauth2 without an authorizationUrl\" as broken would wrongly condemn\n * these machine-to-machine providers.\n */\nexport type BundledAuthMode =\n | 'oauth2'\n | 'oauth2_client_credentials'\n | 'api_key'\n | 'hmac'\n | 'none'\n\n/** The auth mode a connect flow would actually drive for this manifest,\n * resolving `one_of` through its declared `preferred` mode. */\nexport function bundledAuthMode(manifest: ConnectorManifest): BundledAuthMode | undefined {\n const auth = manifest.auth\n const effective =\n auth.kind === 'one_of'\n ? (auth.options.find((option) => option.kind === auth.preferred) ?? auth.options[0])\n : auth\n switch (effective.kind) {\n case 'oauth2':\n return effective.grantType === 'client_credentials' ? 'oauth2_client_credentials' : 'oauth2'\n case 'api-key':\n return 'api_key'\n case 'hmac':\n return 'hmac'\n case 'none':\n return 'none'\n default:\n return undefined\n }\n}\n\n/** The setup hint an api-key connector shows when collecting its credential. */\nexport function bundledApiKeyHint(manifest: ConnectorManifest): string | undefined {\n const auth = manifest.auth\n const candidates = auth.kind === 'one_of' ? auth.options : [auth]\n for (const candidate of candidates) {\n if (candidate.kind === 'api-key') return candidate.hint\n }\n return undefined\n}\n\nexport interface BundledOAuth2AuthContract {\n grantType: 'authorization_code' | 'client_credentials'\n authorizationUrl?: string\n tokenUrl: string\n scopes: string[]\n clientIdEnv: string\n clientSecretEnv?: string\n scopeSeparator: ' ' | ','\n pkce?: 'required' | 'supported' | 'unsupported'\n authorizationClientIdParam: string\n tokenClientIdParam: string\n tokenClientSecretParam: string\n tokenClientAuthMethod: 'none' | 'client_secret_post' | 'client_secret_basic'\n sendScopeParam?: boolean\n extraAuthParams?: Record<string, string>\n tokenRequestHeaders?: Record<string, string>\n urlTemplateMetadata?: Readonly<Record<string, OAuth2UrlTemplateMetadataSpec>>\n}\n\n/** The selected OAuth2 contract for a manifest, including non-redirect\n * client-credentials providers and validated provider-root templates. */\nexport function bundledOAuth2AuthContract(\n manifest: ConnectorManifest,\n): BundledOAuth2AuthContract | undefined {\n const auth = manifest.auth\n const candidates =\n auth.kind === 'one_of'\n ? auth.options.filter((option) => option.kind === auth.preferred)\n : [auth]\n for (const candidate of candidates) {\n if (candidate.kind !== 'oauth2') continue\n const grantType = candidate.grantType ?? 'authorization_code'\n if (grantType === 'authorization_code' && !candidate.authorizationUrl) continue\n return {\n grantType,\n authorizationUrl: candidate.authorizationUrl,\n tokenUrl: candidate.tokenUrl,\n scopes: candidate.scopes,\n clientIdEnv: candidate.clientIdEnv,\n clientSecretEnv: candidate.clientSecretEnv,\n scopeSeparator: candidate.scopeSeparator ?? ' ',\n pkce: candidate.pkce,\n authorizationClientIdParam: candidate.authorizationClientIdParam ?? 'client_id',\n tokenClientIdParam: candidate.tokenClientIdParam ?? 'client_id',\n tokenClientSecretParam: candidate.tokenClientSecretParam ?? 'client_secret',\n tokenClientAuthMethod: candidate.tokenClientAuthMethod ?? 'client_secret_post',\n sendScopeParam: candidate.sendScopeParam,\n extraAuthParams: candidate.extraAuthParams,\n tokenRequestHeaders: candidate.tokenRequestHeaders,\n urlTemplateMetadata: candidate.urlTemplateMetadata,\n }\n }\n return undefined\n}\n\n/** The authorization-code OAuth2 branch of a manifest's auth spec, if it has\n * one. `one_of` manifests are resolved through their `preferred` mode, which\n * is the mode a connect flow would actually drive. Returns undefined for\n * api-key/hmac/none connectors and for `client_credentials` OAuth (which has\n * no user-facing authorize step). */\nexport function bundledOAuth2Auth(manifest: ConnectorManifest):\n | (BundledOAuth2AuthContract & {\n grantType: 'authorization_code'\n authorizationUrl: string\n })\n | undefined {\n const contract = bundledOAuth2AuthContract(manifest)\n if (contract?.grantType !== 'authorization_code' || !contract.authorizationUrl) return undefined\n return {\n ...contract,\n grantType: 'authorization_code',\n authorizationUrl: contract.authorizationUrl,\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TangleIdentityUnreachableError,
3
3
  createTangleIdentityClient
4
- } from "./chunk-P4YUWB77.js";
4
+ } from "./chunk-DCSQH4O6.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-LIY4A33C.js.map
129
+ //# sourceMappingURL=chunk-PIEBVPSW.js.map
@@ -2,7 +2,7 @@ import {
2
2
  DEFAULT_TANGLE_PLATFORM_URL,
3
3
  TangleIdentityUnreachableError,
4
4
  createTangleIdentityClient
5
- } from "./chunk-P4YUWB77.js";
5
+ } from "./chunk-DCSQH4O6.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-RBCF44OD.js.map
106
+ //# sourceMappingURL=chunk-PIF635BP.js.map
@@ -14,7 +14,7 @@ import {
14
14
  TangleIdentityUnreachableError,
15
15
  createTangleIdentityClient,
16
16
  tangleIdentity
17
- } from "./chunk-P4YUWB77.js";
17
+ } from "./chunk-DCSQH4O6.js";
18
18
  import {
19
19
  __export
20
20
  } from "./chunk-PZ5AY32C.js";
@@ -16248,6 +16248,7 @@ var paychexConnector = declarativeRestConnector({
16248
16248
  category: "hr",
16249
16249
  defaultConsistencyModel: "authoritative",
16250
16250
  baseUrl: { metadataKey: "apiBaseUri", fallback: "https://api.paychex.com" },
16251
+ allowedBaseUrlSuffixes: [".paychex.com"],
16251
16252
  // Listing companies is the cheapest read that proves the client-credentials
16252
16253
  // token is valid and the app has been granted access.
16253
16254
  test: { method: "GET", path: "/companies" },
@@ -16844,8 +16845,8 @@ var ripplingConnector = declarativeRestConnector({
16844
16845
  description: "Read Rippling HRIS data (employees, groups, departments, work locations) and update employee records via the Rippling Platform API.",
16845
16846
  auth: {
16846
16847
  kind: "oauth2",
16847
- authorizationUrl: "https://app.rippling.com/apps/{client_id}/install",
16848
- tokenUrl: "https://app.rippling.com/api/o/token/",
16848
+ authorizationUrl: "https://app.rippling.com/apps/PLATFORM/{appName}/authorize",
16849
+ tokenUrl: "https://api.rippling.com/api/o/token/",
16849
16850
  scopes: [
16850
16851
  "company:read",
16851
16852
  "employees:read",
@@ -16857,7 +16858,12 @@ var ripplingConnector = declarativeRestConnector({
16857
16858
  "company_activity:read"
16858
16859
  ],
16859
16860
  clientIdEnv: "RIPPLING_OAUTH_CLIENT_ID",
16860
- clientSecretEnv: "RIPPLING_OAUTH_CLIENT_SECRET"
16861
+ clientSecretEnv: "RIPPLING_OAUTH_CLIENT_SECRET",
16862
+ tokenClientAuthMethod: "client_secret_basic",
16863
+ pkce: "unsupported",
16864
+ urlTemplateMetadata: {
16865
+ appName: { kind: "path-segment" }
16866
+ }
16861
16867
  },
16862
16868
  category: "other",
16863
16869
  defaultConsistencyModel: "authoritative",
@@ -18812,13 +18818,13 @@ var workdayConnector = declarativeRestConnector({
18812
18818
  description: "Read Workday workers, organizations, locations, and time-off data and submit time-off requests through the tenant-scoped Workday REST API.",
18813
18819
  auth: {
18814
18820
  kind: "oauth2",
18815
- // Tenant-scoped at OAuth-client registration time. {host} resolves to
18816
- // the customer's Workday data center (wd5.workday.com, wd2-impl-...),
18821
+ // Tenant-scoped at OAuth-client registration time. {workdayBaseUrl}
18822
+ // resolves to the customer's Workday data-center origin,
18817
18823
  // {tenant} to the customer's tenant id. The platform stores the
18818
18824
  // resolved URLs alongside the OAuth client record, the same pattern
18819
18825
  // Auth0 uses for its tenant-scoped Management API client.
18820
- authorizationUrl: "https://{host}/ccx/oauth2/{tenant}/authorize",
18821
- tokenUrl: "https://{host}/ccx/oauth2/{tenant}/token",
18826
+ authorizationUrl: "{workdayBaseUrl}/ccx/oauth2/{tenant}/authorize",
18827
+ tokenUrl: "{workdayBaseUrl}/ccx/oauth2/{tenant}/token",
18822
18828
  // Workday scopes are Functional Area names; the tenant admin enables
18823
18829
  // matching functional areas on the API Client and the OAuth flow
18824
18830
  // narrows further per request. The set below maps to the action pack:
@@ -18826,7 +18832,17 @@ var workdayConnector = declarativeRestConnector({
18826
18832
  // cover the org/location reads; Time Off covers PTO list + submit.
18827
18833
  scopes: ["Staffing", "Organizations and Roles", "Time Off and Leave"],
18828
18834
  clientIdEnv: "WORKDAY_OAUTH_CLIENT_ID",
18829
- clientSecretEnv: "WORKDAY_OAUTH_CLIENT_SECRET"
18835
+ clientSecretEnv: "WORKDAY_OAUTH_CLIENT_SECRET",
18836
+ tokenClientAuthMethod: "client_secret_basic",
18837
+ sendScopeParam: false,
18838
+ pkce: "unsupported",
18839
+ urlTemplateMetadata: {
18840
+ workdayBaseUrl: {
18841
+ kind: "base-url",
18842
+ allowedBaseUrlSuffixes: [".workday.com", ".myworkday.com"]
18843
+ },
18844
+ tenant: { kind: "path-segment" }
18845
+ }
18830
18846
  },
18831
18847
  category: "other",
18832
18848
  defaultConsistencyModel: "authoritative",
@@ -18837,6 +18853,7 @@ var workdayConnector = declarativeRestConnector({
18837
18853
  // `../staffing/v1/{tenant}` so the staffing functional surface stays
18838
18854
  // reachable from the same connection without a second base URL.
18839
18855
  baseUrl: { metadataKey: "apiBaseUrl" },
18856
+ allowedBaseUrlSuffixes: [".workday.com", ".myworkday.com"],
18840
18857
  defaultHeaders: {
18841
18858
  accept: "application/json"
18842
18859
  },
@@ -149488,4 +149505,4 @@ export {
149488
149505
  getresponseConnector,
149489
149506
  adapters_exports
149490
149507
  };
149491
- //# sourceMappingURL=chunk-BYTP5QJQ.js.map
149508
+ //# sourceMappingURL=chunk-QL6CRJ3E.js.map