@databricks/appkit 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CLAUDE.md +3 -2
  2. package/NOTICE.md +1 -0
  3. package/dist/appkit/package.js +1 -1
  4. package/dist/cache/index.js +1 -2
  5. package/dist/cache/index.js.map +1 -1
  6. package/dist/cache/storage/persistent.js +1 -2
  7. package/dist/cache/storage/persistent.js.map +1 -1
  8. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  9. package/dist/cli/commands/plugin/create/resource-defaults.js +2 -1
  10. package/dist/cli/commands/plugin/create/resource-defaults.js.map +1 -1
  11. package/dist/cli/commands/plugin/schema-resources.js +34 -51
  12. package/dist/cli/commands/plugin/schema-resources.js.map +1 -1
  13. package/dist/cli/commands/plugin/sync/sync.js +20 -6
  14. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  15. package/dist/cli/commands/plugin/validate/validate-manifest.js +71 -157
  16. package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -1
  17. package/dist/cli/commands/plugin/validate/validate.js +2 -2
  18. package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
  19. package/dist/cli/commands/setup.js +2 -2
  20. package/dist/cli/commands/setup.js.map +1 -1
  21. package/dist/connectors/index.js +0 -1
  22. package/dist/connectors/jobs/client.js +1 -2
  23. package/dist/connectors/jobs/client.js.map +1 -1
  24. package/dist/connectors/lakebase/routing-pool.js +1 -2
  25. package/dist/connectors/lakebase/routing-pool.js.map +1 -1
  26. package/dist/connectors/sql-warehouse/client.js +1 -2
  27. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  28. package/dist/context/execution-context.js +9 -13
  29. package/dist/context/execution-context.js.map +1 -1
  30. package/dist/context/index.js +3 -13
  31. package/dist/context/service-context.js +137 -136
  32. package/dist/context/service-context.js.map +1 -1
  33. package/dist/context/user-context.js +1 -5
  34. package/dist/context/user-context.js.map +1 -1
  35. package/dist/core/agent/create-agent.js +1 -2
  36. package/dist/core/agent/create-agent.js.map +1 -1
  37. package/dist/core/appkit.d.ts.map +1 -1
  38. package/dist/core/appkit.js +9 -37
  39. package/dist/core/appkit.js.map +1 -1
  40. package/dist/errors/authentication.js +44 -40
  41. package/dist/errors/authentication.js.map +1 -1
  42. package/dist/errors/base.js +86 -70
  43. package/dist/errors/base.js.map +1 -1
  44. package/dist/errors/configuration.js +70 -66
  45. package/dist/errors/configuration.js.map +1 -1
  46. package/dist/errors/connection.js +50 -46
  47. package/dist/errors/connection.js.map +1 -1
  48. package/dist/errors/execution.js +47 -43
  49. package/dist/errors/execution.js.map +1 -1
  50. package/dist/errors/index.js +10 -27
  51. package/dist/errors/initialization.js +38 -34
  52. package/dist/errors/initialization.js.map +1 -1
  53. package/dist/errors/server.js +34 -31
  54. package/dist/errors/server.js.map +1 -1
  55. package/dist/errors/tunnel.js +47 -43
  56. package/dist/errors/tunnel.js.map +1 -1
  57. package/dist/errors/validation.js +41 -37
  58. package/dist/errors/validation.js.map +1 -1
  59. package/dist/index.d.ts +1 -1
  60. package/dist/index.js +3 -9
  61. package/dist/plugin/dev-reader.js +1 -2
  62. package/dist/plugin/dev-reader.js.map +1 -1
  63. package/dist/plugin/interceptors/retry.js +1 -2
  64. package/dist/plugin/interceptors/retry.js.map +1 -1
  65. package/dist/plugin/interceptors/telemetry.js +1 -2
  66. package/dist/plugin/interceptors/telemetry.js.map +1 -1
  67. package/dist/plugin/plugin.d.ts +12 -4
  68. package/dist/plugin/plugin.d.ts.map +1 -1
  69. package/dist/plugin/plugin.js +59 -28
  70. package/dist/plugin/plugin.js.map +1 -1
  71. package/dist/plugins/agents/agents.d.ts +56 -3
  72. package/dist/plugins/agents/agents.d.ts.map +1 -1
  73. package/dist/plugins/agents/agents.js +160 -8
  74. package/dist/plugins/agents/agents.js.map +1 -1
  75. package/dist/plugins/agents/schemas.js +0 -1
  76. package/dist/plugins/agents/schemas.js.map +1 -1
  77. package/dist/plugins/analytics/analytics.js +1 -2
  78. package/dist/plugins/analytics/analytics.js.map +1 -1
  79. package/dist/plugins/analytics/manifest.js +9 -1
  80. package/dist/plugins/analytics/query.js +2 -4
  81. package/dist/plugins/analytics/query.js.map +1 -1
  82. package/dist/plugins/files/manifest.js +7 -1
  83. package/dist/plugins/files/plugin.js +3 -6
  84. package/dist/plugins/files/plugin.js.map +1 -1
  85. package/dist/plugins/genie/genie.js +1 -2
  86. package/dist/plugins/genie/genie.js.map +1 -1
  87. package/dist/plugins/genie/manifest.js +7 -1
  88. package/dist/plugins/jobs/plugin.js +2 -4
  89. package/dist/plugins/jobs/plugin.js.map +1 -1
  90. package/dist/plugins/lakebase/lakebase.js +1 -2
  91. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  92. package/dist/plugins/lakebase/manifest.js +26 -4
  93. package/dist/plugins/server/index.js +1 -2
  94. package/dist/plugins/server/index.js.map +1 -1
  95. package/dist/plugins/server/vite-dev-server.js +1 -2
  96. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  97. package/dist/plugins/serving/serving.js +1 -2
  98. package/dist/plugins/serving/serving.js.map +1 -1
  99. package/dist/registry/index.d.ts +1 -1
  100. package/dist/registry/manifest-loader.d.ts +4 -4
  101. package/dist/registry/manifest-loader.d.ts.map +1 -1
  102. package/dist/registry/manifest-loader.js +1 -2
  103. package/dist/registry/manifest-loader.js.map +1 -1
  104. package/dist/registry/resource-registry.js +1 -2
  105. package/dist/registry/resource-registry.js.map +1 -1
  106. package/dist/registry/types.d.ts +21 -7
  107. package/dist/registry/types.d.ts.map +1 -1
  108. package/dist/registry/types.generated.d.ts +1 -1
  109. package/dist/registry/types.generated.js +1 -1
  110. package/dist/registry/types.generated.js.map +1 -1
  111. package/dist/registry/types.js +3 -3
  112. package/dist/registry/types.js.map +1 -1
  113. package/dist/schemas/manifest.d.ts +1139 -0
  114. package/dist/schemas/manifest.d.ts.map +1 -0
  115. package/dist/schemas/manifest.js +524 -0
  116. package/dist/schemas/manifest.js.map +1 -0
  117. package/dist/shared/src/index.d.ts +1 -1
  118. package/dist/shared/src/plugin.d.ts +38 -7
  119. package/dist/shared/src/plugin.d.ts.map +1 -1
  120. package/dist/shared/src/schemas/manifest.d.ts +1110 -0
  121. package/dist/shared/src/schemas/manifest.d.ts.map +1 -0
  122. package/dist/stream/arrow-stream-processor.js +1 -2
  123. package/dist/stream/arrow-stream-processor.js.map +1 -1
  124. package/dist/stream/buffers.js +1 -2
  125. package/dist/stream/buffers.js.map +1 -1
  126. package/docs/api/appkit/Enumeration.ResourceType.md +1 -1
  127. package/docs/api/appkit/Interface.PluginManifest.md +57 -3
  128. package/docs/api/appkit/Interface.ResourceEntry.md +6 -4
  129. package/docs/api/appkit/Interface.ResourceRequirement.md +7 -61
  130. package/docs/api/appkit/TypeAlias.ResourceFieldEntry.md +6 -0
  131. package/docs/api/appkit/Variable.agents.md +1 -1
  132. package/docs/api/appkit.md +12 -12
  133. package/docs/app-management.md +1 -1
  134. package/docs/development/ai-assisted-development.md +2 -2
  135. package/docs/development/local-development.md +1 -1
  136. package/docs/development/remote-bridge.md +1 -1
  137. package/docs/development/templates.md +118 -12
  138. package/docs/development.md +1 -1
  139. package/docs/plugins/agents.md +8 -4
  140. package/docs/plugins/custom-plugins.md +33 -23
  141. package/docs/plugins/lakebase.md +1 -1
  142. package/docs/plugins/manifest.md +293 -0
  143. package/docs.md +2 -2
  144. package/llms.txt +3 -2
  145. package/package.json +3 -3
  146. package/sbom.cdx.json +1 -1
  147. package/dist/_virtual/_rolldown/runtime.js +0 -7
  148. package/dist/connectors/lakebase-v1/client.js +0 -15
  149. package/dist/connectors/lakebase-v1/client.js.map +0 -1
  150. package/dist/connectors/lakebase-v1/index.js +0 -3
  151. package/dist/context/index.js.map +0 -1
  152. package/dist/errors/index.js.map +0 -1
  153. package/dist/index.js.map +0 -1
  154. package/dist/schemas/plugin-manifest.generated.d.ts +0 -182
  155. package/dist/schemas/plugin-manifest.generated.d.ts.map +0 -1
  156. package/dist/schemas/plugin-manifest.schema.json +0 -489
  157. package/dist/schemas/template-plugins.schema.json +0 -113
  158. package/dist/shared/src/schemas/plugin-manifest.generated.d.ts +0 -182
  159. package/dist/shared/src/schemas/plugin-manifest.generated.d.ts.map +0 -1
  160. package/docs/api/appkit/Interface.ResourceFieldEntry.md +0 -82
@@ -1,8 +1,7 @@
1
1
  import { createLogger } from "../../logging/logger.js";
2
- import { AppKitError, init_base } from "../../errors/base.js";
2
+ import { AppKitError } from "../../errors/base.js";
3
3
 
4
4
  //#region src/plugin/interceptors/retry.ts
5
- init_base();
6
5
  const logger = createLogger("interceptors:retry");
