@databricks/appkit 0.82.0 → 0.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +6 -0
- package/dist/appkit/package.js +1 -1
- package/dist/connectors/lakebase/index.js +1 -1
- package/dist/connectors/lakebase/routing-pool.d.ts +1 -1
- package/dist/connectors/lakebase/routing-pool.js +3 -3
- package/dist/connectors/lakebase/routing-pool.js.map +1 -1
- package/dist/context/caller-context.d.ts +22 -0
- package/dist/context/caller-context.d.ts.map +1 -0
- package/dist/context/caller-context.js +21 -0
- package/dist/context/caller-context.js.map +1 -0
- package/dist/context/deprecation.js +14 -0
- package/dist/context/deprecation.js.map +1 -0
- package/dist/context/execution-context.d.ts +12 -4
- package/dist/context/execution-context.d.ts.map +1 -1
- package/dist/context/execution-context.js +36 -34
- package/dist/context/execution-context.js.map +1 -1
- package/dist/context/index.d.ts +3 -2
- package/dist/context/index.js +2 -1
- package/dist/context/service-context.d.ts +18 -12
- package/dist/context/service-context.d.ts.map +1 -1
- package/dist/context/service-context.js +44 -54
- package/dist/context/service-context.js.map +1 -1
- package/dist/context/user-context.d.ts +5 -8
- package/dist/context/user-context.d.ts.map +1 -1
- package/dist/context/user-context.js +33 -6
- package/dist/context/user-context.js.map +1 -1
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +4 -2
- package/dist/core/appkit.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -2
- package/dist/plugin/plugin.d.ts +2 -2
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +6 -6
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts +1 -1
- package/dist/plugins/ai-search/ai-search.d.ts +1 -1
- package/dist/plugins/analytics/analytics.d.ts +1 -1
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +3 -1
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/database/database.d.ts +1 -1
- package/dist/plugins/files/plugin.d.ts +11 -11
- package/dist/plugins/files/plugin.js +17 -17
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.d.ts +1 -1
- package/dist/plugins/jobs/plugin.d.ts +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +4 -4
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/server/index.d.ts +1 -1
- package/dist/plugins/server/index.js +2 -2
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +3 -3
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
- package/dist/plugins/server/static-server.js +3 -3
- package/dist/plugins/server/static-server.js.map +1 -1
- package/dist/plugins/server/utils.js +3 -3
- package/dist/plugins/server/utils.js.map +1 -1
- package/dist/plugins/server/vite-dev-server.js +4 -4
- package/dist/plugins/server/vite-dev-server.js.map +1 -1
- package/dist/plugins/serving/serving.d.ts +1 -1
- package/dist/resources/index.d.ts +1 -0
- package/dist/resources/index.js +3 -0
- package/dist/resources/warehouse.d.ts +16 -0
- package/dist/resources/warehouse.d.ts.map +1 -0
- package/dist/resources/warehouse.js +93 -0
- package/dist/resources/warehouse.js.map +1 -0
- package/dist/schemas/manifest.d.ts +9 -0
- package/dist/schemas/manifest.d.ts.map +1 -1
- package/dist/schemas/manifest.js +11 -0
- package/dist/schemas/manifest.js.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +42 -33
- package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
- package/dist/shared/src/schemas/manifest.js +11 -0
- package/dist/shared/src/schemas/manifest.js.map +1 -1
- package/dist/testing/create-test-app.js +2 -2
- package/dist/testing/create-test-app.js.map +1 -1
- package/dist/testing/fixtures.d.ts +3 -2
- package/dist/testing/fixtures.d.ts.map +1 -1
- package/dist/testing/fixtures.js +21 -14
- package/dist/testing/fixtures.js.map +1 -1
- package/dist/testing/reset-singletons.js +1 -1
- package/dist/type-generator/database/generate.js +3 -3
- package/dist/type-generator/database/generate.js.map +1 -1
- package/dist/type-generator/migration.js +2 -2
- package/dist/type-generator/migration.js.map +1 -1
- package/dist/type-generator/serving/server-file-extractor.js +3 -3
- package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
- package/docs/api/appkit/Class.Plugin.md +1 -1
- package/docs/api/appkit/Function.getCurrentActorId.md +12 -0
- package/docs/api/appkit/Function.getCurrentPrincipalKey.md +12 -0
- package/docs/api/appkit/Function.getExecutionContext.md +5 -3
- package/docs/api/appkit/Function.getWarehouseId.md +20 -0
- package/docs/api/appkit/Interface.CallerContext.md +41 -0
- package/docs/api/appkit/Interface.PluginManifest.md +25 -2
- package/docs/api/appkit/TypeAlias.CallerPrincipal.md +13 -0
- package/docs/api/appkit/TypeAlias.ExecutionContext.md +6 -0
- package/docs/api/appkit.md +6 -0
- package/docs/plugins/analytics.md +8 -0
- package/llms.txt +6 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ApiError } from "../../shared/src/workspace-client/errors.js";
|
|
2
2
|
import { AuthenticationError } from "../../errors/authentication.js";
|
|
3
3
|
import "../../errors/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { isCallerContext } from "../../context/caller-context.js";
|
|
5
5
|
import { createLogger } from "../../logging/logger.js";
|
|
6
|
-
import {
|
|
7
|
-
import { getCurrentUserId, getExecutionContext, getWorkspaceClient,
|
|
6
|
+
import { ServiceContext } from "../../context/service-context.js";
|
|
7
|
+
import { getCurrentUserId, getExecutionContext, getWorkspaceClient, runInCallerContext } from "../../context/execution-context.js";
|
|
8
8
|
import "../../context/index.js";
|
|
9
9
|
import { Plugin } from "../../plugin/plugin.js";
|
|
10
10
|
import { toPlugin } from "../../plugin/to-plugin.js";
|
|
@@ -113,7 +113,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
113
113
|
* return a policy user explicitly marked `isServicePrincipal: true`, so
|
|
114
114
|
* even in dev a `usersOnly`-style policy that gates on
|
|
115
115
|
* `!user.isServicePrincipal` cannot be tricked. The matching SDK execution
|
|
116
|
-
* path also falls through to the SP client (no `
|
|
116
|
+
* path also falls through to the SP client (no `runInCallerContext` wrap),
|
|
117
117
|
* so the policy user and the SDK identity stay aligned.
|
|
118
118
|
*/
|
|
119
119
|
_extractUser(req) {
|
|
@@ -191,7 +191,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
191
191
|
* NOTE: This method only selects which identity the *policy* sees. The
|
|
192
192
|
* matching SDK execution identity is selected separately by
|
|
193
193
|
* `_resolveAuthForRequest` and applied via `_runWithAuth` /
|
|
194
|
-
* `
|
|
194
|
+
* `runInCallerContext` in each handler. The two selections are designed to
|
|
195
195
|
* converge on the same identity per the policy-user matrix in the docs —
|
|
196
196
|
* see `docs/docs/plugins/files.md#policy-user-matrix`.
|
|
197
197
|
*/
|
|
@@ -942,7 +942,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
942
942
|
return this.volumeConfigs[volumeKey]?.auth ?? this.config.auth ?? "service-principal";
|
|
943
943
|
}
|
|
944
944
|
/**
|
|
945
|
-
* Build a `
|
|
945
|
+
* Build a `CallerContext` from request headers when both
|
|
946
946
|
* `x-forwarded-access-token` and `x-forwarded-user` are present, otherwise
|
|
947
947
|
* return `null`. Used by OBO route handlers to wrap SDK calls in the
|
|
948
948
|
* end-user's identity. A `null` result means "fall back to the service
|
|
@@ -954,12 +954,12 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
954
954
|
const token = req.header("x-forwarded-access-token")?.trim();
|
|
955
955
|
const userId = req.header("x-forwarded-user")?.trim();
|
|
956
956
|
if (!token || !userId) return null;
|
|
957
|
-
return ServiceContext.
|
|
957
|
+
return ServiceContext.createCallerContext(token, userId);
|
|
958
958
|
}
|
|
959
959
|
/**
|
|
960
960
|
* Build the telemetry attribute hash for the `files.auth_mode` span
|
|
961
961
|
* attribute. The value reflects what operationally happened — i.e.
|
|
962
|
-
* whether `
|
|
962
|
+
* whether `runInCallerContext` actually wrapped the SDK call:
|
|
963
963
|
* - HTTP route on OBO volume + valid token → `"on-behalf-of-user"`.
|
|
964
964
|
* - HTTP route on OBO volume + dev-fallback (no token) →
|
|
965
965
|
* `"service-principal"` (the route falls through to the SP client).
|
|
@@ -973,12 +973,12 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
973
973
|
}
|
|
974
974
|
/**
|
|
975
975
|
* One-shot resolver for HTTP route handlers. Builds the request's
|
|
976
|
-
* `
|
|
976
|
+
* `CallerContext` AT MOST ONCE (when the volume is OBO and the headers are
|
|
977
977
|
* present) and returns both the operationally-effective auth mode and the
|
|
978
|
-
* pre-built `
|
|
978
|
+
* pre-built `CallerContext`.
|
|
979
979
|
*
|
|
980
980
|
* Handlers thread the `userCtx` into `_runWithAuth(userCtx, fn)` to avoid
|
|
981
|
-
* a second `ServiceContext.
|
|
981
|
+
* a second `ServiceContext.createCallerContext()` allocation. That call
|
|
982
982
|
* builds a fresh `WorkspaceClient` per invocation, so doing it twice per
|
|
983
983
|
* request was pure throwaway overhead.
|
|
984
984
|
*/
|
|
@@ -1002,7 +1002,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
1002
1002
|
* `WorkspaceClient` and `getCurrentUserId()` are used — identical
|
|
1003
1003
|
* behavior to pre-OBO releases. This covers both SP volumes and the
|
|
1004
1004
|
* OBO dev-fallback path (where headers were missing).
|
|
1005
|
-
* - `userCtx` is a `
|
|
1005
|
+
* - `userCtx` is a `CallerContext`: wraps `fn` in `runInCallerContext(userCtx)`,
|
|
1006
1006
|
* so SDK calls execute as the end user and `getCurrentUserId()` (and
|
|
1007
1007
|
* therefore cache keys) resolve to the user's ID.
|
|
1008
1008
|
*
|
|
@@ -1011,7 +1011,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
1011
1011
|
* NOT take a `req` so it cannot accidentally re-build the context.
|
|
1012
1012
|
*/
|
|
1013
1013
|
async _runWithAuth(userCtx, fn) {
|
|
1014
|
-
if (userCtx) return
|
|
1014
|
+
if (userCtx) return runInCallerContext(userCtx, fn);
|
|
1015
1015
|
return fn();
|
|
1016
1016
|
}
|
|
1017
1017
|
/**
|
|
@@ -1058,7 +1058,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
1058
1058
|
}
|
|
1059
1059
|
/**
|
|
1060
1060
|
* Wrap each `VolumeAPI` method so its execution runs inside
|
|
1061
|
-
* `
|
|
1061
|
+
* `runInCallerContext(userCtx, ...)`. Used by `VolumeHandle.asUser(req)` to
|
|
1062
1062
|
* force the SDK identity to the end user regardless of the volume's
|
|
1063
1063
|
* `auth` setting. The policy check baked into each method (via
|
|
1064
1064
|
* `createVolumeAPI`) runs inside the same scope, so `getCurrentUserId()`
|
|
@@ -1069,7 +1069,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
1069
1069
|
* propagation — no additional parent span is opened.
|
|
1070
1070
|
*/
|
|
1071
1071
|
_wrapVolumeAPIInUserContext(api, userCtx) {
|
|
1072
|
-
const wrap = (operation, fn) => (...args) => this._withAuthModeAttributes(operation, "on-behalf-of-user", () =>
|
|
1072
|
+
const wrap = (operation, fn) => (...args) => this._withAuthModeAttributes(operation, "on-behalf-of-user", () => runInCallerContext(userCtx, () => fn(...args)));
|
|
1073
1073
|
return {
|
|
1074
1074
|
list: wrap("list", api.list),
|
|
1075
1075
|
read: wrap("read", api.read),
|
|
@@ -1142,7 +1142,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
1142
1142
|
_defineVolumeTools(volumeKey) {
|
|
1143
1143
|
const buildUser = () => {
|
|
1144
1144
|
const ctx = getExecutionContext();
|
|
1145
|
-
return
|
|
1145
|
+
return isCallerContext(ctx) ? { id: ctx.principal.userId } : {
|
|
1146
1146
|
id: ctx.serviceUserId,
|
|
1147
1147
|
isServicePrincipal: true
|
|
1148
1148
|
};
|
|
@@ -1265,7 +1265,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
|
|
|
1265
1265
|
* through the HTTP routes run as the end user; for programmatic calls
|
|
1266
1266
|
* outside a route, use `asUser(req)` to opt into per-user execution.
|
|
1267
1267
|
* `asUser(req)` is a hard override at the SDK level: it forces every
|
|
1268
|
-
* subsequent call to execute as the end user inside `
|
|
1268
|
+
* subsequent call to execute as the end user inside `runInCallerContext`,
|
|
1269
1269
|
* regardless of the volume's `auth` setting. Policies control per-user
|
|
1270
1270
|
* access in either mode.
|
|
1271
1271
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["manifest"],"sources":["../../../src/plugins/files/plugin.ts"],"sourcesContent":["import { STATUS_CODES } from \"node:http\";\nimport { Readable } from \"node:stream\";\n\nimport type express from \"express\";\nimport type {\n AgentToolDefinition,\n IAppRouter,\n PluginExecutionSettings,\n ToolProvider,\n} from \"shared\";\nimport { z } from \"zod\";\n\nimport {\n contentTypeFromPath,\n FILES_MAX_READ_SIZE,\n FilesConnector,\n isSafeInlineContentType,\n runWithFilesSpanAttributes,\n validateCustomContentTypes,\n} from \"../../connectors/files\";\nimport {\n getCurrentUserId,\n getExecutionContext,\n getWorkspaceClient,\n runInUserContext,\n ServiceContext,\n type UserContext,\n} from \"../../context\";\nimport { isUserContext } from \"../../context/user-context\";\nimport { buildToolkitEntries } from \"../../core/agent/build-toolkit\";\nimport {\n defineTool,\n executeFromRegistry,\n type ToolRegistry,\n toolsFromRegistry,\n} from \"../../core/agent/tools/define-tool\";\nimport { AuthenticationError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { ResourceRequirement } from \"../../registry\";\nimport { defineManifest, ResourceType } from \"../../registry\";\nimport { ApiError } from \"../../workspace-client\";\nimport {\n FILES_DOWNLOAD_DEFAULTS,\n FILES_MAX_UPLOAD_SIZE,\n FILES_READ_DEFAULTS,\n FILES_WRITE_DEFAULTS,\n} from \"./defaults\";\nimport { parentDirectory, sanitizeFilename } from \"./helpers\";\nimport manifest from \"./manifest.json\";\nimport {\n type FileAction,\n type FilePolicyUser,\n type FileResource,\n PolicyDeniedError,\n policy,\n} from \"./policy\";\nimport type {\n FilesExport,\n IFilesConfig,\n VolumeAPI,\n VolumeConfig,\n VolumeHandle,\n} from \"./types\";\n\nconst logger = createLogger(\"files\");\n\nexport class FilesPlugin extends Plugin implements ToolProvider {\n name = \"files\";\n\n /** Plugin manifest declaring metadata and resource requirements. */\n static manifest = defineManifest<\"files\">(manifest);\n protected static description = \"Files plugin for Databricks file operations\";\n declare protected config: IFilesConfig;\n\n private volumeConnectors: Record<string, FilesConnector> = {};\n private volumeConfigs: Record<string, VolumeConfig> = {};\n private volumeKeys: string[] = [];\n private tools: ToolRegistry = {};\n\n /**\n * Scans `process.env` for `DATABRICKS_VOLUME_*` keys and merges them with\n * any explicitly configured volumes. Explicit config wins for per-volume\n * overrides; auto-discovered volumes get default `{}` config.\n */\n static discoverVolumes(config: IFilesConfig): Record<string, VolumeConfig> {\n const explicit = config.volumes ?? {};\n const discovered: Record<string, VolumeConfig> = {};\n\n const prefix = \"DATABRICKS_VOLUME_\";\n for (const key of Object.keys(process.env)) {\n if (!key.startsWith(prefix)) continue;\n const suffix = key.slice(prefix.length);\n if (!suffix) continue;\n if (!process.env[key]) continue;\n const volumeKey = suffix.toLowerCase();\n if (!(volumeKey in explicit)) {\n discovered[volumeKey] = {};\n }\n }\n\n return { ...discovered, ...explicit };\n }\n\n /**\n * Generates resource requirements dynamically from discovered + configured volumes.\n * Each volume key maps to a `DATABRICKS_VOLUME_{KEY_UPPERCASE}` env var.\n *\n * ## Per-volume permission scope (SP vs OBO)\n *\n * The returned manifest entries describe a single permission grant per\n * volume, but the *grantee* depends on the volume's `auth` setting at\n * runtime — and that distinction is **not** expressed in the manifest\n * today:\n *\n * - **Service-principal volumes** (the default, `auth: \"service-principal\"`):\n * the app's service principal needs `WRITE_VOLUME` (or read-equivalent)\n * on the UC volume. This matches the manifest entry as written.\n * - **On-behalf-of-user volumes** (`auth: \"on-behalf-of-user\"`): SDK calls\n * execute as the **end user**, so the *user* — not the SP — must hold\n * `WRITE_VOLUME` (or read-equivalent) on the UC volume. The SP only\n * needs to be allowed to mint user-token requests; it does not need\n * direct volume permissions.\n *\n * The static manifest cannot currently express this per-volume split, so\n * callers configuring OBO volumes must communicate the per-user permission\n * requirement out-of-band (docs, runbooks, deployment scripts) until the\n * manifest schema gains a per-volume auth scope field.\n */\n static getResourceRequirements(config: IFilesConfig): ResourceRequirement[] {\n const volumes = FilesPlugin.discoverVolumes(config);\n // TODO: extend plugin-manifest.schema.json to express per-volume auth\n // scope so OBO volumes can declare end-user-required permissions in the\n // manifest itself.\n return Object.keys(volumes).map((key) => ({\n type: ResourceType.VOLUME,\n alias: `volume-${key}`,\n resourceKey: `volume-${key}`,\n description: `Unity Catalog Volume for \"${key}\" file storage`,\n permission: \"WRITE_VOLUME\",\n fields: {\n path: {\n env: `DATABRICKS_VOLUME_${key.toUpperCase()}`,\n description: `Volume path for \"${key}\" (e.g. /Volumes/catalog/schema/volume_name)`,\n },\n },\n required: true,\n }));\n }\n\n /**\n * Extraction for `VolumeHandle.asUser(req)`. In production we require BOTH\n * `x-forwarded-user` and `x-forwarded-access-token`, and throw\n * `AuthenticationError.missingToken` if either is missing — otherwise a\n * request with only `x-forwarded-user: alice` would let policies see Alice\n * as a \"real user\" (`isServicePrincipal: false`) while the SDK call below\n * falls through to the SP client because `_buildUserContextOrNull` returns\n * `null`. Net effect: policy approves the user, SDK runs as SP, privilege\n * confusion (CWE-639/863).\n *\n * In development (`NODE_ENV === \"development\"`) we keep a local-loop\n * convenience: if either header is missing we emit a single warning and\n * return a policy user explicitly marked `isServicePrincipal: true`, so\n * even in dev a `usersOnly`-style policy that gates on\n * `!user.isServicePrincipal` cannot be tricked. The matching SDK execution\n * path also falls through to the SP client (no `runInUserContext` wrap),\n * so the policy user and the SDK identity stay aligned.\n */\n private _extractUser(req: express.Request): FilePolicyUser {\n const userId = req.header(\"x-forwarded-user\")?.trim();\n const token = req.header(\"x-forwarded-access-token\")?.trim();\n if (userId && token) return { id: userId, isServicePrincipal: false };\n if (process.env.NODE_ENV === \"development\") {\n logger.warn(\n \"asUser(req) called without complete x-forwarded-user + x-forwarded-access-token headers — \" +\n \"falling back to service principal identity (dev mode). \" +\n \"In production this request would 401.\",\n );\n return { id: getCurrentUserId(), isServicePrincipal: true };\n }\n if (!token) {\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-access-token header for asUser(req). Both x-forwarded-user and x-forwarded-access-token are required.\",\n );\n }\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-user header. Cannot resolve user ID for asUser(req).\",\n );\n }\n\n /**\n * Extraction for OBO (on-behalf-of-user) volumes on the HTTP path. Both the\n * `x-forwarded-access-token` and `x-forwarded-user` headers must be present\n * for a valid end-user identity. When the token is missing:\n * - In production we throw `AuthenticationError.missingToken` so the route\n * responds with 401 (no SDK call is made).\n * - In development (`NODE_ENV === \"development\"`) we emit a single warning\n * and fall back to the service principal identity so local testing\n * without a reverse proxy continues to work.\n */\n private _extractOboUser(req: express.Request): FilePolicyUser {\n const token = req.header(\"x-forwarded-access-token\")?.trim();\n const userId = req.header(\"x-forwarded-user\")?.trim();\n if (token && userId) {\n return { id: userId, isServicePrincipal: false };\n }\n if (!token && process.env.NODE_ENV === \"development\") {\n logger.warn(\n \"OBO volume requested without x-forwarded-access-token — falling back to service principal identity (dev mode). \" +\n \"In production this request would 401.\",\n );\n return { id: getCurrentUserId(), isServicePrincipal: true };\n }\n throw AuthenticationError.missingToken(\n !token\n ? \"Missing x-forwarded-access-token header for on-behalf-of-user volume.\"\n : \"Missing x-forwarded-user header for on-behalf-of-user volume.\",\n );\n }\n\n /**\n * Check the policy for a volume. No-op if no policy is configured.\n * Throws `PolicyDeniedError` if denied.\n */\n private async _checkPolicy(\n volumeKey: string,\n action: FileAction,\n path: string,\n user: FilePolicyUser,\n resourceOverrides?: Partial<FileResource>,\n ): Promise<void> {\n const policyFn = this.volumeConfigs[volumeKey]?.policy;\n if (typeof policyFn !== \"function\") return;\n\n const resource: FileResource = {\n path,\n volume: volumeKey,\n ...resourceOverrides,\n };\n const allowed = await policyFn(action, resource, user);\n if (!allowed) {\n const userId = user.isServicePrincipal ? \"<service-principal>\" : user.id;\n logger.warn(\n 'Policy denied \"%s\" on volume \"%s\" for user \"%s\"',\n action,\n volumeKey,\n userId,\n );\n throw new PolicyDeniedError(action, volumeKey);\n }\n }\n\n /**\n * HTTP-level wrapper around `_checkPolicy`.\n * Selects the policy user based on the volume's auth mode (resolved via\n * `_resolveAuth`):\n * - `\"service-principal\"` (default): use the `x-forwarded-user` header when\n * present, otherwise fall back to the SP identity (legacy behavior).\n * - `\"on-behalf-of-user\"`: require `x-forwarded-access-token` (and the\n * matching `x-forwarded-user`); 401 in production when missing,\n * dev-fallback to SP identity in development.\n * Then runs the volume policy (403 on denial, 500 on unexpected error).\n * Returns `true` if the request may proceed, `false` if a response was sent.\n *\n * NOTE: This method only selects which identity the *policy* sees. The\n * matching SDK execution identity is selected separately by\n * `_resolveAuthForRequest` and applied via `_runWithAuth` /\n * `runInUserContext` in each handler. The two selections are designed to\n * converge on the same identity per the policy-user matrix in the docs —\n * see `docs/docs/plugins/files.md#policy-user-matrix`.\n */\n private async _enforcePolicy(\n req: express.Request,\n res: express.Response,\n volumeKey: string,\n action: FileAction,\n path: string,\n resourceOverrides?: Partial<FileResource>,\n ): Promise<boolean> {\n let user: FilePolicyUser;\n try {\n const auth = this._resolveAuth(volumeKey);\n if (auth === \"on-behalf-of-user\") {\n user = this._extractOboUser(req);\n } else {\n const headerUserId = req.header(\"x-forwarded-user\")?.trim();\n if (headerUserId) {\n user = { id: headerUserId };\n } else {\n logger.debug(\n \"No x-forwarded-user header — proceeding with service principal identity for policy evaluation.\",\n );\n user = { id: getCurrentUserId(), isServicePrincipal: true };\n }\n }\n } catch (error) {\n if (error instanceof AuthenticationError) {\n logger.warn(\n \"Authentication failed during policy evaluation for volume %s: %O\",\n volumeKey,\n error,\n );\n res.status(401).json({ error: \"Unauthorized\", plugin: this.name });\n return false;\n }\n throw error;\n }\n\n try {\n await this._checkPolicy(volumeKey, action, path, user, resourceOverrides);\n } catch (error) {\n if (error instanceof PolicyDeniedError) {\n res.status(403).json({ error: error.message, plugin: this.name });\n return false;\n }\n // A crashing policy is treated as a server error (fail closed).\n logger.error(\"Policy function threw on volume %s: %O\", volumeKey, error);\n res.status(500).json({\n error: \"Policy evaluation failed\",\n plugin: this.name,\n });\n return false;\n }\n\n return true;\n }\n\n constructor(config: IFilesConfig) {\n super(config);\n this.config = config;\n\n if (config.customContentTypes) {\n validateCustomContentTypes(config.customContentTypes);\n }\n\n const volumes = FilesPlugin.discoverVolumes(config);\n this.volumeKeys = Object.keys(volumes);\n\n for (const key of this.volumeKeys) {\n const volumeCfg = volumes[key];\n const envVar = `DATABRICKS_VOLUME_${key.toUpperCase()}`;\n const volumePath = process.env[envVar];\n\n // Merge per-volume config with plugin-level defaults\n const mergedConfig: VolumeConfig = {\n maxUploadSize: volumeCfg.maxUploadSize ?? config.maxUploadSize,\n maxReadSize: volumeCfg.maxReadSize ?? config.maxReadSize,\n customContentTypes:\n volumeCfg.customContentTypes ?? config.customContentTypes,\n policy: volumeCfg.policy ?? policy.publicRead(),\n auth: volumeCfg.auth,\n };\n this.volumeConfigs[key] = mergedConfig;\n\n this.volumeConnectors[key] = new FilesConnector({\n defaultVolume: volumePath,\n timeout: config.timeout,\n telemetry: config.telemetry,\n customContentTypes: mergedConfig.customContentTypes,\n });\n\n Object.assign(this.tools, this._defineVolumeTools(key));\n\n // Warn at startup for volumes without an explicit policy\n if (!volumeCfg.policy) {\n logger.warn(\n 'Volume \"%s\" has no explicit policy — defaulting to publicRead(). ' +\n \"This also matches header-less HTTP requests (which run as the service principal). \" +\n \"Set a policy in files({ volumes: { %s: { policy: ... } } }) to silence this warning.\",\n key,\n key,\n );\n }\n }\n }\n\n injectRoutes(router: IAppRouter) {\n this.route(router, {\n name: \"volumes\",\n method: \"get\",\n path: \"/volumes\",\n handler: async (_req: express.Request, res: express.Response) => {\n res.json({ volumes: this.volumeKeys });\n },\n });\n\n this.route(router, {\n name: \"list\",\n method: \"get\",\n path: \"/:volumeKey/list\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleList(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"read\",\n method: \"get\",\n path: \"/:volumeKey/read\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleRead(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"download\",\n method: \"get\",\n path: \"/:volumeKey/download\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleDownload(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"raw\",\n method: \"get\",\n path: \"/:volumeKey/raw\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleRaw(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"exists\",\n method: \"get\",\n path: \"/:volumeKey/exists\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleExists(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"metadata\",\n method: \"get\",\n path: \"/:volumeKey/metadata\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleMetadata(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"preview\",\n method: \"get\",\n path: \"/:volumeKey/preview\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handlePreview(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"upload\",\n method: \"post\",\n path: \"/:volumeKey/upload\",\n skipBodyParsing: true,\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleUpload(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"mkdir\",\n method: \"post\",\n path: \"/:volumeKey/mkdir\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleMkdir(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"delete\",\n method: \"delete\",\n path: \"/:volumeKey\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleDelete(req, res, connector, volumeKey);\n },\n });\n }\n\n /**\n * Resolve `:volumeKey` from the request. Returns the connector and key,\n * or sends a 404 and returns `{ connector: undefined }`.\n */\n private _resolveVolume(\n req: express.Request,\n res: express.Response,\n ):\n | { connector: FilesConnector; volumeKey: string }\n | { connector: undefined; volumeKey: undefined } {\n const volumeKey = req.params.volumeKey;\n const connector = this.volumeConnectors[volumeKey];\n if (!connector) {\n const safeKey = volumeKey.replace(/[^a-zA-Z0-9_-]/g, \"\");\n res.status(404).json({\n error: `Unknown volume \"${safeKey}\"`,\n plugin: this.name,\n });\n return { connector: undefined, volumeKey: undefined };\n }\n return { connector, volumeKey };\n }\n\n /**\n * Extract `req.query.path` as a single string when present.\n *\n * Express coerces repeated query parameters (`?path=a&path=b`) to a string\n * array and dotted/nested params (`?path[k]=v`) to an object. Reject those\n * with `400` instead of letting non-string values reach `_isValidPath` /\n * `connector.resolvePath`, which would misbehave on arrays or objects.\n *\n * Returns `{ path }` (with `path` either a string or `undefined` when the\n * query parameter was absent) on success. Returns `undefined` and writes a\n * `400` response when the value is not a single string — callers must\n * check the return for `undefined` before continuing.\n */\n private _readPathQuery(\n req: express.Request,\n res: express.Response,\n ): { path: string | undefined } | undefined {\n const value = req.query.path;\n if (value === undefined || typeof value === \"string\") {\n return { path: value };\n }\n res.status(400).json({\n error: \"path query parameter must be a single string\",\n plugin: this.name,\n });\n return undefined;\n }\n\n /**\n * Validate a file/directory path from user input.\n * Returns `true` if valid, or an error message string if invalid.\n */\n private _isValidPath(path: string | undefined): true | string {\n if (!path) return \"path is required\";\n if (path.length > 4096)\n return `path exceeds maximum length of 4096 characters (got ${path.length})`;\n if (path.includes(\"\\0\")) return \"path must not contain null bytes\";\n return true;\n }\n\n private _readSettings(\n cacheKey: (string | number | object)[],\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n ): PluginExecutionSettings {\n // OBO volumes: disable list/read cache. The cache layer is keyed by\n // `getCurrentUserId()`, so user A's writes can only invalidate user A's\n // cache entry — user B would continue to see stale data for the same\n // volume/path until TTL. Disabling caching trades read performance for\n // correctness; the alternative is a per-(volume, path) generation\n // counter folded into the cache key on writes (a future enhancement).\n const isObo = authMode === \"on-behalf-of-user\";\n const cache = isObo\n ? { ...FILES_READ_DEFAULTS.cache, enabled: false, cacheKey }\n : { ...FILES_READ_DEFAULTS.cache, cacheKey };\n return {\n default: {\n ...FILES_READ_DEFAULTS,\n cache,\n telemetryInterceptor: {\n attributes: this._authModeAttributes(authMode),\n },\n },\n };\n }\n\n private _writeSettings(\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n ): PluginExecutionSettings {\n return {\n default: {\n ...FILES_WRITE_DEFAULTS,\n telemetryInterceptor: {\n attributes: this._authModeAttributes(authMode),\n },\n },\n };\n }\n\n private _downloadSettings(\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n ): PluginExecutionSettings {\n return {\n default: {\n ...FILES_DOWNLOAD_DEFAULTS,\n telemetryInterceptor: {\n attributes: this._authModeAttributes(authMode),\n },\n },\n };\n }\n\n /**\n * Invalidate cached list entries for a directory after a write operation.\n * Must produce the SAME cache-key shape that `_handleList` stored under.\n * `_handleList` builds its key from `req.query.path`: when `path` is\n * provided it uses `connector.resolvePath(path)`, otherwise it uses the\n * sentinel `\"__root__\"`. The invalidation here must derive the matching\n * directory from the FILE path being written:\n *\n * - `\"/Volumes/c/s/v/foo/bar.txt\"` → `parentDirectory` returns\n * `\"/Volumes/c/s/v/foo\"` → resolved path key.\n * - `\"/bar.txt\"` and `\"bar.txt\"` → root-level files: matching list cache\n * was a rootless `list()` call → `\"__root__\"` sentinel.\n * - `\"/Volumes/c/s/v/bar.txt\"` → `parentDirectory` returns the UC\n * volume path (`\"/Volumes/c/s/v\"`). That's also root-level — a\n * rootless `list()` would have cached under `\"__root__\"`, while\n * `list(\"/Volumes/c/s/v\")` and `list(\"/Volumes/c/s/v/\")` would have\n * cached under the volume path with and without trailing slash. All\n * three are invalidated.\n *\n * On OBO volumes the read cache is disabled (see `_readSettings`), so\n * invalidation is a no-op here for `mode === \"on-behalf-of-user\"`. The\n * cache layer is keyed by `getCurrentUserId()`, so user A's writes can\n * only invalidate user A's cache entry — user B would otherwise see stale\n * data for the same volume/path until TTL. Disabling the cache on OBO\n * trades read performance for correctness; the alternative is a\n * per-(volume, path) generation counter folded into the cache key on\n * writes (a future enhancement).\n *\n * Best-effort: a thrown `connector.resolvePath` (e.g. on malformed input)\n * is swallowed here. Invalidation is purely an optimization signal — a\n * missed delete only costs read freshness, not correctness, and\n * propagating the error would convert a successful write into an HTTP\n * 500.\n *\n * Returns a `Promise<void>`; callers MUST `await` this before sending the\n * HTTP success response so a follow-up `GET /list` issued in the same tick\n * cannot race the underlying `cache.delete()` and observe stale data.\n */\n private async _invalidateListCache(\n volumeKey: string,\n writtenPath: string,\n connector: FilesConnector,\n mode: \"service-principal\" | \"on-behalf-of-user\" = \"service-principal\",\n ): Promise<void> {\n if (mode === \"on-behalf-of-user\") {\n // OBO read cache is disabled — nothing to invalidate. Skipping here\n // also prevents accidentally caching a wrong-namespace delete that\n // would mask the missing cross-user invalidation if the cache were\n // ever re-enabled.\n return;\n }\n const parent = parentDirectory(writtenPath);\n const userKey = getCurrentUserId();\n const tryDelete = async (segment: string): Promise<void> => {\n try {\n await this.cache.delete(\n this.cache.generateKey([`files:${volumeKey}:list`, segment], userKey),\n );\n } catch (err) {\n logger.debug(\n \"List-cache invalidation failed for volume=%s segment=%s: %O\",\n volumeKey,\n segment,\n err,\n );\n }\n };\n\n // Compute the UC volume root (e.g. `\"/Volumes/c/s/v\"`) when the\n // connector has a default volume. Used both to detect absolute-path\n // root-level writes and to invalidate every cache key shape that\n // mapped to the volume root listing. `resolvePath(\"\")` returns\n // `\"<defaultVolume>/\"`; strip the trailing slash for comparison.\n let volumeRoot: string | null = null;\n try {\n volumeRoot = connector.resolvePath(\"\").replace(/\\/+$/, \"\");\n } catch (err) {\n logger.debug(\n 'List-cache invalidation: resolvePath(\"\") failed for volume=%s: %O',\n volumeKey,\n err,\n );\n }\n\n // Root-level when the parent is empty/`\"/\"` (relative or `/foo.txt`)\n // OR when the parent equals the UC volume root (absolute UC path\n // `\"/Volumes/c/s/v/foo.txt\"`).\n const isRootLevel =\n !parent ||\n parent === \"/\" ||\n (volumeRoot !== null && parent === volumeRoot);\n\n if (isRootLevel) {\n // A rootless `list()` cached under `\"__root__\"`. Listings via\n // `?path=<volumeRoot>` or `?path=<volumeRoot>/` cached under the\n // volume path with/without trailing slash. Invalidate every shape.\n await tryDelete(\"__root__\");\n if (volumeRoot !== null) {\n await tryDelete(volumeRoot);\n await tryDelete(`${volumeRoot}/`);\n }\n return;\n }\n\n let resolved: string;\n try {\n resolved = connector.resolvePath(parent);\n } catch (err) {\n logger.debug(\n \"List-cache invalidation: resolvePath(%s) failed for volume=%s: %O\",\n parent,\n volumeKey,\n err,\n );\n return;\n }\n await tryDelete(resolved);\n }\n\n private _handleApiError(\n res: express.Response,\n error: unknown,\n fallbackMessage: string,\n ): void {\n if (error instanceof PolicyDeniedError) {\n res.status(403).json({\n error: error.message,\n plugin: this.name,\n });\n return;\n }\n if (error instanceof AuthenticationError) {\n logger.warn(\"Authentication failed in %s: %O\", this.name, error);\n res.status(401).json({ error: \"Unauthorized\", plugin: this.name });\n return;\n }\n if (error instanceof ApiError) {\n const status = error.statusCode ?? 500;\n if (status >= 400 && status < 500) {\n // Don't reflect raw SDK error.message — it can leak internal volume\n // paths, hostnames, or principal names. Use the standard HTTP status\n // text for the public body and log the full error server-side.\n logger.warn(\"Upstream %d in %s: %O\", status, this.name, error);\n res.status(status).json({\n error: STATUS_CODES[status] ?? \"Client Error\",\n statusCode: status,\n plugin: this.name,\n });\n return;\n }\n logger.error(\"Upstream server error in %s: %O\", this.name, error);\n res.status(500).json({ error: fallbackMessage, plugin: this.name });\n return;\n }\n logger.error(\"Unhandled error in %s: %O\", this.name, error);\n res.status(500).json({ error: fallbackMessage, plugin: this.name });\n }\n\n private _sendStatusError(res: express.Response, status: number): void {\n res.status(status).json({\n error: STATUS_CODES[status] ?? \"Unknown Error\",\n plugin: this.name,\n });\n }\n\n private async _handleList(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const path = query.path;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"list\", path ?? \"/\")))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const result = await this.execute(\n async () => connector.list(getWorkspaceClient(), path),\n this._readSettings(\n [\n `files:${volumeKey}:list`,\n path ? connector.resolvePath(path) : \"__root__\",\n ],\n mode,\n ),\n );\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"List failed\");\n }\n });\n }\n\n private async _handleRead(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"read\", path))) return;\n\n const volumeCfg = this.volumeConfigs[volumeKey];\n const maxReadSize = volumeCfg.maxReadSize ?? FILES_MAX_READ_SIZE;\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n\n await this._runWithAuth(userCtx, async () => {\n try {\n // Drain the file body into a memory buffer capped at `maxReadSize`.\n // `/read` is for small text reads — clients wanting large or\n // streaming bodies use `/download` or `/raw`. Buffering preserves\n // the all-or-nothing contract: if the file exceeds the cap we\n // respond 413 atomically without leaking a partial 200 body.\n // Uses download-tier settings (no cache) — `/read` no longer\n // participates in the read-tier cache. Programmatic callers wanting\n // a cached small-file read should use the SDK\n // `volume.read(path, { maxSize })` method directly.\n const response = await this.execute(\n async () => connector.download(getWorkspaceClient(), path),\n this._downloadSettings(mode),\n );\n if (!response.ok) {\n this._sendStatusError(res, response.status);\n return;\n }\n if (!response.data.contents) {\n res.type(\"text/plain\").send(\"\");\n return;\n }\n\n const reader = response.data.contents.getReader();\n const chunks: Uint8Array[] = [];\n let bytesRead = 0;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n bytesRead += value.byteLength;\n if (bytesRead > maxReadSize) {\n res.status(413).json({\n error: `File exceeds maxReadSize (${maxReadSize} bytes). Use /download for large files.`,\n plugin: this.name,\n });\n try {\n await reader.cancel();\n } catch {\n // best-effort cleanup\n }\n return;\n }\n chunks.push(value);\n }\n } finally {\n try {\n reader.releaseLock();\n } catch {\n // best-effort cleanup\n }\n }\n\n res.type(\"text/plain\").send(Buffer.concat(chunks, bytesRead));\n } catch (error) {\n this._handleApiError(res, error, \"Read failed\");\n }\n });\n }\n\n private async _handleDownload(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n return this._serveFile(req, res, connector, volumeKey, {\n mode: \"download\",\n });\n }\n\n private async _handleRaw(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n return this._serveFile(req, res, connector, volumeKey, {\n mode: \"raw\",\n });\n }\n\n /**\n * Shared handler for `/download` and `/raw` endpoints.\n * - `download`: always forces `Content-Disposition: attachment`.\n * - `raw`: adds CSP sandbox; forces attachment only for unsafe content types.\n */\n private async _serveFile(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n opts: { mode: \"download\" | \"raw\" },\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, opts.mode, path)))\n return;\n\n const label = opts.mode === \"download\" ? \"Download\" : \"Raw fetch\";\n const volumeCfg = this.volumeConfigs[volumeKey];\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n\n await this._runWithAuth(userCtx, async () => {\n try {\n const settings = this._downloadSettings(mode);\n const response = await this.execute(\n async () => connector.download(getWorkspaceClient(), path),\n settings,\n );\n\n if (!response.ok) {\n this._sendStatusError(res, response.status);\n return;\n }\n\n const resolvedType = contentTypeFromPath(\n path,\n undefined,\n volumeCfg.customContentTypes,\n );\n const fileName = sanitizeFilename(path.split(\"/\").pop() ?? \"download\");\n\n res.setHeader(\"Content-Type\", resolvedType);\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n\n if (opts.mode === \"raw\") {\n res.setHeader(\"Content-Security-Policy\", \"sandbox\");\n if (!isSafeInlineContentType(resolvedType)) {\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${fileName}\"`,\n );\n }\n } else {\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${fileName}\"`,\n );\n }\n\n if (response.data.contents) {\n const nodeStream = Readable.fromWeb(\n response.data.contents as import(\"node:stream/web\").ReadableStream,\n );\n nodeStream.on(\"error\", (err) => {\n logger.error(\"Stream error during %s: %O\", opts.mode, err);\n if (!res.headersSent) {\n this._sendStatusError(res, 500);\n } else {\n res.destroy();\n }\n });\n nodeStream.pipe(res);\n } else {\n res.end();\n }\n } catch (error) {\n this._handleApiError(res, error, `${label} failed`);\n }\n });\n }\n\n private async _handleExists(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"exists\", path)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const result = await this.execute(\n async () => connector.exists(getWorkspaceClient(), path),\n this._readSettings(\n [`files:${volumeKey}:exists`, connector.resolvePath(path)],\n mode,\n ),\n );\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json({ exists: result.data });\n } catch (error) {\n this._handleApiError(res, error, \"Exists check failed\");\n }\n });\n }\n\n private async _handleMetadata(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"metadata\", path)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const result = await this.execute(\n async () => connector.metadata(getWorkspaceClient(), path),\n this._readSettings(\n [`files:${volumeKey}:metadata`, connector.resolvePath(path)],\n mode,\n ),\n );\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"Metadata fetch failed\");\n }\n });\n }\n\n private async _handlePreview(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"preview\", path)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const result = await this.execute(\n async () => connector.preview(getWorkspaceClient(), path),\n this._readSettings(\n [`files:${volumeKey}:preview`, connector.resolvePath(path)],\n mode,\n ),\n );\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"Preview failed\");\n }\n });\n }\n\n private async _handleUpload(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n const volumeCfg = this.volumeConfigs[volumeKey];\n const maxSize = volumeCfg.maxUploadSize ?? FILES_MAX_UPLOAD_SIZE;\n const rawContentLength = req.headers[\"content-length\"];\n let contentLength: number | undefined;\n\n if (typeof rawContentLength === \"string\" && rawContentLength.length > 0) {\n if (!/^\\d+$/.test(rawContentLength)) {\n res.status(400).json({\n error: \"Invalid Content-Length header.\",\n plugin: this.name,\n });\n return;\n }\n contentLength = Number(rawContentLength);\n }\n\n if (\n !(await this._enforcePolicy(req, res, volumeKey, \"upload\", path, {\n size: contentLength,\n }))\n )\n return;\n\n if (contentLength !== undefined && contentLength > maxSize) {\n res.status(413).json({\n error: `File size (${contentLength} bytes) exceeds maximum allowed size (${maxSize} bytes).`,\n plugin: this.name,\n });\n return;\n }\n\n logger.debug(req, \"Upload started: volume=%s path=%s\", volumeKey, path);\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const rawStream: ReadableStream<Uint8Array> = Readable.toWeb(req);\n\n let bytesReceived = 0;\n const webStream = rawStream.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n bytesReceived += chunk.byteLength;\n if (bytesReceived > maxSize) {\n controller.error(\n new Error(\n `Upload stream exceeds maximum allowed size (${maxSize} bytes)`,\n ),\n );\n return;\n }\n // When the client declared a Content-Length, the policy was\n // gated on that value (lines above pass `size: contentLength`\n // to `_enforcePolicy`). Refuse bytes beyond the declared size\n // so an attacker cannot bypass a per-user policy by sending a\n // small Content-Length and then streaming up to maxSize.\n if (\n contentLength !== undefined &&\n bytesReceived > contentLength\n ) {\n controller.error(\n new Error(\n `Upload stream exceeds declared Content-Length (${contentLength} bytes)`,\n ),\n );\n return;\n }\n controller.enqueue(chunk);\n },\n }),\n );\n\n logger.debug(\n req,\n \"Upload body received: volume=%s path=%s, size=%d bytes\",\n volumeKey,\n path,\n contentLength ?? 0,\n );\n const settings = this._writeSettings(mode);\n // The connector's `upload` resolves `getWorkspaceClient()` and\n // `client.config.authenticate(headers)` synchronously inside this\n // callback. When `_runWithAuth` wraps us in `runInUserContext`, that\n // chain produces user-token Authorization headers on the outgoing\n // `fetch PUT`. The OBO upload-headers test pins this contract.\n const result = await this.trackWrite(() =>\n this.execute(async () => {\n await connector.upload(getWorkspaceClient(), path, webStream);\n return { success: true as const };\n }, settings),\n );\n\n // Awaited before sending the response so that a follow-up\n // `GET /list` issued in the same tick cannot race the\n // underlying `cache.delete()` and observe pre-write data.\n await this._invalidateListCache(volumeKey, path, connector, mode);\n\n if (!result.ok) {\n logger.error(\n req,\n \"Upload failed: volume=%s path=%s, size=%d bytes\",\n volumeKey,\n path,\n contentLength ?? 0,\n );\n this._sendStatusError(res, result.status);\n return;\n }\n\n logger.debug(\n req,\n \"Upload complete: volume=%s path=%s\",\n volumeKey,\n path,\n );\n res.json(result.data);\n } catch (error) {\n if (\n error instanceof Error &&\n (error.message.includes(\"exceeds maximum allowed size\") ||\n error.message.includes(\"exceeds declared Content-Length\"))\n ) {\n res.status(413).json({ error: error.message, plugin: this.name });\n return;\n }\n this._handleApiError(res, error, \"Upload failed\");\n }\n });\n }\n\n private async _handleMkdir(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const dirPath =\n typeof req.body?.path === \"string\" ? req.body.path : undefined;\n\n const valid = this._isValidPath(dirPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"mkdir\", dirPath)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const settings = this._writeSettings(mode);\n const result = await this.trackWrite(() =>\n this.execute(async () => {\n await connector.createDirectory(getWorkspaceClient(), dirPath);\n return { success: true as const };\n }, settings),\n );\n\n // Awaited before sending the response so that a follow-up\n // `GET /list` issued in the same tick cannot race the\n // underlying `cache.delete()` and observe pre-write data.\n await this._invalidateListCache(volumeKey, dirPath, connector, mode);\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"Create directory failed\");\n }\n });\n }\n\n private async _handleDelete(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"delete\", path)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const settings = this._writeSettings(mode);\n const result = await this.trackWrite(() =>\n this.execute(async () => {\n await connector.delete(getWorkspaceClient(), path);\n return { success: true as const };\n }, settings),\n );\n\n // Awaited before sending the response so that a follow-up\n // `GET /list` issued in the same tick cannot race the\n // underlying `cache.delete()` and observe pre-write data.\n await this._invalidateListCache(volumeKey, path, connector, mode);\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"Delete failed\");\n }\n });\n }\n\n private _resolveAuth(\n volumeKey: string,\n ): \"service-principal\" | \"on-behalf-of-user\" {\n return (\n this.volumeConfigs[volumeKey]?.auth ??\n this.config.auth ??\n \"service-principal\"\n );\n }\n\n /**\n * Build a `UserContext` from request headers when both\n * `x-forwarded-access-token` and `x-forwarded-user` are present, otherwise\n * return `null`. Used by OBO route handlers to wrap SDK calls in the\n * end-user's identity. A `null` result means \"fall back to the service\n * principal client\" — for OBO volumes in production, `_enforcePolicy` will\n * already have responded 401 before we get here, so `null` is reachable\n * only on the dev-fallback path.\n */\n private _buildUserContextOrNull(req: express.Request): UserContext | null {\n const token = req.header(\"x-forwarded-access-token\")?.trim();\n const userId = req.header(\"x-forwarded-user\")?.trim();\n if (!token || !userId) return null;\n return ServiceContext.createUserContext(token, userId);\n }\n\n /**\n * Build the telemetry attribute hash for the `files.auth_mode` span\n * attribute. The value reflects what operationally happened — i.e.\n * whether `runInUserContext` actually wrapped the SDK call:\n * - HTTP route on OBO volume + valid token → `\"on-behalf-of-user\"`.\n * - HTTP route on OBO volume + dev-fallback (no token) →\n * `\"service-principal\"` (the route falls through to the SP client).\n * - HTTP route on SP volume → `\"service-principal\"`.\n * - `asUser(req)` programmatic calls with a real user context →\n * `\"on-behalf-of-user\"`.\n * - Any unwrapped path → `\"service-principal\"`.\n */\n private _authModeAttributes(\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n ): { \"files.auth_mode\": string } {\n return { \"files.auth_mode\": authMode };\n }\n\n /**\n * One-shot resolver for HTTP route handlers. Builds the request's\n * `UserContext` AT MOST ONCE (when the volume is OBO and the headers are\n * present) and returns both the operationally-effective auth mode and the\n * pre-built `UserContext`.\n *\n * Handlers thread the `userCtx` into `_runWithAuth(userCtx, fn)` to avoid\n * a second `ServiceContext.createUserContext()` allocation — that call\n * builds a fresh `WorkspaceClient` per invocation, so doing it twice per\n * request was pure throwaway overhead.\n */\n private _resolveAuthForRequest(\n req: express.Request,\n volumeKey: string,\n ): {\n mode: \"service-principal\" | \"on-behalf-of-user\";\n userCtx: UserContext | null;\n } {\n if (this._resolveAuth(volumeKey) !== \"on-behalf-of-user\") {\n return { mode: \"service-principal\", userCtx: null };\n }\n const userCtx = this._buildUserContextOrNull(req);\n return userCtx\n ? { mode: \"on-behalf-of-user\", userCtx }\n : { mode: \"service-principal\", userCtx: null };\n }\n\n /**\n * Run `fn` under the correct execution context.\n * - `userCtx` is `null`: invokes `fn` directly so the service-principal\n * `WorkspaceClient` and `getCurrentUserId()` are used — identical\n * behavior to pre-OBO releases. This covers both SP volumes and the\n * OBO dev-fallback path (where headers were missing).\n * - `userCtx` is a `UserContext`: wraps `fn` in `runInUserContext(userCtx)`,\n * so SDK calls execute as the end user and `getCurrentUserId()` (and\n * therefore cache keys) resolve to the user's ID.\n *\n * The caller is responsible for building `userCtx` exactly once per\n * request via `_resolveAuthForRequest`; this signature deliberately does\n * NOT take a `req` so it cannot accidentally re-build the context.\n */\n private async _runWithAuth(\n userCtx: UserContext | null,\n fn: () => Promise<void>,\n ): Promise<void> {\n if (userCtx) {\n return runInUserContext(userCtx, fn);\n }\n return fn();\n }\n\n /**\n * Tag the span that `FilesConnector.<operation>` opens with\n * `files.auth_mode`. Programmatic VolumeAPI methods bypass\n * `this.execute(...)` (and therefore the `TelemetryInterceptor`), so the\n * connector's own `files.<operation>` span is the natural place to land\n * this attribute. Rather than opening a parent `files.<operation>` span\n * (which would duplicate the connector's span — same name, doubled\n * allocation/export), we propagate the attribute via AsyncLocalStorage\n * and let the connector merge it into its existing span at creation\n * time.\n *\n * The `operation` parameter is unused by the propagation mechanism (the\n * connector knows its own operation), but kept in the signature for API\n * stability with the previous span-creation form.\n */\n private _withAuthModeAttributes<R>(\n _operation: string,\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n fn: () => Promise<R>,\n ): Promise<R> {\n return runWithFilesSpanAttributes(this._authModeAttributes(authMode), fn);\n }\n\n /**\n * Wrap each `VolumeAPI` method so the `FilesConnector` span it produces is\n * tagged with `files.auth_mode = \"service-principal\"`. Used for\n * programmatic calls that don't go through `asUser(req)`.\n *\n * The attribute is attached to the connector's existing span via\n * AsyncLocalStorage propagation (see `_withAuthModeAttributes`); no\n * additional parent span is opened, so each call produces exactly one\n * `files.<operation>` span instead of two.\n */\n private _wrapVolumeAPIWithSPSpan(api: VolumeAPI): VolumeAPI {\n const wrap =\n <Args extends unknown[], R>(\n operation: string,\n fn: (...args: Args) => Promise<R>,\n ): ((...args: Args) => Promise<R>) =>\n (...args: Args) =>\n this._withAuthModeAttributes(operation, \"service-principal\", () =>\n fn(...args),\n );\n\n return {\n list: wrap(\"list\", api.list),\n read: wrap(\"read\", api.read),\n download: wrap(\"download\", api.download),\n exists: wrap(\"exists\", api.exists),\n metadata: wrap(\"metadata\", api.metadata),\n upload: wrap(\"upload\", api.upload),\n createDirectory: wrap(\"createDirectory\", api.createDirectory),\n delete: wrap(\"delete\", api.delete),\n preview: wrap(\"preview\", api.preview),\n };\n }\n\n /**\n * Wrap each `VolumeAPI` method so its execution runs inside\n * `runInUserContext(userCtx, ...)`. Used by `VolumeHandle.asUser(req)` to\n * force the SDK identity to the end user regardless of the volume's\n * `auth` setting. The policy check baked into each method (via\n * `createVolumeAPI`) runs inside the same scope, so `getCurrentUserId()`\n * and any cache `userKey` derived from it also resolve to the user.\n *\n * Each wrapped invocation tags the connector's span with\n * `files.auth_mode = \"on-behalf-of-user\"` via AsyncLocalStorage\n * propagation — no additional parent span is opened.\n */\n private _wrapVolumeAPIInUserContext(\n api: VolumeAPI,\n userCtx: UserContext,\n ): VolumeAPI {\n const wrap =\n <Args extends unknown[], R>(\n operation: string,\n fn: (...args: Args) => Promise<R>,\n ): ((...args: Args) => Promise<R>) =>\n (...args: Args) =>\n this._withAuthModeAttributes(operation, \"on-behalf-of-user\", () =>\n runInUserContext(userCtx, () => fn(...args)),\n );\n\n return {\n list: wrap(\"list\", api.list),\n read: wrap(\"read\", api.read),\n download: wrap(\"download\", api.download),\n exists: wrap(\"exists\", api.exists),\n metadata: wrap(\"metadata\", api.metadata),\n upload: wrap(\"upload\", api.upload),\n createDirectory: wrap(\"createDirectory\", api.createDirectory),\n delete: wrap(\"delete\", api.delete),\n preview: wrap(\"preview\", api.preview),\n };\n }\n\n /**\n * Creates a VolumeAPI for a specific volume key.\n *\n * Enforces the volume's policy before each operation.\n */\n protected createVolumeAPI(\n volumeKey: string,\n user: FilePolicyUser,\n ): VolumeAPI {\n const connector = this.volumeConnectors[volumeKey];\n const check = (\n action: FileAction,\n path: string,\n overrides?: Partial<FileResource>,\n ) => this._checkPolicy(volumeKey, action, path, user, overrides);\n\n return {\n list: async (directoryPath?: string) => {\n await check(\"list\", directoryPath ?? \"/\");\n return connector.list(getWorkspaceClient(), directoryPath);\n },\n read: async (filePath: string, opts?: { maxSize?: number }) => {\n await check(\"read\", filePath);\n return connector.read(getWorkspaceClient(), filePath, opts);\n },\n download: async (filePath: string) => {\n await check(\"download\", filePath);\n return connector.download(getWorkspaceClient(), filePath);\n },\n exists: async (filePath: string) => {\n await check(\"exists\", filePath);\n return connector.exists(getWorkspaceClient(), filePath);\n },\n metadata: async (filePath: string) => {\n await check(\"metadata\", filePath);\n return connector.metadata(getWorkspaceClient(), filePath);\n },\n upload: async (\n filePath: string,\n contents: ReadableStream | Buffer | string,\n opts?: { overwrite?: boolean },\n ) => {\n await check(\"upload\", filePath);\n return connector.upload(getWorkspaceClient(), filePath, contents, opts);\n },\n createDirectory: async (directoryPath: string) => {\n await check(\"mkdir\", directoryPath);\n return connector.createDirectory(getWorkspaceClient(), directoryPath);\n },\n delete: async (filePath: string) => {\n await check(\"delete\", filePath);\n return connector.delete(getWorkspaceClient(), filePath);\n },\n preview: async (filePath: string) => {\n await check(\"preview\", filePath);\n return connector.preview(getWorkspaceClient(), filePath);\n },\n };\n }\n\n /**\n * Builds the agent-tool registry entries for a single volume. One set of\n * tools per configured volume, keyed by `${volumeKey}.${method}`.\n *\n * Each handler resolves the caller's identity from the current execution\n * context (OBO user when the agent run is wrapped in `asUser(req)`, service\n * principal otherwise in local dev) and dispatches through\n * `createVolumeAPI(volumeKey, user)` so the volume's policy is enforced\n * uniformly for agent and HTTP callers.\n */\n private _defineVolumeTools(volumeKey: string): ToolRegistry {\n const buildUser = (): FilePolicyUser => {\n const ctx = getExecutionContext();\n return isUserContext(ctx)\n ? { id: ctx.userId }\n : { id: ctx.serviceUserId, isServicePrincipal: true };\n };\n const api = () => this.createVolumeAPI(volumeKey, buildUser());\n return {\n [`${volumeKey}.list`]: defineTool({\n description: `List files and directories in the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z\n .string()\n .optional()\n .describe(\"Directory path to list (optional, defaults to root)\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().list(args.path);\n },\n }),\n [`${volumeKey}.read`]: defineTool({\n description: `Read a text file from the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"File path to read\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().read(args.path);\n },\n }),\n [`${volumeKey}.exists`]: defineTool({\n description: `Check if a file or directory exists in the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"Path to check\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().exists(args.path);\n },\n }),\n [`${volumeKey}.metadata`]: defineTool({\n description: `Get metadata (size, type, last modified) for a file in the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"File path\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().metadata(args.path);\n },\n }),\n [`${volumeKey}.upload`]: defineTool({\n description: `Upload a text file to the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"Destination file path\"),\n contents: z.string().describe(\"File contents as a string\"),\n overwrite: z\n .boolean()\n .optional()\n .describe(\"Whether to overwrite existing file\"),\n }),\n annotations: { effect: \"destructive\", requiresUserContext: true },\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().upload(args.path, args.contents, {\n overwrite: args.overwrite,\n });\n },\n }),\n [`${volumeKey}.delete`]: defineTool({\n description: `Delete a file from the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"File path to delete\"),\n }),\n annotations: { effect: \"destructive\", requiresUserContext: true },\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().delete(args.path);\n },\n }),\n };\n }\n\n private inflightWrites = 0;\n\n private trackWrite<T>(fn: () => Promise<T>): Promise<T> {\n this.inflightWrites++;\n return fn().finally(() => {\n this.inflightWrites--;\n });\n }\n\n async shutdown(): Promise<void> {\n // Wait up to 10 seconds for in-flight write operations to finish\n const deadline = Date.now() + 10_000;\n while (this.inflightWrites > 0 && Date.now() < deadline) {\n logger.info(\n \"Waiting for %d in-flight write(s) to complete before shutdown…\",\n this.inflightWrites,\n );\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n if (this.inflightWrites > 0) {\n logger.warn(\n \"Shutdown deadline reached with %d in-flight write(s) still pending.\",\n this.inflightWrites,\n );\n }\n this.streamManager.abortAll();\n }\n\n getAgentTools(): AgentToolDefinition[] {\n return toolsFromRegistry(this.tools);\n }\n\n async executeAgentTool(\n name: string,\n args: unknown,\n signal?: AbortSignal,\n ): Promise<unknown> {\n return executeFromRegistry(this.tools, name, args, signal);\n }\n\n toolkit(opts?: import(\"../../core/agent/types\").ToolkitOptions) {\n return buildToolkitEntries(this.name, this.tools, opts);\n }\n\n /**\n * Returns the programmatic API for the Files plugin.\n * Callable with a volume key to get a volume-scoped handle.\n *\n * SP volumes (`auth: \"service-principal\"`, the default) execute as the\n * service principal. OBO volumes (`auth: \"on-behalf-of-user\"`) executed\n * through the HTTP routes run as the end user; for programmatic calls\n * outside a route, use `asUser(req)` to opt into per-user execution.\n * `asUser(req)` is a hard override at the SDK level: it forces every\n * subsequent call to execute as the end user inside `runInUserContext`,\n * regardless of the volume's `auth` setting. Policies control per-user\n * access in either mode.\n *\n * @example\n * ```ts\n * // Service principal access\n * appKit.files(\"uploads\").list()\n *\n * // With policy: pass user identity for access control. The SDK call\n * // also executes as the user (not the service principal).\n * appKit.files(\"uploads\").asUser(req).list()\n * ```\n */\n exports(): FilesExport {\n const resolveVolume = (volumeKey: string): VolumeHandle => {\n if (!this.volumeKeys.includes(volumeKey)) {\n throw new Error(\n `Unknown volume \"${volumeKey}\". Available volumes: ${this.volumeKeys.join(\", \")}`,\n );\n }\n\n // Lazy user resolution: getCurrentUserId() is called when a method\n // is invoked (policy check), not when exports() is called.\n const spUser: FilePolicyUser = {\n get id() {\n return getCurrentUserId();\n },\n isServicePrincipal: true,\n };\n // Default (non-asUser) programmatic surface: every call is tagged\n // with `files.auth_mode = \"service-principal\"` for telemetry parity\n // with the HTTP route path.\n const spApi = this._wrapVolumeAPIWithSPSpan(\n this.createVolumeAPI(volumeKey, spUser),\n );\n\n return {\n ...spApi,\n asUser: (req: express.Request) => {\n const user = this._extractUser(req);\n const api = this.createVolumeAPI(volumeKey, user);\n // Force OBO at the SDK level regardless of the volume's `auth`\n // setting: each method runs inside `runInUserContext` so\n // `getWorkspaceClient()` returns the user-token client. When no\n // user token is available (only reachable in dev mode after the\n // strict `_extractUser` falls back to the SP identity), we skip\n // the OBO wrap and instead apply the SP-mode span wrap so trace\n // output reflects the actual SP execution.\n const userCtx = this._buildUserContextOrNull(req);\n if (!userCtx) return this._wrapVolumeAPIWithSPSpan(api);\n return this._wrapVolumeAPIInUserContext(api, userCtx);\n },\n };\n };\n\n const filesExport = ((volumeKey: string) =>\n resolveVolume(volumeKey)) as FilesExport;\n filesExport.volume = resolveVolume;\n\n return filesExport;\n }\n\n clientConfig(): Record<string, unknown> {\n return { volumes: this.volumeKeys };\n }\n}\n\n/**\n * @internal\n */\nexport const files = Object.assign(toPlugin(FilesPlugin), { policy });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["manifest"],"sources":["../../../src/plugins/files/plugin.ts"],"sourcesContent":["import { STATUS_CODES } from \"node:http\";\nimport { Readable } from \"node:stream\";\n\nimport type express from \"express\";\nimport type {\n AgentToolDefinition,\n IAppRouter,\n PluginExecutionSettings,\n ToolProvider,\n} from \"shared\";\nimport { z } from \"zod\";\n\nimport {\n contentTypeFromPath,\n FILES_MAX_READ_SIZE,\n FilesConnector,\n isSafeInlineContentType,\n runWithFilesSpanAttributes,\n validateCustomContentTypes,\n} from \"../../connectors/files\";\nimport {\n getCurrentUserId,\n getExecutionContext,\n getWorkspaceClient,\n runInCallerContext,\n ServiceContext,\n type CallerContext,\n isCallerContext,\n} from \"../../context\";\nimport { buildToolkitEntries } from \"../../core/agent/build-toolkit\";\nimport {\n defineTool,\n executeFromRegistry,\n type ToolRegistry,\n toolsFromRegistry,\n} from \"../../core/agent/tools/define-tool\";\nimport { AuthenticationError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { ResourceRequirement } from \"../../registry\";\nimport { defineManifest, ResourceType } from \"../../registry\";\nimport { ApiError } from \"../../workspace-client\";\nimport {\n FILES_DOWNLOAD_DEFAULTS,\n FILES_MAX_UPLOAD_SIZE,\n FILES_READ_DEFAULTS,\n FILES_WRITE_DEFAULTS,\n} from \"./defaults\";\nimport { parentDirectory, sanitizeFilename } from \"./helpers\";\nimport manifest from \"./manifest.json\";\nimport {\n type FileAction,\n type FilePolicyUser,\n type FileResource,\n PolicyDeniedError,\n policy,\n} from \"./policy\";\nimport type {\n FilesExport,\n IFilesConfig,\n VolumeAPI,\n VolumeConfig,\n VolumeHandle,\n} from \"./types\";\n\nconst logger = createLogger(\"files\");\n\nexport class FilesPlugin extends Plugin implements ToolProvider {\n name = \"files\";\n\n /** Plugin manifest declaring metadata and resource requirements. */\n static manifest = defineManifest<\"files\">(manifest);\n protected static description = \"Files plugin for Databricks file operations\";\n declare protected config: IFilesConfig;\n\n private volumeConnectors: Record<string, FilesConnector> = {};\n private volumeConfigs: Record<string, VolumeConfig> = {};\n private volumeKeys: string[] = [];\n private tools: ToolRegistry = {};\n\n /**\n * Scans `process.env` for `DATABRICKS_VOLUME_*` keys and merges them with\n * any explicitly configured volumes. Explicit config wins for per-volume\n * overrides; auto-discovered volumes get default `{}` config.\n */\n static discoverVolumes(config: IFilesConfig): Record<string, VolumeConfig> {\n const explicit = config.volumes ?? {};\n const discovered: Record<string, VolumeConfig> = {};\n\n const prefix = \"DATABRICKS_VOLUME_\";\n for (const key of Object.keys(process.env)) {\n if (!key.startsWith(prefix)) continue;\n const suffix = key.slice(prefix.length);\n if (!suffix) continue;\n if (!process.env[key]) continue;\n const volumeKey = suffix.toLowerCase();\n if (!(volumeKey in explicit)) {\n discovered[volumeKey] = {};\n }\n }\n\n return { ...discovered, ...explicit };\n }\n\n /**\n * Generates resource requirements dynamically from discovered + configured volumes.\n * Each volume key maps to a `DATABRICKS_VOLUME_{KEY_UPPERCASE}` env var.\n *\n * ## Per-volume permission scope (SP vs OBO)\n *\n * The returned manifest entries describe a single permission grant per\n * volume, but the *grantee* depends on the volume's `auth` setting at\n * runtime — and that distinction is **not** expressed in the manifest\n * today:\n *\n * - **Service-principal volumes** (the default, `auth: \"service-principal\"`):\n * the app's service principal needs `WRITE_VOLUME` (or read-equivalent)\n * on the UC volume. This matches the manifest entry as written.\n * - **On-behalf-of-user volumes** (`auth: \"on-behalf-of-user\"`): SDK calls\n * execute as the **end user**, so the *user* — not the SP — must hold\n * `WRITE_VOLUME` (or read-equivalent) on the UC volume. The SP only\n * needs to be allowed to mint user-token requests; it does not need\n * direct volume permissions.\n *\n * The static manifest cannot currently express this per-volume split, so\n * callers configuring OBO volumes must communicate the per-user permission\n * requirement out-of-band (docs, runbooks, deployment scripts) until the\n * manifest schema gains a per-volume auth scope field.\n */\n static getResourceRequirements(config: IFilesConfig): ResourceRequirement[] {\n const volumes = FilesPlugin.discoverVolumes(config);\n // TODO: extend plugin-manifest.schema.json to express per-volume auth\n // scope so OBO volumes can declare end-user-required permissions in the\n // manifest itself.\n return Object.keys(volumes).map((key) => ({\n type: ResourceType.VOLUME,\n alias: `volume-${key}`,\n resourceKey: `volume-${key}`,\n description: `Unity Catalog Volume for \"${key}\" file storage`,\n permission: \"WRITE_VOLUME\",\n fields: {\n path: {\n env: `DATABRICKS_VOLUME_${key.toUpperCase()}`,\n description: `Volume path for \"${key}\" (e.g. /Volumes/catalog/schema/volume_name)`,\n },\n },\n required: true,\n }));\n }\n\n /**\n * Extraction for `VolumeHandle.asUser(req)`. In production we require BOTH\n * `x-forwarded-user` and `x-forwarded-access-token`, and throw\n * `AuthenticationError.missingToken` if either is missing — otherwise a\n * request with only `x-forwarded-user: alice` would let policies see Alice\n * as a \"real user\" (`isServicePrincipal: false`) while the SDK call below\n * falls through to the SP client because `_buildUserContextOrNull` returns\n * `null`. Net effect: policy approves the user, SDK runs as SP, privilege\n * confusion (CWE-639/863).\n *\n * In development (`NODE_ENV === \"development\"`) we keep a local-loop\n * convenience: if either header is missing we emit a single warning and\n * return a policy user explicitly marked `isServicePrincipal: true`, so\n * even in dev a `usersOnly`-style policy that gates on\n * `!user.isServicePrincipal` cannot be tricked. The matching SDK execution\n * path also falls through to the SP client (no `runInCallerContext` wrap),\n * so the policy user and the SDK identity stay aligned.\n */\n private _extractUser(req: express.Request): FilePolicyUser {\n const userId = req.header(\"x-forwarded-user\")?.trim();\n const token = req.header(\"x-forwarded-access-token\")?.trim();\n if (userId && token) return { id: userId, isServicePrincipal: false };\n if (process.env.NODE_ENV === \"development\") {\n logger.warn(\n \"asUser(req) called without complete x-forwarded-user + x-forwarded-access-token headers — \" +\n \"falling back to service principal identity (dev mode). \" +\n \"In production this request would 401.\",\n );\n return { id: getCurrentUserId(), isServicePrincipal: true };\n }\n if (!token) {\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-access-token header for asUser(req). Both x-forwarded-user and x-forwarded-access-token are required.\",\n );\n }\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-user header. Cannot resolve user ID for asUser(req).\",\n );\n }\n\n /**\n * Extraction for OBO (on-behalf-of-user) volumes on the HTTP path. Both the\n * `x-forwarded-access-token` and `x-forwarded-user` headers must be present\n * for a valid end-user identity. When the token is missing:\n * - In production we throw `AuthenticationError.missingToken` so the route\n * responds with 401 (no SDK call is made).\n * - In development (`NODE_ENV === \"development\"`) we emit a single warning\n * and fall back to the service principal identity so local testing\n * without a reverse proxy continues to work.\n */\n private _extractOboUser(req: express.Request): FilePolicyUser {\n const token = req.header(\"x-forwarded-access-token\")?.trim();\n const userId = req.header(\"x-forwarded-user\")?.trim();\n if (token && userId) {\n return { id: userId, isServicePrincipal: false };\n }\n if (!token && process.env.NODE_ENV === \"development\") {\n logger.warn(\n \"OBO volume requested without x-forwarded-access-token — falling back to service principal identity (dev mode). \" +\n \"In production this request would 401.\",\n );\n return { id: getCurrentUserId(), isServicePrincipal: true };\n }\n throw AuthenticationError.missingToken(\n !token\n ? \"Missing x-forwarded-access-token header for on-behalf-of-user volume.\"\n : \"Missing x-forwarded-user header for on-behalf-of-user volume.\",\n );\n }\n\n /**\n * Check the policy for a volume. No-op if no policy is configured.\n * Throws `PolicyDeniedError` if denied.\n */\n private async _checkPolicy(\n volumeKey: string,\n action: FileAction,\n path: string,\n user: FilePolicyUser,\n resourceOverrides?: Partial<FileResource>,\n ): Promise<void> {\n const policyFn = this.volumeConfigs[volumeKey]?.policy;\n if (typeof policyFn !== \"function\") return;\n\n const resource: FileResource = {\n path,\n volume: volumeKey,\n ...resourceOverrides,\n };\n const allowed = await policyFn(action, resource, user);\n if (!allowed) {\n const userId = user.isServicePrincipal ? \"<service-principal>\" : user.id;\n logger.warn(\n 'Policy denied \"%s\" on volume \"%s\" for user \"%s\"',\n action,\n volumeKey,\n userId,\n );\n throw new PolicyDeniedError(action, volumeKey);\n }\n }\n\n /**\n * HTTP-level wrapper around `_checkPolicy`.\n * Selects the policy user based on the volume's auth mode (resolved via\n * `_resolveAuth`):\n * - `\"service-principal\"` (default): use the `x-forwarded-user` header when\n * present, otherwise fall back to the SP identity (legacy behavior).\n * - `\"on-behalf-of-user\"`: require `x-forwarded-access-token` (and the\n * matching `x-forwarded-user`); 401 in production when missing,\n * dev-fallback to SP identity in development.\n * Then runs the volume policy (403 on denial, 500 on unexpected error).\n * Returns `true` if the request may proceed, `false` if a response was sent.\n *\n * NOTE: This method only selects which identity the *policy* sees. The\n * matching SDK execution identity is selected separately by\n * `_resolveAuthForRequest` and applied via `_runWithAuth` /\n * `runInCallerContext` in each handler. The two selections are designed to\n * converge on the same identity per the policy-user matrix in the docs —\n * see `docs/docs/plugins/files.md#policy-user-matrix`.\n */\n private async _enforcePolicy(\n req: express.Request,\n res: express.Response,\n volumeKey: string,\n action: FileAction,\n path: string,\n resourceOverrides?: Partial<FileResource>,\n ): Promise<boolean> {\n let user: FilePolicyUser;\n try {\n const auth = this._resolveAuth(volumeKey);\n if (auth === \"on-behalf-of-user\") {\n user = this._extractOboUser(req);\n } else {\n const headerUserId = req.header(\"x-forwarded-user\")?.trim();\n if (headerUserId) {\n user = { id: headerUserId };\n } else {\n logger.debug(\n \"No x-forwarded-user header — proceeding with service principal identity for policy evaluation.\",\n );\n user = { id: getCurrentUserId(), isServicePrincipal: true };\n }\n }\n } catch (error) {\n if (error instanceof AuthenticationError) {\n logger.warn(\n \"Authentication failed during policy evaluation for volume %s: %O\",\n volumeKey,\n error,\n );\n res.status(401).json({ error: \"Unauthorized\", plugin: this.name });\n return false;\n }\n throw error;\n }\n\n try {\n await this._checkPolicy(volumeKey, action, path, user, resourceOverrides);\n } catch (error) {\n if (error instanceof PolicyDeniedError) {\n res.status(403).json({ error: error.message, plugin: this.name });\n return false;\n }\n // A crashing policy is treated as a server error (fail closed).\n logger.error(\"Policy function threw on volume %s: %O\", volumeKey, error);\n res.status(500).json({\n error: \"Policy evaluation failed\",\n plugin: this.name,\n });\n return false;\n }\n\n return true;\n }\n\n constructor(config: IFilesConfig) {\n super(config);\n this.config = config;\n\n if (config.customContentTypes) {\n validateCustomContentTypes(config.customContentTypes);\n }\n\n const volumes = FilesPlugin.discoverVolumes(config);\n this.volumeKeys = Object.keys(volumes);\n\n for (const key of this.volumeKeys) {\n const volumeCfg = volumes[key];\n const envVar = `DATABRICKS_VOLUME_${key.toUpperCase()}`;\n const volumePath = process.env[envVar];\n\n // Merge per-volume config with plugin-level defaults\n const mergedConfig: VolumeConfig = {\n maxUploadSize: volumeCfg.maxUploadSize ?? config.maxUploadSize,\n maxReadSize: volumeCfg.maxReadSize ?? config.maxReadSize,\n customContentTypes:\n volumeCfg.customContentTypes ?? config.customContentTypes,\n policy: volumeCfg.policy ?? policy.publicRead(),\n auth: volumeCfg.auth,\n };\n this.volumeConfigs[key] = mergedConfig;\n\n this.volumeConnectors[key] = new FilesConnector({\n defaultVolume: volumePath,\n timeout: config.timeout,\n telemetry: config.telemetry,\n customContentTypes: mergedConfig.customContentTypes,\n });\n\n Object.assign(this.tools, this._defineVolumeTools(key));\n\n // Warn at startup for volumes without an explicit policy\n if (!volumeCfg.policy) {\n logger.warn(\n 'Volume \"%s\" has no explicit policy — defaulting to publicRead(). ' +\n \"This also matches header-less HTTP requests (which run as the service principal). \" +\n \"Set a policy in files({ volumes: { %s: { policy: ... } } }) to silence this warning.\",\n key,\n key,\n );\n }\n }\n }\n\n injectRoutes(router: IAppRouter) {\n this.route(router, {\n name: \"volumes\",\n method: \"get\",\n path: \"/volumes\",\n handler: async (_req: express.Request, res: express.Response) => {\n res.json({ volumes: this.volumeKeys });\n },\n });\n\n this.route(router, {\n name: \"list\",\n method: \"get\",\n path: \"/:volumeKey/list\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleList(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"read\",\n method: \"get\",\n path: \"/:volumeKey/read\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleRead(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"download\",\n method: \"get\",\n path: \"/:volumeKey/download\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleDownload(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"raw\",\n method: \"get\",\n path: \"/:volumeKey/raw\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleRaw(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"exists\",\n method: \"get\",\n path: \"/:volumeKey/exists\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleExists(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"metadata\",\n method: \"get\",\n path: \"/:volumeKey/metadata\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleMetadata(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"preview\",\n method: \"get\",\n path: \"/:volumeKey/preview\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handlePreview(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"upload\",\n method: \"post\",\n path: \"/:volumeKey/upload\",\n skipBodyParsing: true,\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleUpload(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"mkdir\",\n method: \"post\",\n path: \"/:volumeKey/mkdir\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleMkdir(req, res, connector, volumeKey);\n },\n });\n\n this.route(router, {\n name: \"delete\",\n method: \"delete\",\n path: \"/:volumeKey\",\n handler: async (req: express.Request, res: express.Response) => {\n const { connector, volumeKey } = this._resolveVolume(req, res);\n if (!connector) return;\n await this._handleDelete(req, res, connector, volumeKey);\n },\n });\n }\n\n /**\n * Resolve `:volumeKey` from the request. Returns the connector and key,\n * or sends a 404 and returns `{ connector: undefined }`.\n */\n private _resolveVolume(\n req: express.Request,\n res: express.Response,\n ):\n | { connector: FilesConnector; volumeKey: string }\n | { connector: undefined; volumeKey: undefined } {\n const volumeKey = req.params.volumeKey;\n const connector = this.volumeConnectors[volumeKey];\n if (!connector) {\n const safeKey = volumeKey.replace(/[^a-zA-Z0-9_-]/g, \"\");\n res.status(404).json({\n error: `Unknown volume \"${safeKey}\"`,\n plugin: this.name,\n });\n return { connector: undefined, volumeKey: undefined };\n }\n return { connector, volumeKey };\n }\n\n /**\n * Extract `req.query.path` as a single string when present.\n *\n * Express coerces repeated query parameters (`?path=a&path=b`) to a string\n * array and dotted/nested params (`?path[k]=v`) to an object. Reject those\n * with `400` instead of letting non-string values reach `_isValidPath` /\n * `connector.resolvePath`, which would misbehave on arrays or objects.\n *\n * Returns `{ path }` (with `path` either a string or `undefined` when the\n * query parameter was absent) on success. Returns `undefined` and writes a\n * `400` response when the value is not a single string — callers must\n * check the return for `undefined` before continuing.\n */\n private _readPathQuery(\n req: express.Request,\n res: express.Response,\n ): { path: string | undefined } | undefined {\n const value = req.query.path;\n if (value === undefined || typeof value === \"string\") {\n return { path: value };\n }\n res.status(400).json({\n error: \"path query parameter must be a single string\",\n plugin: this.name,\n });\n return undefined;\n }\n\n /**\n * Validate a file/directory path from user input.\n * Returns `true` if valid, or an error message string if invalid.\n */\n private _isValidPath(path: string | undefined): true | string {\n if (!path) return \"path is required\";\n if (path.length > 4096)\n return `path exceeds maximum length of 4096 characters (got ${path.length})`;\n if (path.includes(\"\\0\")) return \"path must not contain null bytes\";\n return true;\n }\n\n private _readSettings(\n cacheKey: (string | number | object)[],\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n ): PluginExecutionSettings {\n // OBO volumes: disable list/read cache. The cache layer is keyed by\n // `getCurrentUserId()`, so user A's writes can only invalidate user A's\n // cache entry — user B would continue to see stale data for the same\n // volume/path until TTL. Disabling caching trades read performance for\n // correctness; the alternative is a per-(volume, path) generation\n // counter folded into the cache key on writes (a future enhancement).\n const isObo = authMode === \"on-behalf-of-user\";\n const cache = isObo\n ? { ...FILES_READ_DEFAULTS.cache, enabled: false, cacheKey }\n : { ...FILES_READ_DEFAULTS.cache, cacheKey };\n return {\n default: {\n ...FILES_READ_DEFAULTS,\n cache,\n telemetryInterceptor: {\n attributes: this._authModeAttributes(authMode),\n },\n },\n };\n }\n\n private _writeSettings(\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n ): PluginExecutionSettings {\n return {\n default: {\n ...FILES_WRITE_DEFAULTS,\n telemetryInterceptor: {\n attributes: this._authModeAttributes(authMode),\n },\n },\n };\n }\n\n private _downloadSettings(\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n ): PluginExecutionSettings {\n return {\n default: {\n ...FILES_DOWNLOAD_DEFAULTS,\n telemetryInterceptor: {\n attributes: this._authModeAttributes(authMode),\n },\n },\n };\n }\n\n /**\n * Invalidate cached list entries for a directory after a write operation.\n * Must produce the SAME cache-key shape that `_handleList` stored under.\n * `_handleList` builds its key from `req.query.path`: when `path` is\n * provided it uses `connector.resolvePath(path)`, otherwise it uses the\n * sentinel `\"__root__\"`. The invalidation here must derive the matching\n * directory from the FILE path being written:\n *\n * - `\"/Volumes/c/s/v/foo/bar.txt\"` → `parentDirectory` returns\n * `\"/Volumes/c/s/v/foo\"` → resolved path key.\n * - `\"/bar.txt\"` and `\"bar.txt\"` → root-level files: matching list cache\n * was a rootless `list()` call → `\"__root__\"` sentinel.\n * - `\"/Volumes/c/s/v/bar.txt\"` → `parentDirectory` returns the UC\n * volume path (`\"/Volumes/c/s/v\"`). That's also root-level — a\n * rootless `list()` would have cached under `\"__root__\"`, while\n * `list(\"/Volumes/c/s/v\")` and `list(\"/Volumes/c/s/v/\")` would have\n * cached under the volume path with and without trailing slash. All\n * three are invalidated.\n *\n * On OBO volumes the read cache is disabled (see `_readSettings`), so\n * invalidation is a no-op here for `mode === \"on-behalf-of-user\"`. The\n * cache layer is keyed by `getCurrentUserId()`, so user A's writes can\n * only invalidate user A's cache entry — user B would otherwise see stale\n * data for the same volume/path until TTL. Disabling the cache on OBO\n * trades read performance for correctness; the alternative is a\n * per-(volume, path) generation counter folded into the cache key on\n * writes (a future enhancement).\n *\n * Best-effort: a thrown `connector.resolvePath` (e.g. on malformed input)\n * is swallowed here. Invalidation is purely an optimization signal — a\n * missed delete only costs read freshness, not correctness, and\n * propagating the error would convert a successful write into an HTTP\n * 500.\n *\n * Returns a `Promise<void>`; callers MUST `await` this before sending the\n * HTTP success response so a follow-up `GET /list` issued in the same tick\n * cannot race the underlying `cache.delete()` and observe stale data.\n */\n private async _invalidateListCache(\n volumeKey: string,\n writtenPath: string,\n connector: FilesConnector,\n mode: \"service-principal\" | \"on-behalf-of-user\" = \"service-principal\",\n ): Promise<void> {\n if (mode === \"on-behalf-of-user\") {\n // OBO read cache is disabled — nothing to invalidate. Skipping here\n // also prevents accidentally caching a wrong-namespace delete that\n // would mask the missing cross-user invalidation if the cache were\n // ever re-enabled.\n return;\n }\n const parent = parentDirectory(writtenPath);\n const userKey = getCurrentUserId();\n const tryDelete = async (segment: string): Promise<void> => {\n try {\n await this.cache.delete(\n this.cache.generateKey([`files:${volumeKey}:list`, segment], userKey),\n );\n } catch (err) {\n logger.debug(\n \"List-cache invalidation failed for volume=%s segment=%s: %O\",\n volumeKey,\n segment,\n err,\n );\n }\n };\n\n // Compute the UC volume root (e.g. `\"/Volumes/c/s/v\"`) when the\n // connector has a default volume. Used both to detect absolute-path\n // root-level writes and to invalidate every cache key shape that\n // mapped to the volume root listing. `resolvePath(\"\")` returns\n // `\"<defaultVolume>/\"`; strip the trailing slash for comparison.\n let volumeRoot: string | null = null;\n try {\n volumeRoot = connector.resolvePath(\"\").replace(/\\/+$/, \"\");\n } catch (err) {\n logger.debug(\n 'List-cache invalidation: resolvePath(\"\") failed for volume=%s: %O',\n volumeKey,\n err,\n );\n }\n\n // Root-level when the parent is empty/`\"/\"` (relative or `/foo.txt`)\n // OR when the parent equals the UC volume root (absolute UC path\n // `\"/Volumes/c/s/v/foo.txt\"`).\n const isRootLevel =\n !parent ||\n parent === \"/\" ||\n (volumeRoot !== null && parent === volumeRoot);\n\n if (isRootLevel) {\n // A rootless `list()` cached under `\"__root__\"`. Listings via\n // `?path=<volumeRoot>` or `?path=<volumeRoot>/` cached under the\n // volume path with/without trailing slash. Invalidate every shape.\n await tryDelete(\"__root__\");\n if (volumeRoot !== null) {\n await tryDelete(volumeRoot);\n await tryDelete(`${volumeRoot}/`);\n }\n return;\n }\n\n let resolved: string;\n try {\n resolved = connector.resolvePath(parent);\n } catch (err) {\n logger.debug(\n \"List-cache invalidation: resolvePath(%s) failed for volume=%s: %O\",\n parent,\n volumeKey,\n err,\n );\n return;\n }\n await tryDelete(resolved);\n }\n\n private _handleApiError(\n res: express.Response,\n error: unknown,\n fallbackMessage: string,\n ): void {\n if (error instanceof PolicyDeniedError) {\n res.status(403).json({\n error: error.message,\n plugin: this.name,\n });\n return;\n }\n if (error instanceof AuthenticationError) {\n logger.warn(\"Authentication failed in %s: %O\", this.name, error);\n res.status(401).json({ error: \"Unauthorized\", plugin: this.name });\n return;\n }\n if (error instanceof ApiError) {\n const status = error.statusCode ?? 500;\n if (status >= 400 && status < 500) {\n // Don't reflect raw SDK error.message — it can leak internal volume\n // paths, hostnames, or principal names. Use the standard HTTP status\n // text for the public body and log the full error server-side.\n logger.warn(\"Upstream %d in %s: %O\", status, this.name, error);\n res.status(status).json({\n error: STATUS_CODES[status] ?? \"Client Error\",\n statusCode: status,\n plugin: this.name,\n });\n return;\n }\n logger.error(\"Upstream server error in %s: %O\", this.name, error);\n res.status(500).json({ error: fallbackMessage, plugin: this.name });\n return;\n }\n logger.error(\"Unhandled error in %s: %O\", this.name, error);\n res.status(500).json({ error: fallbackMessage, plugin: this.name });\n }\n\n private _sendStatusError(res: express.Response, status: number): void {\n res.status(status).json({\n error: STATUS_CODES[status] ?? \"Unknown Error\",\n plugin: this.name,\n });\n }\n\n private async _handleList(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const path = query.path;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"list\", path ?? \"/\")))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const result = await this.execute(\n async () => connector.list(getWorkspaceClient(), path),\n this._readSettings(\n [\n `files:${volumeKey}:list`,\n path ? connector.resolvePath(path) : \"__root__\",\n ],\n mode,\n ),\n );\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"List failed\");\n }\n });\n }\n\n private async _handleRead(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"read\", path))) return;\n\n const volumeCfg = this.volumeConfigs[volumeKey];\n const maxReadSize = volumeCfg.maxReadSize ?? FILES_MAX_READ_SIZE;\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n\n await this._runWithAuth(userCtx, async () => {\n try {\n // Drain the file body into a memory buffer capped at `maxReadSize`.\n // `/read` is for small text reads — clients wanting large or\n // streaming bodies use `/download` or `/raw`. Buffering preserves\n // the all-or-nothing contract: if the file exceeds the cap we\n // respond 413 atomically without leaking a partial 200 body.\n // Uses download-tier settings (no cache) — `/read` no longer\n // participates in the read-tier cache. Programmatic callers wanting\n // a cached small-file read should use the SDK\n // `volume.read(path, { maxSize })` method directly.\n const response = await this.execute(\n async () => connector.download(getWorkspaceClient(), path),\n this._downloadSettings(mode),\n );\n if (!response.ok) {\n this._sendStatusError(res, response.status);\n return;\n }\n if (!response.data.contents) {\n res.type(\"text/plain\").send(\"\");\n return;\n }\n\n const reader = response.data.contents.getReader();\n const chunks: Uint8Array[] = [];\n let bytesRead = 0;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n bytesRead += value.byteLength;\n if (bytesRead > maxReadSize) {\n res.status(413).json({\n error: `File exceeds maxReadSize (${maxReadSize} bytes). Use /download for large files.`,\n plugin: this.name,\n });\n try {\n await reader.cancel();\n } catch {\n // best-effort cleanup\n }\n return;\n }\n chunks.push(value);\n }\n } finally {\n try {\n reader.releaseLock();\n } catch {\n // best-effort cleanup\n }\n }\n\n res.type(\"text/plain\").send(Buffer.concat(chunks, bytesRead));\n } catch (error) {\n this._handleApiError(res, error, \"Read failed\");\n }\n });\n }\n\n private async _handleDownload(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n return this._serveFile(req, res, connector, volumeKey, {\n mode: \"download\",\n });\n }\n\n private async _handleRaw(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n return this._serveFile(req, res, connector, volumeKey, {\n mode: \"raw\",\n });\n }\n\n /**\n * Shared handler for `/download` and `/raw` endpoints.\n * - `download`: always forces `Content-Disposition: attachment`.\n * - `raw`: adds CSP sandbox; forces attachment only for unsafe content types.\n */\n private async _serveFile(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n opts: { mode: \"download\" | \"raw\" },\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, opts.mode, path)))\n return;\n\n const label = opts.mode === \"download\" ? \"Download\" : \"Raw fetch\";\n const volumeCfg = this.volumeConfigs[volumeKey];\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n\n await this._runWithAuth(userCtx, async () => {\n try {\n const settings = this._downloadSettings(mode);\n const response = await this.execute(\n async () => connector.download(getWorkspaceClient(), path),\n settings,\n );\n\n if (!response.ok) {\n this._sendStatusError(res, response.status);\n return;\n }\n\n const resolvedType = contentTypeFromPath(\n path,\n undefined,\n volumeCfg.customContentTypes,\n );\n const fileName = sanitizeFilename(path.split(\"/\").pop() ?? \"download\");\n\n res.setHeader(\"Content-Type\", resolvedType);\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n\n if (opts.mode === \"raw\") {\n res.setHeader(\"Content-Security-Policy\", \"sandbox\");\n if (!isSafeInlineContentType(resolvedType)) {\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${fileName}\"`,\n );\n }\n } else {\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${fileName}\"`,\n );\n }\n\n if (response.data.contents) {\n const nodeStream = Readable.fromWeb(\n response.data.contents as import(\"node:stream/web\").ReadableStream,\n );\n nodeStream.on(\"error\", (err) => {\n logger.error(\"Stream error during %s: %O\", opts.mode, err);\n if (!res.headersSent) {\n this._sendStatusError(res, 500);\n } else {\n res.destroy();\n }\n });\n nodeStream.pipe(res);\n } else {\n res.end();\n }\n } catch (error) {\n this._handleApiError(res, error, `${label} failed`);\n }\n });\n }\n\n private async _handleExists(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"exists\", path)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const result = await this.execute(\n async () => connector.exists(getWorkspaceClient(), path),\n this._readSettings(\n [`files:${volumeKey}:exists`, connector.resolvePath(path)],\n mode,\n ),\n );\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json({ exists: result.data });\n } catch (error) {\n this._handleApiError(res, error, \"Exists check failed\");\n }\n });\n }\n\n private async _handleMetadata(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"metadata\", path)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const result = await this.execute(\n async () => connector.metadata(getWorkspaceClient(), path),\n this._readSettings(\n [`files:${volumeKey}:metadata`, connector.resolvePath(path)],\n mode,\n ),\n );\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"Metadata fetch failed\");\n }\n });\n }\n\n private async _handlePreview(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"preview\", path)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const result = await this.execute(\n async () => connector.preview(getWorkspaceClient(), path),\n this._readSettings(\n [`files:${volumeKey}:preview`, connector.resolvePath(path)],\n mode,\n ),\n );\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"Preview failed\");\n }\n });\n }\n\n private async _handleUpload(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n const volumeCfg = this.volumeConfigs[volumeKey];\n const maxSize = volumeCfg.maxUploadSize ?? FILES_MAX_UPLOAD_SIZE;\n const rawContentLength = req.headers[\"content-length\"];\n let contentLength: number | undefined;\n\n if (typeof rawContentLength === \"string\" && rawContentLength.length > 0) {\n if (!/^\\d+$/.test(rawContentLength)) {\n res.status(400).json({\n error: \"Invalid Content-Length header.\",\n plugin: this.name,\n });\n return;\n }\n contentLength = Number(rawContentLength);\n }\n\n if (\n !(await this._enforcePolicy(req, res, volumeKey, \"upload\", path, {\n size: contentLength,\n }))\n )\n return;\n\n if (contentLength !== undefined && contentLength > maxSize) {\n res.status(413).json({\n error: `File size (${contentLength} bytes) exceeds maximum allowed size (${maxSize} bytes).`,\n plugin: this.name,\n });\n return;\n }\n\n logger.debug(req, \"Upload started: volume=%s path=%s\", volumeKey, path);\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const rawStream: ReadableStream<Uint8Array> = Readable.toWeb(req);\n\n let bytesReceived = 0;\n const webStream = rawStream.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n bytesReceived += chunk.byteLength;\n if (bytesReceived > maxSize) {\n controller.error(\n new Error(\n `Upload stream exceeds maximum allowed size (${maxSize} bytes)`,\n ),\n );\n return;\n }\n // When the client declared a Content-Length, the policy was\n // gated on that value (lines above pass `size: contentLength`\n // to `_enforcePolicy`). Refuse bytes beyond the declared size\n // so an attacker cannot bypass a per-user policy by sending a\n // small Content-Length and then streaming up to maxSize.\n if (\n contentLength !== undefined &&\n bytesReceived > contentLength\n ) {\n controller.error(\n new Error(\n `Upload stream exceeds declared Content-Length (${contentLength} bytes)`,\n ),\n );\n return;\n }\n controller.enqueue(chunk);\n },\n }),\n );\n\n logger.debug(\n req,\n \"Upload body received: volume=%s path=%s, size=%d bytes\",\n volumeKey,\n path,\n contentLength ?? 0,\n );\n const settings = this._writeSettings(mode);\n // The connector's `upload` resolves `getWorkspaceClient()` and\n // `client.config.authenticate(headers)` synchronously inside this\n // callback. When `_runWithAuth` wraps us in `runInCallerContext`, that\n // chain produces user-token Authorization headers on the outgoing\n // `fetch PUT`. The OBO upload-headers test pins this contract.\n const result = await this.trackWrite(() =>\n this.execute(async () => {\n await connector.upload(getWorkspaceClient(), path, webStream);\n return { success: true as const };\n }, settings),\n );\n\n // Awaited before sending the response so that a follow-up\n // `GET /list` issued in the same tick cannot race the\n // underlying `cache.delete()` and observe pre-write data.\n await this._invalidateListCache(volumeKey, path, connector, mode);\n\n if (!result.ok) {\n logger.error(\n req,\n \"Upload failed: volume=%s path=%s, size=%d bytes\",\n volumeKey,\n path,\n contentLength ?? 0,\n );\n this._sendStatusError(res, result.status);\n return;\n }\n\n logger.debug(\n req,\n \"Upload complete: volume=%s path=%s\",\n volumeKey,\n path,\n );\n res.json(result.data);\n } catch (error) {\n if (\n error instanceof Error &&\n (error.message.includes(\"exceeds maximum allowed size\") ||\n error.message.includes(\"exceeds declared Content-Length\"))\n ) {\n res.status(413).json({ error: error.message, plugin: this.name });\n return;\n }\n this._handleApiError(res, error, \"Upload failed\");\n }\n });\n }\n\n private async _handleMkdir(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const dirPath =\n typeof req.body?.path === \"string\" ? req.body.path : undefined;\n\n const valid = this._isValidPath(dirPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"mkdir\", dirPath)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const settings = this._writeSettings(mode);\n const result = await this.trackWrite(() =>\n this.execute(async () => {\n await connector.createDirectory(getWorkspaceClient(), dirPath);\n return { success: true as const };\n }, settings),\n );\n\n // Awaited before sending the response so that a follow-up\n // `GET /list` issued in the same tick cannot race the\n // underlying `cache.delete()` and observe pre-write data.\n await this._invalidateListCache(volumeKey, dirPath, connector, mode);\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"Create directory failed\");\n }\n });\n }\n\n private async _handleDelete(\n req: express.Request,\n res: express.Response,\n connector: FilesConnector,\n volumeKey: string,\n ): Promise<void> {\n const query = this._readPathQuery(req, res);\n if (!query) return;\n const rawPath = query.path;\n\n const valid = this._isValidPath(rawPath);\n if (valid !== true) {\n res.status(400).json({ error: valid, plugin: this.name });\n return;\n }\n const path = rawPath as string;\n\n if (!(await this._enforcePolicy(req, res, volumeKey, \"delete\", path)))\n return;\n\n const { mode, userCtx } = this._resolveAuthForRequest(req, volumeKey);\n await this._runWithAuth(userCtx, async () => {\n try {\n const settings = this._writeSettings(mode);\n const result = await this.trackWrite(() =>\n this.execute(async () => {\n await connector.delete(getWorkspaceClient(), path);\n return { success: true as const };\n }, settings),\n );\n\n // Awaited before sending the response so that a follow-up\n // `GET /list` issued in the same tick cannot race the\n // underlying `cache.delete()` and observe pre-write data.\n await this._invalidateListCache(volumeKey, path, connector, mode);\n\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n\n res.json(result.data);\n } catch (error) {\n this._handleApiError(res, error, \"Delete failed\");\n }\n });\n }\n\n private _resolveAuth(\n volumeKey: string,\n ): \"service-principal\" | \"on-behalf-of-user\" {\n return (\n this.volumeConfigs[volumeKey]?.auth ??\n this.config.auth ??\n \"service-principal\"\n );\n }\n\n /**\n * Build a `CallerContext` from request headers when both\n * `x-forwarded-access-token` and `x-forwarded-user` are present, otherwise\n * return `null`. Used by OBO route handlers to wrap SDK calls in the\n * end-user's identity. A `null` result means \"fall back to the service\n * principal client\" — for OBO volumes in production, `_enforcePolicy` will\n * already have responded 401 before we get here, so `null` is reachable\n * only on the dev-fallback path.\n */\n private _buildUserContextOrNull(req: express.Request): CallerContext | null {\n const token = req.header(\"x-forwarded-access-token\")?.trim();\n const userId = req.header(\"x-forwarded-user\")?.trim();\n if (!token || !userId) return null;\n return ServiceContext.createCallerContext(token, userId);\n }\n\n /**\n * Build the telemetry attribute hash for the `files.auth_mode` span\n * attribute. The value reflects what operationally happened — i.e.\n * whether `runInCallerContext` actually wrapped the SDK call:\n * - HTTP route on OBO volume + valid token → `\"on-behalf-of-user\"`.\n * - HTTP route on OBO volume + dev-fallback (no token) →\n * `\"service-principal\"` (the route falls through to the SP client).\n * - HTTP route on SP volume → `\"service-principal\"`.\n * - `asUser(req)` programmatic calls with a real user context →\n * `\"on-behalf-of-user\"`.\n * - Any unwrapped path → `\"service-principal\"`.\n */\n private _authModeAttributes(\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n ): { \"files.auth_mode\": string } {\n return { \"files.auth_mode\": authMode };\n }\n\n /**\n * One-shot resolver for HTTP route handlers. Builds the request's\n * `CallerContext` AT MOST ONCE (when the volume is OBO and the headers are\n * present) and returns both the operationally-effective auth mode and the\n * pre-built `CallerContext`.\n *\n * Handlers thread the `userCtx` into `_runWithAuth(userCtx, fn)` to avoid\n * a second `ServiceContext.createCallerContext()` allocation. That call\n * builds a fresh `WorkspaceClient` per invocation, so doing it twice per\n * request was pure throwaway overhead.\n */\n private _resolveAuthForRequest(\n req: express.Request,\n volumeKey: string,\n ): {\n mode: \"service-principal\" | \"on-behalf-of-user\";\n userCtx: CallerContext | null;\n } {\n if (this._resolveAuth(volumeKey) !== \"on-behalf-of-user\") {\n return { mode: \"service-principal\", userCtx: null };\n }\n const userCtx = this._buildUserContextOrNull(req);\n return userCtx\n ? { mode: \"on-behalf-of-user\", userCtx }\n : { mode: \"service-principal\", userCtx: null };\n }\n\n /**\n * Run `fn` under the correct execution context.\n * - `userCtx` is `null`: invokes `fn` directly so the service-principal\n * `WorkspaceClient` and `getCurrentUserId()` are used — identical\n * behavior to pre-OBO releases. This covers both SP volumes and the\n * OBO dev-fallback path (where headers were missing).\n * - `userCtx` is a `CallerContext`: wraps `fn` in `runInCallerContext(userCtx)`,\n * so SDK calls execute as the end user and `getCurrentUserId()` (and\n * therefore cache keys) resolve to the user's ID.\n *\n * The caller is responsible for building `userCtx` exactly once per\n * request via `_resolveAuthForRequest`; this signature deliberately does\n * NOT take a `req` so it cannot accidentally re-build the context.\n */\n private async _runWithAuth(\n userCtx: CallerContext | null,\n fn: () => Promise<void>,\n ): Promise<void> {\n if (userCtx) {\n return runInCallerContext(userCtx, fn);\n }\n return fn();\n }\n\n /**\n * Tag the span that `FilesConnector.<operation>` opens with\n * `files.auth_mode`. Programmatic VolumeAPI methods bypass\n * `this.execute(...)` (and therefore the `TelemetryInterceptor`), so the\n * connector's own `files.<operation>` span is the natural place to land\n * this attribute. Rather than opening a parent `files.<operation>` span\n * (which would duplicate the connector's span — same name, doubled\n * allocation/export), we propagate the attribute via AsyncLocalStorage\n * and let the connector merge it into its existing span at creation\n * time.\n *\n * The `operation` parameter is unused by the propagation mechanism (the\n * connector knows its own operation), but kept in the signature for API\n * stability with the previous span-creation form.\n */\n private _withAuthModeAttributes<R>(\n _operation: string,\n authMode: \"service-principal\" | \"on-behalf-of-user\",\n fn: () => Promise<R>,\n ): Promise<R> {\n return runWithFilesSpanAttributes(this._authModeAttributes(authMode), fn);\n }\n\n /**\n * Wrap each `VolumeAPI` method so the `FilesConnector` span it produces is\n * tagged with `files.auth_mode = \"service-principal\"`. Used for\n * programmatic calls that don't go through `asUser(req)`.\n *\n * The attribute is attached to the connector's existing span via\n * AsyncLocalStorage propagation (see `_withAuthModeAttributes`); no\n * additional parent span is opened, so each call produces exactly one\n * `files.<operation>` span instead of two.\n */\n private _wrapVolumeAPIWithSPSpan(api: VolumeAPI): VolumeAPI {\n const wrap =\n <Args extends unknown[], R>(\n operation: string,\n fn: (...args: Args) => Promise<R>,\n ): ((...args: Args) => Promise<R>) =>\n (...args: Args) =>\n this._withAuthModeAttributes(operation, \"service-principal\", () =>\n fn(...args),\n );\n\n return {\n list: wrap(\"list\", api.list),\n read: wrap(\"read\", api.read),\n download: wrap(\"download\", api.download),\n exists: wrap(\"exists\", api.exists),\n metadata: wrap(\"metadata\", api.metadata),\n upload: wrap(\"upload\", api.upload),\n createDirectory: wrap(\"createDirectory\", api.createDirectory),\n delete: wrap(\"delete\", api.delete),\n preview: wrap(\"preview\", api.preview),\n };\n }\n\n /**\n * Wrap each `VolumeAPI` method so its execution runs inside\n * `runInCallerContext(userCtx, ...)`. Used by `VolumeHandle.asUser(req)` to\n * force the SDK identity to the end user regardless of the volume's\n * `auth` setting. The policy check baked into each method (via\n * `createVolumeAPI`) runs inside the same scope, so `getCurrentUserId()`\n * and any cache `userKey` derived from it also resolve to the user.\n *\n * Each wrapped invocation tags the connector's span with\n * `files.auth_mode = \"on-behalf-of-user\"` via AsyncLocalStorage\n * propagation — no additional parent span is opened.\n */\n private _wrapVolumeAPIInUserContext(\n api: VolumeAPI,\n userCtx: CallerContext,\n ): VolumeAPI {\n const wrap =\n <Args extends unknown[], R>(\n operation: string,\n fn: (...args: Args) => Promise<R>,\n ): ((...args: Args) => Promise<R>) =>\n (...args: Args) =>\n this._withAuthModeAttributes(operation, \"on-behalf-of-user\", () =>\n runInCallerContext(userCtx, () => fn(...args)),\n );\n\n return {\n list: wrap(\"list\", api.list),\n read: wrap(\"read\", api.read),\n download: wrap(\"download\", api.download),\n exists: wrap(\"exists\", api.exists),\n metadata: wrap(\"metadata\", api.metadata),\n upload: wrap(\"upload\", api.upload),\n createDirectory: wrap(\"createDirectory\", api.createDirectory),\n delete: wrap(\"delete\", api.delete),\n preview: wrap(\"preview\", api.preview),\n };\n }\n\n /**\n * Creates a VolumeAPI for a specific volume key.\n *\n * Enforces the volume's policy before each operation.\n */\n protected createVolumeAPI(\n volumeKey: string,\n user: FilePolicyUser,\n ): VolumeAPI {\n const connector = this.volumeConnectors[volumeKey];\n const check = (\n action: FileAction,\n path: string,\n overrides?: Partial<FileResource>,\n ) => this._checkPolicy(volumeKey, action, path, user, overrides);\n\n return {\n list: async (directoryPath?: string) => {\n await check(\"list\", directoryPath ?? \"/\");\n return connector.list(getWorkspaceClient(), directoryPath);\n },\n read: async (filePath: string, opts?: { maxSize?: number }) => {\n await check(\"read\", filePath);\n return connector.read(getWorkspaceClient(), filePath, opts);\n },\n download: async (filePath: string) => {\n await check(\"download\", filePath);\n return connector.download(getWorkspaceClient(), filePath);\n },\n exists: async (filePath: string) => {\n await check(\"exists\", filePath);\n return connector.exists(getWorkspaceClient(), filePath);\n },\n metadata: async (filePath: string) => {\n await check(\"metadata\", filePath);\n return connector.metadata(getWorkspaceClient(), filePath);\n },\n upload: async (\n filePath: string,\n contents: ReadableStream | Buffer | string,\n opts?: { overwrite?: boolean },\n ) => {\n await check(\"upload\", filePath);\n return connector.upload(getWorkspaceClient(), filePath, contents, opts);\n },\n createDirectory: async (directoryPath: string) => {\n await check(\"mkdir\", directoryPath);\n return connector.createDirectory(getWorkspaceClient(), directoryPath);\n },\n delete: async (filePath: string) => {\n await check(\"delete\", filePath);\n return connector.delete(getWorkspaceClient(), filePath);\n },\n preview: async (filePath: string) => {\n await check(\"preview\", filePath);\n return connector.preview(getWorkspaceClient(), filePath);\n },\n };\n }\n\n /**\n * Builds the agent-tool registry entries for a single volume. One set of\n * tools per configured volume, keyed by `${volumeKey}.${method}`.\n *\n * Each handler resolves the caller's identity from the current execution\n * context (OBO user when the agent run is wrapped in `asUser(req)`, service\n * principal otherwise in local dev) and dispatches through\n * `createVolumeAPI(volumeKey, user)` so the volume's policy is enforced\n * uniformly for agent and HTTP callers.\n */\n private _defineVolumeTools(volumeKey: string): ToolRegistry {\n const buildUser = (): FilePolicyUser => {\n const ctx = getExecutionContext();\n return isCallerContext(ctx)\n ? { id: ctx.principal.userId }\n : { id: ctx.serviceUserId, isServicePrincipal: true };\n };\n const api = () => this.createVolumeAPI(volumeKey, buildUser());\n return {\n [`${volumeKey}.list`]: defineTool({\n description: `List files and directories in the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z\n .string()\n .optional()\n .describe(\"Directory path to list (optional, defaults to root)\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().list(args.path);\n },\n }),\n [`${volumeKey}.read`]: defineTool({\n description: `Read a text file from the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"File path to read\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().read(args.path);\n },\n }),\n [`${volumeKey}.exists`]: defineTool({\n description: `Check if a file or directory exists in the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"Path to check\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().exists(args.path);\n },\n }),\n [`${volumeKey}.metadata`]: defineTool({\n description: `Get metadata (size, type, last modified) for a file in the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"File path\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().metadata(args.path);\n },\n }),\n [`${volumeKey}.upload`]: defineTool({\n description: `Upload a text file to the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"Destination file path\"),\n contents: z.string().describe(\"File contents as a string\"),\n overwrite: z\n .boolean()\n .optional()\n .describe(\"Whether to overwrite existing file\"),\n }),\n annotations: { effect: \"destructive\", requiresUserContext: true },\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().upload(args.path, args.contents, {\n overwrite: args.overwrite,\n });\n },\n }),\n [`${volumeKey}.delete`]: defineTool({\n description: `Delete a file from the \"${volumeKey}\" volume`,\n schema: z.object({\n path: z.string().describe(\"File path to delete\"),\n }),\n annotations: { effect: \"destructive\", requiresUserContext: true },\n execute: (args, signal) => {\n signal?.throwIfAborted();\n return api().delete(args.path);\n },\n }),\n };\n }\n\n private inflightWrites = 0;\n\n private trackWrite<T>(fn: () => Promise<T>): Promise<T> {\n this.inflightWrites++;\n return fn().finally(() => {\n this.inflightWrites--;\n });\n }\n\n async shutdown(): Promise<void> {\n // Wait up to 10 seconds for in-flight write operations to finish\n const deadline = Date.now() + 10_000;\n while (this.inflightWrites > 0 && Date.now() < deadline) {\n logger.info(\n \"Waiting for %d in-flight write(s) to complete before shutdown…\",\n this.inflightWrites,\n );\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n if (this.inflightWrites > 0) {\n logger.warn(\n \"Shutdown deadline reached with %d in-flight write(s) still pending.\",\n this.inflightWrites,\n );\n }\n this.streamManager.abortAll();\n }\n\n getAgentTools(): AgentToolDefinition[] {\n return toolsFromRegistry(this.tools);\n }\n\n async executeAgentTool(\n name: string,\n args: unknown,\n signal?: AbortSignal,\n ): Promise<unknown> {\n return executeFromRegistry(this.tools, name, args, signal);\n }\n\n toolkit(opts?: import(\"../../core/agent/types\").ToolkitOptions) {\n return buildToolkitEntries(this.name, this.tools, opts);\n }\n\n /**\n * Returns the programmatic API for the Files plugin.\n * Callable with a volume key to get a volume-scoped handle.\n *\n * SP volumes (`auth: \"service-principal\"`, the default) execute as the\n * service principal. OBO volumes (`auth: \"on-behalf-of-user\"`) executed\n * through the HTTP routes run as the end user; for programmatic calls\n * outside a route, use `asUser(req)` to opt into per-user execution.\n * `asUser(req)` is a hard override at the SDK level: it forces every\n * subsequent call to execute as the end user inside `runInCallerContext`,\n * regardless of the volume's `auth` setting. Policies control per-user\n * access in either mode.\n *\n * @example\n * ```ts\n * // Service principal access\n * appKit.files(\"uploads\").list()\n *\n * // With policy: pass user identity for access control. The SDK call\n * // also executes as the user (not the service principal).\n * appKit.files(\"uploads\").asUser(req).list()\n * ```\n */\n exports(): FilesExport {\n const resolveVolume = (volumeKey: string): VolumeHandle => {\n if (!this.volumeKeys.includes(volumeKey)) {\n throw new Error(\n `Unknown volume \"${volumeKey}\". Available volumes: ${this.volumeKeys.join(\", \")}`,\n );\n }\n\n // Lazy user resolution: getCurrentUserId() is called when a method\n // is invoked (policy check), not when exports() is called.\n const spUser: FilePolicyUser = {\n get id() {\n return getCurrentUserId();\n },\n isServicePrincipal: true,\n };\n // Default (non-asUser) programmatic surface: every call is tagged\n // with `files.auth_mode = \"service-principal\"` for telemetry parity\n // with the HTTP route path.\n const spApi = this._wrapVolumeAPIWithSPSpan(\n this.createVolumeAPI(volumeKey, spUser),\n );\n\n return {\n ...spApi,\n asUser: (req: express.Request) => {\n const user = this._extractUser(req);\n const api = this.createVolumeAPI(volumeKey, user);\n // Force OBO at the SDK level regardless of the volume's `auth`\n // setting: each method runs inside `runInCallerContext` so\n // `getWorkspaceClient()` returns the user-token client. When no\n // user token is available (only reachable in dev mode after the\n // strict `_extractUser` falls back to the SP identity), we skip\n // the OBO wrap and instead apply the SP-mode span wrap so trace\n // output reflects the actual SP execution.\n const userCtx = this._buildUserContextOrNull(req);\n if (!userCtx) return this._wrapVolumeAPIWithSPSpan(api);\n return this._wrapVolumeAPIInUserContext(api, userCtx);\n },\n };\n };\n\n const filesExport = ((volumeKey: string) =>\n resolveVolume(volumeKey)) as FilesExport;\n filesExport.volume = resolveVolume;\n\n return filesExport;\n }\n\n clientConfig(): Record<string, unknown> {\n return { volumes: this.volumeKeys };\n }\n}\n\n/**\n * @internal\n */\nexport const files = Object.assign(toPlugin(FilesPlugin), { policy });\n"],"mappings":"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"}
|
|
@@ -3,9 +3,9 @@ import { IAppRouter, ToPlugin } from "../../shared/src/plugin.js";
|
|
|
3
3
|
import { GenieStreamEvent } from "../../shared/src/genie.js";
|
|
4
4
|
import "../../shared/src/index.js";
|
|
5
5
|
import { ToolkitEntry, ToolkitOptions } from "../../core/agent/types.js";
|
|
6
|
+
import { PluginManifest } from "../../registry/types.js";
|
|
6
7
|
import { Plugin } from "../../plugin/plugin.js";
|
|
7
8
|
import "../../plugin/index.js";
|
|
8
|
-
import { PluginManifest } from "../../registry/types.js";
|
|
9
9
|
import { GenieConversationHistoryResponse } from "../../connectors/genie/types.js";
|
|
10
10
|
import { IGenieConfig } from "./types.js";
|
|
11
11
|
import "../../index.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { IAppRouter, ToPlugin } from "../../shared/src/plugin.js";
|
|
2
2
|
import "../../shared/src/index.js";
|
|
3
|
+
import { PluginManifest, ResourceRequirement } from "../../registry/types.js";
|
|
3
4
|
import { Plugin } from "../../plugin/plugin.js";
|
|
4
5
|
import "../../plugin/index.js";
|
|
5
|
-
import { PluginManifest, ResourceRequirement } from "../../registry/types.js";
|
|
6
6
|
import "../../registry/index.js";
|
|
7
7
|
import { IJobsConfig, JobAPI, JobConfig, JobsExport } from "./types.js";
|
|
8
8
|
import "../../index.js";
|
|
@@ -4,9 +4,9 @@ import "../../shared/src/index.js";
|
|
|
4
4
|
import { ToolkitEntry, ToolkitOptions } from "../../core/agent/types.js";
|
|
5
5
|
import { LakebasePool } from "../../connectors/lakebase/routing-pool.js";
|
|
6
6
|
import "../../connectors/lakebase/index.js";
|
|
7
|
+
import { PluginManifest } from "../../registry/types.js";
|
|
7
8
|
import { Plugin } from "../../plugin/plugin.js";
|
|
8
9
|
import "../../plugin/index.js";
|
|
9
|
-
import { PluginManifest } from "../../registry/types.js";
|
|
10
10
|
import { ILakebaseConfig } from "./types.js";
|
|
11
11
|
import "../../index.js";
|
|
12
12
|
import "../agents/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lakebase.d.ts","names":[],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEA;;;;;;;;;;;;;;;cAAa,cAAA,SAAuB,MAAA,YAAkB,YAAA;EAkOU;EAAA,OAhOvD,QAAA,EAFmB,cAAA;EAAA,UAIR,MAAA,EAAQ,eAAA;EAAA,QAClB,IAAA;EAAA,QACA,cAAA;EAwEG;;;;;;;;EA9DL,KAAA,CAAA,GAAK,OAAA;EAhByC;;;;;;;;;;;;;;;;;;EA2E9C,KAAA,WAAgB,cAAA,OAAA,CACpB,IAAA,UACA,MAAA,eACC,OAAA,CAAQ,WAAA,CAAY,CAAA;EAAZ;;;;;;;;;;;;;;EAAA,QAmBG,oBAAA;EA4HH;;;;;;;;;;;;EA7FL,QAAA,CAAA,GAAY,OAAA;EArDN;;;;;EAAA,QAkFJ,KAAA;cAEI,MAAA,EAAQ,eAAA;EAAA,QAYZ,cAAA;EA2CR,aAAA,CAAA,GAAiB,mBAAA;EAIX,gBAAA,CACJ,IAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,GACR,OAAA;EAIH,OAAA,CAAQ,IAAA,GAJE,cAAA,GAIoD,MAAA,SAAA,YAAA;;;;;UAQtD,gBAAA;;;;;;;;;;;;;EAyBR,OAAA,CAAA;UAGwB,YAAA;sBA3LF,cAAA,QAAc,IAAA,UACtB,MAAA,iBAEX,OAAA,CAAQ,WAAA,CAAY,CAAA;;;;;;;;;;iBA/
|
|
1
|
+
{"version":3,"file":"lakebase.d.ts","names":[],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEA;;;;;;;;;;;;;;;cAAa,cAAA,SAAuB,MAAA,YAAkB,YAAA;EAkOU;EAAA,OAhOvD,QAAA,EAFmB,cAAA;EAAA,UAIR,MAAA,EAAQ,eAAA;EAAA,QAClB,IAAA;EAAA,QACA,cAAA;EAwEG;;;;;;;;EA9DL,KAAA,CAAA,GAAK,OAAA;EAhByC;;;;;;;;;;;;;;;;;;EA2E9C,KAAA,WAAgB,cAAA,OAAA,CACpB,IAAA,UACA,MAAA,eACC,OAAA,CAAQ,WAAA,CAAY,CAAA;EAAZ;;;;;;;;;;;;;;EAAA,QAmBG,oBAAA;EA4HH;;;;;;;;;;;;EA7FL,QAAA,CAAA,GAAY,OAAA;EArDN;;;;;EAAA,QAkFJ,KAAA;cAEI,MAAA,EAAQ,eAAA;EAAA,QAYZ,cAAA;EA2CR,aAAA,CAAA,GAAiB,mBAAA;EAIX,gBAAA,CACJ,IAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,GACR,OAAA;EAIH,OAAA,CAAQ,IAAA,GAJE,cAAA,GAIoD,MAAA,SAAA,YAAA;;;;;UAQtD,gBAAA;;;;;;;;;;;;;EAyBR,OAAA,CAAA;UAGwB,YAAA;sBA3LF,cAAA,QAAc,IAAA,UACtB,MAAA,iBAEX,OAAA,CAAQ,WAAA,CAAY,CAAA;;;;;;;;;;iBA/Fb,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkSC,QAAA,EAAQ,QAAA,QAAA,cAAA,EAAA,eAAA"}
|