@tangle-network/agent-integrations 0.73.0 → 0.74.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/bundled-adapter-manifests.json +1 -1
- package/dist/{adapters-D8TvHRA1.js → adapters-DVwQyQCI.js} +414 -631
- package/dist/{adapters-D8TvHRA1.js.map → adapters-DVwQyQCI.js.map} +1 -1
- package/dist/{bundled-manifest-data-MdURriam.js → bundled-manifest-data-Bj_ENela.js} +276 -12
- package/dist/{bundled-manifest-data-MdURriam.js.map → bundled-manifest-data-Bj_ENela.js.map} +1 -1
- package/dist/cloudbeds-BL5EF3xl.js +1195 -0
- package/dist/cloudbeds-BL5EF3xl.js.map +1 -0
- package/dist/connect/index.js +1 -1
- package/dist/connectors/adapters/index.d.ts +1 -1
- package/dist/connectors/adapters/index.js +3 -2
- package/dist/connectors/index.d.ts +2 -2
- package/dist/connectors/index.js +5 -4
- package/dist/{consumer-DaKqPDG-.d.ts → consumer-BslMZthR.d.ts} +2 -2
- package/dist/{consumer-DaKqPDG-.d.ts.map → consumer-BslMZthR.d.ts.map} +1 -1
- package/dist/consumer.d.ts +1 -1
- package/dist/consumer.js +1 -1
- package/dist/{index-b05PzTtX.d.ts → index-B227L17C.d.ts} +2 -2
- package/dist/{index-b05PzTtX.d.ts.map → index-B227L17C.d.ts.map} +1 -1
- package/dist/{index-udV3gGjx.d.ts → index-Gn72pQCA.d.ts} +10 -1
- package/dist/index-Gn72pQCA.d.ts.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -6
- package/dist/internal/bundled-manifest-runtime.js +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/registry.js +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/sandbox.d.ts +1 -1
- package/dist/{specs-BYUQOsSC.js → specs-BnY10gv2.js} +2 -2
- package/dist/{specs-BYUQOsSC.js.map → specs-BnY10gv2.js.map} +1 -1
- package/dist/specs.js +1 -1
- package/dist/{src-c7Ru9_WT.js → src-Bgyicg7S.js} +4 -4
- package/dist/{src-c7Ru9_WT.js.map → src-Bgyicg7S.js.map} +1 -1
- package/dist/{tangle-id-D2kPF0rX.js → tangle-id-81ShqJ7k.js} +3 -262
- package/dist/tangle-id-81ShqJ7k.js.map +1 -0
- package/dist/types-BSDXGSi5.js +263 -0
- package/dist/types-BSDXGSi5.js.map +1 -0
- package/dist/webhooks/index.d.ts +5 -0
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +92 -2
- package/dist/webhooks/index.js.map +1 -1
- package/docs/hospitality-connectors.md +91 -11
- package/docs/knowledge-provider-contracts.md +86 -0
- package/package.json +2 -1
- package/dist/index-udV3gGjx.d.ts.map +0 -1
- package/dist/tangle-id-D2kPF0rX.js.map +0 -1
- package/dist/webhooks-FsjqRN_0.js +0 -117
- package/dist/webhooks-FsjqRN_0.js.map +0 -1
|
@@ -1,264 +1,5 @@
|
|
|
1
1
|
import { c as isRealNonPlaceholderEmail } from "./billing-access-policy-DJ63AxZ5.js";
|
|
2
|
-
|
|
3
|
-
/** Thrown by `executeMutation` when upstream rejects on CAS — caught and
|
|
4
|
-
* rewrapped by MutationGuard. */
|
|
5
|
-
var ResourceContention = class extends Error {
|
|
6
|
-
alternatives;
|
|
7
|
-
currentState;
|
|
8
|
-
name = "ResourceContention";
|
|
9
|
-
constructor(message, alternatives = [], currentState) {
|
|
10
|
-
super(message);
|
|
11
|
-
this.alternatives = alternatives;
|
|
12
|
-
this.currentState = currentState;
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
/** Thrown when the connector finds the user's grant has been revoked or the
|
|
16
|
-
* access token is no longer valid AND refresh failed — reconnecting fixes it.
|
|
17
|
-
* Surfaces to the UI as "Reconnect required". Carries optional structured
|
|
18
|
-
* `{status, reason, body}` so the platform can confirm a 401/403 really is a
|
|
19
|
-
* credential reason before flipping the connection to reconnect_required. */
|
|
20
|
-
var CredentialsExpired = class extends Error {
|
|
21
|
-
dataSourceId;
|
|
22
|
-
name = "CredentialsExpired";
|
|
23
|
-
status;
|
|
24
|
-
reason;
|
|
25
|
-
body;
|
|
26
|
-
constructor(message, dataSourceId, init) {
|
|
27
|
-
super(message);
|
|
28
|
-
this.dataSourceId = dataSourceId;
|
|
29
|
-
this.status = init?.status;
|
|
30
|
-
this.reason = init?.reason;
|
|
31
|
-
this.body = init?.body;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
/** Thrown when the provider rejects the call for a configuration or permission
|
|
35
|
-
* reason that reconnecting the credential CANNOT fix — the API isn't enabled
|
|
36
|
-
* for the project (`accessNotConfigured`), a required scope is missing
|
|
37
|
-
* (`insufficientPermissions`), or the resource is forbidden
|
|
38
|
-
* (`PERMISSION_DENIED`). Distinct from CredentialsExpired so the platform
|
|
39
|
-
* surfaces "fix the project/scope" instead of sending the user into an
|
|
40
|
-
* unresolvable reconnect loop. */
|
|
41
|
-
var ProviderConfigError = class extends Error {
|
|
42
|
-
dataSourceId;
|
|
43
|
-
name = "ProviderConfigError";
|
|
44
|
-
status;
|
|
45
|
-
reason;
|
|
46
|
-
body;
|
|
47
|
-
constructor(message, dataSourceId, init) {
|
|
48
|
-
super(message);
|
|
49
|
-
this.dataSourceId = dataSourceId;
|
|
50
|
-
this.status = init.status;
|
|
51
|
-
this.reason = init.reason;
|
|
52
|
-
this.body = init.body;
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
/** Thrown when the provider throttles the call (quota / rate limit). Gmail and
|
|
56
|
-
* Calendar surface daily/per-user limits as HTTP 403 with a quota `reason`
|
|
57
|
-
* (`rateLimitExceeded`/`dailyLimitExceeded`/`userRateLimitExceeded`), NOT 429
|
|
58
|
-
* — so it is classified by reason, not status, and must never read as a
|
|
59
|
-
* credential failure. The message includes "rate limit" so a platform that
|
|
60
|
-
* only inspects the message still routes it to rate-limited. */
|
|
61
|
-
var ProviderRateLimited = class extends Error {
|
|
62
|
-
dataSourceId;
|
|
63
|
-
name = "ProviderRateLimited";
|
|
64
|
-
status;
|
|
65
|
-
reason;
|
|
66
|
-
body;
|
|
67
|
-
retryAfterMs;
|
|
68
|
-
constructor(message, dataSourceId, init) {
|
|
69
|
-
super(message);
|
|
70
|
-
this.dataSourceId = dataSourceId;
|
|
71
|
-
this.status = init.status;
|
|
72
|
-
this.reason = init.reason;
|
|
73
|
-
this.body = init.body;
|
|
74
|
-
this.retryAfterMs = init.retryAfterMs;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
/** Thrown when the provider settles a call with a non-2xx status that is not
|
|
78
|
-
* a credential, permission or rate-limit failure: a 400 naming a bad field, a
|
|
79
|
-
* 404, a 409, or a 5xx. Carries `{status, reason, body}` so the platform
|
|
80
|
-
* answers a refusal as a refusal (a 4xx with the provider's message) instead
|
|
81
|
-
* of a gateway fault, and keeps the provider's own error code as `reason`. */
|
|
82
|
-
var ProviderRequestError = class extends Error {
|
|
83
|
-
dataSourceId;
|
|
84
|
-
name = "ProviderRequestError";
|
|
85
|
-
status;
|
|
86
|
-
reason;
|
|
87
|
-
body;
|
|
88
|
-
constructor(message, dataSourceId, init) {
|
|
89
|
-
super(message);
|
|
90
|
-
this.dataSourceId = dataSourceId;
|
|
91
|
-
this.status = init.status;
|
|
92
|
-
this.reason = init.reason;
|
|
93
|
-
this.body = init.body;
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
/** Thrown before any provider request when a caller's argument breaks the
|
|
97
|
-
* capability's declared contract: a value that fails its `pattern`, or one the
|
|
98
|
-
* provider would refuse with a misleading answer (a commit sha where a branch
|
|
99
|
-
* name belongs reads back as "Branch not found"). `field` names the argument,
|
|
100
|
-
* and the message says what to pass instead. `status` is 400 so a platform
|
|
101
|
-
* classifier answers it as a settled refusal: the same arguments fail again. */
|
|
102
|
-
var InvalidCapabilityArgument = class extends Error {
|
|
103
|
-
field;
|
|
104
|
-
name = "InvalidCapabilityArgument";
|
|
105
|
-
status = 400;
|
|
106
|
-
reason = "invalid_argument";
|
|
107
|
-
constructor(message, field) {
|
|
108
|
-
super(message);
|
|
109
|
-
this.field = field;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
/** Validate the static connector manifest before a provider registers it.
|
|
113
|
-
* This catches the expensive mistakes early: duplicate capability names,
|
|
114
|
-
* mutation capabilities without CAS, authoritative fire-and-forget writes,
|
|
115
|
-
* and invalid rate-limit specs. */
|
|
116
|
-
function validateConnectorManifest(manifest) {
|
|
117
|
-
const issues = [];
|
|
118
|
-
if (!manifest.kind.trim()) issues.push({
|
|
119
|
-
path: "kind",
|
|
120
|
-
message: "kind is required"
|
|
121
|
-
});
|
|
122
|
-
if (!manifest.displayName.trim()) issues.push({
|
|
123
|
-
path: "displayName",
|
|
124
|
-
message: "displayName is required"
|
|
125
|
-
});
|
|
126
|
-
const consistencyModels = /* @__PURE__ */ new Set([
|
|
127
|
-
"authoritative",
|
|
128
|
-
"cache",
|
|
129
|
-
"advisory"
|
|
130
|
-
]);
|
|
131
|
-
if (!consistencyModels.has(manifest.defaultConsistencyModel)) issues.push({
|
|
132
|
-
path: "defaultConsistencyModel",
|
|
133
|
-
message: "unsupported consistency model"
|
|
134
|
-
});
|
|
135
|
-
validateAuthSpec(manifest.auth, issues);
|
|
136
|
-
const seen = /* @__PURE__ */ new Set();
|
|
137
|
-
for (const [index, capability] of manifest.capabilities.entries()) {
|
|
138
|
-
const path = `capabilities[${index}]`;
|
|
139
|
-
if (!capability.name.trim()) issues.push({
|
|
140
|
-
path: `${path}.name`,
|
|
141
|
-
message: "capability name is required"
|
|
142
|
-
});
|
|
143
|
-
if (seen.has(capability.name)) issues.push({
|
|
144
|
-
path: `${path}.name`,
|
|
145
|
-
message: `duplicate capability name: ${capability.name}`
|
|
146
|
-
});
|
|
147
|
-
seen.add(capability.name);
|
|
148
|
-
if (capability.consistencyModel !== void 0 && !consistencyModels.has(capability.consistencyModel)) issues.push({
|
|
149
|
-
path: `${path}.consistencyModel`,
|
|
150
|
-
message: "unsupported consistency model"
|
|
151
|
-
});
|
|
152
|
-
if (capability.class === "mutation") {
|
|
153
|
-
if (!capability.cas) issues.push({
|
|
154
|
-
path: `${path}.cas`,
|
|
155
|
-
message: "mutation capability must declare a CAS strategy"
|
|
156
|
-
});
|
|
157
|
-
if ((capability.consistencyModel ?? manifest.defaultConsistencyModel) === "authoritative" && capability.cas === "none") issues.push({
|
|
158
|
-
path: `${path}.cas`,
|
|
159
|
-
message: "authoritative mutations cannot use cas=\"none\""
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (manifest.rateLimit) {
|
|
164
|
-
if (!Number.isFinite(manifest.rateLimit.requests) || manifest.rateLimit.requests <= 0) issues.push({
|
|
165
|
-
path: "rateLimit.requests",
|
|
166
|
-
message: "rateLimit.requests must be positive"
|
|
167
|
-
});
|
|
168
|
-
if (!Number.isFinite(manifest.rateLimit.windowMs) || manifest.rateLimit.windowMs <= 0) issues.push({
|
|
169
|
-
path: "rateLimit.windowMs",
|
|
170
|
-
message: "rateLimit.windowMs must be positive"
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
return {
|
|
174
|
-
ok: issues.length === 0,
|
|
175
|
-
issues
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function validateAuthSpec(auth, issues) {
|
|
179
|
-
if (auth.kind === "oauth2") {
|
|
180
|
-
validateOAuth2AuthSpec(auth, issues, "auth");
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (auth.kind !== "one_of") return;
|
|
184
|
-
if (auth.options.length < 2) issues.push({
|
|
185
|
-
path: "auth.options",
|
|
186
|
-
message: "one_of auth must declare at least two options"
|
|
187
|
-
});
|
|
188
|
-
if (!new Set(auth.options.map((option) => option.kind)).has(auth.preferred)) issues.push({
|
|
189
|
-
path: "auth.preferred",
|
|
190
|
-
message: "one_of preferred auth must match an option kind"
|
|
191
|
-
});
|
|
192
|
-
for (const [index, option] of auth.options.entries()) if (option.kind === "oauth2") validateOAuth2AuthSpec(option, issues, `auth.options[${index}]`);
|
|
193
|
-
}
|
|
194
|
-
function validateOAuth2AuthSpec(auth, issues, path) {
|
|
195
|
-
if ((auth.grantType ?? "authorization_code") === "authorization_code" && !auth.authorizationUrl?.trim()) issues.push({
|
|
196
|
-
path: `${path}.authorizationUrl`,
|
|
197
|
-
message: "authorization_code grant requires authorizationUrl"
|
|
198
|
-
});
|
|
199
|
-
if (auth.pkce !== void 0 && auth.pkce !== "required" && auth.pkce !== "supported" && auth.pkce !== "unsupported") issues.push({
|
|
200
|
-
path: `${path}.pkce`,
|
|
201
|
-
message: "pkce must be required, supported, or unsupported"
|
|
202
|
-
});
|
|
203
|
-
for (const [key, spec] of Object.entries(auth.urlTemplateMetadata ?? {})) {
|
|
204
|
-
if (spec.kind !== "base-url" && spec.kind !== "path-segment") {
|
|
205
|
-
issues.push({
|
|
206
|
-
path: `${path}.urlTemplateMetadata.${key}.kind`,
|
|
207
|
-
message: "OAuth URL metadata kind must be base-url or path-segment"
|
|
208
|
-
});
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
const placeholder = `{${key}}`;
|
|
212
|
-
if (!auth.authorizationUrl?.includes(placeholder) && !auth.tokenUrl.includes(placeholder)) issues.push({
|
|
213
|
-
path: `${path}.urlTemplateMetadata.${key}`,
|
|
214
|
-
message: `OAuth URL metadata is unused; add ${placeholder} to an OAuth endpoint`
|
|
215
|
-
});
|
|
216
|
-
if (spec.kind === "base-url") {
|
|
217
|
-
if (!spec.requirePublicHttps && !spec.allowedBaseUrls?.length && !spec.allowedBaseUrlSuffixes?.length) issues.push({
|
|
218
|
-
path: `${path}.urlTemplateMetadata.${key}`,
|
|
219
|
-
message: "OAuth base URL metadata requires an allowlist or public HTTPS policy"
|
|
220
|
-
});
|
|
221
|
-
for (const suffix of spec.allowedBaseUrlSuffixes ?? []) if (!/^\.[a-z0-9.-]+$/i.test(suffix)) issues.push({
|
|
222
|
-
path: `${path}.urlTemplateMetadata.${key}.allowedBaseUrlSuffixes`,
|
|
223
|
-
message: "OAuth base URL hostname suffixes must start with a dot"
|
|
224
|
-
});
|
|
225
|
-
} else for (const endpoint of [auth.authorizationUrl, auth.tokenUrl]) if (endpoint?.includes(placeholder) && !isOAuthPathSegmentPlaceholder(endpoint, placeholder)) issues.push({
|
|
226
|
-
path: `${path}.urlTemplateMetadata.${key}`,
|
|
227
|
-
message: `OAuth path metadata ${placeholder} must occupy a complete URL path segment`
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
if (auth.tokenClientAuthMethod !== void 0 && auth.tokenClientAuthMethod !== "none" && auth.tokenClientAuthMethod !== "client_secret_post" && auth.tokenClientAuthMethod !== "client_secret_basic") issues.push({
|
|
231
|
-
path: `${path}.tokenClientAuthMethod`,
|
|
232
|
-
message: "tokenClientAuthMethod must be none, client_secret_post, or client_secret_basic"
|
|
233
|
-
});
|
|
234
|
-
if ((auth.tokenClientAuthMethod ?? "client_secret_post") !== "none" && !auth.clientSecretEnv?.trim()) issues.push({
|
|
235
|
-
path: `${path}.clientSecretEnv`,
|
|
236
|
-
message: "confidential OAuth clients require clientSecretEnv"
|
|
237
|
-
});
|
|
238
|
-
for (const header of Object.keys(auth.tokenRequestHeaders ?? {})) if (header.toLowerCase() === "authorization" || header.toLowerCase() === "content-type") issues.push({
|
|
239
|
-
path: `${path}.tokenRequestHeaders.${header}`,
|
|
240
|
-
message: "tokenRequestHeaders cannot override Authorization or Content-Type"
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
function isOAuthPathSegmentPlaceholder(template, placeholder) {
|
|
244
|
-
const schemeIndex = template.indexOf("://");
|
|
245
|
-
const rootEnd = schemeIndex >= 0 ? schemeIndex + 3 : template.startsWith("{") ? template.indexOf("}") + 1 : 0;
|
|
246
|
-
const pathStart = template.indexOf("/", rootEnd);
|
|
247
|
-
const queryStart = template.search(/[?#]/);
|
|
248
|
-
let cursor = 0;
|
|
249
|
-
while (true) {
|
|
250
|
-
const index = template.indexOf(placeholder, cursor);
|
|
251
|
-
if (index < 0) return true;
|
|
252
|
-
const end = index + placeholder.length;
|
|
253
|
-
if (pathStart < 0 || index < pathStart || queryStart >= 0 && index >= queryStart || template[index - 1] !== "/" || end < template.length && template[end] !== "/" && template[end] !== "?" && template[end] !== "#") return false;
|
|
254
|
-
cursor = end;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function assertValidConnectorManifest(manifest) {
|
|
258
|
-
const result = validateConnectorManifest(manifest);
|
|
259
|
-
if (!result.ok) throw new Error(`Invalid connector manifest ${manifest.kind || "<unknown>"}: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
|
|
260
|
-
}
|
|
261
|
-
//#endregion
|
|
2
|
+
import { t as CredentialsExpired } from "./types-BSDXGSi5.js";
|
|
262
3
|
//#region src/connectors/adapters/tangle-id.ts
|
|
263
4
|
/**
|
|
264
5
|
* @stable Tangle Identity — `id.tangle.tools` connector + verifier.
|
|
@@ -1003,6 +744,6 @@ function isPlatformProductPrincipal(value) {
|
|
|
1003
744
|
return value === "router" || value === "sandbox";
|
|
1004
745
|
}
|
|
1005
746
|
//#endregion
|
|
1006
|
-
export { TangleIdentityUnreachableError as a,
|
|
747
|
+
export { TangleIdentityUnreachableError as a, TANGLE_SERVICE_TOKEN_PREFIX as i, TANGLE_API_KEY_PREFIX as n, createTangleIdentityClient as o, TANGLE_BROKER_TOKEN_PREFIX as r, tangleIdentity as s, DEFAULT_TANGLE_PLATFORM_URL as t };
|
|
1007
748
|
|
|
1008
|
-
//# sourceMappingURL=tangle-id-
|
|
749
|
+
//# sourceMappingURL=tangle-id-81ShqJ7k.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tangle-id-81ShqJ7k.js","names":[],"sources":["../src/connectors/adapters/tangle-id.ts"],"sourcesContent":["/**\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'\nimport { isRealNonPlaceholderEmail } from '../../billing-access-policy.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/** Broker keys are scoped to hub execution, never user or owner identity. */\nexport const TANGLE_BROKER_TOKEN_PREFIX = 'sk-tan-broker-'\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 /** Routing hint sent with the authenticated service token. This is not key provenance. */\n serviceName?: string\n /** Product the authenticated service expects this key to serve. API-key\n * authentication fails closed when this is absent. */\n expectedProduct?: PlatformKeyProduct\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 /** Stable Platform API-key row id. Present only for API-key auth. */\n apiKeyId?: string\n /** Product the credential is scoped to, when known. */\n product?: PlatformKeyProduct\n /** Immutable Platform service that provisioned this key. */\n provisionedByService?: string\n /** Platform proof that a human controls a real inbox. */\n emailVerified?: boolean\n /** Platform-owned machine identity. */\n servicePrincipal?: boolean\n /** Real email when Platform returns it for this credential. */\n email?: 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 | 'email_verification_required'\n | 'real_email_required'\n | 'product_scope_required'\n | 'product_scope_mismatch'\n\n/** Product values accepted by Platform's shared key-verification contract. */\nexport type PlatformKeyProduct =\n | 'router'\n | 'sandbox'\n | 'hub'\n | 'intelligence'\n | 'blueprint-agent'\n | 'evals'\n | 'agent-builder'\n | 'audits'\n | 'tax-agent'\n | 'legal-agent'\n | 'gtm-agent'\n | 'creative-agent'\n | 'insurance-agent'\n\n/** Products accepted by Platform's `expectedProduct` request field. */\nexport type PlatformProductPrincipal = 'router' | 'sandbox'\n\n/** Request accepted by Platform's authenticated `/v1/keys/verify` route. */\nexport interface PlatformKeyVerifyRequest {\n key: string\n expectedProduct?: PlatformProductPrincipal\n}\n\n/** Shared response from Platform's authenticated `/v1/keys/verify` route. */\nexport interface PlatformKeyVerifyResponse {\n valid: boolean\n email?: string\n emailVerified?: boolean\n emailVerificationRequired?: boolean\n servicePrincipal?: boolean\n provisionedByService?: string\n userId?: string\n ownerId?: string\n teamId?: string\n ownerType?: 'user' | 'team'\n memberUserId?: string\n keyId?: string\n product?: PlatformKeyProduct\n name?: string\n scopes?: string[]\n budgetRemaining?: number\n monthlyBudgetRemaining?: number\n allowedModels?: string[]\n rpmLimit?: number\n plan?: string\n sandboxTier?: 'free' | 'pro' | 'enterprise'\n blueprintAgentTier?: 'free' | 'pro' | 'enterprise'\n role?: 'admin' | 'member' | 'readonly'\n}\n\ntype VerifiedPlatformKeyResponse = PlatformKeyVerifyResponse & {\n valid: true\n emailVerified: true\n servicePrincipal: boolean\n provisionedByService: string\n userId: string\n ownerId: string\n ownerType: 'user' | 'team'\n keyId: string\n name: string\n}\n\nexport interface TangleUserSummary {\n id: string\n email?: string\n emailVerified?: boolean\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?.trim()\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? PLATFORM_FETCH_TIMEOUT_MS\n\n if (\n serviceToken &&\n (!serviceToken.startsWith(TANGLE_SERVICE_TOKEN_PREFIX) || serviceToken.length <= TANGLE_SERVICE_TOKEN_PREFIX.length)\n ) {\n throw new TangleIdentityUnreachableError('tangle-id: serviceToken must start with svc_')\n }\n if (serviceToken && !serviceName) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: serviceName is required for service-to-service calls',\n )\n }\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 if (!serviceName) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: serviceName is required for service-to-service calls',\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(\n token: string,\n requireProductScope: boolean,\n ): Promise<TangleTokenVerifyResult> {\n const res = await jsonFetch('/v1/keys/verify', {\n method: 'POST',\n headers: s2sHeaders(),\n body: JSON.stringify({\n key: token,\n ...(isPlatformProductPrincipal(opts.expectedProduct)\n ? { expectedProduct: opts.expectedProduct }\n : {}),\n } satisfies PlatformKeyVerifyRequest),\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 Partial<PlatformKeyVerifyResponse> | null\n if (!body || typeof body.valid !== 'boolean') {\n return { valid: false, reason: 'malformed' }\n }\n if (!body.valid) {\n return { valid: false, reason: 'revoked' }\n }\n if (body.emailVerificationRequired === true) {\n return { valid: false, reason: 'email_verification_required' }\n }\n if (!isValidPlatformKeyResponse(body)) return { valid: false, reason: 'malformed' }\n if (requireProductScope && !opts.expectedProduct) {\n return { valid: false, reason: 'product_scope_required' }\n }\n if (requireProductScope && body.product !== opts.expectedProduct) {\n return { valid: false, reason: 'product_scope_mismatch' }\n }\n const scopes = [\n ...(Array.isArray(body.scopes) ? body.scopes : []),\n ...(Array.isArray(body.allowedModels) ? body.allowedModels : []),\n ].filter((value, index, all): value is string => typeof value === 'string' && all.indexOf(value) === index)\n if (body.product) scopes.push(`product:${body.product}`)\n return {\n valid: true,\n kind: 'api_key',\n userId: body.userId,\n workspaceId: body.ownerId,\n ownerType: body.ownerType,\n emailVerified: true,\n scopes,\n credentialId: body.keyId,\n apiKeyId: body.keyId,\n ...(body.product ? { product: body.product } : {}),\n provisionedByService: body.provisionedByService,\n servicePrincipal: body.servicePrincipal,\n ...(body.email ? { email: body.email } : {}),\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; emailVerified?: boolean }\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 if (body.user.emailVerified !== true) {\n return { valid: false, reason: 'email_verification_required' }\n }\n if (!isRealNonPlaceholderEmail(body.user.email)) {\n return { valid: false, reason: 'real_email_required' }\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 email: body.user.email,\n emailVerified: true,\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 if (token.startsWith(TANGLE_BROKER_TOKEN_PREFIX)) {\n return { valid: false, reason: 'service_token_refused' }\n }\n if (token.length <= TANGLE_API_KEY_PREFIX.length) {\n return { valid: false, reason: 'malformed' }\n }\n return verifyApiKey(token, true)\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 | {\n success?: boolean\n data?: {\n id?: string\n email?: string\n emailVerified?: boolean\n name?: string | null\n image?: string | null\n }\n }\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 ...(typeof data.emailVerified === 'boolean' ? { emailVerified: data.emailVerified } : {}),\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 if (token.startsWith(TANGLE_BROKER_TOKEN_PREFIX)) {\n throw new TangleIdentityUnreachableError(\n 'tangle-id: refusing to revoke a broker token through the user-key path',\n )\n }\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, false)\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\nfunction isValidPlatformKeyResponse(\n value: Partial<PlatformKeyVerifyResponse>,\n): value is VerifiedPlatformKeyResponse {\n if (value.valid !== true || value.emailVerified !== true) return false\n if (typeof value.servicePrincipal !== 'boolean') return false\n if (!readRequiredString(value.userId)) return false\n if (!readRequiredString(value.ownerId)) return false\n if (value.ownerType !== 'user' && value.ownerType !== 'team') return false\n if (!readRequiredString(value.keyId)) return false\n if (!readRequiredString(value.name)) return false\n if (!readRequiredString(value.provisionedByService)) return false\n if (value.servicePrincipal === false && !isRealNonPlaceholderEmail(value.email)) return false\n return true\n}\n\nfunction readRequiredString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\nfunction isPlatformProductPrincipal(value: unknown): value is PlatformProductPrincipal {\n return value === 'router' || value === 'sandbox'\n}\n"],"mappings":"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"}
|