7
6
  /**
8
7
  * Determines whether an error is safe to retry.
@@ -1 +1 @@
1
- {"version":3,"file":"retry.js","names":[],"sources":["../../../src/plugin/interceptors/retry.ts"],"sourcesContent":["import type { RetryConfig } from \"shared\";\nimport { AppKitError } from \"../../errors/base\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nconst logger = createLogger(\"interceptors:retry\");\n\n/**\n * Determines whether an error is safe to retry.\n *\n * Priority:\n * 1. AppKitError — reads the `isRetryable` boolean property.\n * 2. Databricks SDK ApiError (duck-typed) — calls `isRetryable()` method,\n * or falls back to status-code heuristic (5xx / 429 → retryable).\n * 3. Unknown errors — treated as retryable to preserve backward compatibility.\n */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof AppKitError) {\n return error.isRetryable;\n }\n\n if (error instanceof Error && \"statusCode\" in error) {\n const record = error as Record<string, unknown>;\n if (typeof record.statusCode !== \"number\") {\n return true;\n }\n if (typeof record.isRetryable === \"function\") {\n return (record.isRetryable as () => boolean)();\n }\n return record.statusCode >= 500 || record.statusCode === 429;\n }\n\n return true;\n}\n\nexport class RetryInterceptor implements ExecutionInterceptor {\n private attempts: number;\n private initialDelay: number;\n private maxDelay: number;\n\n constructor(config: RetryConfig) {\n this.attempts = config.attempts ?? 3;\n this.initialDelay = config.initialDelay ?? 1000;\n this.maxDelay = config.maxDelay ?? 30000;\n }\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n let lastError: Error | unknown;\n\n for (let attempt = 1; attempt <= this.attempts; attempt++) {\n try {\n const result = await fn();\n\n if (attempt > 1) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n }\n\n return result;\n } catch (error) {\n lastError = error;\n\n if (attempt === this.attempts) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n throw error;\n }\n\n if (context.signal?.aborted) {\n throw error;\n }\n\n if (!isRetryableError(error)) {\n throw error;\n }\n\n const delay = this.calculateDelay(attempt);\n await this.sleep(delay);\n }\n }\n\n throw lastError;\n }\n\n private calculateDelay(attempt: number): number {\n const delay = this.initialDelay * 2 ** (attempt - 1);\n const capped = Math.min(delay, this.maxDelay);\n\n return capped * Math.random();\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"mappings":";;;;WACgD;AAIhD,MAAM,SAAS,aAAa,qBAAqB;;;;;;;;;;AAWjD,SAAS,iBAAiB,OAAyB;AACjD,KAAI,iBAAiB,YACnB,QAAO,MAAM;AAGf,KAAI,iBAAiB,SAAS,gBAAgB,OAAO;EACnD,MAAM,SAAS;AACf,MAAI,OAAO,OAAO,eAAe,SAC/B,QAAO;AAET,MAAI,OAAO,OAAO,gBAAgB,WAChC,QAAQ,OAAO,aAA+B;AAEhD,SAAO,OAAO,cAAc,OAAO,OAAO,eAAe;;AAG3D,QAAO;;AAGT,IAAa,mBAAb,MAA8D;CAC5D,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,QAAqB;AAC/B,OAAK,WAAW,OAAO,YAAY;AACnC,OAAK,eAAe,OAAO,gBAAgB;AAC3C,OAAK,WAAW,OAAO,YAAY;;CAGrC,MAAM,UACJ,IACA,SACY;EACZ,IAAI;AAEJ,OAAK,IAAI,UAAU,GAAG,WAAW,KAAK,UAAU,UAC9C,KAAI;GACF,MAAM,SAAS,MAAM,IAAI;AAEzB,OAAI,UAAU,EACZ,QAAO,OAAO,EAAE,aAAa,EAC3B,gBAAgB,UAAU,GAC3B,CAAC;AAGJ,UAAO;WACA,OAAO;AACd,eAAY;AAEZ,OAAI,YAAY,KAAK,UAAU;AAC7B,WAAO,OAAO,EAAE,aAAa,EAC3B,gBAAgB,UAAU,GAC3B,CAAC;AACF,UAAM;;AAGR,OAAI,QAAQ,QAAQ,QAClB,OAAM;AAGR,OAAI,CAAC,iBAAiB,MAAM,CAC1B,OAAM;GAGR,MAAM,QAAQ,KAAK,eAAe,QAAQ;AAC1C,SAAM,KAAK,MAAM,MAAM;;AAI3B,QAAM;;CAGR,AAAQ,eAAe,SAAyB;EAC9C,MAAM,QAAQ,KAAK,eAAe,MAAM,UAAU;AAGlD,SAFe,KAAK,IAAI,OAAO,KAAK,SAAS,GAE7B,KAAK,QAAQ;;CAG/B,AAAQ,MAAM,IAA2B;AACvC,SAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
1
+ {"version":3,"file":"retry.js","names":[],"sources":["../../../src/plugin/interceptors/retry.ts"],"sourcesContent":["import type { RetryConfig } from \"shared\";\nimport { AppKitError } from \"../../errors/base\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nconst logger = createLogger(\"interceptors:retry\");\n\n/**\n * Determines whether an error is safe to retry.\n *\n * Priority:\n * 1. AppKitError — reads the `isRetryable` boolean property.\n * 2. Databricks SDK ApiError (duck-typed) — calls `isRetryable()` method,\n * or falls back to status-code heuristic (5xx / 429 → retryable).\n * 3. Unknown errors — treated as retryable to preserve backward compatibility.\n */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof AppKitError) {\n return error.isRetryable;\n }\n\n if (error instanceof Error && \"statusCode\" in error) {\n const record = error as Record<string, unknown>;\n if (typeof record.statusCode !== \"number\") {\n return true;\n }\n if (typeof record.isRetryable === \"function\") {\n return (record.isRetryable as () => boolean)();\n }\n return record.statusCode >= 500 || record.statusCode === 429;\n }\n\n return true;\n}\n\nexport class RetryInterceptor implements ExecutionInterceptor {\n private attempts: number;\n private initialDelay: number;\n private maxDelay: number;\n\n constructor(config: RetryConfig) {\n this.attempts = config.attempts ?? 3;\n this.initialDelay = config.initialDelay ?? 1000;\n this.maxDelay = config.maxDelay ?? 30000;\n }\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n let lastError: Error | unknown;\n\n for (let attempt = 1; attempt <= this.attempts; attempt++) {\n try {\n const result = await fn();\n\n if (attempt > 1) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n }\n\n return result;\n } catch (error) {\n lastError = error;\n\n if (attempt === this.attempts) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n throw error;\n }\n\n if (context.signal?.aborted) {\n throw error;\n }\n\n if (!isRetryableError(error)) {\n throw error;\n }\n\n const delay = this.calculateDelay(attempt);\n await this.sleep(delay);\n }\n }\n\n throw lastError;\n }\n\n private calculateDelay(attempt: number): number {\n const delay = this.initialDelay * 2 ** (attempt - 1);\n const capped = Math.min(delay, this.maxDelay);\n\n return capped * Math.random();\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"mappings":";;;;AAKA,MAAM,SAAS,aAAa,qBAAqB;;;;;;;;;;AAWjD,SAAS,iBAAiB,OAAyB;AACjD,KAAI,iBAAiB,YACnB,QAAO,MAAM;AAGf,KAAI,iBAAiB,SAAS,gBAAgB,OAAO;EACnD,MAAM,SAAS;AACf,MAAI,OAAO,OAAO,eAAe,SAC/B,QAAO;AAET,MAAI,OAAO,OAAO,gBAAgB,WAChC,QAAQ,OAAO,aAA+B;AAEhD,SAAO,OAAO,cAAc,OAAO,OAAO,eAAe;;AAG3D,QAAO;;AAGT,IAAa,mBAAb,MAA8D;CAC5D,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,QAAqB;AAC/B,OAAK,WAAW,OAAO,YAAY;AACnC,OAAK,eAAe,OAAO,gBAAgB;AAC3C,OAAK,WAAW,OAAO,YAAY;;CAGrC,MAAM,UACJ,IACA,SACY;EACZ,IAAI;AAEJ,OAAK,IAAI,UAAU,GAAG,WAAW,KAAK,UAAU,UAC9C,KAAI;GACF,MAAM,SAAS,MAAM,IAAI;AAEzB,OAAI,UAAU,EACZ,QAAO,OAAO,EAAE,aAAa,EAC3B,gBAAgB,UAAU,GAC3B,CAAC;AAGJ,UAAO;WACA,OAAO;AACd,eAAY;AAEZ,OAAI,YAAY,KAAK,UAAU;AAC7B,WAAO,OAAO,EAAE,aAAa,EAC3B,gBAAgB,UAAU,GAC3B,CAAC;AACF,UAAM;;AAGR,OAAI,QAAQ,QAAQ,QAClB,OAAM;AAGR,OAAI,CAAC,iBAAiB,MAAM,CAC1B,OAAM;GAGR,MAAM,QAAQ,KAAK,eAAe,QAAQ;AAC1C,SAAM,KAAK,MAAM,MAAM;;AAI3B,QAAM;;CAGR,AAAQ,eAAe,SAAyB;EAC9C,MAAM,QAAQ,KAAK,eAAe,MAAM,UAAU;AAGlD,SAFe,KAAK,IAAI,OAAO,KAAK,SAAS,GAE7B,KAAK,QAAQ;;CAG/B,AAAQ,MAAM,IAA2B;AACvC,SAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
@@ -1,9 +1,8 @@
1
- import { getCurrentUserId, init_execution_context, isInUserContext } from "../../context/execution-context.js";
1
+ import { getCurrentUserId, isInUserContext } from "../../context/execution-context.js";
2
2
  import { SpanStatusCode } from "../../telemetry/index.js";
3
3
  import { isDevOboFallback } from "../plugin.js";
4
4
 
5
5
  //#region src/plugin/interceptors/telemetry.ts
6
- init_execution_context();
7
6
  var TelemetryInterceptor = class {
8
7
  constructor(telemetry, config) {
9
8
  this.telemetry = telemetry;
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.js","names":[],"sources":["../../../src/plugin/interceptors/telemetry.ts"],"sourcesContent":["import type { TelemetryConfig } from \"shared\";\nimport {\n getCurrentUserId,\n isInUserContext,\n} from \"../../context/execution-context\";\nimport type { ITelemetry, Span } from \"../../telemetry\";\nimport { SpanStatusCode } from \"../../telemetry\";\nimport { isDevOboFallback } from \"../plugin\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nexport class TelemetryInterceptor implements ExecutionInterceptor {\n constructor(\n private telemetry: ITelemetry,\n private config?: TelemetryConfig,\n ) {}\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n const spanName = this.config?.spanName || \"plugin.execute\";\n\n // abort operation if signal is aborted\n if (context.signal?.aborted) {\n throw new Error(\"Operation aborted before execution\");\n }\n\n return this.telemetry.startActiveSpan(\n spanName,\n { attributes: this.config?.attributes },\n async (span: Span) => {\n let abortHandler: (() => void) | undefined;\n let isAborted = false;\n\n if (context.signal) {\n abortHandler = () => {\n // abort span if not recording\n if (!span.isRecording()) return;\n isAborted = true;\n span.setAttribute(\"cancelled\", true);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: \"Operation cancelled by client\",\n });\n span.end();\n };\n context.signal.addEventListener(\"abort\", abortHandler, {\n once: true,\n });\n }\n\n try {\n span.setAttribute(\n \"execution.context\",\n isInUserContext() ? \"user\" : \"service\",\n );\n span.setAttribute(\"caller.id\", getCurrentUserId());\n if (isDevOboFallback()) {\n span.setAttribute(\"execution.obo_dev_fallback\", true);\n }\n\n const result = await fn();\n if (!isAborted) {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n return result;\n } catch (error) {\n if (!isAborted) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n throw error;\n } finally {\n if (abortHandler && context.signal) {\n context.signal.removeEventListener(\"abort\", abortHandler);\n }\n if (!isAborted) {\n span.end();\n }\n }\n },\n );\n }\n}\n"],"mappings":";;;;;wBAIyC;AAMzC,IAAa,uBAAb,MAAkE;CAChE,YACE,AAAQ,WACR,AAAQ,QACR;EAFQ;EACA;;CAGV,MAAM,UACJ,IACA,SACY;EACZ,MAAM,WAAW,KAAK,QAAQ,YAAY;AAG1C,MAAI,QAAQ,QAAQ,QAClB,OAAM,IAAI,MAAM,qCAAqC;AAGvD,SAAO,KAAK,UAAU,gBACpB,UACA,EAAE,YAAY,KAAK,QAAQ,YAAY,EACvC,OAAO,SAAe;GACpB,IAAI;GACJ,IAAI,YAAY;AAEhB,OAAI,QAAQ,QAAQ;AAClB,yBAAqB;AAEnB,SAAI,CAAC,KAAK,aAAa,CAAE;AACzB,iBAAY;AACZ,UAAK,aAAa,aAAa,KAAK;AACpC,UAAK,UAAU;MACb,MAAM,eAAe;MACrB,SAAS;MACV,CAAC;AACF,UAAK,KAAK;;AAEZ,YAAQ,OAAO,iBAAiB,SAAS,cAAc,EACrD,MAAM,MACP,CAAC;;AAGJ,OAAI;AACF,SAAK,aACH,qBACA,iBAAiB,GAAG,SAAS,UAC9B;AACD,SAAK,aAAa,aAAa,kBAAkB,CAAC;AAClD,QAAI,kBAAkB,CACpB,MAAK,aAAa,8BAA8B,KAAK;IAGvD,MAAM,SAAS,MAAM,IAAI;AACzB,QAAI,CAAC,UACH,MAAK,UAAU,EAAE,MAAM,eAAe,IAAI,CAAC;AAE7C,WAAO;YACA,OAAO;AACd,QAAI,CAAC,WAAW;AACd,UAAK,gBAAgB,MAAe;AACpC,UAAK,UAAU,EAAE,MAAM,eAAe,OAAO,CAAC;;AAEhD,UAAM;aACE;AACR,QAAI,gBAAgB,QAAQ,OAC1B,SAAQ,OAAO,oBAAoB,SAAS,aAAa;AAE3D,QAAI,CAAC,UACH,MAAK,KAAK;;IAIjB"}
1
+ {"version":3,"file":"telemetry.js","names":[],"sources":["../../../src/plugin/interceptors/telemetry.ts"],"sourcesContent":["import type { TelemetryConfig } from \"shared\";\nimport {\n getCurrentUserId,\n isInUserContext,\n} from \"../../context/execution-context\";\nimport type { ITelemetry, Span } from \"../../telemetry\";\nimport { SpanStatusCode } from \"../../telemetry\";\nimport { isDevOboFallback } from \"../plugin\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nexport class TelemetryInterceptor implements ExecutionInterceptor {\n constructor(\n private telemetry: ITelemetry,\n private config?: TelemetryConfig,\n ) {}\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n const spanName = this.config?.spanName || \"plugin.execute\";\n\n // abort operation if signal is aborted\n if (context.signal?.aborted) {\n throw new Error(\"Operation aborted before execution\");\n }\n\n return this.telemetry.startActiveSpan(\n spanName,\n { attributes: this.config?.attributes },\n async (span: Span) => {\n let abortHandler: (() => void) | undefined;\n let isAborted = false;\n\n if (context.signal) {\n abortHandler = () => {\n // abort span if not recording\n if (!span.isRecording()) return;\n isAborted = true;\n span.setAttribute(\"cancelled\", true);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: \"Operation cancelled by client\",\n });\n span.end();\n };\n context.signal.addEventListener(\"abort\", abortHandler, {\n once: true,\n });\n }\n\n try {\n span.setAttribute(\n \"execution.context\",\n isInUserContext() ? \"user\" : \"service\",\n );\n span.setAttribute(\"caller.id\", getCurrentUserId());\n if (isDevOboFallback()) {\n span.setAttribute(\"execution.obo_dev_fallback\", true);\n }\n\n const result = await fn();\n if (!isAborted) {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n return result;\n } catch (error) {\n if (!isAborted) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n throw error;\n } finally {\n if (abortHandler && context.signal) {\n context.signal.removeEventListener(\"abort\", abortHandler);\n }\n if (!isAborted) {\n span.end();\n }\n }\n },\n );\n }\n}\n"],"mappings":";;;;;AAUA,IAAa,uBAAb,MAAkE;CAChE,YACE,AAAQ,WACR,AAAQ,QACR;EAFQ;EACA;;CAGV,MAAM,UACJ,IACA,SACY;EACZ,MAAM,WAAW,KAAK,QAAQ,YAAY;AAG1C,MAAI,QAAQ,QAAQ,QAClB,OAAM,IAAI,MAAM,qCAAqC;AAGvD,SAAO,KAAK,UAAU,gBACpB,UACA,EAAE,YAAY,KAAK,QAAQ,YAAY,EACvC,OAAO,SAAe;GACpB,IAAI;GACJ,IAAI,YAAY;AAEhB,OAAI,QAAQ,QAAQ;AAClB,yBAAqB;AAEnB,SAAI,CAAC,KAAK,aAAa,CAAE;AACzB,iBAAY;AACZ,UAAK,aAAa,aAAa,KAAK;AACpC,UAAK,UAAU;MACb,MAAM,eAAe;MACrB,SAAS;MACV,CAAC;AACF,UAAK,KAAK;;AAEZ,YAAQ,OAAO,iBAAiB,SAAS,cAAc,EACrD,MAAM,MACP,CAAC;;AAGJ,OAAI;AACF,SAAK,aACH,qBACA,iBAAiB,GAAG,SAAS,UAC9B;AACD,SAAK,aAAa,aAAa,kBAAkB,CAAC;AAClD,QAAI,kBAAkB,CACpB,MAAK,aAAa,8BAA8B,KAAK;IAGvD,MAAM,SAAS,MAAM,IAAI;AACzB,QAAI,CAAC,UACH,MAAK,UAAU,EAAE,MAAM,eAAe,IAAI,CAAC;AAE7C,WAAO;YACA,OAAO;AACd,QAAI,CAAC,WAAW;AACd,UAAK,gBAAgB,MAAe;AACpC,UAAK,UAAU,EAAE,MAAM,eAAe,OAAO,CAAC;;AAEhD,UAAM;aACE;AACR,QAAI,gBAAgB,QAAQ,OAC1B,SAAQ,OAAO,oBAAoB,SAAS,aAAa;AAE3D,QAAI,CAAC,UACH,MAAK,KAAK;;IAIjB"}
@@ -233,11 +233,19 @@ declare abstract class Plugin<TConfig extends BasePluginConfig = BasePluginConfi
233
233
  */
234
234
  asUser(req: express.Request): this;
235
235
  /**
236
- * Creates a proxy that wraps method calls in a user context.
237
- * This allows all plugin methods to automatically use the user's
238
- * Databricks credentials.
236
+ * Creates a proxy of `this` where every method call — and every function
237
+ * in the result of `exports()` — runs inside `wrapCall`.
238
+ *
239
+ * `wrapCall` decides the per-call scope. Two strategies are used today:
240
+ * - real OBO: fn => (...args) => runInUserContext(userContext, () => fn(...args))
241
+ * - dev fallback: fn => (...args) => otelContext.with(DEV_OBO_FALLBACK_KEY=true, () => fn(...args))
242
+ *
243
+ * `exports` is intercepted because methods captured in the returned
244
+ * exports object never re-enter the proxy's `get` trap. Wrapping them
245
+ * here is the only way to make the user context follow function
246
+ * references back out of the plugin.
239
247
  */
240
- private _createUserContextProxy;
248
+ private _createAsUserProxy;
241
249
  protected executeStream<T>(res: IAppResponse, fn: StreamExecuteHandler<T>, options: StreamExecutionSettings, userKey?: string): Promise<void>;
242
250
  /**
243
251
  * Execute a function with the plugin's interceptor chain.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAwLsB,MAAA,iBACJ,gBAAA,GAAmB,gBAAA,aACxB,UAAA;EAAA,UA6BW,MAAA,EAAQ,OAAA;EAAA,UA3BpB,OAAA;EAAA,UACA,KAAA,EAAQ,YAAA;EAAA,UACR,GAAA,EAAK,UAAA;EAAA,UACL,aAAA,EAAe,aAAA;EAAA,UACf,aAAA,EAAe,aAAA;EAAA,UACf,SAAA,EAAY,UAAA;EAAA,UACZ,OAAA,GAAU,aAAA;EAgSb;EAAA,QA7RC,mBAAA;EA8RF;EAAA,QA3RE,oBAAA;EA2RN;;;;;;EAAA,OAnRK,KAAA,EAAO,WAAA;EAsWE;;;EAjWhB,IAAA;cAEsB,MAAA,EAAQ,OAAA;EAAA,QAoBtB,gBAAA;EA4UN;;;;;;;;EAvTF,aAAA,CACE,IAAA;IACE,OAAA;IACA,eAAA,GAAkB,gBAAA;EAAA;EAgBtB,YAAA,CAAa,CAAA,EAAG,OAAA,CAAQ,MAAA;EAIlB,KAAA,CAAA,GAAK,OAAA;EAEX,YAAA,CAAA,GAAgB,iBAAA;EAIhB,uBAAA,CAAA,GAA2B,WAAA;EAI3B,qBAAA,CAAA;EAkZc;;;;;;;;;;;;;;;;;;;;;;;;EAtXd,OAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDA,YAAA,CAAA,GAAgB,MAAA;;;;;;;;;;;YAcN,aAAA,CAAc,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;;;;;;EAmBrC,MAAA,CAAO,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;UA0DZ,uBAAA;EAAA,UAwBQ,aAAA,GAAA,CACd,GAAA,EAAK,YAAA,EACL,EAAA,EAAI,oBAAA,CAAqB,CAAA,GACzB,OAAA,EAAS,uBAAA,EACT,OAAA,YAAgB,OAAA;;;;;;;;;;YAgFF,OAAA,GAAA,CACd,EAAA,GAAK,MAAA,GAAS,WAAA,KAAgB,OAAA,CAAQ,CAAA,GACtC,OAAA,EAAS,uBAAA,EACT,OAAA,YACC,OAAA,CAAQ,eAAA,CAAgB,CAAA;EAAA,UAmDjB,gBAAA,CAAiB,IAAA,UAAc,IAAA;EAAA,UAI/B,KAAA,YAAA,CACR,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAChB,MAAA,EAAQ,WAAA;EAAA,QAeF,qBAAA;EAAA,QAaA,kBAAA;EAAA,QAqCM,wBAAA;EAAA,QAqBN,iBAAA;AAAA"}
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAuNsB,MAAA,iBACJ,gBAAA,GAAmB,gBAAA,aACxB,UAAA;EAAA,UA6BW,MAAA,EAAQ,OAAA;EAAA,UA3BpB,OAAA;EAAA,UACA,KAAA,EAAQ,YAAA;EAAA,UACR,GAAA,EAAK,UAAA;EAAA,UACL,aAAA,EAAe,aAAA;EAAA,UACf,aAAA,EAAe,aAAA;EAAA,UACf,SAAA,EAAY,UAAA;EAAA,UACZ,OAAA,GAAU,aAAA;EA0SO;EAAA,QAvSnB,mBAAA;EAwSG;EAAA,QArSH,oBAAA;EAsSN;;;;;;EAAA,OA9RK,KAAA,EAAO,WAAA;EA+W0B;;;EA1WxC,IAAA;cAEsB,MAAA,EAAQ,OAAA;EAAA,QAoBtB,gBAAA;EAuVG;;;;;;;;EAlUX,aAAA,CACE,IAAA;IACE,OAAA;IACA,eAAA,GAAkB,gBAAA;EAAA;EAgBtB,YAAA,CAAa,CAAA,EAAG,OAAA,CAAQ,MAAA;EAIlB,KAAA,CAAA,GAAK,OAAA;EAEX,YAAA,CAAA,GAAgB,iBAAA;EAIhB,uBAAA,CAAA,GAA2B,WAAA;EAI3B,qBAAA,CAAA;EAgbyB;;;;;;;;;;;;;;;;;;;;;;;;EApZzB,OAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDA,YAAA,CAAA,GAAgB,MAAA;;;;;;;;;;;YAcN,aAAA,CAAc,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;;;;;;EAmBrC,MAAA,CAAO,GAAA,EAAK,OAAA,CAAQ,OAAA;;;;;;;;;;;;;;UAyDZ,kBAAA;EAAA,UAkCQ,aAAA,GAAA,CACd,GAAA,EAAK,YAAA,EACL,EAAA,EAAI,oBAAA,CAAqB,CAAA,GACzB,OAAA,EAAS,uBAAA,EACT,OAAA,YAAgB,OAAA;;;;;;;;;;YAgFF,OAAA,GAAA,CACd,EAAA,GAAK,MAAA,GAAS,WAAA,KAAgB,OAAA,CAAQ,CAAA,GACtC,OAAA,EAAS,uBAAA,EACT,OAAA,YACC,OAAA,CAAQ,eAAA,CAAgB,CAAA;EAAA,UAmDjB,gBAAA,CAAiB,IAAA,UAAc,IAAA;EAAA,UAI/B,KAAA,YAAA,CACR,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAChB,MAAA,EAAQ,WAAA;EAAA,QAeF,qBAAA;EAAA,QAaA,kBAAA;EAAA,QAqCM,wBAAA;EAAA,QAqBN,iBAAA;AAAA"}
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../logging/logger.js";
2
2
  import { AppKitError } from "../errors/base.js";
3
3
  import { AuthenticationError } from "../errors/authentication.js";
4
- import { init_errors } from "../errors/index.js";
4
+ import "../errors/index.js";
5
5
  import { ServiceContext } from "../context/service-context.js";
6
6
  import { getCurrentUserId, runInUserContext } from "../context/execution-context.js";
7
7
  import { normalizeTelemetryOptions } from "../telemetry/config.js";
@@ -9,7 +9,7 @@ import { TelemetryManager } from "../telemetry/telemetry-manager.js";
9
9
  import "../telemetry/index.js";
10
10
  import { deepMerge } from "../utils/merge.js";
11
11
  import { CacheManager } from "../cache/index.js";
12
- import { init_context } from "../context/index.js";
12
+ import "../context/index.js";
13
13
  import { AppManager } from "../app/index.js";
14
14
  import { StreamManager } from "../stream/stream-manager.js";
15
15
  import "../stream/index.js";
@@ -21,21 +21,45 @@ import { TimeoutInterceptor } from "./interceptors/timeout.js";
21
21
  import { context, createContextKey } from "@opentelemetry/api";
22
22
 
23
23
  //#region src/plugin/plugin.ts
24
- init_context();
25
- init_errors();
26
24
  const logger = createLogger("plugin");
27
25
  /**
28
- * Symbol used to expose the UserContext from an asUser() proxy.
29
- * Allows wrapWithAsUser in appkit.ts to retrieve the context and
30
- * wrap export methods in runInUserContext().
31
- */
32
- const USER_CONTEXT_SYMBOL = Symbol("appkit.userContext");
33
- /**
34
26
  * OTel context key for marking OBO dev mode fallback.
35
27
  * Set when asUser() is called in development mode without a user token.
36
28
  */
37
29
  const DEV_OBO_FALLBACK_KEY = createContextKey("appkit.devOboFallback");
38
30
  /**
31
+ * Returns true if `value` is a plain object literal (not an array, Date,
32
+ * class instance, etc.). Used to decide whether to recurse into nested
33
+ * export shapes when wrapping functions.
34
+ *
35
+ * @internal exported so the AppKit core can reuse the same predicate for
36
+ * its `bindExportMethods` walk; not part of the public package surface.
37
+ */
38
+ function isPlainObject(value) {
39
+ if (typeof value !== "object" || value === null) return false;
40
+ const proto = Object.getPrototypeOf(value);
41
+ return proto === Object.prototype || proto === null;
42
+ }
43
+ /**
44
+ * Returns a deep copy of `exports` where every function has been replaced
45
+ * with `wrap(fn)`, walking into nested plain objects.
46
+ *
47
+ * Used by the asUser proxy to make the user context follow function
48
+ * references that escape the proxy via `exports()`. The original input is
49
+ * not mutated, so plugins that memoize `exports()` are safe — each call
50
+ * through the proxy yields an independent, freshly wrapped view.
51
+ */
52
+ function wrapExportFunctions(exports, wrap) {
53
+ const result = {};
54
+ for (const key of Object.keys(exports)) {
55
+ const val = exports[key];
56
+ if (typeof val === "function") result[key] = wrap(val);
57
+ else if (isPlainObject(val)) result[key] = wrapExportFunctions(val, wrap);
58
+ else result[key] = val;
59
+ }
60
+ return result;
61
+ }
62
+ /**
39
63
  * Returns true if the current execution is an OBO dev mode fallback
40
64
  * (asUser() was called but fell back to service principal due to missing token).
41
65
  */
@@ -323,36 +347,43 @@ var Plugin = class {
323
347
  const isDev = process.env.NODE_ENV === "development";
324
348
  if (!token && isDev) {
325
349
  logger.warn("asUser() called without user token in development mode. Skipping user impersonation.");
326
- return new Proxy(this, { get: (target, prop, receiver) => {
327
- const value = Reflect.get(target, prop, receiver);
328
- if (typeof value !== "function") return value;
329
- if (typeof prop === "string" && EXCLUDED_FROM_PROXY.has(prop)) return value;
330
- return (...args) => {
331
- const ctx = context.active().setValue(DEV_OBO_FALLBACK_KEY, true);
332
- return context.with(ctx, () => value.apply(target, args));
333
- };
334
- } });
350
+ return this._createAsUserProxy((fn) => (...args) => {
351
+ const ctx = context.active().setValue(DEV_OBO_FALLBACK_KEY, true);
352
+ return context.with(ctx, () => fn(...args));
353
+ });
335
354
  }
336
355
  if (!token) throw AuthenticationError.missingToken("user token");
337
356
  if (!userId && !isDev) throw AuthenticationError.missingUserId();
338
357
  const effectiveUserId = userId || "dev-user";
339
358
  const userContext = ServiceContext.createUserContext(token, effectiveUserId, void 0, userEmail ?? void 0);
340
- return this._createUserContextProxy(userContext);
359
+ return this._createAsUserProxy((fn) => (...args) => runInUserContext(userContext, () => fn(...args)));
341
360
  }
342
361
  /**
343
- * Creates a proxy that wraps method calls in a user context.
344
- * This allows all plugin methods to automatically use the user's
345
- * Databricks credentials.
362
+ * Creates a proxy of `this` where every method call — and every function
363
+ * in the result of `exports()` — runs inside `wrapCall`.
364
+ *
365
+ * `wrapCall` decides the per-call scope. Two strategies are used today:
366
+ * - real OBO: fn => (...args) => runInUserContext(userContext, () => fn(...args))
367
+ * - dev fallback: fn => (...args) => otelContext.with(DEV_OBO_FALLBACK_KEY=true, () => fn(...args))
368
+ *
369
+ * `exports` is intercepted because methods captured in the returned
370
+ * exports object never re-enter the proxy's `get` trap. Wrapping them
371
+ * here is the only way to make the user context follow function
372
+ * references back out of the plugin.
346
373
  */
347
- _createUserContextProxy(userContext) {
374
+ _createAsUserProxy(wrapCall) {
348
375
  return new Proxy(this, { get: (target, prop, receiver) => {
349
- if (prop === USER_CONTEXT_SYMBOL) return userContext;
350
376
  const value = Reflect.get(target, prop, receiver);
351
377
  if (typeof value !== "function") return value;
352
378
  if (typeof prop === "string" && EXCLUDED_FROM_PROXY.has(prop)) return value;
353
- return (...args) => {
354
- return runInUserContext(userContext, () => value.apply(target, args));
379
+ if (prop === "exports") return () => {
380
+ const raw = value.call(target);
381
+ if (raw == null) return {};
382
+ if (typeof raw === "function") return raw;
383
+ if (isPlainObject(raw)) return wrapExportFunctions(raw, wrapCall);
384
+ return raw;
355
385
  };
386
+ return wrapCall(value.bind(target));
356
387
  } });
357
388
  }
358
389
  async executeStream(res, fn, options, userKey) {
@@ -465,5 +496,5 @@ var Plugin = class {
465
496
  };
466
497
 
467
498
  //#endregion
468
- export { Plugin, USER_CONTEXT_SYMBOL, isDevOboFallback };
499
+ export { Plugin, isDevOboFallback, isPlainObject };
469
500
  //# sourceMappingURL=plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":["otelContext","context"],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":["import { createContextKey, context as otelContext } from \"@opentelemetry/api\";\nimport type express from \"express\";\nimport type {\n BasePlugin,\n BasePluginConfig,\n IAppResponse,\n PluginEndpointMap,\n PluginExecuteConfig,\n PluginExecutionSettings,\n PluginPhase,\n RouteConfig,\n StreamExecuteHandler,\n StreamExecutionSettings,\n} from \"shared\";\nimport { AppManager } from \"../app\";\nimport { CacheManager } from \"../cache\";\nimport {\n getCurrentUserId,\n runInUserContext,\n ServiceContext,\n type UserContext,\n} from \"../context\";\nimport type { PluginContext } from \"../core/plugin-context\";\nimport { AppKitError, AuthenticationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { StreamManager } from \"../stream\";\nimport {\n type ITelemetry,\n normalizeTelemetryOptions,\n TelemetryManager,\n} from \"../telemetry\";\nimport { deepMerge } from \"../utils\";\nimport { DevFileReader } from \"./dev-reader\";\nimport type { ExecutionResult } from \"./execution-result\";\nimport { CacheInterceptor } from \"./interceptors/cache\";\nimport { RetryInterceptor } from \"./interceptors/retry\";\nimport { TelemetryInterceptor } from \"./interceptors/telemetry\";\nimport { TimeoutInterceptor } from \"./interceptors/timeout\";\nimport type {\n ExecutionInterceptor,\n InterceptorContext,\n} from \"./interceptors/types\";\n\nconst logger = createLogger(\"plugin\");\n\n/**\n * Symbol used to expose the UserContext from an asUser() proxy.\n * Allows wrapWithAsUser in appkit.ts to retrieve the context and\n * wrap export methods in runInUserContext().\n */\nexport const USER_CONTEXT_SYMBOL = Symbol(\"appkit.userContext\");\n\n/**\n * OTel context key for marking OBO dev mode fallback.\n * Set when asUser() is called in development mode without a user token.\n */\nconst DEV_OBO_FALLBACK_KEY = createContextKey(\"appkit.devOboFallback\");\n\n/**\n * Returns true if the current execution is an OBO dev mode fallback\n * (asUser() was called but fell back to service principal due to missing token).\n */\nexport function isDevOboFallback(): boolean {\n return otelContext.active().getValue(DEV_OBO_FALLBACK_KEY) === true;\n}\n\n/**\n * Narrow an unknown thrown value to an Error that carries a numeric\n * `statusCode` property (e.g. `ApiError` from `@databricks/sdk-experimental`).\n */\nfunction hasHttpStatusCode(\n error: unknown,\n): error is Error & { statusCode: number } {\n return (\n error instanceof Error &&\n \"statusCode\" in error &&\n typeof (error as Record<string, unknown>).statusCode === \"number\"\n );\n}\n\n/**\n * Methods that should not be proxied by asUser().\n * These are lifecycle/internal methods that don't make sense\n * to execute in a user context.\n */\nconst EXCLUDED_FROM_PROXY = new Set([\n // Lifecycle methods\n \"setup\",\n \"shutdown\",\n \"attachContext\",\n \"injectRoutes\",\n \"getEndpoints\",\n \"getSkipBodyParsingPaths\",\n \"abortActiveOperations\",\n \"clientConfig\",\n // asUser itself - prevent chaining like .asUser().asUser()\n \"asUser\",\n // Internal methods\n \"constructor\",\n]);\n\n/**\n * Base abstract class for creating AppKit plugins.\n *\n * All plugins must declare a static `manifest` property with their metadata\n * and resource requirements. The manifest defines:\n * - `required` resources: Always needed for the plugin to function\n * - `optional` resources: May be needed depending on plugin configuration\n *\n * ## Static vs Runtime Resource Requirements\n *\n * The manifest is static and doesn't know the plugin's runtime configuration.\n * For resources that become required based on config options, plugins can\n * implement a static `getResourceRequirements(config)` method.\n *\n * At runtime, this method is called with the actual config to determine\n * which \"optional\" resources should be treated as \"required\".\n *\n * @example Basic plugin with static requirements\n * ```typescript\n * import { Plugin, toPlugin, PluginManifest, ResourceType } from '@databricks/appkit';\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * displayName: 'My Plugin',\n * description: 'Does something awesome',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: []\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest;\n * }\n * ```\n *\n * @example Plugin with config-dependent resources\n * ```typescript\n * interface MyConfig extends BasePluginConfig {\n * enableCaching?: boolean;\n * }\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: [\n * // Database is optional in the static manifest\n * { type: ResourceType.DATABASE, alias: 'cache', description: 'Required if caching enabled', ... }\n * ]\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest<\"myPlugin\">;\n *\n * // Runtime method: converts optional resources to required based on config\n * static getResourceRequirements(config: MyConfig) {\n * const resources = [];\n * if (config.enableCaching) {\n * // When caching is enabled, Database becomes required\n * resources.push({\n * type: ResourceType.DATABASE,\n * alias: 'cache',\n * resourceKey: 'database',\n * description: 'Cache storage for query results',\n * permission: 'CAN_CONNECT_AND_CREATE',\n * fields: {\n * instance_name: { env: 'DATABRICKS_CACHE_INSTANCE' },\n * database_name: { env: 'DATABRICKS_CACHE_DB' },\n * },\n * required: true // Mark as required at runtime\n * });\n * }\n * return resources;\n * }\n * }\n * ```\n */\nexport abstract class Plugin<\n TConfig extends BasePluginConfig = BasePluginConfig,\n> implements BasePlugin\n{\n protected isReady = false;\n protected cache!: CacheManager;\n protected app: AppManager;\n protected devFileReader: DevFileReader;\n protected streamManager: StreamManager;\n protected telemetry!: ITelemetry;\n protected context?: PluginContext;\n\n /** Registered endpoints for this plugin */\n private registeredEndpoints: PluginEndpointMap = {};\n\n /** Paths that opt out of JSON body parsing (e.g. file upload routes) */\n private skipBodyParsingPaths: Set<string> = new Set();\n\n /**\n * Plugin initialization phase.\n * - 'core': Initialized first (e.g., config plugins)\n * - 'normal': Initialized second (most plugins)\n * - 'deferred': Initialized last (e.g., server plugin)\n */\n static phase: PluginPhase = \"normal\";\n\n /**\n * Plugin name identifier.\n */\n name: string;\n\n constructor(protected config: TConfig) {\n this.name =\n config.name ??\n (this.constructor as { manifest?: { name: string } }).manifest?.name ??\n \"plugin\";\n this.streamManager = new StreamManager();\n this.app = new AppManager();\n this.devFileReader = DevFileReader.getInstance();\n this.context = (config as Record<string, unknown>).context as\n | PluginContext\n | undefined;\n\n // Eagerly bind telemetry + cache if the core services have already been\n // initialized (normal createApp path, or tests that mock CacheManager).\n // If they haven't, we leave these undefined and rely on `attachContext`\n // being called later — this lets factories eagerly construct plugin\n // instances at module top-level before `createApp` has run.\n this.tryAttachContext();\n }\n\n private tryAttachContext(): void {\n try {\n this.cache = CacheManager.getInstanceSync();\n } catch {\n return;\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.isReady = true;\n }\n\n /**\n * Binds runtime dependencies (telemetry provider, cache, plugin context) to\n * this plugin. Called by `AppKit._createApp` after construction and before\n * `setup()`. Idempotent: safe to call if the constructor already bound them\n * eagerly. Kept separate so factories can eagerly construct plugin instances\n * without running this before `TelemetryManager.initialize()` /\n * `CacheManager.getInstance()` have run.\n */\n attachContext(\n deps: {\n context?: unknown;\n telemetryConfig?: BasePluginConfig[\"telemetry\"];\n } = {},\n ): void {\n if (!this.cache) {\n this.cache = CacheManager.getInstanceSync();\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n deps.telemetryConfig ?? this.config.telemetry,\n );\n if (deps.context !== undefined) {\n this.context = deps.context as PluginContext;\n }\n this.isReady = true;\n }\n\n injectRoutes(_: express.Router) {\n return;\n }\n\n async setup() {}\n\n getEndpoints(): PluginEndpointMap {\n return this.registeredEndpoints;\n }\n\n getSkipBodyParsingPaths(): ReadonlySet<string> {\n return this.skipBodyParsingPaths;\n }\n\n abortActiveOperations(): void {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for this plugin.\n * Override this to define a custom public API.\n * By default, returns an empty object.\n *\n * The returned object becomes the plugin's public API on the AppKit instance\n * (e.g. `appkit.myPlugin.method()`). AppKit automatically binds method context\n * and adds `asUser(req)` for user-scoped execution.\n *\n * @example\n * ```ts\n * class MyPlugin extends Plugin {\n * private getData() { return []; }\n *\n * exports() {\n * return { getData: this.getData };\n * }\n * }\n *\n * // After registration:\n * const appkit = await createApp({ plugins: [myPlugin()] });\n * appkit.myPlugin.getData();\n * ```\n */\n exports(): unknown {\n return {};\n }\n\n /**\n * Returns startup config to expose to the client.\n * Override this to surface server-side values that are safe to publish to the\n * frontend, such as feature flags, resource IDs, or other app boot settings.\n *\n * This runs once when the server starts, so it should not depend on\n * request-scoped or user-specific state.\n *\n * String values that match non-public environment variables are redacted\n * unless you intentionally expose them via a matching `PUBLIC_APPKIT_` env var.\n *\n * Values must be JSON-serializable plain data (no functions, Dates, classes,\n * Maps, Sets, BigInts, or circular references).\n * By default returns an empty object (plugin contributes nothing to client config).\n *\n * On the client, read the config with the `usePluginClientConfig` hook\n * (React) or the `getPluginClientConfig` function (vanilla JS), both\n * from `@databricks/appkit-ui`.\n *\n * @example\n * ```ts\n * // Server — plugin definition\n * class MyPlugin extends Plugin<MyConfig> {\n * clientConfig() {\n * return {\n * warehouseId: this.config.warehouseId,\n * features: { darkMode: true },\n * };\n * }\n * }\n *\n * // Client — React component\n * import { usePluginClientConfig } from \"@databricks/appkit-ui/react\";\n *\n * interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }\n *\n * const config = usePluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * config.warehouseId; // \"abc-123\"\n *\n * // Client — vanilla JS\n * import { getPluginClientConfig } from \"@databricks/appkit-ui/js\";\n *\n * const config = getPluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * ```\n */\n clientConfig(): Record<string, unknown> {\n return {};\n }\n\n /**\n * Resolve the effective user ID from a request.\n *\n * Returns the `x-forwarded-user` header when present. In development mode\n * (`NODE_ENV=development`) falls back to the current context user ID so\n * that callers outside an active `runInUserContext` scope still get a\n * consistent value.\n *\n * @throws AuthenticationError in production when no user header is present.\n */\n protected resolveUserId(req: express.Request): string {\n const userId = req.header(\"x-forwarded-user\");\n if (userId) return userId;\n if (process.env.NODE_ENV === \"development\") return getCurrentUserId();\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-user header. Cannot resolve user ID.\",\n );\n }\n\n /**\n * Execute operations using the user's identity from the request.\n * Returns a proxy of this plugin where all method calls execute\n * with the user's Databricks credentials instead of the service principal.\n *\n * @param req - The Express request containing the user token in headers\n * @returns A proxied plugin instance that executes as the user\n * @throws AuthenticationError if user token is not available in request headers (production only).\n * In development mode (`NODE_ENV=development`), skips user impersonation instead of throwing.\n */\n asUser(req: express.Request): this {\n const token = req.header(\"x-forwarded-access-token\");\n const userId = req.header(\"x-forwarded-user\");\n const userEmail = req.header(\"x-forwarded-email\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n // In local development, skip user impersonation\n // since there's no user token available\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n\n // Return a proxy that marks execution as OBO dev fallback via OTel context,\n // so telemetry spans can distinguish intended OBO calls from regular SP calls\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n const value = Reflect.get(target, prop, receiver);\n if (typeof value !== \"function\") return value;\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop))\n return value;\n\n return (...args: unknown[]) => {\n const ctx = otelContext\n .active()\n .setValue(DEV_OBO_FALLBACK_KEY, true);\n return otelContext.with(ctx, () => value.apply(target, args));\n };\n },\n }) as this;\n }\n\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n if (!userId && !isDev) {\n throw AuthenticationError.missingUserId();\n }\n\n const effectiveUserId = userId || \"dev-user\";\n\n const userContext = ServiceContext.createUserContext(\n token,\n effectiveUserId,\n undefined,\n userEmail ?? undefined,\n );\n\n // Return a proxy that wraps method calls in user context\n return this._createUserContextProxy(userContext);\n }\n\n /**\n * Creates a proxy that wraps method calls in a user context.\n * This allows all plugin methods to automatically use the user's\n * Databricks credentials.\n */\n private _createUserContextProxy(userContext: UserContext): this {\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n // Expose userContext via symbol so wrapWithAsUser can wrap exports\n if (prop === USER_CONTEXT_SYMBOL) return userContext;\n\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof value !== \"function\") {\n return value;\n }\n\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop)) {\n return value;\n }\n\n return (...args: unknown[]) => {\n return runInUserContext(userContext, () => value.apply(target, args));\n };\n },\n }) as this;\n }\n\n // streaming execution with interceptors\n protected async executeStream<T>(\n res: IAppResponse,\n fn: StreamExecuteHandler<T>,\n options: StreamExecutionSettings,\n userKey?: string,\n ) {\n // destructure options\n const {\n stream: streamConfig,\n default: defaultConfig,\n user: userConfig,\n } = options;\n\n // build execution options\n const executeConfig = this._buildExecutionConfig({\n default: defaultConfig,\n user: userConfig,\n });\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const self = this;\n // capture the active OTel context (HTTP span) before entering the async generator,\n // where it would otherwise be lost across the async boundary\n const parentOtelContext = otelContext.active();\n\n // wrapper function to ensure it returns a generator\n const asyncWrapperFn = async function* (streamSignal?: AbortSignal) {\n // build execution context\n const context: InterceptorContext = {\n signal: streamSignal,\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n // build interceptors\n const interceptors = self._buildInterceptors(executeConfig);\n\n // wrap the function to ensure it returns a promise\n const wrappedFn = async () => {\n const result = await fn(context.signal);\n return result;\n };\n\n // execute the function with interceptors, restoring the parent OTel context\n // so telemetry spans are linked as children of the HTTP request span\n const result = await otelContext.with(parentOtelContext, () =>\n self._executeWithInterceptors(\n wrappedFn as (signal?: AbortSignal) => Promise<T>,\n interceptors,\n context,\n ),\n );\n\n // check if result is a generator\n if (self._checkIfGenerator(result)) {\n yield* result;\n } else {\n yield result;\n }\n };\n\n // stream the result to the client. The effective user key is forwarded\n // to the stream manager so that reconnections to existing streamIds are\n // bound to the original creator (prevents cross-user stream takeover via\n // guessed/leaked IDs).\n await this.streamManager.stream(\n res,\n asyncWrapperFn,\n streamConfig,\n effectiveUserKey,\n );\n }\n\n /**\n * Execute a function with the plugin's interceptor chain.\n *\n * Returns an {@link ExecutionResult} discriminated union:\n * - `{ ok: true, data: T }` on success\n * - `{ ok: false, status: number, message: string }` on failure\n *\n * Errors are never thrown — the method is production-safe.\n */\n protected async execute<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n options: PluginExecutionSettings,\n userKey?: string,\n ): Promise<ExecutionResult<T>> {\n const executeConfig = this._buildExecutionConfig(options);\n\n const interceptors = this._buildInterceptors(executeConfig);\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const context: InterceptorContext = {\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n try {\n const data = await this._executeWithInterceptors(\n fn,\n interceptors,\n context,\n );\n return { ok: true, data };\n } catch (error) {\n logger.error(\"Plugin execution failed\", { error, plugin: this.name });\n\n if (error instanceof AppKitError) {\n return {\n ok: false,\n status: error.statusCode,\n message: error.message,\n };\n }\n\n if (hasHttpStatusCode(error)) {\n const isDev = process.env.NODE_ENV !== \"production\";\n const isClientError = error.statusCode >= 400 && error.statusCode < 500;\n return {\n ok: false,\n status: error.statusCode,\n message: isDev || isClientError ? error.message : \"Server error\",\n };\n }\n\n const isDev = process.env.NODE_ENV !== \"production\";\n return {\n ok: false,\n status: 500,\n message:\n isDev && error instanceof Error ? error.message : \"Server error\",\n };\n }\n }\n\n protected registerEndpoint(name: string, path: string): void {\n this.registeredEndpoints[name] = path;\n }\n\n protected route<_TResponse>(\n router: express.Router,\n config: RouteConfig,\n ): void {\n const { name, method, path, handler } = config;\n\n router[method](path, handler);\n\n const fullPath = `/api/${this.name}${path}`;\n this.registerEndpoint(name, fullPath);\n\n if (config.skipBodyParsing) {\n this.skipBodyParsingPaths.add(fullPath);\n }\n }\n\n // build execution options by merging defaults, plugin config, and user overrides\n private _buildExecutionConfig(\n options: PluginExecutionSettings,\n ): PluginExecuteConfig {\n const { default: methodDefaults, user: userOverride } = options;\n\n // Merge: method defaults <- plugin config <- user override (highest priority)\n return deepMerge(\n deepMerge(methodDefaults, this.config),\n userOverride ?? {},\n ) as PluginExecuteConfig;\n }\n\n // build interceptors based on execute options\n private _buildInterceptors(\n options: PluginExecuteConfig,\n ): ExecutionInterceptor[] {\n const interceptors: ExecutionInterceptor[] = [];\n\n // order matters: telemetry → timeout → retry → cache (innermost to outermost)\n\n const telemetryConfig = normalizeTelemetryOptions(this.config.telemetry);\n if (\n telemetryConfig.traces &&\n (options.telemetryInterceptor?.enabled ?? true)\n ) {\n interceptors.push(\n new TelemetryInterceptor(this.telemetry, options.telemetryInterceptor),\n );\n }\n\n if (options.timeout && options.timeout > 0) {\n interceptors.push(new TimeoutInterceptor(options.timeout));\n }\n\n if (\n options.retry?.enabled &&\n options.retry.attempts &&\n options.retry.attempts > 1\n ) {\n interceptors.push(new RetryInterceptor(options.retry));\n }\n\n if (options.cache?.enabled && options.cache.cacheKey?.length) {\n interceptors.push(new CacheInterceptor(this.cache, options.cache));\n }\n\n return interceptors;\n }\n\n // execute method wrapped with interceptors\n private async _executeWithInterceptors<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n interceptors: ExecutionInterceptor[],\n context: InterceptorContext,\n ): Promise<T> {\n // no interceptors, execute directly\n if (interceptors.length === 0) {\n return fn(context.signal);\n }\n // build nested execution chain from interceptors\n let wrappedFn = () => fn(context.signal);\n\n // wrap each interceptor around the previous function\n for (const interceptor of interceptors) {\n const previousFn = wrappedFn;\n wrappedFn = () => interceptor.intercept(previousFn, context);\n }\n\n return wrappedFn();\n }\n\n private _checkIfGenerator(\n result: any,\n ): result is AsyncGenerator<any, void, unknown> {\n return (\n result && typeof result === \"object\" && Symbol.asyncIterator in result\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","names":["otelContext","context"],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":["import { createContextKey, context as otelContext } from \"@opentelemetry/api\";\nimport type express from \"express\";\nimport type {\n BasePlugin,\n BasePluginConfig,\n IAppResponse,\n PluginEndpointMap,\n PluginExecuteConfig,\n PluginExecutionSettings,\n PluginPhase,\n RouteConfig,\n StreamExecuteHandler,\n StreamExecutionSettings,\n} from \"shared\";\nimport { AppManager } from \"../app\";\nimport { CacheManager } from \"../cache\";\nimport { getCurrentUserId, runInUserContext, ServiceContext } from \"../context\";\nimport type { PluginContext } from \"../core/plugin-context\";\nimport { AppKitError, AuthenticationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { StreamManager } from \"../stream\";\nimport {\n type ITelemetry,\n normalizeTelemetryOptions,\n TelemetryManager,\n} from \"../telemetry\";\nimport { deepMerge } from \"../utils\";\nimport { DevFileReader } from \"./dev-reader\";\nimport type { ExecutionResult } from \"./execution-result\";\nimport { CacheInterceptor } from \"./interceptors/cache\";\nimport { RetryInterceptor } from \"./interceptors/retry\";\nimport { TelemetryInterceptor } from \"./interceptors/telemetry\";\nimport { TimeoutInterceptor } from \"./interceptors/timeout\";\nimport type {\n ExecutionInterceptor,\n InterceptorContext,\n} from \"./interceptors/types\";\n\nconst logger = createLogger(\"plugin\");\n\n/**\n * OTel context key for marking OBO dev mode fallback.\n * Set when asUser() is called in development mode without a user token.\n */\nconst DEV_OBO_FALLBACK_KEY = createContextKey(\"appkit.devOboFallback\");\n\n/**\n * Returns true if `value` is a plain object literal (not an array, Date,\n * class instance, etc.). Used to decide whether to recurse into nested\n * export shapes when wrapping functions.\n *\n * @internal exported so the AppKit core can reuse the same predicate for\n * its `bindExportMethods` walk; not part of the public package surface.\n */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Returns a deep copy of `exports` where every function has been replaced\n * with `wrap(fn)`, walking into nested plain objects.\n *\n * Used by the asUser proxy to make the user context follow function\n * references that escape the proxy via `exports()`. The original input is\n * not mutated, so plugins that memoize `exports()` are safe — each call\n * through the proxy yields an independent, freshly wrapped view.\n */\nfunction wrapExportFunctions(\n exports: Record<string, unknown>,\n wrap: (fn: (...a: unknown[]) => unknown) => (...a: unknown[]) => unknown,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(exports)) {\n const val = exports[key];\n if (typeof val === \"function\") {\n result[key] = wrap(val as (...a: unknown[]) => unknown);\n } else if (isPlainObject(val)) {\n result[key] = wrapExportFunctions(val, wrap);\n } else {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * Returns true if the current execution is an OBO dev mode fallback\n * (asUser() was called but fell back to service principal due to missing token).\n */\nexport function isDevOboFallback(): boolean {\n return otelContext.active().getValue(DEV_OBO_FALLBACK_KEY) === true;\n}\n\n/**\n * Narrow an unknown thrown value to an Error that carries a numeric\n * `statusCode` property (e.g. `ApiError` from `@databricks/sdk-experimental`).\n */\nfunction hasHttpStatusCode(\n error: unknown,\n): error is Error & { statusCode: number } {\n return (\n error instanceof Error &&\n \"statusCode\" in error &&\n typeof (error as Record<string, unknown>).statusCode === \"number\"\n );\n}\n\n/**\n * Methods that should not be proxied by asUser().\n * These are lifecycle/internal methods that don't make sense\n * to execute in a user context.\n */\nconst EXCLUDED_FROM_PROXY = new Set([\n // Lifecycle methods\n \"setup\",\n \"shutdown\",\n \"attachContext\",\n \"injectRoutes\",\n \"getEndpoints\",\n \"getSkipBodyParsingPaths\",\n \"abortActiveOperations\",\n \"clientConfig\",\n // asUser itself - prevent chaining like .asUser().asUser()\n \"asUser\",\n // Internal methods\n \"constructor\",\n]);\n\n/**\n * Base abstract class for creating AppKit plugins.\n *\n * All plugins must declare a static `manifest` property with their metadata\n * and resource requirements. The manifest defines:\n * - `required` resources: Always needed for the plugin to function\n * - `optional` resources: May be needed depending on plugin configuration\n *\n * ## Static vs Runtime Resource Requirements\n *\n * The manifest is static and doesn't know the plugin's runtime configuration.\n * For resources that become required based on config options, plugins can\n * implement a static `getResourceRequirements(config)` method.\n *\n * At runtime, this method is called with the actual config to determine\n * which \"optional\" resources should be treated as \"required\".\n *\n * @example Basic plugin with static requirements\n * ```typescript\n * import { Plugin, toPlugin, PluginManifest, ResourceType } from '@databricks/appkit';\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * displayName: 'My Plugin',\n * description: 'Does something awesome',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: []\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest;\n * }\n * ```\n *\n * @example Plugin with config-dependent resources\n * ```typescript\n * interface MyConfig extends BasePluginConfig {\n * enableCaching?: boolean;\n * }\n *\n * const myManifest: PluginManifest = {\n * name: 'myPlugin',\n * resources: {\n * required: [\n * { type: ResourceType.SQL_WAREHOUSE, alias: 'warehouse', ... }\n * ],\n * optional: [\n * // Database is optional in the static manifest\n * { type: ResourceType.DATABASE, alias: 'cache', description: 'Required if caching enabled', ... }\n * ]\n * }\n * };\n *\n * class MyPlugin extends Plugin<MyConfig> {\n * static manifest = myManifest<\"myPlugin\">;\n *\n * // Runtime method: converts optional resources to required based on config\n * static getResourceRequirements(config: MyConfig) {\n * const resources = [];\n * if (config.enableCaching) {\n * // When caching is enabled, Database becomes required\n * resources.push({\n * type: ResourceType.DATABASE,\n * alias: 'cache',\n * resourceKey: 'database',\n * description: 'Cache storage for query results',\n * permission: 'CAN_CONNECT_AND_CREATE',\n * fields: {\n * instance_name: { env: 'DATABRICKS_CACHE_INSTANCE' },\n * database_name: { env: 'DATABRICKS_CACHE_DB' },\n * },\n * required: true // Mark as required at runtime\n * });\n * }\n * return resources;\n * }\n * }\n * ```\n */\nexport abstract class Plugin<\n TConfig extends BasePluginConfig = BasePluginConfig,\n> implements BasePlugin\n{\n protected isReady = false;\n protected cache!: CacheManager;\n protected app: AppManager;\n protected devFileReader: DevFileReader;\n protected streamManager: StreamManager;\n protected telemetry!: ITelemetry;\n protected context?: PluginContext;\n\n /** Registered endpoints for this plugin */\n private registeredEndpoints: PluginEndpointMap = {};\n\n /** Paths that opt out of JSON body parsing (e.g. file upload routes) */\n private skipBodyParsingPaths: Set<string> = new Set();\n\n /**\n * Plugin initialization phase.\n * - 'core': Initialized first (e.g., config plugins)\n * - 'normal': Initialized second (most plugins)\n * - 'deferred': Initialized last (e.g., server plugin)\n */\n static phase: PluginPhase = \"normal\";\n\n /**\n * Plugin name identifier.\n */\n name: string;\n\n constructor(protected config: TConfig) {\n this.name =\n config.name ??\n (this.constructor as { manifest?: { name: string } }).manifest?.name ??\n \"plugin\";\n this.streamManager = new StreamManager();\n this.app = new AppManager();\n this.devFileReader = DevFileReader.getInstance();\n this.context = (config as Record<string, unknown>).context as\n | PluginContext\n | undefined;\n\n // Eagerly bind telemetry + cache if the core services have already been\n // initialized (normal createApp path, or tests that mock CacheManager).\n // If they haven't, we leave these undefined and rely on `attachContext`\n // being called later — this lets factories eagerly construct plugin\n // instances at module top-level before `createApp` has run.\n this.tryAttachContext();\n }\n\n private tryAttachContext(): void {\n try {\n this.cache = CacheManager.getInstanceSync();\n } catch {\n return;\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.isReady = true;\n }\n\n /**\n * Binds runtime dependencies (telemetry provider, cache, plugin context) to\n * this plugin. Called by `AppKit._createApp` after construction and before\n * `setup()`. Idempotent: safe to call if the constructor already bound them\n * eagerly. Kept separate so factories can eagerly construct plugin instances\n * without running this before `TelemetryManager.initialize()` /\n * `CacheManager.getInstance()` have run.\n */\n attachContext(\n deps: {\n context?: unknown;\n telemetryConfig?: BasePluginConfig[\"telemetry\"];\n } = {},\n ): void {\n if (!this.cache) {\n this.cache = CacheManager.getInstanceSync();\n }\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n deps.telemetryConfig ?? this.config.telemetry,\n );\n if (deps.context !== undefined) {\n this.context = deps.context as PluginContext;\n }\n this.isReady = true;\n }\n\n injectRoutes(_: express.Router) {\n return;\n }\n\n async setup() {}\n\n getEndpoints(): PluginEndpointMap {\n return this.registeredEndpoints;\n }\n\n getSkipBodyParsingPaths(): ReadonlySet<string> {\n return this.skipBodyParsingPaths;\n }\n\n abortActiveOperations(): void {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for this plugin.\n * Override this to define a custom public API.\n * By default, returns an empty object.\n *\n * The returned object becomes the plugin's public API on the AppKit instance\n * (e.g. `appkit.myPlugin.method()`). AppKit automatically binds method context\n * and adds `asUser(req)` for user-scoped execution.\n *\n * @example\n * ```ts\n * class MyPlugin extends Plugin {\n * private getData() { return []; }\n *\n * exports() {\n * return { getData: this.getData };\n * }\n * }\n *\n * // After registration:\n * const appkit = await createApp({ plugins: [myPlugin()] });\n * appkit.myPlugin.getData();\n * ```\n */\n exports(): unknown {\n return {};\n }\n\n /**\n * Returns startup config to expose to the client.\n * Override this to surface server-side values that are safe to publish to the\n * frontend, such as feature flags, resource IDs, or other app boot settings.\n *\n * This runs once when the server starts, so it should not depend on\n * request-scoped or user-specific state.\n *\n * String values that match non-public environment variables are redacted\n * unless you intentionally expose them via a matching `PUBLIC_APPKIT_` env var.\n *\n * Values must be JSON-serializable plain data (no functions, Dates, classes,\n * Maps, Sets, BigInts, or circular references).\n * By default returns an empty object (plugin contributes nothing to client config).\n *\n * On the client, read the config with the `usePluginClientConfig` hook\n * (React) or the `getPluginClientConfig` function (vanilla JS), both\n * from `@databricks/appkit-ui`.\n *\n * @example\n * ```ts\n * // Server — plugin definition\n * class MyPlugin extends Plugin<MyConfig> {\n * clientConfig() {\n * return {\n * warehouseId: this.config.warehouseId,\n * features: { darkMode: true },\n * };\n * }\n * }\n *\n * // Client — React component\n * import { usePluginClientConfig } from \"@databricks/appkit-ui/react\";\n *\n * interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }\n *\n * const config = usePluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * config.warehouseId; // \"abc-123\"\n *\n * // Client — vanilla JS\n * import { getPluginClientConfig } from \"@databricks/appkit-ui/js\";\n *\n * const config = getPluginClientConfig<MyPluginConfig>(\"myPlugin\");\n * ```\n */\n clientConfig(): Record<string, unknown> {\n return {};\n }\n\n /**\n * Resolve the effective user ID from a request.\n *\n * Returns the `x-forwarded-user` header when present. In development mode\n * (`NODE_ENV=development`) falls back to the current context user ID so\n * that callers outside an active `runInUserContext` scope still get a\n * consistent value.\n *\n * @throws AuthenticationError in production when no user header is present.\n */\n protected resolveUserId(req: express.Request): string {\n const userId = req.header(\"x-forwarded-user\");\n if (userId) return userId;\n if (process.env.NODE_ENV === \"development\") return getCurrentUserId();\n throw AuthenticationError.missingToken(\n \"Missing x-forwarded-user header. Cannot resolve user ID.\",\n );\n }\n\n /**\n * Execute operations using the user's identity from the request.\n * Returns a proxy of this plugin where all method calls execute\n * with the user's Databricks credentials instead of the service principal.\n *\n * @param req - The Express request containing the user token in headers\n * @returns A proxied plugin instance that executes as the user\n * @throws AuthenticationError if user token is not available in request headers (production only).\n * In development mode (`NODE_ENV=development`), skips user impersonation instead of throwing.\n */\n asUser(req: express.Request): this {\n const token = req.header(\"x-forwarded-access-token\");\n const userId = req.header(\"x-forwarded-user\");\n const userEmail = req.header(\"x-forwarded-email\");\n const isDev = process.env.NODE_ENV === \"development\";\n\n // In local development, skip user impersonation since there's no user\n // token available. Mark execution as OBO dev fallback via OTel context\n // so telemetry can distinguish intended OBO calls from regular SP calls.\n if (!token && isDev) {\n logger.warn(\n \"asUser() called without user token in development mode. Skipping user impersonation.\",\n );\n\n return this._createAsUserProxy((fn) => (...args) => {\n const ctx = otelContext.active().setValue(DEV_OBO_FALLBACK_KEY, true);\n return otelContext.with(ctx, () => fn(...args));\n });\n }\n\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n if (!userId && !isDev) {\n throw AuthenticationError.missingUserId();\n }\n\n const effectiveUserId = userId || \"dev-user\";\n\n const userContext = ServiceContext.createUserContext(\n token,\n effectiveUserId,\n undefined,\n userEmail ?? undefined,\n );\n\n return this._createAsUserProxy(\n (fn) =>\n (...args) =>\n runInUserContext(userContext, () => fn(...args)),\n );\n }\n\n /**\n * Creates a proxy of `this` where every method call — and every function\n * in the result of `exports()` — runs inside `wrapCall`.\n *\n * `wrapCall` decides the per-call scope. Two strategies are used today:\n * - real OBO: fn => (...args) => runInUserContext(userContext, () => fn(...args))\n * - dev fallback: fn => (...args) => otelContext.with(DEV_OBO_FALLBACK_KEY=true, () => fn(...args))\n *\n * `exports` is intercepted because methods captured in the returned\n * exports object never re-enter the proxy's `get` trap. Wrapping them\n * here is the only way to make the user context follow function\n * references back out of the plugin.\n */\n private _createAsUserProxy(\n wrapCall: (\n fn: (...a: unknown[]) => unknown,\n ) => (...a: unknown[]) => unknown,\n ): this {\n return new Proxy(this, {\n get: (target, prop, receiver) => {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof value !== \"function\") return value;\n if (typeof prop === \"string\" && EXCLUDED_FROM_PROXY.has(prop))\n return value;\n\n if (prop === \"exports\") {\n return () => {\n const raw = (value as () => unknown).call(target);\n if (raw == null) return {};\n // Callable exports (e.g. files, jobs) manage per-call asUser\n // themselves; leave them untouched.\n if (typeof raw === \"function\") return raw;\n if (isPlainObject(raw)) {\n return wrapExportFunctions(raw, wrapCall);\n }\n return raw;\n };\n }\n\n const fn = (value as (...a: unknown[]) => unknown).bind(target);\n return wrapCall(fn);\n },\n }) as this;\n }\n\n // streaming execution with interceptors\n protected async executeStream<T>(\n res: IAppResponse,\n fn: StreamExecuteHandler<T>,\n options: StreamExecutionSettings,\n userKey?: string,\n ) {\n // destructure options\n const {\n stream: streamConfig,\n default: defaultConfig,\n user: userConfig,\n } = options;\n\n // build execution options\n const executeConfig = this._buildExecutionConfig({\n default: defaultConfig,\n user: userConfig,\n });\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const self = this;\n // capture the active OTel context (HTTP span) before entering the async generator,\n // where it would otherwise be lost across the async boundary\n const parentOtelContext = otelContext.active();\n\n // wrapper function to ensure it returns a generator\n const asyncWrapperFn = async function* (streamSignal?: AbortSignal) {\n // build execution context\n const context: InterceptorContext = {\n signal: streamSignal,\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n // build interceptors\n const interceptors = self._buildInterceptors(executeConfig);\n\n // wrap the function to ensure it returns a promise\n const wrappedFn = async () => {\n const result = await fn(context.signal);\n return result;\n };\n\n // execute the function with interceptors, restoring the parent OTel context\n // so telemetry spans are linked as children of the HTTP request span\n const result = await otelContext.with(parentOtelContext, () =>\n self._executeWithInterceptors(\n wrappedFn as (signal?: AbortSignal) => Promise<T>,\n interceptors,\n context,\n ),\n );\n\n // check if result is a generator\n if (self._checkIfGenerator(result)) {\n yield* result;\n } else {\n yield result;\n }\n };\n\n // stream the result to the client. The effective user key is forwarded\n // to the stream manager so that reconnections to existing streamIds are\n // bound to the original creator (prevents cross-user stream takeover via\n // guessed/leaked IDs).\n await this.streamManager.stream(\n res,\n asyncWrapperFn,\n streamConfig,\n effectiveUserKey,\n );\n }\n\n /**\n * Execute a function with the plugin's interceptor chain.\n *\n * Returns an {@link ExecutionResult} discriminated union:\n * - `{ ok: true, data: T }` on success\n * - `{ ok: false, status: number, message: string }` on failure\n *\n * Errors are never thrown — the method is production-safe.\n */\n protected async execute<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n options: PluginExecutionSettings,\n userKey?: string,\n ): Promise<ExecutionResult<T>> {\n const executeConfig = this._buildExecutionConfig(options);\n\n const interceptors = this._buildInterceptors(executeConfig);\n\n // get user key from context if not provided\n const effectiveUserKey = userKey ?? getCurrentUserId();\n\n const context: InterceptorContext = {\n metadata: new Map(),\n userKey: effectiveUserKey,\n };\n\n try {\n const data = await this._executeWithInterceptors(\n fn,\n interceptors,\n context,\n );\n return { ok: true, data };\n } catch (error) {\n logger.error(\"Plugin execution failed\", { error, plugin: this.name });\n\n if (error instanceof AppKitError) {\n return {\n ok: false,\n status: error.statusCode,\n message: error.message,\n };\n }\n\n if (hasHttpStatusCode(error)) {\n const isDev = process.env.NODE_ENV !== \"production\";\n const isClientError = error.statusCode >= 400 && error.statusCode < 500;\n return {\n ok: false,\n status: error.statusCode,\n message: isDev || isClientError ? error.message : \"Server error\",\n };\n }\n\n const isDev = process.env.NODE_ENV !== \"production\";\n return {\n ok: false,\n status: 500,\n message:\n isDev && error instanceof Error ? error.message : \"Server error\",\n };\n }\n }\n\n protected registerEndpoint(name: string, path: string): void {\n this.registeredEndpoints[name] = path;\n }\n\n protected route<_TResponse>(\n router: express.Router,\n config: RouteConfig,\n ): void {\n const { name, method, path, handler } = config;\n\n router[method](path, handler);\n\n const fullPath = `/api/${this.name}${path}`;\n this.registerEndpoint(name, fullPath);\n\n if (config.skipBodyParsing) {\n this.skipBodyParsingPaths.add(fullPath);\n }\n }\n\n // build execution options by merging defaults, plugin config, and user overrides\n private _buildExecutionConfig(\n options: PluginExecutionSettings,\n ): PluginExecuteConfig {\n const { default: methodDefaults, user: userOverride } = options;\n\n // Merge: method defaults <- plugin config <- user override (highest priority)\n return deepMerge(\n deepMerge(methodDefaults, this.config),\n userOverride ?? {},\n ) as PluginExecuteConfig;\n }\n\n // build interceptors based on execute options\n private _buildInterceptors(\n options: PluginExecuteConfig,\n ): ExecutionInterceptor[] {\n const interceptors: ExecutionInterceptor[] = [];\n\n // order matters: telemetry → timeout → retry → cache (innermost to outermost)\n\n const telemetryConfig = normalizeTelemetryOptions(this.config.telemetry);\n if (\n telemetryConfig.traces &&\n (options.telemetryInterceptor?.enabled ?? true)\n ) {\n interceptors.push(\n new TelemetryInterceptor(this.telemetry, options.telemetryInterceptor),\n );\n }\n\n if (options.timeout && options.timeout > 0) {\n interceptors.push(new TimeoutInterceptor(options.timeout));\n }\n\n if (\n options.retry?.enabled &&\n options.retry.attempts &&\n options.retry.attempts > 1\n ) {\n interceptors.push(new RetryInterceptor(options.retry));\n }\n\n if (options.cache?.enabled && options.cache.cacheKey?.length) {\n interceptors.push(new CacheInterceptor(this.cache, options.cache));\n }\n\n return interceptors;\n }\n\n // execute method wrapped with interceptors\n private async _executeWithInterceptors<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n interceptors: ExecutionInterceptor[],\n context: InterceptorContext,\n ): Promise<T> {\n // no interceptors, execute directly\n if (interceptors.length === 0) {\n return fn(context.signal);\n }\n // build nested execution chain from interceptors\n let wrappedFn = () => fn(context.signal);\n\n // wrap each interceptor around the previous function\n for (const interceptor of interceptors) {\n const previousFn = wrappedFn;\n wrappedFn = () => interceptor.intercept(previousFn, context);\n }\n\n return wrappedFn();\n }\n\n private _checkIfGenerator(\n result: any,\n ): result is AsyncGenerator<any, void, unknown> {\n return (\n result && typeof result === \"object\" && Symbol.asyncIterator in result\n );\n }\n}\n"],"mappings":"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"}
@@ -115,12 +115,64 @@ declare class AgentsPlugin extends Plugin implements ToolProvider {
115
115
  private connectHostedTools;
116
116
  getAgentTools(): AgentToolDefinition[];
117
117
  executeAgentTool(): Promise<unknown>;
118
- private mountInvocationsRoute;
118
+ /**
119
+ * Mount the non-streaming invoke endpoints outside the `/api/<plugin>`
120
+ * namespace. `/invocations` and `/responses` are aliases — both run the
121
+ * default agent to completion and return a single JSON response. Streaming
122
+ * lives on `POST /chat` (mounted in `injectRoutes`).
123
+ */
124
+ private mountInvokeRoutes;
119
125
  injectRoutes(router: IAppRouter): void;
120
126
  clientConfig(): Record<string, unknown>;
121
127
  private _handleChat;
122
- private _handleInvocations;
128
+ /**
129
+ * Returns the names of tools in `registered.toolIndex` whose annotations
130
+ * would trip the approval gate. Used by the non-streaming invoke path
131
+ * (`/invocations`, `/responses`) to fail-fast before the adapter runs:
132
+ * those endpoints have no channel back to the user mid-call, so an agent
133
+ * whose tool surface includes approval-gated tools cannot be served.
134
+ *
135
+ * Returns an empty list when the plugin is configured with
136
+ * `approval.requireForDestructive: false` — operators who explicitly
137
+ * disabled HITL keep the invoke surface unrestricted.
138
+ */
139
+ private collectApprovalRequiredToolNames;
140
+ /**
141
+ * Shared handler for `POST /invocations` and `POST /responses`. Runs the
142
+ * default agent to completion and returns a single JSON response in the
143
+ * OpenAI Responses non-streaming shape. The two endpoints are aliases —
144
+ * streaming clients must use `POST /chat`.
145
+ *
146
+ * Rejects with HTTP 400 when the resolved agent has any approval-gated
147
+ * tool in scope: HITL requires a live SSE channel, which this surface
148
+ * does not provide. See {@link collectApprovalRequiredToolNames}.
149
+ */
150
+ private _handleInvoke;
123
151
  private _streamAgent;
152
+ /**
153
+ * Non-streaming counterpart to {@link _streamAgent} used by `/invocations`
154
+ * and `/responses`. Drives the adapter to completion, persists the
155
+ * assistant turn to the thread store, and returns a single JSON envelope
156
+ * shaped like the OpenAI Responses non-streaming API.
157
+ *
158
+ * No `EventChannel`, no `AgentEventTranslator`, no SSE — the caller is
159
+ * waiting on one HTTP response. The approval gate is force-disabled in
160
+ * the per-run state as defense-in-depth: `_handleInvoke` already rejects
161
+ * up-front if any tool in scope would require approval, but pinning
162
+ * `requireForDestructive: false` here means a tool that somehow slips
163
+ * past the precheck (e.g. annotations mutated at runtime) still won't
164
+ * stall the request waiting for an approval prompt that no one can
165
+ * answer.
166
+ *
167
+ * The `RunState` shape is otherwise unchanged so {@link dispatchToolCall}
168
+ * — including sub-agent recursion via {@link runSubAgent} — keeps the
169
+ * same tool-call budget, abort signal, and timeout enforcement as the
170
+ * streaming path. A still-typed translator is constructed but only
171
+ * consulted for `finalize()` so any in-flight `approval_pending` event
172
+ * synthesis (which would have been a coding bug given the precheck) is
173
+ * a dropped no-op instead of a runtime crash.
174
+ */
175
+ private _runAgentNonStreaming;
124
176
  /**
125
177
  * Dispatch a single tool call from either the top-level adapter or a
126
178
  * sub-agent. Centralising this in one method is what makes the budget
@@ -169,7 +221,8 @@ declare class AgentsPlugin extends Plugin implements ToolProvider {
169
221
  /**
170
222
  * Plugin factory for the agents plugin. Reads `config/agents/*.md` by default,
171
223
  * resolves toolkits/tools from registered plugins, exposes `appkit.agents.*`
172
- * runtime API and mounts `/invocations`.
224
+ * runtime API and mounts `POST /invocations` and `POST /responses` (aliased
225
+ * non-streaming invoke endpoints) plus `POST /chat` (streaming, HITL-capable).
173
226
  *
174
227
  * @example
175
228
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","names":[],"sources":["../../../src/plugins/agents/agents.ts"],"mappings":";;;;;;;;;;cAkIa,YAAA,SAAqB,MAAA,YAAkB,YAAA;EAAA,OAC3C,QAAA,EAAuB,cAAA;EAAA,OACvB,KAAA,EAAO,WAAA;EAAA,UAEI,MAAA,EAAQ,kBAAA;EAAA,QAElB,MAAA;EAAA,QACA,gBAAA;EAAA,QACA,aAAA;EARgB;;;;;;EAAA,QAkBhB,gBAAA;EAAA,QACA,SAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;cAEI,MAAA,EAAQ,kBAAA;EAgqBJ;;;;;;;;;;EAAA,QAjoBR,oBAAA;EAAA,YAKI,sBAAA,CAAA;EA3DoB;EAAA,YAqFpB,cAAA,CAAA;EApFL;EAAA,QAuGC,gBAAA;EAtGD;;;;;;EAAA,QAgHC,WAAA;EAhGA;;;;;EAAA,QAiHA,aAAA;EAYF,KAAA,CAAA,GAAK,OAAA;EAzFH;;;;;;EAuGF,MAAA,CAAA,GAAU,OAAA;EAdL;;;;;EAAA,QAwCG,kBAAA;EAAA,QAmEN,iBAAA;EAAA,QAMM,mBAAA;EAiEA;;;;;;EAAA,QAvCN,mBAAA;EAAA,QAmBM,oBAAA;EAAA,QAoBA,cAAA;EAmTY;;;;;EAAA,QA7QZ,cAAA;EAgVE;;;;;;;;;;;EAAA,QAhPR,eAAA;EA01BA;;;;;;;;;;;;EAAA,QA9zBA,eAAA;EAAA,QAkBM,gBAAA;EAAA,QA0DA,kBAAA;EAiEd,aAAA,CAAA,GAAiB,mBAAA;EAIX,gBAAA,CAAA,GAAoB,OAAA;EAAA,QAMlB,qBAAA;EAWR,YAAA,CAAa,MAAA,EAAQ,UAAA;EAkDrB,YAAA,CAAA,GAAgB,MAAA;EAAA,QAOF,WAAA;EAAA,QAkEA,kBAAA;EAAA,QAqEA,YAAA;EA8jBH;;;;;;;;;;;EAAA,QArZG,gBAAA;EAqZG;;;;;;;;;;;;;;EAAA,QAxSH,WAAA;EAAA,QA2FA,aAAA;EAAA,QA2BA,cAAA;EAAA,QAuCA,kBAAA;EAAA,QASA,gBAAA;EAAA,QAUA,mBAAA;EAAA,QAaN,YAAA;EAAA,QASA,aAAA;EAgBF,QAAA,CAAA,GAAY,OAAA;EAQlB,OAAA,CAAA;6BAE2B,GAAA,EAAO,eAAA,KAAe,OAAA;;2BAG3B,eAAA;;;oCAGS,OAAA,CAAA,MAAA;EAAA;EAAA,QAIjB,iBAAA;AAAA;;;;;;;;;;;;;;;cA8DH,MAAA,EAAM,QAAA,QAAA,YAAA,EAAA,kBAAA"}
1
+ {"version":3,"file":"agents.d.ts","names":[],"sources":["../../../src/plugins/agents/agents.ts"],"mappings":";;;;;;;;;;cAmIa,YAAA,SAAqB,MAAA,YAAkB,YAAA;EAAA,OAC3C,QAAA,EAAuB,cAAA;EAAA,OACvB,KAAA,EAAO,WAAA;EAAA,UAEI,MAAA,EAAQ,kBAAA;EAAA,QAElB,MAAA;EAAA,QACA,gBAAA;EAAA,QACA,aAAA;EARgB;;;;;;EAAA,QAkBhB,gBAAA;EAAA,QACA,SAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;cAEI,MAAA,EAAQ,kBAAA;EAoqBJ;;;;;;;;;;EAAA,QAroBR,oBAAA;EAAA,YAKI,sBAAA,CAAA;EA3DoB;EAAA,YAqFpB,cAAA,CAAA;EApFL;EAAA,QAuGC,gBAAA;EAtGD;;;;;;EAAA,QAgHC,WAAA;EAhGA;;;;;EAAA,QAiHA,aAAA;EAYF,KAAA,CAAA,GAAK,OAAA;EAzFH;;;;;;EAuGF,MAAA,CAAA,GAAU,OAAA;EAdL;;;;;EAAA,QAwCG,kBAAA;EAAA,QAmEN,iBAAA;EAAA,QAMM,mBAAA;EAiEA;;;;;;EAAA,QAvCN,mBAAA;EAAA,QAmBM,oBAAA;EAAA,QAoBA,cAAA;EAmTY;;;;;EAAA,QA7QZ,cAAA;EAoVE;;;;;;;;;;;EAAA,QApPR,eAAA;EAkhCM;;;;;;;;;;;;EAAA,QAt/BN,eAAA;EAAA,QAkBM,gBAAA;EAAA,QA0DA,kBAAA;EAiEd,aAAA,CAAA,GAAiB,mBAAA;EAIX,gBAAA,CAAA,GAAoB,OAAA;;;;;;;UAYlB,iBAAA;EASR,YAAA,CAAa,MAAA,EAAQ,UAAA;EAkDrB,YAAA,CAAA,GAAgB,MAAA;EAAA,QAOF,WAAA;;;;;;;;;;;;UA6EN,gCAAA;;;;;;;;;;;UAuBM,aAAA;EAAA,QAoFA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;UAqLA,qBAAA;;;;;;;;;;;;UA6IA,gBAAA;;;;;;;;;;;;;;;UA6GA,WAAA;EAAA,QA2FA,aAAA;EAAA,QA2BA,cAAA;EAAA,QAuCA,kBAAA;EAAA,QASA,gBAAA;EAAA,QAUA,mBAAA;EAAA,QAaN,YAAA;EAAA,QASA,aAAA;EAgBF,QAAA,CAAA,GAAY,OAAA;EAQlB,OAAA,CAAA;6BAE2B,GAAA,EAAO,eAAA,KAAe,OAAA;;2BAG3B,eAAA;;;oCAGS,OAAA,CAAA,MAAA;EAAA;EAAA,QAIjB,iBAAA;AAAA;;;;;;;;;;;;;;;;cA+DH,MAAA,EAAM,QAAA,QAAA,YAAA,EAAA,kBAAA"}