@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
6,284 lines • 222 kB
JavaScript
// @bun
import {
require_ts_morph
} from "./chunk-np5wcwfv.js";
import {
BuiltInActions,
BuiltInWorkflows,
Errors,
Primitives,
setAdkCommand
} from "./chunk-dq2xpa24.js";
import {
ne
} from "./chunk-6w0knnta.js";
import {
Autonomous,
extractMissingRequiredFields
} from "./chunk-40x04ckt.js";
import {
require_semver
} from "./chunk-2a5b6azq.js";
import {
Uk
} from "./chunk-3xrpxgq4.js";
import {
require_src
} from "./chunk-g8mm42v1.js";
import {
__require,
__toESM
} from "./chunk-dhs2bg35.js";
// ../adk/dist/dependencies/index.js
import fs from "fs/promises";
import path from "path";
import fs2 from "fs/promises";
import path2 from "path";
import os from "os";
import fs3 from "fs/promises";
import path3 from "path";
import os2 from "os";
var semver = __toESM(require_semver(), 1);
import fs4 from "fs/promises";
import path4 from "path";
import os3 from "os";
import fs5 from "fs/promises";
import path5 from "path";
import fs6 from "fs/promises";
import path6 from "path";
import crypto from "crypto";
var import_ts_morph = __toESM(require_ts_morph(), 1);
import { readFileSync } from "fs";
import * as path8 from "path";
import * as fs8 from "fs/promises";
import * as path9 from "path";
var import_debug = __toESM(require_src(), 1);
import { createRequire as createRequire2 } from "module";
import fs9 from "fs/promises";
import path10 from "path";
var semver2 = __toESM(require_semver(), 1);
var import_ts_morph2 = __toESM(require_ts_morph(), 1);
var import_ts_morph3 = __toESM(require_ts_morph(), 1);
import { existsSync } from "fs";
import * as path11 from "path";
import * as fs11 from "fs/promises";
import * as path15 from "path";
import * as fs10 from "fs/promises";
import * as path12 from "path";
import { readFileSync as readFileSync2 } from "fs";
import * as path14 from "path";
var __defProp = Object.defineProperty;
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: __exportSetter.bind(all, name)
});
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
function sortKeysDeep(value) {
if (Array.isArray(value))
return value.map(sortKeysDeep);
if (value && typeof value === "object") {
const obj = value;
const sorted = {};
for (const key of Object.keys(obj).sort()) {
sorted[key] = sortKeysDeep(obj[key]);
}
return sorted;
}
return value;
}
function jsonEqual(a, b) {
return JSON.stringify(sortKeysDeep(a)) === JSON.stringify(sortKeysDeep(b));
}
var integrationDependencyEntrySchema;
var pluginDependencyMappingSchema;
var pluginDependencyEntrySchema;
var integrationSnapshotEntrySchema;
var pluginSnapshotEntrySchema;
var dependencyStateSchema;
var dependencySnapshotSchema;
var dependencyMigrationMarkerSchema;
var init_types = __esm(() => {
integrationDependencyEntrySchema = ne.object({
name: ne.string(),
version: ne.string(),
enabled: ne.boolean(),
config: ne.record(ne.any()).default({}),
configurationType: ne.string().optional(),
missingFields: ne.array(ne.string()).optional(),
authorizationPending: ne.boolean().optional()
});
pluginDependencyMappingSchema = ne.object({
integrationAlias: ne.string()
});
pluginDependencyEntrySchema = ne.object({
name: ne.string(),
version: ne.string(),
enabled: ne.boolean().default(true),
config: ne.record(ne.any()).default({}),
dependencies: ne.record(pluginDependencyMappingSchema).default({}),
missingFields: ne.array(ne.string()).optional()
});
integrationSnapshotEntrySchema = integrationDependencyEntrySchema.extend({
cloudAlias: ne.string().optional(),
cloudId: ne.string().optional(),
updatedAt: ne.string().optional()
});
pluginSnapshotEntrySchema = pluginDependencyEntrySchema.extend({
cloudAlias: ne.string().optional(),
cloudId: ne.string().optional(),
updatedAt: ne.string().optional()
});
dependencyStateSchema = ne.object({
$schema: ne.string().optional(),
version: ne.literal(1),
env: ne.enum(["dev", "prod"]),
integrations: ne.record(integrationDependencyEntrySchema).default({}),
plugins: ne.record(pluginDependencyEntrySchema).default({})
});
dependencySnapshotSchema = ne.object({
$schema: ne.string().optional(),
version: ne.literal(1),
env: ne.enum(["dev", "prod"]),
botId: ne.string(),
fetchedAt: ne.string(),
botUpdatedAt: ne.string().optional(),
stale: ne.boolean().optional(),
integrations: ne.record(integrationSnapshotEntrySchema).default({}),
plugins: ne.record(pluginSnapshotEntrySchema).default({})
});
dependencyMigrationMarkerSchema = ne.object({
version: ne.literal(1),
migratedAt: ne.string(),
sources: ne.array(ne.enum(["lock", "agentConfig", "cloud"]))
});
});
var AdkError;
var init_errors = __esm(() => {
AdkError = class AdkError2 extends Error {
static __IS_ADK_BASE_ERROR = true;
code;
expected;
details;
suggestion;
constructor(opts) {
super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
this.name = this.constructor.name;
this.code = opts.code;
this.expected = opts.expected ?? false;
if (opts.details !== undefined) {
this.details = opts.details;
}
if (opts.suggestion !== undefined) {
this.suggestion = opts.suggestion;
}
}
};
});
var init_src = __esm(() => {
init_errors();
});
var DEPENDENCY_ERROR_CODES;
var DependencyError;
var DEPENDENCY_WARNING_CODES;
var init_errors2 = __esm(() => {
init_src();
DEPENDENCY_ERROR_CODES = [
"AUTH_REQUIRED",
"INTEGRATION_NOT_FOUND",
"PLUGIN_NOT_FOUND",
"INTERFACE_NOT_FOUND",
"VERSION_NOT_FOUND",
"MISSING_INPUT",
"MISSING_DEPENDENCY",
"AMBIGUOUS_DEPENDENCY",
"INTERFACE_NOT_IMPLEMENTED",
"SNAPSHOT_DRIFT",
"UNINSTALL_REQUIRES_CONFIRMATION",
"SAME_SOURCE_TARGET",
"SOURCE_SNAPSHOT_MISSING",
"MIGRATION_CONFLICT",
"INVALID_CONFIG",
"BOT_NOT_FOUND",
"BUILTIN_INTERFACE_IMMUTABLE",
"PROD_CONFIRMATION_REQUIRED",
"UNCONFIGURED_DEPENDENCIES"
];
DependencyError = class DependencyError2 extends AdkError {
constructor(opts) {
super({ ...opts, expected: true });
}
};
DEPENDENCY_WARNING_CODES = ["MIGRATED_DEPENDENCIES", "CLOUD_FETCH_PARTIAL", "NO_PROD_BOT"];
});
function schemaRequiredFields(config) {
const required = config?.schema?.required;
if (!Array.isArray(required))
return [];
return required.filter((field) => typeof field === "string");
}
function configurationRequiresInput(config) {
if (!config)
return false;
if (configGatesOnAuthorization(config))
return true;
return schemaRequiredFields(config).length > 0;
}
function configGatesOnAuthorization(config) {
return !!(config?.identifier?.required || config?.identifier?.linkTemplateScript);
}
function missingRequiredFields(config, values) {
return schemaRequiredFields(config).filter((field) => values[field] === undefined);
}
function unconfigured(missingFields) {
if (missingFields.length > 0)
return { state: "unconfigured", missingFields };
return {
state: "unconfigured",
missingFields: [],
reason: "requires authorization or credentials (configure it in the Control Panel)"
};
}
function authorizationPendingVerdict() {
return {
state: "unconfigured",
missingFields: [],
reason: "requires authorization \u2014 connect it in the Control Panel, then re-deploy"
};
}
function snapshotOnlyVerdict(enabled, persistedMissingFields) {
if (persistedMissingFields.length > 0)
return unconfigured(persistedMissingFields);
if (!enabled)
return { state: "disabled" };
return { state: "available" };
}
function integrationRequiresConfiguration(definition) {
const config = definition.configuration;
if (!config)
return true;
return configurationRequiresInput(config);
}
function pluginRequiresConfiguration(definition) {
return schemaRequiredFields(definition.configuration).length > 0;
}
function integrationRequiresAuthorization(definition, configurationType) {
const { config } = resolveActiveConfiguration(definition, configurationType);
return configGatesOnAuthorization(config);
}
function resolveActiveConfiguration(spec, configurationType) {
if (configurationType && configurationType !== "default") {
const variant = spec.configurations?.[configurationType];
if (variant)
return { config: variant, isDefault: false, variantMissing: false };
return { config: undefined, isDefault: false, variantMissing: true };
}
return { config: spec.configuration, isDefault: true, variantMissing: false };
}
function computeIntegrationStatus(input) {
if (!input.installed)
return { state: "not_installed" };
if (!input.spec)
return { state: "unresolved", reason: "integration definition could not be resolved" };
const {
config: activeConfig,
isDefault,
variantMissing
} = resolveActiveConfiguration(input.spec, input.configurationType);
if (variantMissing) {
return { state: "unresolved", reason: `configuration variant '${input.configurationType}' not found in spec` };
}
const requiresConfig = isDefault ? integrationRequiresConfiguration(input.spec) : configurationRequiresInput(activeConfig);
const values = input.config ?? {};
const schemaMissing = requiresConfig ? missingRequiredFields(activeConfig, values) : [];
let configIncomplete = false;
if (input.persistedMissingFields !== undefined) {
configIncomplete = input.persistedMissingFields.length > 0;
} else if (requiresConfig) {
if (schemaMissing.length > 0) {
configIncomplete = true;
} else if (input.cloudEnabled === false && configGatesOnAuthorization(activeConfig)) {
configIncomplete = true;
}
}
if (configIncomplete) {
const missingFields = input.persistedMissingFields && input.persistedMissingFields.length > 0 ? input.persistedMissingFields : schemaMissing;
return unconfigured(missingFields);
}
if (input.authorizationPending)
return authorizationPendingVerdict();
if (!input.enabled || input.cloudEnabled === false)
return { state: "disabled" };
return { state: "available" };
}
function transitiveDependencyVerdict(dependencyStatuses) {
const broken = (dependencyStatuses ?? []).find((dep) => dep.verdict.state !== "available");
if (!broken)
return null;
const state = broken.verdict.state === "disabled" ? "disabled" : "unresolved";
return { state, reason: `dependency '${broken.alias}' is ${broken.verdict.state}` };
}
function mapPluginDependencyStatuses(dependencies, verdictFor) {
return Object.values(dependencies ?? {}).map((dep) => dep.integrationAlias).filter((alias) => !!alias).map((alias) => ({
alias,
verdict: verdictFor(alias) ?? {
state: "unresolved",
reason: `backing integration '${alias}' is not declared in the project`
}
}));
}
function computePluginStatus(input) {
if (!input.installed)
return { state: "not_installed" };
if (!input.spec)
return { state: "unresolved", reason: "plugin definition could not be resolved" };
const transitive = transitiveDependencyVerdict(input.dependencyStatuses);
if (transitive)
return transitive;
const requiresConfig = pluginRequiresConfiguration(input.spec);
const values = input.config ?? {};
const schemaMissing = requiresConfig ? missingRequiredFields(input.spec.configuration, values) : [];
let configIncomplete = false;
if (input.persistedMissingFields !== undefined) {
configIncomplete = input.persistedMissingFields.length > 0;
} else if (requiresConfig) {
configIncomplete = schemaMissing.length > 0;
}
if (configIncomplete) {
const missingFields = input.persistedMissingFields && input.persistedMissingFields.length > 0 ? input.persistedMissingFields : schemaMissing;
return unconfigured(missingFields);
}
if (!input.enabled)
return { state: "disabled" };
return { state: "available" };
}
function computeSnapshotOnlyStatus(input) {
if (!input.installed)
return { state: "not_installed" };
const verdict = snapshotOnlyVerdict(input.enabled, input.missingFields ?? []);
if (verdict.state === "available" || verdict.state === "disabled") {
if (input.authorizationPending)
return authorizationPendingVerdict();
}
return verdict;
}
function isCallable(state) {
return state === "available";
}
var pluginDependencyMappingSchema2;
var dependenciesSchema;
var agentInfoSchema;
var agentLocalInfoSchema;
var ValidationErrorCode;
var ValidationSeverity;
var ProjectState;
var init_types2 = __esm(() => {
pluginDependencyMappingSchema2 = ne.object({
integrationAlias: ne.string(),
integrationInterfaceAlias: ne.string().optional()
});
dependenciesSchema = ne.object({
integrations: ne.record(ne.union([
ne.string(),
ne.object({
version: ne.string(),
enabled: ne.boolean(),
configurationType: ne.string().optional(),
config: ne.record(ne.any()).optional()
})
])).optional(),
plugins: ne.record(ne.object({
version: ne.string(),
config: ne.record(ne.any()).optional(),
dependencies: ne.record(pluginDependencyMappingSchema2).optional(),
missingFields: ne.array(ne.string()).optional()
})).optional()
});
agentInfoSchema = ne.object({
botId: ne.string().describe("The bot ID from Botpress deployment"),
workspaceId: ne.string().describe("The workspace ID where the bot is deployed"),
apiUrl: ne.string().optional().describe("The Botpress API URL (e.g., https://api.botpress.cloud)")
});
agentLocalInfoSchema = ne.object({
botId: ne.string().optional().describe("The bot ID (overrides agent.json for local development)"),
workspaceId: ne.string().optional().describe("The workspace ID (overrides agent.json for local development)"),
apiUrl: ne.string().optional().describe("The Botpress API URL (overrides agent.json for local development)"),
devId: ne.string().optional().describe("The development bot ID used during local development")
});
((ValidationErrorCode2) => {
ValidationErrorCode2["DIRECTORY_NOT_FOUND"] = "DIRECTORY_NOT_FOUND";
ValidationErrorCode2["DIRECTORY_ACCESS_ERROR"] = "DIRECTORY_ACCESS_ERROR";
ValidationErrorCode2["REQUIRED_FILE_MISSING"] = "REQUIRED_FILE_MISSING";
ValidationErrorCode2["INVALID_STRUCTURE"] = "INVALID_STRUCTURE";
ValidationErrorCode2["INVALID_CONFIG_SYNTAX"] = "INVALID_CONFIG_SYNTAX";
ValidationErrorCode2["INVALID_CONFIG_SCHEMA"] = "INVALID_CONFIG_SCHEMA";
ValidationErrorCode2["MISSING_REQUIRED_FIELD"] = "MISSING_REQUIRED_FIELD";
ValidationErrorCode2["AGENT_NOT_LINKED"] = "AGENT_NOT_LINKED";
ValidationErrorCode2["INVALID_DEPENDENCIES_SYNTAX"] = "INVALID_DEPENDENCIES_SYNTAX";
ValidationErrorCode2["INVALID_DEPENDENCIES_SCHEMA"] = "INVALID_DEPENDENCIES_SCHEMA";
ValidationErrorCode2["INVALID_VERSION_FORMAT"] = "INVALID_VERSION_FORMAT";
ValidationErrorCode2["INVALID_INTEGRATION_ALIAS"] = "INVALID_INTEGRATION_ALIAS";
ValidationErrorCode2["INVALID_PLUGIN_ALIAS"] = "INVALID_PLUGIN_ALIAS";
ValidationErrorCode2["UNKNOWN_INTEGRATION"] = "UNKNOWN_INTEGRATION";
ValidationErrorCode2["UNKNOWN_PLUGIN"] = "UNKNOWN_PLUGIN";
ValidationErrorCode2["INVALID_PLUGIN_DEPENDENCY"] = "INVALID_PLUGIN_DEPENDENCY";
ValidationErrorCode2["INCOMPATIBLE_VERSION"] = "INCOMPATIBLE_VERSION";
ValidationErrorCode2["CIRCULAR_DEPENDENCY"] = "CIRCULAR_DEPENDENCY";
ValidationErrorCode2["FILE_TOO_LARGE"] = "FILE_TOO_LARGE";
ValidationErrorCode2["INVALID_FILE_TYPE"] = "INVALID_FILE_TYPE";
ValidationErrorCode2["INVALID_FILE_NAME"] = "INVALID_FILE_NAME";
ValidationErrorCode2["DUPLICATE_FILE_NAME"] = "DUPLICATE_FILE_NAME";
ValidationErrorCode2["DUPLICATE_PRIMITIVE"] = "DUPLICATE_PRIMITIVE";
ValidationErrorCode2["INVALID_PRIMITIVE_DEFINITION"] = "INVALID_PRIMITIVE_DEFINITION";
ValidationErrorCode2["TABLE_TOO_MANY_COLUMNS"] = "TABLE_TOO_MANY_COLUMNS";
ValidationErrorCode2["BUILD_FAILED"] = "BUILD_FAILED";
ValidationErrorCode2["SYNTAX_ERROR"] = "SYNTAX_ERROR";
ValidationErrorCode2["TYPE_ERROR"] = "TYPE_ERROR";
ValidationErrorCode2["IMPORT_ERROR"] = "IMPORT_ERROR";
})(ValidationErrorCode ||= {});
((ValidationSeverity2) => {
ValidationSeverity2["ERROR"] = "error";
ValidationSeverity2["WARNING"] = "warning";
ValidationSeverity2["INFO"] = "info";
})(ValidationSeverity ||= {});
((ProjectState2) => {
ProjectState2["Unloaded"] = "unloaded";
ProjectState2["Loading"] = "loading";
ProjectState2["Ready"] = "ready";
ProjectState2["Building"] = "building";
ProjectState2["Error"] = "error";
})(ProjectState ||= {});
});
class ValidationErrors {
static $type = "ValidationError";
static isValidationError(error) {
return error !== null && typeof error === "object" && "$type" in error && error.$type === "ValidationError";
}
static directoryNotFound(path7) {
return {
$type: ValidationErrors.$type,
code: "DIRECTORY_NOT_FOUND",
severity: "error",
message: `Project directory not found: ${path7}`,
hint: "Ensure the directory exists and you have permission to access it",
context: { path: path7 }
};
}
static directoryAccessError(path7, error) {
return {
$type: ValidationErrors.$type,
code: "DIRECTORY_ACCESS_ERROR",
severity: "error",
message: `Cannot access project directory: ${error}`,
hint: "Check file system permissions",
context: { path: path7, error }
};
}
static requiredFileMissing(file) {
return {
$type: ValidationErrors.$type,
code: "REQUIRED_FILE_MISSING",
severity: "error",
message: `Required file '${file}' not found`,
file,
hint: `Create a ${file} file in your project root`,
documentation: "https://docs.botpress.com/adk/project-structure"
};
}
static invalidStructure(directory, expected) {
return {
$type: ValidationErrors.$type,
code: "INVALID_STRUCTURE",
severity: "warning",
message: `Expected '${directory}' to be a ${expected}`,
file: directory,
hint: `Ensure ${directory} is a ${expected}, not a ${expected === "directory" ? "file" : "directory"}`
};
}
static invalidConfigSyntax(file, error, line, column) {
return {
$type: ValidationErrors.$type,
code: "INVALID_CONFIG_SYNTAX",
severity: "error",
message: `Invalid syntax in ${file}: ${error}`,
file,
line,
column,
hint: "Check for syntax errors like missing commas, brackets, or quotes"
};
}
static invalidConfigSchema(file, field, error) {
return {
$type: ValidationErrors.$type,
code: "INVALID_CONFIG_SCHEMA",
severity: "error",
message: `Invalid configuration in ${file}: ${error}`,
file,
hint: `Check the '${field}' field matches the expected schema`,
context: { field, error }
};
}
static missingRequiredField(file, field) {
return {
$type: ValidationErrors.$type,
code: "MISSING_REQUIRED_FIELD",
severity: "error",
message: `Missing required field '${field}' in ${file}`,
file,
hint: `Add the '${field}' field to your configuration`,
documentation: "https://docs.botpress.com/adk/configuration"
};
}
static tableTooManyColumns(tableName, filePath, columnCount, max) {
return {
$type: ValidationErrors.$type,
code: "TABLE_TOO_MANY_COLUMNS",
severity: "error",
message: `Table '${tableName}' has ${columnCount} columns (max ${max})`,
file: filePath,
hint: `Reduce columns or split into multiple tables. The limit is ${max} columns per table.`,
context: { tableName, columnCount, max }
};
}
static invalidDependenciesSyntax(error, line) {
return {
$type: ValidationErrors.$type,
code: "INVALID_DEPENDENCIES_SYNTAX",
severity: "error",
message: `Invalid syntax in agent.config.ts dependencies: ${error}`,
file: "agent.config.ts",
line,
hint: "Ensure agent.config.ts exports a valid dependencies object"
};
}
static invalidVersionFormat(integration, version) {
return {
$type: ValidationErrors.$type,
code: "INVALID_VERSION_FORMAT",
severity: "error",
message: `Invalid version format '${version}' for integration '${integration}'`,
file: "agent.config.ts",
hint: 'Use exact versioning (e.g., "1.2.3", "2.0.0", "1.5.0")',
context: { integration, version }
};
}
static invalidIntegrationAlias(alias) {
return {
$type: ValidationErrors.$type,
code: "INVALID_INTEGRATION_ALIAS",
severity: "error",
message: `Invalid integration alias '${alias}'`,
file: "agent.config.ts",
hint: 'Integration aliases must be 2-100 characters and contain only lowercase letters, numbers, underscores, and hyphens (e.g., "slack", "my-slack", "slack_prod")',
context: { alias }
};
}
static unknownIntegration(integration, source, detailedMessage) {
return {
$type: ValidationErrors.$type,
code: "UNKNOWN_INTEGRATION",
severity: "error",
message: detailedMessage || `Unknown integration '${integration}' from source '${source}'`,
file: "agent.config.ts",
hint: detailedMessage ? undefined : `Check if the integration name is correct or if it exists in ${source}`,
context: { integration, source }
};
}
static integrationVersionError(integration, errorMessage) {
return {
$type: ValidationErrors.$type,
code: "UNKNOWN_INTEGRATION",
severity: "error",
message: errorMessage,
file: "agent.config.ts",
hint: `Update the version for "${integration}" in agent.config.ts dependencies`
};
}
static unknownInterface(errorMessage) {
return {
$type: ValidationErrors.$type,
code: "UNKNOWN_INTEGRATION",
severity: "error",
message: errorMessage,
file: "agent.config.ts"
};
}
static incompatibleVersion(integration, required, available) {
return {
$type: ValidationErrors.$type,
code: "INCOMPATIBLE_VERSION",
severity: "error",
message: `Integration '${integration}' requires version ${required}, but only ${available} is available`,
file: "agent.config.ts",
hint: "Update the version requirement or check for compatible versions",
context: { integration, required, available }
};
}
static invalidPluginAlias(alias) {
return {
$type: ValidationErrors.$type,
code: "INVALID_PLUGIN_ALIAS",
severity: "error",
message: `Invalid plugin alias '${alias}'`,
file: "agent.config.ts",
hint: 'Plugin aliases must be 2-100 characters and contain only lowercase letters, numbers, underscores, and hyphens (e.g., "hitl", "my-plugin")',
context: { alias }
};
}
static invalidPluginVersionFormat(plugin, version) {
return {
$type: ValidationErrors.$type,
code: "INVALID_VERSION_FORMAT",
severity: "error",
message: `Invalid version format '${version}' for plugin '${plugin}'`,
file: "agent.config.ts",
hint: 'Use exact versioning (e.g., "1.2.3", "2.0.0", "1.5.0")',
context: { plugin, version }
};
}
static unknownPlugin(plugin, source, detailedMessage) {
return {
$type: ValidationErrors.$type,
code: "UNKNOWN_PLUGIN",
severity: "error",
message: detailedMessage || `Unknown plugin '${plugin}' from source '${source}'`,
file: "agent.config.ts",
hint: detailedMessage ? undefined : `Check if the plugin name is correct or if it exists on the Botpress Hub`,
context: { plugin, source }
};
}
static invalidPluginDependency(plugin, integrationAlias, availableIntegrations) {
const available = availableIntegrations.length > 0 ? availableIntegrations.join(", ") : "(none)";
return {
$type: ValidationErrors.$type,
code: "INVALID_PLUGIN_DEPENDENCY",
severity: "error",
message: `Plugin "${plugin}" references integration "${integrationAlias}" in its dependencies, but "${integrationAlias}" is not declared in dependencies.integrations`,
file: "agent.config.ts",
hint: `Add "${integrationAlias}" to dependencies.integrations, or update the plugin dependency to reference one of: ${available}`,
context: { plugin, integrationAlias, availableIntegrations }
};
}
static pluginVersionError(plugin, errorMessage) {
return {
$type: ValidationErrors.$type,
code: "UNKNOWN_PLUGIN",
severity: "error",
message: errorMessage,
file: "agent.config.ts",
hint: `Update the version for "${plugin}" in agent.config.ts dependencies`
};
}
static fileTooLarge(file, size, maxSize) {
return {
$type: ValidationErrors.$type,
code: "FILE_TOO_LARGE",
severity: "error",
message: `File '${file}' is too large (${formatBytes(size)} > ${formatBytes(maxSize)})`,
file,
hint: "Reduce file size or split into smaller files",
context: { size, maxSize }
};
}
static invalidFileType(file, type, allowedTypes) {
return {
$type: ValidationErrors.$type,
code: "INVALID_FILE_TYPE",
severity: "error",
message: `Invalid file type '${type}' for file '${file}'`,
file,
hint: `Allowed file types: ${allowedTypes.join(", ")}`,
context: { type, allowedTypes }
};
}
static invalidFileName(file, reason) {
return {
$type: ValidationErrors.$type,
code: "INVALID_FILE_NAME",
severity: "error",
message: `Invalid file name '${file}': ${reason}`,
file,
hint: "Use lowercase letters, numbers, and hyphens only"
};
}
static duplicateFileName(file, existingFile) {
return {
$type: ValidationErrors.$type,
code: "DUPLICATE_FILE_NAME",
severity: "error",
message: `Duplicate file name '${file}' conflicts with '${existingFile}'`,
file,
hint: "Rename one of the files to avoid conflicts",
context: { existingFile }
};
}
static buildFailed(error, file) {
return {
$type: ValidationErrors.$type,
code: "BUILD_FAILED",
severity: "error",
message: `Build failed: ${error}`,
file,
hint: "Check the error message and fix any issues in your code"
};
}
static syntaxError(file, error, line, column) {
return {
$type: ValidationErrors.$type,
code: "SYNTAX_ERROR",
severity: "error",
message: `Syntax error: ${error}`,
file,
line,
column,
hint: "Check for missing semicolons, brackets, or other syntax issues"
};
}
static typeError(file, error, line) {
return {
$type: ValidationErrors.$type,
code: "TYPE_ERROR",
severity: "error",
message: `Type error: ${error}`,
file,
line,
hint: "Ensure types match expected values and imports are correct"
};
}
static importError(file, module, error) {
return {
$type: ValidationErrors.$type,
code: "IMPORT_ERROR",
severity: "error",
message: `Cannot import '${module}': ${error}`,
file,
hint: "Check if the module exists and is properly installed",
context: { module }
};
}
static unsafeKnowledgePath(knowledgeBase, pattern) {
return {
$type: ValidationErrors.$type,
code: "INVALID_FILE_NAME",
severity: "error",
message: `Knowledge base '${knowledgeBase}' contains unsafe path pattern '${pattern}'`,
file: `src/knowledge/${knowledgeBase}.ts`,
hint: "Knowledge patterns must not reference files outside the agent directory (remove ../ or absolute paths)",
context: { knowledgeBase, pattern }
};
}
static agentNotLinked() {
return {
$type: ValidationErrors.$type,
code: "AGENT_NOT_LINKED",
severity: "error",
message: "Agent is not linked to a workspace",
file: "agent.json",
hint: 'Please run "adk link" to link your agent to a workspace'
};
}
static workspaceIdMissing() {
return {
$type: ValidationErrors.$type,
code: "AGENT_NOT_LINKED",
severity: "error",
message: "No workspaceId found in agent.json",
file: "agent.json",
hint: 'Please run "adk link" to link your agent to a workspace'
};
}
static botIdMissing() {
return {
$type: ValidationErrors.$type,
code: "AGENT_NOT_LINKED",
severity: "error",
message: "No botId found in agent.json",
file: "agent.json",
hint: 'Please run "adk link" to link your agent to a bot'
};
}
static info(message, file) {
return {
$type: ValidationErrors.$type,
code: "INVALID_STRUCTURE",
severity: "info",
message,
file
};
}
static warning(message, file, hint) {
return {
$type: ValidationErrors.$type,
code: "INVALID_STRUCTURE",
severity: "warning",
message,
file,
hint
};
}
}
function formatBytes(bytes) {
if (bytes === 0)
return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}
var init_validation_errors = __esm(() => {
init_types2();
});
function isIntegrationAliasValid(alias) {
return alias.length >= INTEGRATION_ALIAS_MIN_LENGTH && alias.length <= INTEGRATION_ALIAS_MAX_LENGTH && INTEGRATION_ALIAS_REGEX.test(alias);
}
class IntegrationParser {
static parseIntegrationRef(versionString) {
const result = integrationRefSchema.safeParse(versionString);
if (!result.success) {
throw new AdkError({
code: "INVALID_VERSION_FORMAT",
expected: true,
message: result.error.errors[0]?.message || "Invalid integration version format"
});
}
return result.data;
}
static parseIntegrations(dependencies) {
const integrations = [];
const errors = [];
if (!dependencies.integrations) {
return { integrations, errors };
}
for (const [alias, value] of Object.entries(dependencies.integrations)) {
try {
if (!isIntegrationAliasValid(alias)) {
errors.push(ValidationErrors.invalidIntegrationAlias(alias));
continue;
}
const normalized = typeof value === "string" ? { version: value } : value;
const ref = this.parseIntegrationRef(normalized.version);
const versionResult = versionSchema.safeParse(ref.version);
if (!versionResult.success) {
errors.push(ValidationErrors.invalidVersionFormat(alias, ref.version));
}
integrations.push({
alias,
ref,
enabled: "enabled" in normalized ? normalized.enabled : undefined,
configurationType: "configurationType" in normalized ? normalized.configurationType : undefined,
config: "config" in normalized ? normalized.config : undefined
});
} catch (error) {
errors.push(ValidationErrors.invalidDependenciesSyntax(`Invalid integration '${alias}': ${error instanceof Error ? error.message : String(error)}`));
}
}
return { integrations, errors };
}
static checkDuplicates(integrations) {
const errors = [];
const seen = new Map;
for (const integration of integrations) {
const key = integration.ref.fullName;
const group = seen.get(key) || [];
group.push(integration);
seen.set(key, group);
}
for (const [integrationName, group] of seen.entries()) {
if (group.length > 1) {
const duplicateConfigs = [];
for (let i = 0;i < group.length; i++) {
for (let j = i + 1;j < group.length; j++) {
const configA = JSON.stringify(group[i].config ?? {});
const configB = JSON.stringify(group[j].config ?? {});
if (configA === configB) {
if (!duplicateConfigs.includes(group[i].alias)) {
duplicateConfigs.push(group[i].alias);
}
if (!duplicateConfigs.includes(group[j].alias)) {
duplicateConfigs.push(group[j].alias);
}
}
}
}
if (duplicateConfigs.length > 0) {
errors.push(ValidationErrors.warning(`Integration '${integrationName}' has aliases with identical configurations: ${duplicateConfigs.join(", ")}. Consider using different configs or removing duplicates.`, "agent.config.ts"));
}
}
}
return errors;
}
}
function isPluginAliasValid(alias) {
return alias.length >= PLUGIN_ALIAS_MIN_LENGTH && alias.length <= PLUGIN_ALIAS_MAX_LENGTH && PLUGIN_ALIAS_REGEX.test(alias);
}
class PluginParser {
static parsePluginRef(versionString) {
const result = pluginRefSchema.safeParse(versionString);
if (!result.success) {
throw new AdkError({
code: "INVALID_VERSION_FORMAT",
expected: true,
message: result.error.errors[0]?.message || "Invalid plugin version format"
});
}
return result.data;
}
static parsePlugins(dependencies) {
const plugins = [];
const errors = [];
if (!dependencies.plugins) {
return { plugins, errors };
}
for (const [alias, value] of Object.entries(dependencies.plugins)) {
try {
if (!isPluginAliasValid(alias)) {
errors.push(ValidationErrors.invalidPluginAlias(alias));
continue;
}
const ref = this.parsePluginRef(value.version);
const versionResult = versionSchema.safeParse(ref.version);
if (!versionResult.success) {
errors.push(ValidationErrors.invalidPluginVersionFormat(alias, ref.version));
}
plugins.push({
alias,
ref,
config: value.config,
dependencies: value.dependencies
});
} catch (error) {
errors.push(ValidationErrors.invalidDependenciesSyntax(`Invalid plugin '${alias}': ${error instanceof Error ? error.message : String(error)}`));
}
}
return { plugins, errors };
}
static validateDependencyReferences(dependencies) {
const errors = [];
if (!dependencies.plugins) {
return errors;
}
const integrationAliases = Object.keys(dependencies.integrations || {});
for (const [alias, pluginConfig] of Object.entries(dependencies.plugins)) {
if (!pluginConfig.dependencies) {
continue;
}
for (const [_depAlias, depMapping] of Object.entries(pluginConfig.dependencies)) {
if (!integrationAliases.includes(depMapping.integrationAlias)) {
errors.push(ValidationErrors.invalidPluginDependency(alias, depMapping.integrationAlias, integrationAliases));
}
}
}
return errors;
}
static checkDuplicates(plugins) {
const errors = [];
const seen = new Map;
for (const plugin of plugins) {
const key = plugin.ref.fullName;
const group = seen.get(key) || [];
group.push(plugin);
seen.set(key, group);
}
for (const [pluginName, group] of seen.entries()) {
if (group.length > 1) {
const duplicateConfigs = [];
for (let i = 0;i < group.length; i++) {
for (let j = i + 1;j < group.length; j++) {
const configA = JSON.stringify(group[i].config ?? {});
const configB = JSON.stringify(group[j].config ?? {});
if (configA === configB) {
if (!duplicateConfigs.includes(group[i].alias)) {
duplicateConfigs.push(group[i].alias);
}
if (!duplicateConfigs.includes(group[j].alias)) {
duplicateConfigs.push(group[j].alias);
}
}
}
}
if (duplicateConfigs.length > 0) {
errors.push(ValidationErrors.warning(`Plugin '${pluginName}' has aliases with identical configurations: ${duplicateConfigs.join(", ")}. Consider using different configs or removing duplicates.`, "agent.config.ts"));
}
}
}
return errors;
}
}
var INTEGRATION_ALIAS_MIN_LENGTH = 2;
var INTEGRATION_ALIAS_MAX_LENGTH = 100;
var INTEGRATION_ALIAS_REGEX;
var integrationRefSchema;
var versionSchema;
var PLUGIN_ALIAS_MIN_LENGTH = 2;
var PLUGIN_ALIAS_MAX_LENGTH = 100;
var PLUGIN_ALIAS_REGEX;
var pluginRefSchema;
var init_dependencies_parser = __esm(() => {
init_src();
init_validation_errors();
INTEGRATION_ALIAS_REGEX = /^(?:[a-z][a-z0-9_-]*\/)?[a-z][a-z0-9_-]*$/;
integrationRefSchema = ne.string().transform((val, ctx) => {
const match = val.match(/^(?:([^/]+)\/)?([^@]+)@(.+)$/);
if (!match) {
ctx.addIssue({
code: "custom",
message: `Invalid integration version format: ${val}. Expected format: 'name@version' or 'workspace/name@version'`
});
return;
}
const [, workspace, name, version] = match;
return {
workspace: workspace || undefined,
name,
version,
fullName: workspace ? `${workspace}/${name}` : name
};
});
versionSchema = ne.string().refine((version) => {
if (version === "latest")
return true;
const semverPattern = /^(\d+)\.(\d+)\.(\d+)(-[\w.]+)?(\+[\w.]+)?$/;
return semverPattern.test(version);
}, { message: "Version must be exact semver (e.g., 1.2.3) or 'latest'" });
PLUGIN_ALIAS_REGEX = /^[a-z][a-z0-9_-]*$/;
pluginRefSchema = ne.string().transform((val, ctx) => {
const match = val.match(/^([^/@]+)@(.+)$/);
if (!match) {
ctx.addIssue({
code: "custom",
message: `Invalid plugin version format: ${val}. Expected format: 'name@version' (no workspace prefix)`
});
return;
}
const [, name, version] = match;
return {
name,
version,
fullName: name
};
});
});
var DEFAULT_API_URL = "https://api.botpress.cloud";
var BUILTIN_INTERFACES;
var MAX_TABLE_COLUMNS = 20;
var init_constants = __esm(() => {
BUILTIN_INTERFACES = {
"typing-indicator": "typing-indicator@0.0.3",
llm: "llm@9.0.0",
listable: "listable@0.0.2"
};
});
async function readLocalInfo(agentPath) {
const localPath = path.join(agentPath, "agent.local.json");
try {
const localContent = await fs.readFile(localPath, "utf-8");
const localData = JSON.parse(localContent);
const localResult = agentLocalInfoSchema.safeParse(localData);
if (!localResult.success) {
const issue = localResult.error.errors[0];
throw ValidationErrors.warning(`agent.local.json has an invalid field: ${issue?.path.join(".")} \u2014 ${issue?.message}`, "agent.local.json");
}
return localResult.data;
} catch (error) {
if (error.code === "ENOENT") {
return null;
}
if (ValidationErrors.isValidationError(error)) {
throw error;
}
throw ValidationErrors.warning(`Failed to read agent.local.json: ${error.message}`, "agent.local.json");
}
}
function applyLocalOverrides(agentInfo, localInfo) {
if (localInfo.botId) {
agentInfo.botId = localInfo.botId;
}
if (localInfo.workspaceId) {
agentInfo.workspaceId = localInfo.workspaceId;
}
if (localInfo.apiUrl) {
agentInfo.apiUrl = localInfo.apiUrl;
}
if (localInfo.devId) {
agentInfo.devId = localInfo.devId;
}
}
async function resolveAgent(agentPath, options = {}) {
const { required = false, requireWorkspace = false, requireBot = false } = options;
const agentJsonPath = path.join(agentPath, "agent.json");
const localInfo = await readLocalInfo(agentPath);
let agentInfo = null;
try {
const agentJsonContent = await fs.readFile(agentJsonPath, "utf-8");
let agentData;
try {
agentData = JSON.parse(agentJsonContent);
} catch (parseError) {
throw ValidationErrors.invalidConfigSyntax("agent.json", parseError.message);
}
const validationResult = agentInfoSchema.safeParse(agentData);
if (!validationResult.success) {
const zodError = validationResult.error.errors[0];
throw ValidationErrors.invalidConfigSchema("agent.json", zodError?.path.join(".") || "unknown", zodError?.message || "Invalid schema");
}
const parsed = validationResult.data;
agentInfo = {
botId: parsed.botId,
workspaceId: parsed.workspaceId,
apiUrl: parsed.apiUrl
};
} catch (error) {
if (error.code === "ENOENT") {} else if (ValidationErrors.isValidationError(error)) {
throw error;
} else {
throw ValidationErrors.warning(`Failed to read agent.json: ${error.message}`, "agent.json");
}
}
if (!agentInfo) {
if (localInfo?.botId && localInfo?.workspaceId) {
agentInfo = {
botId: localInfo.botId,
workspaceId: localInfo.workspaceId,
apiUrl: localInfo.apiUrl,
devId: localInfo.devId
};
} else if (required || requireWorkspace || requireBot) {
throw ValidationErrors.requiredFileMissing("agent.json or agent.local.json");
} else {
return null;
}
} else {
if (localInfo) {
applyLocalOverrides(agentInfo, localInfo);
}
}
if (!agentInfo.apiUrl) {
agentInfo.apiUrl = DEFAULT_API_URL;
}
if (requireWorkspace && !agentInfo.workspaceId) {
throw ValidationErrors.workspaceIdMissing();
}
if (requireBot && !agentInfo.botId) {
throw ValidationErrors.botIdMissing();
}
return agentInfo;
}
var init_agent_resolver = __esm(() => {
init_types2();
init_validation_errors();
init_constants();
});
var exports_credentials = {};
__export(exports_credentials, {
CredentialsManager: () => CredentialsManager
});
class CredentialsManager {
credentialsPath;
configDir;
profileOverride;
constructor() {
this.configDir = path2.join(os.homedir(), ".adk");
this.credentialsPath = path2.join(this.configDir, "credentials");
}
setProfileOverride(profile) {
this.profileOverride = profile;
}
async ensureConfigDir() {
try {
await fs2.mkdir(this.configDir, { recursive: true });
} catch {}
}
async readCredentials() {
try {
const data = await fs2.readFile(this.credentialsPath, "utf-8");
return JSON.parse(data);
} catch {
return {
profiles: {},
profileMetadata: {},
currentProfile: "default"
};
}
}
async writeCredentials(store) {
await this.ensureConfigDir();
await fs2.writeFile(this.credentialsPath, JSON.stringify(store, null, 2));
await fs2.chmod(this.credentialsPath, 384);
}
async saveCredentials(profileName, credentials, userInfo) {
const store = await this.readCredentials();
store.profiles[profileName] = credentials;
if (!store.profileMetadata) {
store.profileMetadata = {};
}
store.profileMetadata[profileName] = {
lastUsed: new Date().toISOString(),
...userInfo
};
if (Object.keys(store.profiles).length === 1) {
store.currentProfile = profileName;
}
await this.writeCredentials(store);
}
async getCredentials(profileName) {
const store = await this.readCredentials();
const profile = profileName || store.currentProfile;
if (!store.profiles[profile]) {
return null;
}
return store.profiles[profile];
}
async listProfiles() {
const store = await this.readCredentials();
if (!store.profileMetadata) {
store.profileMetadata = {};
}
return Object.entries(store.profiles).map(([name, credentials]) => {
const metadata = store.profileMetadata[name] || {};
return {
name,
credentials,
apiUrl: credentials.apiUrl,
lastUsed: metadata.lastUsed || new Date().toISOString(),
email: metadata.email,
displayName: metadata.displayName,
accountId: metadata.accountId,
createdAt: metadata.createdAt
};
});
}
async getCurrentProfile() {
const store = await this.readCredentials();
return store.currentProfile || "default";
}
async getCurrentProfileDetails() {
const store = await this.readCredentials();
const currentProfileName = store.currentProfile || "default";
if (!store.profiles[currentProfileName]) {
return null;
}
const credentials = store.profiles[currentProfileName];
const metadata = store.profileMetadata?.[currentProfileName] || {};
return {
name: currentProfileName,
credentials,
apiUrl: credentials.apiUrl,
lastUsed: metadata.lastUsed || new Date().toISOString(),
email: metadata.email,
displayName: metadata.displayName,
accountId: metadata.accountId,
createdAt: metadata.createdAt
};
}
async setCurrentProfile(profileName) {
const store = await this.readCredentials();
if (!store.profiles[profileName]) {
throw new AdkError({
code: "PROFILE_NOT_FOUND",
message: `Profile '${profileName}' not found`,
expected: true
});
}
if (!store.profileMetadata) {
store.profileMetadata = {};
}
store.profileMetadata[profileName] = {
...store.profileMetadata[profileName],
lastUsed: new Date().toISOString()
};
store.currentProfile = profileName;
await this.writeCredentials(store);
}
async deleteProfile(profileName) {
const store = await this.readCredentials();
if (!store.profiles[profileName]) {
throw new AdkError({
code: "PROFILE_NOT_FOUND",
message: `Profile '${profileName}' not found`,
expected: true
});
}
delete store.profiles[profileName];
delete store.profileMetadata?.[profileName];
if (store.currentProfile === profileName) {
const remainingProfiles = Object.keys(store.profiles);
store.currentProfile = remainingProfiles.length > 0 ? remainingProfiles[0] : "default";
}
await this.writeCredentials(store);
}
async getActiveCredentials() {
const profileName = this.profileOverride;
const credentials = await this.getCredentials(profileName);
if (!credentials) {
const displayName = profileName || "default";
throw new AdkError({
code: "NOT_AUTHENTICATED",
message: `No credentials found for profile '${displayName}'. ` + `Please run 'adk login' to authenticate.`,
expected: true,
suggestion: "Run 'adk login' to authenticate."
});
}
return credentials;
}
findProfileByApiUrl(store, apiUrl) {
const normalizedUrl = apiUrl.replace(/\/+$/, "");
for (const [, credentials] of Object.entries(store.profiles)) {
if (!credentials.apiUrl)
continue;
const profileUrl = credentials.apiUrl.replace(/\/+$/, "");
if (profileUrl === normalizedUrl) {
return credentials;
}
}
return null;
}
async getAgentCredentials(agentPath) {
const agentInfo = await resolveAgent(agentPath, {
required: true,
requireWorkspace: true
});
const agentApiUrl = agentInfo.apiUrl;
let baseCredentials;
const hasExplicitProfile = !!this.profileOverride;
if (hasExplicitProfile) {
baseCredentials = await this.getActiveCredentials();
} else {
const store = await this.readCredentials();
const matchingCredentials = this.findProfileByApiUrl(store, agentApiUrl);
if (matchingCredentials) {
baseCredentials = matchingCredentials;
} else {
const profileName = store.currentProfile || "default";
const activeCredentials = store.profiles[profileName];
if (!activeCredentials) {
throw new AdkError({
code: "NOT_AUTHENTICATED",
message: `No credentials found for profile '${profileName}'. ` + `Please run 'adk login' to authenticate.`,
expected: true,
suggestion: "Run 'adk login' to authenticate."
});
}
baseCredentials = activeCredentials;
}
}
return {
...baseCredentials,
apiUrl: agentApiUrl,
workspaceId: agentInfo.workspaceId,
botId: agentInfo.botId
};
}
}
var init_credentials = __esm(() => {
init_src();
init_agent_resolver();
});
class AuthService {
apiUrl;
constructor(apiUrl) {
this.apiUrl = apiUrl || "https://api.botpress.cloud";
}
async validateToken(token) {
if (!token || !token.startsWith("bp_")) {
throw new AdkError({
code: "INVALID_TOKEN_FORMAT",
message: 'Invalid token format. Token should start with "bp_"',
expected: true
});
}
try {
const client = new Uk({
apiUrl: this.apiUrl,
token,
headers: {
"x-multiple-integrations": "true"
}
});
const accountResponse = await client.getAccount({});
const { account } = accountResponse;
const workspaces = await client.list.workspaces({}).collect();
return {
workspaceId: workspaces[0]?.id,
workspaceName: workspaces[0]?.name,
accountId: account.id,
email: account.email,
displayName: account.displayName,
createdAt: account.createdAt
};
} catch (error) {
if (error instanceof Error) {
if (error.message.includes("401") || error.message.includes("Unauthorized")) {
throw new AdkError({
code: "AUTH_FAILED",
message: "Invalid token. Please check your API token and try again.",
expected: true,
cause: error
});
}
if (error.message.includes("Network") || error.message.includes("ENOTFOUND")) {
throw new AdkError({
code: "NETWORK_ERROR",
message: `Unable to connect to ${this.apiUrl}. Please check your internet connection and API URL.`,
expected: true,
cause: error
});
}
}
throw error;
}
}
async testConnection(token) {
try {
await this.validateToken(token);
return true;
} catch {
return false;
}
}
}
var init_service = __esm(() => {
init_src();
});
class BpCliImporter {
bpCachePath;
constructor() {
this.bpCachePath = path3.join(os2.homedir(), ".botpress", "global.cache.json");
}
async hasBpCliCredentials() {
try {
await fs3.access(this.bpCachePath);
return true;
} catch {
return false;
}
}
async getBpCliCredentials() {
try {
const data = await fs3.readFile(this.bpCachePath, "utf-8");
const credentials = JSON.parse(data);
if (!credentials.token || !credentials.workspaceId || !credentials.apiUrl) {
return null;
}
return credentials;
} catch {
return null;
}
}
async importFromBpCli(profileName = "default") {
const bpCredentials = await this.getBpCliCredentials();
if (!bpCredentials) {
return false;
}
try {
await auth.login(bpCredentials.token, {
profile: profileName,
apiUrl: bpCredentials.apiUrl
});
const credentialsManager = new (await Promise.resolve().then(() => (init_credentials(), exports_credentials))).CredentialsManager;
const store = await credentialsManager["readCredentials"]();
if (store.profiles[profileName]) {
store.profiles[profileName].workspaceId = bpCredentials.workspaceId;
if (bpCredentials.botId) {
store.profiles[profileName].botId = bpCredentials.botId;
}
await credentialsManager["writeCredentials"](store);
}
return true;
} catch {
return false;
}
}
}
var bpCliImporter;
var init_bp_cli_import = __esm(() => {
init_auth();
bpCliImporter = new BpCliImporter;
});
class Auth {
credentialsManager;
constructor() {
this.credentialsManager = new CredentialsManager;
}
async login(token, options = {}) {
const apiUrl = options.apiUrl ?? "https://api.botpress.cloud";
const authService = new AuthService(apiUrl);
const authResult = await authService.validateToken(token);
const autoResolved = options.profile == null;
const profile = options.profile ?? await this.resolveProfileName(authResult);
try {
const client = new Uk({ apiUrl, token });
await client.setAccountPreference({ key: "adkCliConnected", value: true });
} catch {}
await this.credentialsManager.saveCredentials(profile, {
token,
apiUrl,
workspaceId: authResult.workspaceId,
workspaceName: authResult.workspaceName,
botId: authResult.botId
}, {
email: authResult.email,
displayName: authResult.displayName,
accountId: authResult.accountId,
createdAt: authResult.createdAt
});
if (autoResolved) {
await this.credentialsManager.setCurrentProfile(profile);
}
clearProjectClientCache();
}
async resolveProfileName(authResult) {
const profiles = await this.credentialsManager.listProfiles();
if (profiles.length === 0) {
return "default";
}
if (authResult.accountId) {
const existing = profiles.find((p) => p.accountId === authResult.accountId);
if (existing) {
return existing.name;
}
}
if (authResult.email) {
const baseName = authResult.email.split("@")[0] || "profile";
const existingNames2 = new Set(profiles.map((p) => p.name));
if (!existingNames2.has(baseName)) {
return baseName;
}
let i2 = 2;
while (existingNames2.has(`${baseName}-${i2}`)) {
i2++;
}
return `${baseName}-${i2}`;
}
const existingNames = new Set(profiles.map((p) => p.name));
let i = profiles.length + 1;
while (existingNames.has(`profile-${i}`)) {
i++;
}
return `profile-${i}`;
}
async logout(profile) {
if (profile) {
await this.credentialsManager.deleteProfile(profile);
} else {
const currentProfile = await this.credentialsManager.getCurrentProfile();
await this.credentialsManager.deleteProfile(currentProfile);
}
clearProjectClientCache();
}
async listProfiles() {
return this.credentialsManager.listProfiles();
}
async getCurrentProfile() {
return this.credentialsManager.getCurrentProfile();
}
async getCurrentProfileDetails() {
return this.credentialsManager.getCurrentProfileDetails();
}
async setCurrentProfile(profileName) {
await this.credentialsManager.setCurrentProfile(profileName);
clearProjectClientCache();
}
setProfileOverride(profile) {
this.credentialsManager.setProfileOverride(profile);
clearProjectClientCache();
}
async getActiveCredentials() {
return this.credentialsManager.getActiveCredentials();
}
async getAgentCredentials(agentPath) {
return this.credentialsManager.getAgentCredentials(agentPath);
}
async validateToken(token, apiUrl) {
const authService = new AuthService(apiUrl);
return authService.testConnection(token);
}
}
async function resolveProjectCredentials(options = {}) {
const { project, credentials: providedCredentials } = options;
const agentInfo = project?.agentInfo ?? (project?.path ? await resolveAgent(project.path) : undefined);
const baseCredentials = providedCredentials || (project?.path && agentInfo?.workspaceId ? await auth.getAgentCredentials(project.path) : await auth.getActiveCredentials());
const workspaceId = options.workspaceId || agentInfo?.workspaceId || baseCredentials.workspaceId;
const botId = options.botId || agentInfo?.botId || baseCredentials.botId;
return {
...baseCredentials,
apiUrl: options.apiUrl || agentInfo?.apiUrl || baseCredentials.apiUrl,
...workspaceId ? { workspaceId } : {},
...botId ? { botId } : {}
};
}
async function resolveWorkspaceCredentials(options = {}) {
const credentials = await resolveProjectCredentials(options);
if (!credentials.workspaceId) {
throw new AdkError({
code: "WORKSPACE_ID_MISSING",
message: 'No workspace ID found. Please login again with "adk login" or link your agent first.',
expected: true,
suggestion: 'Login again with "adk login" or link your agent first.'
});
}
return {
...credentials,
workspaceId: credentials.workspaceId
};
}
async function getProjectClient(options = {}) {
const credentials = await resolveWorkspaceCredentials(options);
const botId = options.botId;
const headers = {
"x-multiple-integrations": "true",
...options.headers ?? {}
};
const cacheKey = stableStringify({
token: credentials.token,
apiUrl: credentials.apiUrl,
workspaceId: credentials.workspaceId,
botId,
integrationId: options.integrationId,
integrationAlias: options.integrationAlias,
headers
});
const cached = projectClientCache.get(cacheKey);
if (cached) {
projectClientCache.delete(cacheKey);
projectClientCache.set(cacheKey, cached);
return cached;
}
const client = new Uk({
token: credentials.token,
apiUrl: credentials.apiUrl,
workspaceId: credentials.workspaceId,
...botId ? { botId } : {},
...options.integrationId ? { integrationId: options.integrationId } : {},
...options.integrationAlias ? { integrationAlias: options.integrationAlias } : {},
headers
});
if (projectClientCache.size >= MAX_PROJECT_CLIENT_CACHE_ENTRIES) {
const oldestKey = projectClientCache.keys().next().value;
if (oldestKey) {
projectClientCache.delete(oldestKey);
}
}
projectClientCache.set(cacheKey, client);
return client;
}
function clearProjectClientCache() {
projectClientCache.clear();
}
var auth;
var projectClientCache;
var MAX_PROJECT_CLIENT_CACHE_ENTRIES = 32;
var stableStringify = (value) => {
if (value === undefined) {
return "undefined";
}
if (!value || typeof value !== "object") {
return JSON.stringify(value);
}
if (Array.isArray(value)) {
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
}
const record = value;
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
};
var init_auth = __esm(() => {
init_src();
init_credentials();
init_service();
init_agent_resolver();
init_credentials();
init_bp_cli_import();
auth = new Auth;
projectClientCache = new Map;
});
class CatalogClientFactory {
options;
client;
constructor(options = {}) {
this.options = options;
}
async getClient() {
if (this.client)
return this.client;
const { project, workspaceId, credentials, apiUrl } = this.options;
const hasProjectScope = !!(project || workspaceId);
if (hasProjectScope) {
this.client = await getProjectClient({
project,
credentials,
apiUrl,
workspaceId,
headers: { ...CATALOG_HEADERS }
});
} else if (credentials) {
this.client = new Uk({
token: credentials.token,
apiUrl: apiUrl || credentials.apiUrl,
...credentials.workspaceId ? { workspaceId: credentials.workspaceId } : {},
headers: { ...CATALOG_HEADERS }
});
} else {
this.client = await getProjectClient({ headers: { ...CATALOG_HEADERS } });
}
return this.client;
}
}
var CATALOG_HEADERS;
var init_client_factory = __esm(() => {
init_auth();
CATALOG_HEADERS = { "x-multiple-integrations": "true" };
});
class ResolutionCache {
cacheDir;
resolutionsDir;
definitionsDir;
idField;
noCache;
constructor(config, noCache = false) {
this.idField = config.idField;
this.noCache = noCache;
const cacheRoot = config.cacheRoot ?? path4.join(os3.homedir(), ".adk", "cache");
this.cacheDir = path4.join(cacheRoot, config.cacheType);
this.resolutionsDir = path4.join(this.cacheDir, "resolutions");
this.definitionsDir = path4.join(this.cacheDir, "definitions");
}
async ensureCacheDirs() {
await fs4.mkdir(this.resolutionsDir, { recursive: true });
await fs4.mkdir(this.definitionsDir, { recursive: true });
}
async getResolution(name, version, workspace, options) {
if (this.noCache) {
return null;
}
try {
const key = this.getResolutionKey(name, version, workspace);
const cachePath = path4.join(this.resolutionsDir, `${key}.json`);
const data = await fs4.readFile(cachePath, "utf-8");
const raw = JSON.parse(data);
if (!options?.allowStale) {
const cachedAt = new Date(raw.cachedAt);
const ageMinutes = (Date.now() - cachedAt.getTime()) / (1000 * 60);
if (ageMinutes > RESOLUTION_TTL_MINUTES) {
return null;
}
}
const id = raw[this.idField];
if (!id) {
return null;
}
return { id, updatedAt: raw.updatedAt, cachedAt: raw.cachedAt };
} catch {
return null;
}
}
async setResolution(name, version, workspace, id, updatedAt) {
await this.ensureCacheDirs();
const key = this.getResolutionKey(name, version, workspace);
const cachePath = path4.join(this.resolutionsDir, `${key}.json`);
const resolution = {
[this.idField]: id,
updatedAt,
cachedAt: new Date().toISOString()
};
await fs4.writeFile(cachePath, JSON.stringify(resolution));
}
async getDefinition(id, updatedAt) {
if (this.noCache) {
return null;
}
try {
const key = this.getDefinitionKey(id, updatedAt);
const cachePath = path4.join(this.definitionsDir, `${key}.json`);
const data = await fs4.readFile(cachePath, "utf-8");
const cached = JSON.parse(data);
return cached.definition;
} catch {
return null;
}
}
async setDefinition(id, updatedAt, definition) {
await this.ensureCacheDirs();
const key = this.getDefinitionKey(id, updatedAt);
const cachePath = path4.join(this.definitionsDir, `${key}.json`);
const cached = {
definition,
cachedAt: new Date().toISOString()
};
await fs4.writeFile(cachePath, JSON.stringify(cached));
}
async clear() {
try {
const resolutionFiles = await fs4.readdir(this.resolutionsDir);
await Promise.all(resolutionFiles.map((file) => fs4.unlink(path4.join(this.resolutionsDir, file))));
const definitionFiles = await fs4.readdir(this.definitionsDir);
await Promise.all(definitionFiles.map((file) => fs4.unlink(path4.join(this.definitionsDir, file))));
} catch {}
}
async getStats() {
const getDirectoryStats = async (dir) => {
try {
const files = await fs4.readdir(dir);
let totalSize = 0;
for (const file of files) {
const stats = await fs4.stat(path4.join(dir, file));
totalSize += stats.size;
}
return { count: files.length, sizeBytes: totalSize };
} catch {
return { count: 0, sizeBytes: 0 };
}
};
const [resolutions, definitions] = await Promise.all([
getDirectoryStats(this.resolutionsDir),
getDirectoryStats(this.definitionsDir)
]);
return { resolutions, definitions };
}
getResolutionKey(name, version, workspace) {
const prefix = workspace ? `${workspace}_` : "";
const key = `${prefix}${name}_${version}`;
return key.replace(/[^a-zA-Z0-9_-]/g, "_");
}
getDefinitionKey(id, updatedAt) {
const key = `${id}_${updatedAt}`;
return key.replace(/[^a-zA-Z0-9_-]/g, "_");
}
}
var RESOLUTION_TTL_MINUTES = 5;
var init_resolution_cache = () => {};
class CatalogService {
source;
cache;
constructor(source, noCache = false) {
this.source = source;
this.cache = new ResolutionCache(source.cacheConfig, noCache);
}
async getDefinition(ref) {
const cachedResolution = await this.cache.getResolution(ref.name, ref.version, ref.workspace);
if (cachedResolution) {
const cachedDefinition = await this.cache.getDefinition(cachedResolution.id, cachedResolution.updatedAt);
if (cachedDefinition) {
return cachedDefinition;
}
}
try {
const { id, updatedAt, definition } = await this.source.fetchByRef(ref);
await this.cache.setResolution(ref.name, ref.version, ref.workspace, id, updatedAt);
await this.cache.setDefinition(id, updatedAt, definition);
return definition;
} catch (err) {
if (!(err instanceof AdkError && err.expected)) {
const stale = await this.getStaleDefinition(ref);
if (stale !== null) {
return stale;
}
}
throw err;
}
}
async getStaleDefinition(ref) {
const stale = await this.cache.getResolution(ref.name, ref.version, ref.workspace, { allowStale: true });
if (!stale)
return null;
return this.cache.getDefinition(stale.id, stale.updatedAt);
}
async getCacheStats() {
const stats = await this.cache.getStats();
return {
count: stats.resolutions.count + stats.definitions.count,
sizeBytes: stats.resolutions.sizeBytes + stats.definitions.sizeBytes
};
}
async clearCache() {
await this.cache.clear();
}
}
var init_catalog_service = __esm(() => {
init_src();
init_resolution_cache();
});
function collectCatalogSearchResult(results, entry, query) {
const rank = getCatalogSearchRank(entry, query);
if (rank === undefined) {
return;
}
const key = entry.name.toLowerCase();
const existing = results.get(key);
if (!existing || isBetterCatalogResult({ entry, rank }, existing)) {
results.set(key, { entry, rank });
}
}
function getSortedCatalogSearchResults(results, limit) {
return [...results.values()].sort((a, b) => a.rank - b.rank || a.entry.name.localeCompare(b.entry.name) || compareVersions(b.entry.version, a.entry.version)).slice(0, limit).map(({ entry }) => entry);
}
function getSortedVersions(versions) {
return [...versions].sort((a, b) => compareVersions(b, a));
}
function getCatalogSearchRank(entry, query) {
const q = normalize(query);
if (!q) {
return 0;
}
const name = normalize(entry.name);
const title = normalize(entry.title);
const description = normalize(entry.description);
if (name === q)
return 0;
if (title === q)
return 1;
if (name.startsWith(q))
return 2;
if (title.startsWith(q))
return 3;
if (name.includes(q))
return 4;
if (title.includes(q))
return 5;
if (description.includes(q))
return 6;
return;
}
function isBetterCatalogResult(candidate, existing) {
if (candidate.rank !== existing.rank) {
return candidate.rank < existing.rank;
}
const versionComparison = compareVersions(candidate.entry.version, existing.entry.version);
if (versionComparison !== 0) {
return versionComparison > 0;
}
return (candidate.entry.updatedAt ?? "").localeCompare(existing.entry.updatedAt ?? "") > 0;
}
function compareVersions(a, b) {
const validA = semver.valid(a);
const validB = semver.valid(b);
if (validA && validB) {
return semver.compare(validA, validB);
}
return a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });
}
function normalize(value) {
return (value ?? "").trim().toLowerCase();
}
var init_search_ranking = () => {};
class IntegrationCatalogSource {
clientFactory;
cacheConfig = { cacheType: "integrations", idField: "integrationId" };
constructor(clientFactory) {
this.clientFactory = clientFactory;
}
async fetchByRef(ref) {
const client = await this.clientFactory.getClient();
const integration = await this._findPrivateOrPublicIntegration(client, ref);
if (!integration) {
throw await this._buildIntegrationNotFoundError(client, ref);
}
return { id: integration.id, updatedAt: integration.updatedAt, definition: integration };
}
async search(query, limit = 20) {
const client = await this.clientFactory.getClient();
const results = new Map;
const scanLimit = Math.max(limit * 5, 50);
for await (const integration of client.list.publicIntegrations({
name: query,
sortBy: "updatedAt",
direction: "desc"
})) {
collectCatalogSearchResult(results, toHubCacheEntry(integration), query);
}
let scanned = 0;
for await (const integration of client.list.publicIntegrations({
search: query,
sortBy: "popularity",
direction: "desc",
limit: scanLimit
})) {
collectCatalogSearchResult(results, toHubCacheEntry(integration), query);
scanned += 1;
if (scanned >= scanLimit)
break;
}
return getSortedCatalogSearchResults(results, limit);
}
async findImplementersOfInterface(interfaceName, limit = 10) {
const client = await this.clientFactory.getClient();
const seen = new Map;
for await (const integration of client.list.publicIntegrations({
interfaceName,
sortBy: "popularity",
direction: "desc",
limit
})) {
const entry = toHubCacheEntry(integration);
if (!seen.has(entry.name))
seen.set(entry.name, entry);
if (seen.size >= limit)
break;
}
return [...seen.values()];
}
async listVersions(name) {
const client = await this.clientFactory.getClient();
const versions = new Set;
for await (const integration of client.list.publicIntegrations({
name,
sortBy: "updatedAt",
direction: "desc"
})) {
versions.add(integration.version);
}
return getSortedVersions(versions);
}
async _findPrivateOrPublicIntegration(client, ref) {
const privateIntegration = await this._findPrivateIntegration(client, ref);
if (privateIntegration) {
return privateIntegration;
}
const publicIntegration = await this._findPublicIntegration(client, ref);
if (publicIntegration) {
return publicIntegration;
}
return;
}
async _findPrivateIntegration(client, ref) {
try {
const version = ref.workspace && ref.version !== "latest" ? "latest" : ref.version;
const response = await client.getIntegrationByName({
name: ref.fullName,
version
});
return response.integration;
} catch (error) {
if (this._isResourceNotFoundError(error)) {
return;
}
throw error;
}
}
async _findPublicIntegration(client, ref) {
try {
const response = await client.getPublicIntegration({
name: ref.fullName,
version: ref.version
});
return response.integration;
} catch (error) {
if (this._isResourceNotFoundError(error)) {
return;
}
throw error;
}
}
_isResourceNotFoundError(error) {
if (error && typeof error === "object" && "type" in error) {
return error.type === "ResourceNotFound";
}
return false;
}
async _buildIntegrationNotFoundError(client, ref) {
if (ref.version !== "latest") {
const latestExists = await this._findPrivateOrPublicIntegration(client, { ...ref, version: "latest" });
if (latestExists) {
const scope = ref.workspace ? `workspace "${ref.workspace}"` : "the official Botpress hub";
return new AdkError({
code: "INTEGRATION_NOT_FOUND",
message: `Integration "${ref.name}" version "${ref.version}" not found in ${scope} ` + `(latest is ${latestExists.version}). Run 'adk integrations info ${ref.fullName}' to see available versions.`,
expected: true
});
}
}
if (!ref.workspace) {
return new AdkError({
code: "INTEGRATION_NOT_FOUND",
message: `Integration "${ref.name}" not found in the official Botpress hub`,
expected: true
});
}
let currentWorkspaceHandle;
let workspaceId;
try {
const credentials = await resolveWorkspaceCredentials({
project: this.clientFactory.options.project,
credentials: this.clientFactory.options.credentials,
apiUrl: this.clientFactory.options.apiUrl,
workspaceId: this.clientFactory.options.workspaceId
});
workspaceId = credentials.workspaceId;
if (workspaceId) {
const currentWorkspace = await client.getWorkspace({ id: workspaceId });
currentWorkspaceHandle = currentWorkspace?.handle;
}
} catch {
return new AdkError({
code: "INTEGRATION_NOT_FOUND",
message: `Integration "${ref.name}" not found in workspace "${ref.workspace}"`,
expected: true
});
}
if (currentWorkspaceHandle === ref.workspace) {
return new AdkError({
code: "INTEGRATION_NOT_FOUND",
message: `Integration "${ref.name}" not found in workspace "${ref.workspace}" (workspaceId: ${workspaceId}). ` + `Are you sure you published the integration? ` + `Run 'adk deploy' to publish it to your workspace.`,
expected: true,
suggestion: "Are you sure you published the integration? Run 'adk deploy' to publish it to your workspace."
});
}
return new AdkError({
code: "INTEGRATION_NOT_FOUND",
message: `Integration "${ref.name}" not found in workspace "${ref.workspace}" (current workspaceId: ${workspaceId}). ` + `This integration may be private. Private integrations can only be installed in the same workspace. ` + `If you want to share this integration with other workspaces, deploy it with --visibility="unlisted" or --visibility="public".`,
expected: true
});
}
}
function toHubCacheEntry(integration) {
return {
id: integration.id,
name: integration.name,
version: integration.version,
updatedAt: integration.updatedAt,
createdAt: integration.createdAt,
title: integration.title,
description: integration.description,
iconUrl: integration.iconUrl,
public: integration.public,
visibility: integration.visibility,
ownerWorkspace: integration.ownerWorkspace ? { id: integration.ownerWorkspace.id, name: integration.ownerWorkspace.name } : undefined,
verificationStatus: integration.verificationStatus
};
}
var init_integration_source = __esm(() => {
init_src();
init_auth();
init_search_ranking();
});
class IntegrationManager {
source;
service;
constructor(options = {}) {
const { noCache, ...clientOptions } = options;
this.source = new IntegrationCatalogSource(new CatalogClientFactory(clientOptions));
this.service = new CatalogService(this.source, noCache || false);
}
async loadIntegrations(dependencies) {
const errors = [];
const warnings = [];
const parseResult = IntegrationParser.parseIntegrations(dependencies);
const integrations = parseResult.integrations;
errors.push(...parseResult.errors);
const duplicateWarnings = IntegrationParser.checkDuplicates(integrations);
warnings.push(...duplicateWarnings);
const fetchPromises = integrations.map(async (integration) => {
try {
const definition = await this.fetchIntegration(integration.ref);
integration.definition = definition;
const validation = this.validateIntegration(integration);
integration.validationResult = validation;
if (!validation.valid) {
validation.errors.forEach(() => {
errors.push(ValidationErrors.unknownIntegration(integration.alias, integration.ref.fullName));
});
}
if (validation.warnings.length > 0) {
validation.warnings.forEach((warn) => {
warnings.push(ValidationErrors.warning(warn, "agent.config.ts"));
});
}
} catch (error) {
if (error instanceof Error && error.message.includes("version")) {
errors.push(ValidationErrors.integrationVersionError(integration.alias, error.message));
} else {
const errorMessage = error instanceof Error ? error.message : undefined;
errors.push(ValidationErrors.unknownIntegration(integration.alias, integration.ref.fullName, errorMessage));
}
}
});
await Promise.all(fetchPromises);
const hasChannels = integrations.some((i) => i.definition?.channels && Object.keys(i.definition.channels).length > 0);
if (!hasChannels && integrations.length > 0) {
warnings.push(ValidationErrors.warning("No integrations with channels found. Your agent may not be able to receive messages.", "agent.config.ts", "Add an integration with channels (e.g., slack, webchat) to enable conversations"));
}
return { integrations, errors, warnings };
}
async fetchIntegration(ref) {
return this.service.getDefinition(ref);
}
async searchIntegrations(query, limit = 20) {
return this.source.search(query, limit);
}
async findImplementersOfInterface(interfaceName, limit = 10) {
return this.source.findImplementersOfInterface(interfaceName, limit);
}
async listIntegrationVersions(name) {
return this.source.listVersions(name);
}
async getCacheStats() {
return this.service.getCacheStats();
}
async clearCache() {
await this.service.clearCache();
}
validateIntegration(integration) {
const errors = [];
const warnings = [];
if (!integration.definition) {
errors.push(`Integration definition not found for ${integration.alias}`);
return { valid: false, errors, warnings };
}
const hasChannels = integration.definition.channels && Object.keys(integration.definition.channels).length > 0;
if (!integration.config && integration.enabled !== undefined) {
let requiresConfig = false;
if (integration.definition.configurations) {
requiresConfig = Object.values(integration.definition.configurations).some((config) => config.identifier?.required === true);
}
if (!requiresConfig && integration.definition.configuration) {
const config = integration.definition.configuration;
const schema = config.schema;
requiresConfig = schema?.required && schema.required.length > 0;
}
if (requiresConfig) {
warnings.push(`Integration '${integration.alias}' requires configuration. Add a config object in agent.config.ts dependencies`);
}
}
return {
valid: errors.length === 0,
errors,
warnings,
missingChannels: !hasChannels
};
}
}
var init_manager = __esm(() => {
init_dependencies_parser();
init_validation_errors();
init_client_factory();
init_catalog_service();
init_integration_source();
});
var init_types3 = () => {};
var defaultAdkFolder = ".adk";
class AssetsCacheManager {
projectPath;
cachePath;
cache = null;
constructor(projectPath) {
this.projectPath = projectPath;
this.cachePath = path5.join(projectPath, defaultAdkFolder, "assets-cache.json");
}
async load() {
if (this.cache) {
return this.cache;
}
try {
const content = await fs5.readFile(this.cachePath, "utf-8");
this.cache = JSON.parse(content);
return this.cache;
} catch {
this.cache = {
version: "1.0",
entries: {}
};
return this.cache;
}
}
async save() {
if (!this.cache) {
return;
}
const cacheDir = path5.dirname(this.cachePath);
await fs5.mkdir(cacheDir, { recursive: true });
await fs5.writeFile(this.cachePath, JSON.stringify(this.cache, null, 2), "utf-8");
}
async getEntry(assetPath) {
const cache = await this.load();
return cache.entries[assetPath] || null;
}
async setEntry(assetPath, localHash, remoteHash, metadata) {
const cache = await this.load();
cache.entries[assetPath] = {
path: assetPath,
localHash,
remoteHash,
metadata,
lastUpdated: new Date().toISOString()
};
await this.save();
}
async isStale(assetPath) {
const entry = await this.getEntry(assetPath);
if (!entry)
return false;
return entry.localHash !== entry.remoteHash;
}
async removeEntry(assetPath) {
const cache = await this.load();
delete cache.entries[assetPath];
await this.save();
}
async clear() {
this.cache = {
version: "1.0",
entries: {}
};
await this.save();
}
async getAllEntries() {
const cache = await this.load();
return Object.values(cache.entries);
}
}
var init_cache = () => {};
class AssetsManager {
projectPath;
assetsPath;
client;
botId;
credentials;
cacheManager;
constructor(options) {
this.projectPath = options.projectPath;
this.assetsPath = path6.join(this.projectPath, "assets");
this.botId = options.botId;
this.credentials = options.credentials;
this.cacheManager = new AssetsCacheManager(this.projectPath);
}
async getClient() {
if (!this.client) {
if (!this.botId) {
throw new AdkError({
code: "BOT_ID_REQUIRED",
message: "Bot ID is required for asset operations. Please deploy your agent first or create agent.json with botId and workspaceId.",
expected: true,
suggestion: "Deploy your agent first or create agent.json with botId and workspaceId."
});
}
this.client = await getProjectClient({
project: { path: this.projectPath },
credentials: this.credentials,
botId: this.botId,
headers: {
"x-multiple-integrations": "true"
}
});
}
return this.client;
}
assertBotId(operation) {
if (!this.botId) {
throw new AdkError({
code: "BOT_ID_REQUIRED",
message: `Operation "${operation}" requires a bot ID. ` + "Please deploy your agent first or create agent.json with botId and workspaceId.",
expected: true,
suggestion: "Deploy your agent first or create agent.json with botId and workspaceId."
});
}
}
async hasAssetsDirectory() {
try {
const stats = await fs6.stat(this.assetsPath);
return stats.isDirectory();
} catch {
return false;
}
}
async getLocalAssets() {
if (!await this.hasAssetsDirectory()) {
return [];
}
const files = await this.scanDirectory(this.assetsPath);
const assets = [];
for (const filePath of files) {
try {
const stats = await fs6.stat(filePath);
if (stats.isFile()) {
const relativePath = path6.relative(this.assetsPath, filePath);
const content = await fs6.readFile(filePath);
const hash = this.calculateHash(content);
const mime = this.getMimeType(filePath);
assets.push({
relativePath,
absolutePath: filePath,
name: path6.basename(filePath),
size: stats.size,
mime,
hash,
stats: {
mtime: stats.mtime,
size: stats.size
}
});
}
} catch (error) {
console.warn(`Warning: Could not read asset file ${filePath}:`, error);
}
}
return assets;
}
async getRemoteAssets() {
this.assertBotId("get remote assets");
const client = await this.getClient();
try {
const response = await client.listFiles({
tags: {
type: "asset",
adk: "true"
}
});
return response.files.map((file) => ({
path: file.tags?.path || file.key,
name: path6.basename(file.tags?.path || file.key),
size: file.size || 0,
mime: file.contentType,
hash: file.tags?.hash || "",
createdAt: file.createdAt,
updatedAt: file.updatedAt,
fileId: file.id,
url: file.url || ""
}));
} catch (error) {
throw new AdkError({
code: "ASSET_FETCH_FAILED",
message: `Failed to fetch remote assets: ${error}`,
expected: true,
cause: error
});
}
}
async createSyncPlan() {
const [localAssets, remoteAssets] = await Promise.all([this.getLocalAssets(), this.getRemoteAssets()]);
const items = [];
const remoteMap = new Map;
for (const remote of remoteAssets) {
remoteMap.set(remote.path, remote);
}
for (const local of localAssets) {
const remote = remoteMap.get(local.relativePath);
if (!remote) {
items.push({
operation: "create",
localFile: local,
reason: "New local file"
});
} else if (local.hash !== remote.hash) {
items.push({
operation: "update",
localFile: local,
remoteFile: remote,
reason: "Content changed"
});
} else {
items.push({
operation: "none",
localFile: local,
remoteFile: remote,
reason: "Up to date"
});
}
remoteMap.delete(local.relativePath);
}
for (const [, remote] of remoteMap) {
items.push({
operation: "delete",
remoteFile: remote,
reason: "Local file deleted"
});
}
const totalCreate = items.filter((i) => i.operation === "create").length;
const totalUpdate = items.filter((i) => i.operation === "update").length;
const totalDelete = items.filter((i) => i.operation === "delete").length;
const hasChanges = totalCreate > 0 || totalUpdate > 0 || totalDelete > 0;
return {
items,
totalCreate,
totalUpdate,
totalDelete,
hasChanges
};
}
async executeSync(plan, options = {}) {
this.assertBotId("sync assets");
if (options.dryRun) {
return {
applied: false,
success: [],
skipped: [],
failed: [],
summary: {
created: 0,
updated: 0,
deleted: 0,
skipped: 0,
failed: 0
}
};
}
const client = await this.getClient();
const success = [];
const skipped = [];
const failed = [];
for (const item of plan.items) {
if (item.operation === "none") {
success.push(item);
continue;
}
try {
switch (item.operation) {
case "create":
case "update":
if (item.localFile) {
await this.uploadAsset(client, item.localFile);
success.push(item);
}
break;
case "delete":
if (item.remoteFile?.fileId) {
if (!options.confirmDestructive) {
skipped.push(item);
continue;
}
await client.deleteFile({ id: item.remoteFile.fileId });
success.push(item);
}
break;
}
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
failed.push({ item, error: err });
if (options.bailOnFailure) {
break;
}
}
}
return {
applied: true,
success,
skipped,
failed,
summary: {
created: success.filter((i) => i.operation === "create").length,
updated: success.filter((i) => i.operation === "update").length,
deleted: success.filter((i) => i.operation === "delete").length,
skipped: skipped.length,
failed: failed.length
}
};
}
async uploadAsset(client, localFile) {
const content = await fs6.readFile(localFile.absolutePath);
await client.uploadFile({
key: localFile.relativePath,
content,
contentType: localFile.mime,
tags: {
type: "asset",
adk: "true",
path: localFile.relativePath,
hash: localFile.hash,
name: localFile.name,
size: localFile.size.toString()
},
index: false
});
}
async generateTypes() {
const localAssets = await this.getLocalAssets();
const pathUnion = localAssets.map((asset) => `"${asset.relativePath}"`).join(" | ");
const paths = localAssets.map((asset) => ` "${asset.relativePath}": "${asset.relativePath}";`).join(`
`);
return `// Auto-generated asset types
import { Asset } from '@botpress/runtime';
export type AssetPaths = ${pathUnion || "never"};
export interface AssetPathMap {
${paths}
}
// Runtime asset access
declare global {
const assets: {
get<T extends AssetPaths>(path: T): Promise<Asset>;
list(): Asset[];
getSyncStatus(): {
synced: boolean;
neverSynced: string[];
stale: string[];
upToDate: string[];
};
};
}
`;
}
async createAssetsIndex() {
const remoteAssets = await this.getRemoteAssets();
return {
files: remoteAssets,
generatedAt: new Date().toISOString(),
totalFiles: remoteAssets.length,
totalSize: remoteAssets.reduce((sum, asset) => sum + asset.size, 0)
};
}
async getEnrichedLocalAssets() {
const localAssets = await this.getLocalAssets();
const enrichedAssets = [];
let remoteAssetsMap = new Map;
try {
if (this.botId) {
const remoteAssets = await this.getRemoteAssets();
remoteAssetsMap = new Map(remoteAssets.map((asset) => [asset.path, asset]));
}
} catch (error) {
console.debug("Could not fetch remote assets:", error);
}
for (const localAsset of localAssets) {
const cachedEntry = await this.cacheManager.getEntry(localAsset.relativePath);
const remoteAsset = remoteAssetsMap.get(localAsset.relativePath);
if (cachedEntry) {
enrichedAssets.push(cachedEntry.metadata);
await this.cacheManager.setEntry(localAsset.relativePath, localAsset.hash, cachedEntry.remoteHash, cachedEntry.metadata);
} else if (remoteAsset) {
enrichedAssets.push(remoteAsset);
await this.cacheManager.setEntry(localAsset.relativePath, localAsset.hash, remoteAsset.hash, remoteAsset);
} else {
const placeholderAsset = {
url: `__PLACEHOLDER_URL_${localAsset.relativePath}__`,
path: localAsset.relativePath,
size: localAsset.size,
name: localAsset.name,
mime: localAsset.mime,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
fileId: `__PLACEHOLDER_ID_${localAsset.relativePath}__`,
hash: localAsset.hash
};
enrichedAssets.push(placeholderAsset);
}
}
return enrichedAssets;
}
async scanDirectory(dir, files = []) {
const items = await fs6.readdir(dir);
for (const item of items) {
const fullPath = path6.join(dir, item);
const stats = await fs6.stat(fullPath);
if (stats.isDirectory()) {
await this.scanDirectory(fullPath, files);
} else {
files.push(fullPath);
}
}
return files;
}
calculateHash(content) {
return crypto.createHash("sha256").update(content).digest("hex");
}
getMimeType(filePath) {
const ext = path6.extname(filePath).toLowerCase();
const mimeTypes = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".webp": "image/webp",
".pdf": "application/pdf",
".txt": "text/plain",
".md": "text/markdown",
".html": "text/html",
".css": "text/css",
".js": "application/javascript",
".json": "application/json",
".xml": "application/xml",
".zip": "application/zip",
".mp4": "video/mp4",
".webm": "video/webm",
".mp3": "audio/mpeg",
".wav": "audio/wav",
".ogg": "audio/ogg"
};
return mimeTypes[ext] || "application/octet-stream";
}
}
var init_manager2 = __esm(() => {
init_src();
init_auth();
init_types3();
init_cache();
});
var init_fs = () => {};
var init_updater = __esm(() => {
init_fs();
});
var init_assets = __esm(() => {
init_types3();
init_manager2();
init_updater();
init_cache();
});
var init_cache2 = () => {};
var init_config_utils = __esm(() => {
init_auth();
});
var init_integrations = __esm(() => {
init_manager();
init_dependencies_parser();
init_cache2();
init_config_utils();
});
class InterfaceParser {
static parseInterfaceRef(versionString) {
const match = versionString.match(/^(?:([^/]+)\/)?([^@]+)@(.+)$/);
if (!match) {
throw new AdkError({
code: "INVALID_VERSION_FORMAT",
message: `Invalid interface version format: ${versionString}. Expected format: 'name@version' or 'workspace/name@version'`,
expected: true
});
}
const [, workspace, name, version] = match;
return {
workspace: workspace || undefined,
name,
version,
fullName: workspace ? `${workspace}/${name}` : name
};
}
static parseInterfaces(_dependencies) {
const interfaces = [];
const errors = [];
for (const [alias, versionString] of Object.entries(BUILTIN_INTERFACES)) {
try {
const ref = this.parseInterfaceRef(versionString);
if (!this.isValidVersion(ref.version)) {
errors.push(ValidationErrors.invalidVersionFormat(alias, ref.version));
}
interfaces.push({ alias, ref, config: undefined });
} catch (error) {
errors.push(ValidationErrors.invalidDependenciesSyntax(`Invalid interface '${alias}': ${error instanceof Error ? error.message : String(error)}`));
}
}
return { interfaces, errors };
}
static isValidVersion(version) {
if (version === "latest")
return true;
const semverPattern = /^(\d+)\.(\d+)\.(\d+)(-[\w.]+)?(\+[\w.]+)?$/;
return semverPattern.test(version);
}
}
var init_parser = __esm(() => {
init_src();
init_validation_errors();
init_constants();
});
class InterfaceCatalogSource {
clientFactory;
cacheConfig = { cacheType: "interfaces", idField: "interfaceId" };
constructor(clientFactory) {
this.clientFactory = clientFactory;
}
async fetchByRef(ref) {
const client = await this.clientFactory.getClient();
let interfaceResponse;
let versionError = null;
try {
interfaceResponse = await client.getInterfaceByName({
name: ref.name,
version: ref.version
});
} catch {
try {
interfaceResponse = await client.getPublicInterface({
name: ref.name,
version: ref.version
});
} catch (publicError) {
versionError = publicError;
}
}
if (!interfaceResponse && versionError) {
let latestVersion = null;
try {
const latestPrivate = await client.getInterfaceByName({
name: ref.name,
version: "latest"
});
latestVersion = latestPrivate.interface.version;
} catch {
try {
const latestPublic = await client.getPublicInterface({
name: ref.name,
version: "latest"
});
latestVersion = latestPublic.interface.version;
} catch {
const location2 = ref.workspace ? `workspace "${ref.workspace}"` : "the official Botpress hub";
throw new AdkError({
code: "INTERFACE_NOT_FOUND",
message: `Interface "${ref.name}" does not exist in ${location2}`,
expected: true
});
}
}
const location = ref.workspace ? `workspace "${ref.workspace}"` : "Botpress";
throw new AdkError({
code: "VERSION_NOT_FOUND",
message: `Interface "${ref.name}" version "${ref.version}" not found in ${location}. Latest available version is "${latestVersion}"`,
expected: true
});
}
const intf = interfaceResponse.interface;
return { id: intf.id, updatedAt: intf.updatedAt, definition: intf };
}
}
var init_interface_source = __esm(() => {
init_src();
});
class InterfaceManager {
service;
constructor(options = {}) {
const { noCache, ...clientOptions } = options;
this.service = new CatalogService(new InterfaceCatalogSource(new CatalogClientFactory(clientOptions)), noCache || false);
}
async loadInterfaces(dependencies) {
const errors = [];
const warnings = [];
const parseResult = InterfaceParser.parseInterfaces(dependencies);
const interfaces = parseResult.interfaces;
errors.push(...parseResult.errors);
const fetchPromises = interfaces.map(async (intf) => {
try {
const definition = await this.fetchInterface(intf.ref);
intf.definition = definition;
const validation = this.validateInterface(intf);
intf.validationResult = validation;
if (!validation.valid) {
validation.errors.forEach((msg) => {
errors.push(ValidationErrors.warning(msg, "agent.config.ts"));
});
}
} catch (error) {
if (error instanceof Error && error.message.includes("version")) {
errors.push(ValidationErrors.unknownInterface(error.message));
} else {
errors.push(ValidationErrors.invalidDependenciesSyntax(`Unknown interface '${intf.alias}' (${intf.ref.fullName})`));
}
}
});
await Promise.all(fetchPromises);
return { interfaces, errors, warnings };
}
async fetchInterface(ref) {
return this.service.getDefinition(ref);
}
validateInterface(intf) {
const errors = [];
const warnings = [];
if (!intf.definition) {
errors.push(`Interface definition not found for ${intf.alias}`);
return { valid: false, errors, warnings };
}
return { valid: errors.length === 0, errors, warnings };
}
async getCacheStats() {
return this.service.getCacheStats();
}
async clearCache() {
await this.service.clearCache();
}
}
var init_manager3 = __esm(() => {
init_parser();
init_validation_errors();
init_client_factory();
init_catalog_service();
init_interface_source();
});
var init_enhanced_cache = __esm(() => {
init_resolution_cache();
});
var init_interfaces = __esm(() => {
init_manager3();
init_parser();
init_enhanced_cache();
});
function orderKeys(obj, keyOrder) {
const objKeys = Object.keys(obj);
const orderedKeys = [];
const remainingKeys = [];
if (keyOrder) {
for (const key of keyOrder) {
const keyStr = String(key);
if (objKeys.includes(keyStr)) {
orderedKeys.push(keyStr);
}
}
}
for (const key of objKeys) {
if (!orderedKeys.includes(key)) {
remainingKeys.push(key);
}
}
remainingKeys.sort();
const finalKeys = [...orderedKeys, ...remainingKeys];
const result = {};
for (const key of finalKeys) {
result[key] = obj[key];
}
return result;
}
function orderIntegrationKeys(integrations) {
const result = {};
for (const [name, config] of Object.entries(integrations)) {
if (typeof config === "object" && config !== null && !Array.isArray(config)) {
result[name] = orderKeys(config, integrationKeyOrder);
} else {
result[name] = config;
}
}
return result;
}
function stringifyWithOrder(obj, keyOrder, space = 2) {
const orderedObj = orderKeys(obj, keyOrder);
if ("integrations" in orderedObj) {
const integrations = orderedObj.integrations;
if (integrations && typeof integrations === "object") {
orderedObj.integrations = orderIntegrationKeys(integrations);
}
}
return JSON.stringify(orderedObj, null, space);
}
var agentInfoKeyOrder;
var agentLocalInfoKeyOrder;
var dependenciesKeyOrder;
var integrationKeyOrder;
var init_json_ordering = __esm(() => {
agentInfoKeyOrder = ["botId", "workspaceId", "apiUrl"];
agentLocalInfoKeyOrder = ["botId", "workspaceId", "apiUrl", "devId"];
dependenciesKeyOrder = ["integrations"];
integrationKeyOrder = ["version", "enabled", "configurationType", "config"];
});
async function expandExports(options) {
const { absolutePath, relPath, filename, onWarning } = options;
let result = {};
try {
const importPath = `${absolutePath}?t=${Date.now()}`;
const exports = await import(importPath);
if (typeof exports === "object" && exports !== null) {
for (const key of Object.keys(exports)) {
try {
result[key] = exports[key];
} catch (error) {
if (Errors.isAdkError(error)) {
onWarning({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "warning",
message: error.message,
file: relPath,
hint: `Invalid primitive instantiation in ${filename} -> ${key}`
});
}
}
}
if (typeof exports.default === "object" && exports.default !== null) {
try {
const definition = Primitives.Definitions.getDefinition(exports.default);
if (!definition) {
for (const key of Object.keys(exports.default)) {
try {
result[`default.${key}`] = exports.default[key];
} catch (error) {
if (Errors.isAdkError(error)) {
onWarning({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "warning",
message: error.message,
file: relPath,
hint: `Invalid primitive instantiation in ${filename} -> default.${key}`
});
}
}
}
}
} catch (error) {
if (Errors.isAdkError(error)) {
onWarning({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "warning",
message: error.message,
file: relPath,
hint: `Invalid primitive instantiation in ${filename} -> default`
});
}
}
}
}
return result;
} catch (importError) {
if (Errors.isAdkError(importError)) {
onWarning({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "warning",
message: importError.message,
file: relPath,
hint: `Invalid primitive instantiation in ${filename}`
});
return {};
}
let currentError = importError;
while (currentError) {
if (Errors.isAdkError(currentError)) {
onWarning({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "warning",
message: currentError.message,
file: relPath,
hint: `Invalid primitive instantiation in ${filename}`
});
return {};
}
currentError = currentError instanceof Error ? currentError.cause : undefined;
}
throw importError;
}
}
var init_expand_exports = __esm(() => {
init_types2();
});
var BP_TSX_SUFFIX = ".bp.tsx";
var BP_CSS_SUFFIX = ".bp.css";
var BP_TYPES_SUFFIX = ".bp.types.ts";
var BP_BUNDLE_SUFFIXES;
var COMPONENTS_DIR = "src/components";
var init_component_files = __esm(() => {
BP_BUNDLE_SUFFIXES = [BP_TSX_SUFFIX, BP_CSS_SUFFIX, BP_TYPES_SUFFIX];
});
function resolveComponentSources(absolutePath) {
const project = new import_ts_morph.Project({ useInMemoryFileSystem: false, skipAddingFilesFromTsConfig: true });
const sourceFile = project.createSourceFile(absolutePath, readFileSync(absolutePath, "utf-8"), { overwrite: true });
const importBindings = collectBpTsxImports(sourceFile, absolutePath);
if (importBindings.size === 0) {
return new Map;
}
const localToSource = resolveLocalBindings(sourceFile, importBindings);
return resolveExports(sourceFile, localToSource, importBindings);
}
function collectBpTsxImports(sourceFile, absolutePath) {
const imports = new Map;
const dir = path8.dirname(absolutePath);
for (const decl of sourceFile.getImportDeclarations()) {
const specifier = decl.getModuleSpecifierValue();
if (!specifier.endsWith(BP_TSX_SUFFIX))
continue;
const resolved = path8.resolve(dir, specifier);
const defaultImport = decl.getDefaultImport();
if (defaultImport) {
imports.set(defaultImport.getText(), resolved);
}
const namespaceImport = decl.getNamespaceImport();
if (namespaceImport) {
imports.set(namespaceImport.getText(), resolved);
}
}
return imports;
}
function resolveLocalBindings(sourceFile, importBindings) {
const result = new Map;
for (const stmt of sourceFile.getVariableStatements()) {
for (const decl of stmt.getDeclarations()) {
const initializer = decl.getInitializer();
if (!initializer)
continue;
const source = sourceFromCustomComponentExpr(initializer, importBindings);
if (source) {
result.set(decl.getName(), source);
}
}
}
return result;
}
function resolveExports(sourceFile, localToSource, importBindings) {
const exportMap = new Map;
for (const stmt of sourceFile.getVariableStatements()) {
if (!stmt.isExported())
continue;
for (const decl of stmt.getDeclarations()) {
const source = localToSource.get(decl.getName());
if (source)
exportMap.set(decl.getName(), source);
}
}
for (const assignment of sourceFile.getExportAssignments()) {
if (assignment.isExportEquals())
continue;
const expr = assignment.getExpression();
const direct = sourceFromCustomComponentExpr(expr, importBindings);
if (direct) {
exportMap.set("default", direct);
continue;
}
if (expr.getKind() === import_ts_morph.SyntaxKind.Identifier) {
const fromLocal = localToSource.get(expr.getText());
if (fromLocal)
exportMap.set("default", fromLocal);
}
}
for (const decl of sourceFile.getExportDeclarations()) {
if (decl.getModuleSpecifier())
continue;
for (const named of decl.getNamedExports()) {
const localName = named.getNameNode().getText();
const aliasNode = named.getAliasNode();
const exportName = aliasNode ? aliasNode.getText() : localName;
const source = localToSource.get(localName);
if (source)
exportMap.set(exportName, source);
}
}
return exportMap;
}
function sourceFromCustomComponentExpr(expr, importBindings) {
if (expr.getKind() !== import_ts_morph.SyntaxKind.NewExpression)
return;
const newExpr = expr.asKindOrThrow(import_ts_morph.SyntaxKind.NewExpression);
if (newExpr.getExpression().getText() !== CUSTOM_COMPONENT_CLASS)
return;
const args = newExpr.getArguments();
const firstArg = args[0];
if (!firstArg || firstArg.getKind() !== import_ts_morph.SyntaxKind.Identifier)
return;
return importBindings.get(firstArg.getText());
}
var CUSTOM_COMPONENT_CLASS = "CustomComponent";
var init_component_source_resolver = __esm(() => {
init_component_files();
});
function getColumnCount(table) {
const properties = table.definition.schema?.properties;
return properties ? Object.keys(properties).length : 0;
}
function findTableColumnViolations(tables, max = MAX_TABLE_COLUMNS) {
return tables.map((table) => ({
name: table.definition.name,
path: table.path,
columnCount: getColumnCount(table)
})).filter((t) => t.columnCount > max);
}
var init_table_validation = __esm(() => {
init_constants();
});
function isBotpressInternalId(alias) {
return BP_INTERNAL_ID_PREFIXES.some((prefix) => alias.startsWith(prefix));
}
function isFriendlyAlias(alias) {
return FRIENDLY_ALIAS_RE.test(alias) && !isBotpressInternalId(alias);
}
function generateFriendlyAlias(cloudName, cloudAlias, used) {
if (isFriendlyAlias(cloudAlias) && !used.has(cloudAlias))
return cloudAlias;
const base = cloudName?.split("/").at(-1) ?? cloudAlias;
const sanitized = base.toLowerCase().replace(/[^a-z0-9_-]/g, "-");
let candidate = sanitized;
let n = 2;
while (used.has(candidate)) {
candidate = `${sanitized}-${n}`;
n++;
}
return candidate;
}
var FRIENDLY_ALIAS_RE;
var BP_INTERNAL_ID_PREFIXES;
var init_alias_utils = __esm(() => {
FRIENDLY_ALIAS_RE = /^[a-z0-9_-]{2,100}$/;
BP_INTERNAL_ID_PREFIXES = ["intver_", "plgver_", "int_", "plg_"];
});
class DependencySnapshotStore {
projectPath;
dirPath;
constructor(opts) {
this.projectPath = opts.projectPath;
this.dirPath = path9.join(opts.projectPath, ".adk", "dependencies");
}
getSnapshotPath(env) {
return path9.join(this.dirPath, `${env}.json`);
}
getMigrationMarkerPath() {
return path9.join(this.dirPath, "migration.json");
}
async exists(env) {
try {
await fs8.access(this.getSnapshotPath(env));
return true;
} catch {
return false;
}
}
async read(env, options) {
const filePath = this.getSnapshotPath(env);
let raw;
try {
raw = await fs8.readFile(filePath, "utf8");
} catch (err) {
if (err.code === "ENOENT")
return null;
throw err;
}
try {
return dependencySnapshotSchema.parse(JSON.parse(raw));
} catch (err) {
if (options?.tolerant)
return null;
throw new DependencyError({
code: "INVALID_CONFIG",
message: `Dependency snapshot at ${filePath} failed schema validation`,
details: { issues: err.issues ?? String(err) }
});
}
}
async readOrEmpty(env, options) {
const snapshot = await this.read(env, options);
return snapshot ?? emptyDependencySnapshot(env, options?.botId, options?.fetchedAt);
}
async write(snapshot) {
const validated = dependencySnapshotSchema.parse(snapshot);
await fs8.mkdir(this.dirPath, { recursive: true });
const filePath = this.getSnapshotPath(validated.env);
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
await fs8.writeFile(tmp, JSON.stringify(sortKeysDeep(validated), null, 2) + `
`, "utf8");
await fs8.rename(tmp, filePath);
}
async delete(env) {
try {
await fs8.unlink(this.getSnapshotPath(env));
} catch (err) {
if (err.code !== "ENOENT")
throw err;
}
}
async hasMigrationMarker() {
try {
await fs8.access(this.getMigrationMarkerPath());
return true;
} catch {
return false;
}
}
async writeMigrationMarker(marker) {
const validated = dependencyMigrationMarkerSchema.parse(marker);
await fs8.mkdir(this.dirPath, { recursive: true });
const filePath = this.getMigrationMarkerPath();
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
await fs8.writeFile(tmp, JSON.stringify(sortKeysDeep(validated), null, 2) + `
`, "utf8");
await fs8.rename(tmp, filePath);
}
async refreshFromCloud(opts) {
const previous = await this.readForRefresh(opts.env, opts.onWarning);
const { bot } = await opts.client.getBot({ id: opts.botId });
const snapshot = dependencySnapshotFromBot({
bot,
botId: opts.botId,
env: opts.env,
fetchedAt: opts.fetchedAt ?? new Date,
previous
});
await annotateAuthorizationPending({
snapshot,
bot,
integrationRegistry: opts.integrationRegistry
});
if (previous && cloudSnapshotUnchanged(previous, snapshot)) {
return previous;
}
await this.write(snapshot);
return snapshot;
}
async readForRefresh(env, onWarning) {
try {
return await this.read(env);
} catch (err) {
if (!(err instanceof DependencyError) || err.code !== "INVALID_CONFIG")
throw err;
const filePath = this.getSnapshotPath(env);
await this.delete(env);
onWarning?.({
code: "SNAPSHOT_CORRUPT",
message: `Removed corrupt dependency snapshot at ${filePath}; refreshing from Cloud.`,
env,
path: filePath
});
return null;
}
}
}
function emptyDependencySnapshot(env, botId = "local", fetchedAt = new Date(0)) {
return {
version: 1,
env,
botId,
fetchedAt: fetchedAt.toISOString(),
integrations: {},
plugins: {}
};
}
function cloudSnapshotUnchanged(previous, next) {
if (previous.botId !== next.botId || previous.env !== next.env)
return false;
if (previous.botUpdatedAt !== next.botUpdatedAt)
return false;
return jsonEqual({ integrations: previous.integrations, plugins: previous.plugins }, { integrations: next.integrations, plugins: next.plugins });
}
async function annotateAuthorizationPending(opts) {
const registry = opts.integrationRegistry;
if (!registry)
return;
const cloudIntegrations = opts.bot.integrations ?? {};
const specCache = new Map;
const getSpec = (name, version) => {
const key = `${name}@${version}`;
const cached = specCache.get(key);
if (cached)
return cached;
const promise = registry.getSpec(name, version).catch(() => null);
specCache.set(key, promise);
return promise;
};
await Promise.all(Object.entries(opts.snapshot.integrations).map(async ([alias, entry]) => {
const cloudAlias = entry.cloudAlias ?? alias;
const cloud = cloudIntegrations[cloudAlias];
if (!cloud)
return;
if (cloud.identifier) {
delete entry.authorizationPending;
return;
}
const spec = await getSpec(entry.name, entry.version);
if (!spec)
return;
const requiresAuthorization = integrationRequiresAuthorization(spec, entry.configurationType);
if (requiresAuthorization) {
entry.authorizationPending = true;
} else {
delete entry.authorizationPending;
}
}));
}
function dependencySnapshotFromBot(opts) {
const integrations = {};
const usedIntegrationAliases = new Set;
for (const [cloudAlias, cloud] of Object.entries(opts.bot.integrations ?? {})) {
const cloudName = typeof cloud.name === "string" ? cloud.name : undefined;
const config = cloud.configuration ?? {};
const version = cloud.version ?? "0.0.0";
const configurationType = typeof cloud.configurationType === "string" && cloud.configurationType && cloud.configurationType !== "default" ? cloud.configurationType : undefined;
const alias = chooseCloudAlias({
cloudAlias,
cloudName,
previous: opts.previous?.integrations ?? {},
used: usedIntegrationAliases,
matches: (entry) => entry.version === version && entry.configurationType === configurationType && jsonEqual(entry.config, config)
});
usedIntegrationAliases.add(alias);
const previousEntry = opts.previous?.integrations[alias];
const previousMissingFields = previousEntry?.missingFields?.length && !cloud.enabled && previousEntry.version === version && previousEntry.configurationType === configurationType && jsonEqual(previousEntry.config, config) ? previousEntry.missingFields : undefined;
integrations[alias] = {
name: cloud.name ?? "",
version,
enabled: Boolean(cloud.enabled),
config,
...configurationType ? { configurationType } : {},
...previousMissingFields ? { missingFields: previousMissingFields } : {},
...!cloud.identifier && previousEntry?.authorizationPending ? { authorizationPending: true } : {},
...typeof cloud.id === "string" ? { cloudId: cloud.id } : {},
cloudAlias,
...typeof cloud.updatedAt === "string" ? { updatedAt: cloud.updatedAt } : {}
};
}
const plugins = {};
const usedPluginAliases = new Set;
for (const [cloudAlias, cloud] of Object.entries(opts.bot.plugins ?? {})) {
const cloudName = typeof cloud.name === "string" ? cloud.name : undefined;
const config = cloud.configuration ?? {};
const version = cloud.version ?? "0.0.0";
const dependencies = {};
for (const [ifaceAlias, dep] of Object.entries(cloud.interfaces ?? {})) {
if (typeof dep.integrationAlias === "string") {
dependencies[ifaceAlias] = { integrationAlias: dep.integrationAlias };
}
}
const alias = chooseCloudAlias({
cloudAlias,
cloudName,
previous: opts.previous?.plugins ?? {},
used: usedPluginAliases,
matches: (entry) => entry.version === version && jsonEqual(entry.config, config)
});
usedPluginAliases.add(alias);
const previousEntry = opts.previous?.plugins[alias];
const previousMissingFields = previousEntry?.missingFields?.length && !(cloud.enabled ?? true) && previousEntry.version === version && jsonEqual(previousEntry.config, config) ? previousEntry.missingFields : undefined;
plugins[alias] = {
name: cloud.name ?? "",
version,
enabled: cloud.enabled ?? true,
config,
dependencies,
...previousMissingFields ? { missingFields: previousMissingFields } : {},
...typeof cloud.id === "string" ? { cloudId: cloud.id } : {},
cloudAlias,
...typeof cloud.updatedAt === "string" ? { updatedAt: cloud.updatedAt } : {}
};
}
return {
version: 1,
env: opts.env,
botId: opts.botId,
fetchedAt: opts.fetchedAt.toISOString(),
...typeof opts.bot.updatedAt === "string" ? { botUpdatedAt: opts.bot.updatedAt } : {},
integrations,
plugins
};
}
function chooseCloudAlias(opts) {
if (isFriendlyAlias(opts.cloudAlias) && opts.previous[opts.cloudAlias] && !opts.used.has(opts.cloudAlias)) {
return opts.cloudAlias;
}
if (opts.cloudName) {
if (opts.matches) {
const exactMatch = Object.entries(opts.previous).find(([alias, entry]) => !opts.used.has(alias) && entry.name === opts.cloudName && opts.matches(entry));
if (exactMatch)
return exactMatch[0];
}
const match = Object.entries(opts.previous).find(([alias, entry]) => !opts.used.has(alias) && entry.name === opts.cloudName);
if (match)
return match[0];
}
return generateFriendlyAlias(opts.cloudName, opts.cloudAlias, opts.used);
}
var init_snapshot_store = __esm(() => {
init_alias_utils();
init_errors2();
init_types();
});
function dependencyStateToDependencies(state) {
const integrations = {};
for (const [alias, entry] of Object.entries(state.integrations)) {
integrations[alias] = {
version: `${entry.name}@${entry.version}`,
enabled: entry.enabled,
config: entry.config,
...entry.configurationType ? { configurationType: entry.configurationType } : {}
};
}
const plugins = {};
for (const [alias, entry] of Object.entries(state.plugins)) {
plugins[alias] = {
version: `${entry.name}@${entry.version}`,
config: entry.config,
dependencies: entry.dependencies,
...entry.missingFields !== undefined ? { missingFields: entry.missingFields } : {}
};
}
return { integrations, plugins };
}
function getToolDefinition(value) {
if (typeof value !== "object" || value === null) {
return;
}
const maybeTool = value;
const isAutonomousToolInstance = value instanceof Autonomous.Tool;
const looksLikeAutonomousTool = typeof maybeTool.name === "string" && typeof maybeTool.execute === "function" && typeof maybeTool.toJSON === "function";
if (!isAutonomousToolInstance && !looksLikeAutonomousTool) {
return;
}
return {
name: maybeTool.name,
description: maybeTool.description
};
}
var debug;
var AgentProject;
var init_agent_project = __esm(() => {
init_src();
init_assets();
init_integrations();
init_interfaces();
init_json_ordering();
init_expand_exports();
init_component_source_resolver();
init_component_files();
init_agent_resolver();
init_types2();
init_validation_errors();
init_table_validation();
init_constants();
init_snapshot_store();
debug = import_debug.default("adk:agent-project");
AgentProject = class AgentProject2 {
static _projectCache = new Map;
_options;
_path;
_config;
_dependencies;
_agentInfo;
_state = "unloaded";
_errors = [];
_warnings = [];
_lastBuildTime;
_integrations;
_interfaces;
_integrationManager;
_interfaceManager;
_assetsManager;
_conversations = [];
_knowledge = [];
_triggers = [];
_workflows = [];
_actions = [];
_tables = [];
_customComponents = [];
_tools = [];
constructor(projectPath, options = {}) {
this._options = options;
this._path = path10.resolve(projectPath);
this._integrationManager = new IntegrationManager({
noCache: options.noCache
});
this._interfaceManager = new InterfaceManager({
noCache: options.noCache
});
this._assetsManager = new AssetsManager({
projectPath: this._path
});
}
static async load(projectPath, options = {}) {
const resolvedPath = path10.resolve(projectPath);
const cacheKey = AgentProject2._getCacheKey(resolvedPath, options);
if (!options.noCache) {
const cached = AgentProject2._projectCache.get(cacheKey);
if (cached) {
return cached;
}
}
const project = new AgentProject2(resolvedPath, options);
await project.reload();
if (!options.noCache) {
AgentProject2._projectCache.set(cacheKey, project);
}
return project;
}
static _getCacheKey(resolvedPath, options) {
const adkCommand = options.adkCommand ?? "default";
const offline = options.offline ? "offline" : "online";
return `${resolvedPath}\x00${adkCommand}\x00${offline}`;
}
static clearCache() {
AgentProject2._projectCache.clear();
}
static clearCacheForPath(projectPath) {
const resolvedPath = path10.resolve(projectPath);
const prefix = `${resolvedPath}\x00`;
for (const key of AgentProject2._projectCache.keys()) {
if (key.startsWith(prefix)) {
AgentProject2._projectCache.delete(key);
}
}
}
get path() {
return this._path;
}
get conversations() {
return this._conversations;
}
get knowledge() {
return this._knowledge;
}
get triggers() {
return this._triggers;
}
get workflows() {
return this._workflows;
}
get actions() {
return this._actions;
}
get tables() {
return this._tables;
}
get customComponents() {
return this._customComponents;
}
get tools() {
return this._tools;
}
get config() {
return this._config;
}
get dependencies() {
return this._dependencies;
}
get agentInfo() {
return this._agentInfo;
}
get state() {
return this._state;
}
get info() {
return {
path: this._path,
config: this._config,
dependencies: this._dependencies || {
integrations: {}
},
agentInfo: this._agentInfo,
state: this._state,
lastBuildTime: this._lastBuildTime,
errors: [...this._errors],
warnings: [...this._warnings],
errorCount: this._errors.filter((e) => e.severity === "error").length,
warningCount: this._warnings.filter((e) => e.severity === "warning").length,
infoCount: [...this._errors, ...this._warnings].filter((e) => e.severity === "info").length
};
}
async reload() {
this._state = "loading";
try {
this._conversations = [];
this._knowledge = [];
this._triggers = [];
this._workflows = [];
this._actions = [];
this._tables = [];
this._tools = [];
this._errors = [];
this._warnings = [];
this._customComponents = [];
const validation = await this.validate();
this._errors = validation.errors;
this._warnings = validation.warnings;
if (!validation.valid) {
this._state = "error";
throw new AdkError({
code: "PROJECT_VALIDATION_FAILED",
expected: true,
message: `Project validation failed: ${validation.errors[0]?.message}`,
details: { errors: validation.errors }
});
}
await this.loadAgentInfo();
this._integrationManager = new IntegrationManager({
noCache: this._options.noCache,
project: this
});
this._interfaceManager = new InterfaceManager({
noCache: this._options.noCache,
project: this
});
this._assetsManager = new AssetsManager({
projectPath: this._path,
botId: this._agentInfo?.botId
});
await this.loadConfig();
await this.loadBuiltInWorkflows();
await this.loadBuiltInActions();
await this.loadAgentPrimitives();
if (this._errors.length > 0) {
this._state = "error";
} else {
this._state = "ready";
}
} catch (error) {
this._state = "error";
throw error;
}
}
async validate() {
const errors = [];
const warnings = [];
const info = [];
try {
await fs9.access(this._path);
const requiredFiles = ["agent.config.ts"];
for (const file of requiredFiles) {
try {
await fs9.access(path10.join(this._path, file));
} catch {
errors.push(ValidationErrors.requiredFileMissing(file));
}
}
try {
await fs9.access(path10.join(this._path, "agent.json"));
} catch {
info.push({
$type: "ValidationError",
code: "MISSING_REQUIRED_FIELD",
severity: "info",
message: "agent.json not found - this file will be required for deployment and remote operations",
file: "agent.json",
hint: "Create agent.json with botId and workspaceId after deploying your agent"
});
}
const expectedDirs = ["actions", "workflows", "conversations", "assets"];
for (const dir of expectedDirs) {
try {
const stats = await fs9.stat(path10.join(this._path, dir));
if (!stats.isDirectory()) {
warnings.push(ValidationErrors.invalidStructure(dir, "directory"));
}
} catch {}
}
} catch (error) {
errors.push(ValidationErrors.directoryAccessError(this._path, String(error)));
}
for (const violation of findTableColumnViolations(this._tables)) {
errors.push(ValidationErrors.tableTooManyColumns(violation.name, violation.path, violation.columnCount, MAX_TABLE_COLUMNS));
}
const errorsBySeverity = {
errors: errors.filter((e) => e.severity === "error"),
warnings: [...warnings, ...errors.filter((e) => e.severity === "warning")],
info: [...info, ...errors.filter((e) => e.severity === "info")]
};
return {
valid: errorsBySeverity.errors.length === 0,
errors: errorsBySeverity.errors,
warnings: errorsBySeverity.warnings,
info: errorsBySeverity.info,
errorCount: errorsBySeverity.errors.length,
warningCount: errorsBySeverity.warnings.length,
infoCount: errorsBySeverity.info.length
};
}
get integrations() {
if (!this._integrations) {
throw new AdkError({ code: "PROJECT_NOT_LOADED", expected: false, message: "Integrations not loaded" });
}
return this._integrations;
}
get interfaces() {
if (!this._interfaces) {
throw new AdkError({ code: "PROJECT_NOT_LOADED", expected: false, message: "Interfaces not loaded" });
}
return this._interfaces;
}
async getIntegrations() {
if (!this._dependencies || !this._integrations) {
throw new AdkError({ code: "PROJECT_NOT_LOADED", expected: false, message: "Project not loaded" });
}
return this._integrations.map((parsed) => ({
name: parsed.alias,
version: parsed.ref.version,
workspace: parsed.ref.workspace,
config: parsed.config,
installed: false,
installedVersion: undefined,
hasChannels: parsed.definition?.channels && Object.keys(parsed.definition.channels).length > 0
}));
}
async createAssetSyncPlan() {
if (this._state !== "ready") {
throw new AdkError({
code: "PROJECT_NOT_READY",
expected: false,
message: "Project must be in Ready state to create asset sync plan"
});
}
this.requiresAgentInfo("create asset sync plan");
return await this._assetsManager.createSyncPlan();
}
async syncAssets(options) {
if (this._state !== "ready") {
throw new AdkError({
code: "PROJECT_NOT_READY",
expected: false,
message: "Project must be in Ready state to sync assets"
});
}
this.requiresAgentInfo("sync assets");
const plan = await this._assetsManager.createSyncPlan();
return await this._assetsManager.executeSync(plan, options);
}
async hasAssetsDirectory() {
return await this._assetsManager.hasAssetsDirectory();
}
get assetsManager() {
return this._assetsManager;
}
async createAgentInfo(info) {
const agentJsonData = {
botId: info.botId,
workspaceId: info.workspaceId,
apiUrl: info.apiUrl
};
const agentPath = path10.join(this._path, "agent.json");
const agentContent = stringifyWithOrder(agentJsonData, agentInfoKeyOrder);
await fs9.writeFile(agentPath, agentContent);
this._agentInfo = agentJsonData;
}
async updateAgentInfo(updates) {
if (!this._agentInfo) {
throw new AdkError({
code: "AGENT_INFO_MISSING",
expected: false,
message: "No agent.json found. Use createAgentInfo() first."
});
}
const updatedInfo = {
botId: updates.botId ?? this._agentInfo.botId,
workspaceId: updates.workspaceId ?? this._agentInfo.workspaceId,
apiUrl: "apiUrl" in updates ? updates.apiUrl : this._agentInfo.apiUrl,
...this._agentInfo.devId ? { devId: this._agentInfo.devId } : {}
};
const agentJsonData = {
botId: updatedInfo.botId,
workspaceId: updatedInfo.workspaceId,
apiUrl: updatedInfo.apiUrl
};
const agentPath = path10.join(this._path, "agent.json");
const agentContent = stringifyWithOrder(agentJsonData, agentInfoKeyOrder);
await fs9.writeFile(agentPath, agentContent);
this._agentInfo = updatedInfo;
}
async createAgentLocalInfo(info) {
const localPath = path10.join(this._path, "agent.local.json");
let existing = {};
try {
const content2 = await fs9.readFile(localPath, "utf-8");
existing = JSON.parse(content2);
} catch {}
const merged = { ...existing, ...info };
const content = stringifyWithOrder(merged, agentLocalInfoKeyOrder);
await fs9.writeFile(localPath, content);
if (this._agentInfo) {
if (merged.botId)
this._agentInfo.botId = merged.botId;
if (merged.workspaceId)
this._agentInfo.workspaceId = merged.workspaceId;
if (merged.apiUrl)
this._agentInfo.apiUrl = merged.apiUrl;
if (merged.devId)
this._agentInfo.devId = merged.devId;
} else if (merged.botId && merged.workspaceId) {
this._agentInfo = {
botId: merged.botId,
workspaceId: merged.workspaceId,
apiUrl: merged.apiUrl,
devId: merged.devId
};
}
}
async updateAgentLocalInfo(updates) {
const localPath = path10.join(this._path, "agent.local.json");
let existing = {};
try {
const content = await fs9.readFile(localPath, "utf-8");
existing = JSON.parse(content);
} catch {}
const updated = { ...existing, ...updates };
for (const key of Object.keys(updated)) {
if (updated[key] === undefined) {
delete updated[key];
}
}
if (Object.keys(updated).length === 0) {
try {
await fs9.unlink(localPath);
} catch {}
} else {
const content = stringifyWithOrder(updated, agentLocalInfoKeyOrder);
await fs9.writeFile(localPath, content);
}
if (this._agentInfo) {
const agentJsonPath = path10.join(this._path, "agent.json");
let base = {
botId: this._agentInfo.botId,
workspaceId: this._agentInfo.workspaceId,
apiUrl: this._agentInfo.apiUrl
};
try {
const agentJsonContent = await fs9.readFile(agentJsonPath, "utf-8");
const parsed = agentInfoSchema.parse(JSON.parse(agentJsonContent));
base = {
botId: parsed.botId,
workspaceId: parsed.workspaceId,
apiUrl: parsed.apiUrl
};
} catch {}
const local = updated;
this._agentInfo = {
botId: local.botId ?? base.botId,
workspaceId: local.workspaceId ?? base.workspaceId,
apiUrl: local.apiUrl ?? base.apiUrl,
devId: local.devId
};
}
}
requiresAgentInfo(operation) {
if (!this._agentInfo?.botId) {
throw new AdkError({
code: "BOT_ID_REQUIRED",
expected: true,
message: `Operation "${operation}" requires a bot ID. Please create agent.json with botId and workspaceId after deploying your agent.`,
suggestion: "Create agent.json with botId and workspaceId after deploying your agent."
});
}
}
async loadConfig() {
this._dependencies = { integrations: {} };
this._integrations = [];
this._interfaces = [];
try {
const configPath = path10.join(this._path, "agent.config.ts");
debug("loading agent.config.ts from %s", configPath);
const projectRequire = createRequire2(path10.join(this._path, "package.json"));
debug("created projectRequire from %s", path10.join(this._path, "package.json"));
try {
const resolvedPath = projectRequire.resolve(configPath);
debug("resolved config path: %s", resolvedPath);
if (projectRequire.cache[resolvedPath]) {
debug("clearing require.cache for %s", resolvedPath);
delete projectRequire.cache[resolvedPath];
}
} catch {
debug("config not in require.cache (first load)");
}
let configModule;
try {
const configUrl = `${configPath}?t=${Date.now()}`;
debug("importing from: %s", configUrl);
configModule = await import(configUrl);
debug("successfully loaded agent.config.ts");
} catch (importError) {
debug("failed to load agent.config.ts: %O", importError);
throw importError;
}
if (!configModule || !configModule.default) {
this._errors.push({
$type: "ValidationError",
code: "INVALID_CONFIG_SCHEMA",
severity: "error",
message: "agent.config.ts does not export a default configuration object",
file: "agent.config.ts"
});
return;
}
const { isAgentConfig } = await import("./chunk-qk5pnkf4.js");
if (!isAgentConfig(configModule.default)) {
this._errors.push({
$type: "ValidationError",
code: "INVALID_CONFIG_SCHEMA",
severity: "error",
message: "agent.config.ts must export the result of defineConfig(). Example: export default defineConfig({ name: 'my-agent', ... })",
file: "agent.config.ts",
hint: "Wrap your config object with defineConfig() from @botpress/runtime"
});
return;
}
this._config = configModule.default;
const dependencyEnv = this._options.adkCommand === "adk-deploy" ? "prod" : "dev";
const snapshotStore = new DependencySnapshotStore({ projectPath: this._path });
let snapshotBasedDependencies;
let dependencySnapshotReadFailed = false;
try {
const snapshot = await snapshotStore.read(dependencyEnv);
const state = snapshot ?? { version: 1, env: dependencyEnv, integrations: {}, plugins: {} };
const hasDependencies = Object.keys(state.integrations).length > 0 || Object.keys(state.plugins).length > 0;
if (hasDependencies) {
snapshotBasedDependencies = dependencyStateToDependencies(state);
}
} catch (err) {
this._warnings.push({
$type: "ValidationError",
code: "INVALID_DEPENDENCIES_SCHEMA",
severity: "warning",
message: `Could not read .adk/dependencies/${dependencyEnv}.json: ${err.message}. Refresh dependencies from Cloud; agent.config.ts dependencies is not used when a snapshot exists but is invalid.`,
file: `.adk/dependencies/${dependencyEnv}.json`
});
dependencySnapshotReadFailed = true;
}
if (snapshotBasedDependencies) {
this._dependencies = snapshotBasedDependencies;
if (!this._options.offline) {
const [intRes, ifRes] = await Promise.all([
this._integrationManager.loadIntegrations(this._dependencies),
this._interfaceManager.loadInterfaces(this._dependencies)
]);
this._integrations = intRes.integrations;
this._interfaces = ifRes.interfaces;
this._errors.push(...intRes.errors, ...ifRes.errors);
this._warnings.push(...intRes.warnings, ...ifRes.warnings);
} else {
this._integrations = [];
this._interfaces = [];
}
} else {
this._dependencies = { integrations: {} };
this._integrations = [];
this._interfaces = [];
}
} catch (error) {
const err = error;
debug("loadConfig error: %O", err);
let detailedMessage = `Failed to load agent.config.ts: ${err.message}`;
if (err.stack) {
debug(`stack trace:
%s`, err.stack);
}
let hint;
if (err.message.includes("Cannot find module")) {
const moduleMatch = err.message.match(/Cannot find module '([^']+)'/);
const moduleName = moduleMatch?.[1] || "unknown";
hint = `Module "${moduleName}" is not installed. Run "bun install" to install dependencies.`;
detailedMessage += `
Stack trace:
${err.stack?.split(`
`).slice(0, 5).join(`
`)}`;
}
this._errors.push({
$type: "ValidationError",
code: "INVALID_CONFIG_SYNTAX",
severity: "error",
message: detailedMessage,
file: "agent.config.ts",
hint
});
}
}
async loadAgentInfo() {
try {
const agentInfo = await resolveAgent(this._path, { required: false });
this._agentInfo = agentInfo ?? undefined;
} catch (error) {
if (ValidationErrors.isValidationError(error)) {
this._errors.push(error);
} else if (error instanceof Error) {
this._errors.push(ValidationErrors.warning(`Failed to load agent.json: ${error.message}`, "agent.json"));
}
this._agentInfo = undefined;
}
}
async loadBuiltInWorkflows() {
for (const wf of Object.values(BuiltInWorkflows)) {
const definition = Primitives.Definitions.getDefinition(wf);
if (Primitives.Definitions.isWorkflowDefinition(definition)) {
this._workflows.push({
definition,
export: "default",
path: "<adk:builtin>"
});
}
}
}
async loadBuiltInActions() {
for (const action of Object.values(BuiltInActions)) {
const definition = Primitives.Definitions.getDefinition(action);
if (Primitives.Definitions.isActionDefinition(definition)) {
this._actions.push({
definition,
export: "default",
path: "<adk:builtin>"
});
}
}
}
async registerDataSourceWorkflows(knowledgeBase, kbPath, kbExport) {
try {
if (!knowledgeBase.sources || !Array.isArray(knowledgeBase.sources)) {
return;
}
for (const source of knowledgeBase.sources) {
if (!source.syncWorkflow) {
continue;
}
const workflowDefinition = source.syncWorkflow.getDefinition();
const existing = this._workflows.find((p) => p.definition.name === workflowDefinition.name);
if (existing) {
continue;
}
this._workflows.push({
definition: workflowDefinition,
export: `${kbExport}.sources[${knowledgeBase.sources.indexOf(source)}].syncWorkflow`,
path: kbPath
});
}
} catch (error) {
console.warn(`Failed to register data source workflows for ${kbPath}:`, error);
}
}
isBarrelReexport(existing, newPath, newExport, newDefinition) {
const isNewBarrel = /^index\.[tj]s$/i.test(path10.basename(newPath));
const isExistingBarrel = /^index\.[tj]s$/i.test(path10.basename(existing.path));
if (!isNewBarrel && !isExistingBarrel) {
return false;
}
if (isExistingBarrel && !isNewBarrel) {
existing.path = newPath;
existing.export = newExport;
existing.definition = newDefinition;
}
return true;
}
isToolBarrelReexport(existingPath, newPath) {
const isNewBarrel = /^index\.[tj]s$/i.test(path10.basename(newPath));
const isExistingBarrel = /^index\.[tj]s$/i.test(path10.basename(existingPath));
if (!isNewBarrel && !isExistingBarrel) {
return false;
}
return true;
}
shouldPreferToolDefinition(existingPath, newPath) {
const isNewBarrel = /^index\.[tj]s$/i.test(path10.basename(newPath));
const isExistingBarrel = /^index\.[tj]s$/i.test(path10.basename(existingPath));
return isExistingBarrel && !isNewBarrel;
}
getChannelsList(channelSpec) {
if (channelSpec === "*") {
return ["*"];
} else if (Array.isArray(channelSpec)) {
return channelSpec;
} else {
return [channelSpec];
}
}
async loadAgentPrimitives() {
if (this._options.adkCommand) {
setAdkCommand(this._options.adkCommand);
}
const src = path10.join(this._path, "src");
if (!await fs9.stat(src).catch(() => false)) {
this._errors.push({
$type: "ValidationError",
code: "INVALID_STRUCTURE",
severity: "error",
message: `\`src\` directory not found at expected path: ${src}`
});
return;
}
const allFiles = await fs9.readdir(src, {
withFileTypes: true,
recursive: true
});
for (const file of allFiles.filter((x) => x.isFile())) {
const filename = file.name;
const absolutePath = path10.join(file.parentPath, file.name);
const relPath = path10.relative(this.path, absolutePath);
if (filename.toLowerCase().endsWith(".d.ts")) {
continue;
}
if (filename.toLowerCase().endsWith(".test.ts") || filename.toLowerCase().endsWith(".test.js")) {
continue;
}
if (!filename.toLowerCase().endsWith(".js") && !filename.toLowerCase().endsWith(".ts")) {
continue;
}
if (file.isSymbolicLink()) {
this._errors.push({
$type: "ValidationError",
code: "INVALID_FILE_TYPE",
severity: "warning",
message: `Skipping symbolic link in conversations directory: ${relPath}`,
file: relPath
});
continue;
}
try {
const expandedExports = await expandExports({
absolutePath,
relPath,
filename,
onWarning: (warning) => this._warnings.push(warning)
});
let componentSources;
for (const key of Object.keys(expandedExports)) {
let definition;
try {
definition = Primitives.Definitions.getDefinition(expandedExports[key]);
} catch (error) {
if (Errors.isAdkError(error)) {
this._warnings.push({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "warning",
message: error.message,
file: relPath,
hint: `Check the primitive definition in ${filename} -> ${key}`
});
continue;
}
throw error;
}
if (Primitives.Definitions.isConversationDefinition(definition)) {
const overlapping = this._conversations.find((p) => {
const existingChannels = this.getChannelsList(p.definition.channel);
const newChannels = this.getChannelsList(definition.channel);
return existingChannels.some((ch) => newChannels.includes(ch));
});
if (overlapping) {
if (this.isBarrelReexport(overlapping, relPath, key, definition)) {
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "DUPLICATE_PRIMITIVE",
severity: "warning",
message: `Overlapping conversation channels found: ${filename} -> ${key} overlaps with ${overlapping.path} -> ${overlapping.export}`,
file: relPath
});
continue;
}
this._conversations.push({
definition,
export: key,
path: relPath
});
} else if (Primitives.Definitions.isKnowledgeDefinition(definition)) {
const existing = this._knowledge.find((p) => p.definition.name === definition.name);
if (existing) {
if (this.isBarrelReexport(existing, relPath, key, definition)) {
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "DUPLICATE_PRIMITIVE",
severity: "warning",
message: `Duplicate knowledge definition found: ${filename} -> ${key} (already defined in ${existing.path} -> ${existing.export})`,
file: relPath
});
continue;
}
this._knowledge.push({
definition,
export: key,
path: relPath
});
await this.registerDataSourceWorkflows(expandedExports[key], relPath, key);
} else if (Primitives.Definitions.isTriggerDefinition(definition)) {
const existing = this._triggers.find((p) => p.definition.name === definition.name);
if (existing) {
if (this.isBarrelReexport(existing, relPath, key, definition)) {
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "DUPLICATE_PRIMITIVE",
severity: "warning",
message: `Duplicate trigger definition found: ${filename} -> ${key} (already defined in ${existing.path} -> ${existing.export})`,
file: relPath
});
continue;
}
this._triggers.push({
definition,
export: key,
path: relPath
});
} else if (Primitives.Definitions.isWorkflowDefinition(definition)) {
const existing = this._workflows.find((p) => p.definition.name === definition.name);
if (existing) {
if (this.isBarrelReexport(existing, relPath, key, definition)) {
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "DUPLICATE_PRIMITIVE",
severity: "warning",
message: `Duplicate workflow definition found: ${filename} -> ${key} (already defined in ${existing.path} -> ${existing.export})`,
file: relPath
});
continue;
}
this._workflows.push({
definition,
export: key,
path: relPath
});
} else if (Primitives.Definitions.isActionDefinition(definition)) {
const existing = this._actions.find((p) => p.definition.name === definition.name);
if (existing) {
if (this.isBarrelReexport(existing, relPath, key, definition)) {
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "DUPLICATE_PRIMITIVE",
severity: "warning",
message: `Duplicate action definition found: ${filename} -> ${key} (already defined in ${existing.path} -> ${existing.export})`,
file: relPath
});
continue;
}
this._actions.push({
definition,
export: key,
path: relPath
});
} else if (Primitives.Definitions.isCustomComponentDefinition(definition)) {
const posixRelPath = relPath.split(/[\\/]+/).join("/");
if (!posixRelPath.startsWith(`${COMPONENTS_DIR}/`)) {
this._warnings.push({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "warning",
message: `Custom components must be defined under \`${COMPONENTS_DIR}/\`. Found "${definition.name}" in ${relPath}.`,
file: relPath
});
continue;
}
if (componentSources === undefined) {
try {
componentSources = resolveComponentSources(absolutePath);
} catch (error) {
componentSources = new Map;
this._warnings.push({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "warning",
message: `Failed to parse component sources from ${relPath}: ${error instanceof Error ? error.message : String(error)}`,
file: relPath
});
}
}
const source = componentSources.get(key);
const existing = this._customComponents.find((p) => p.definition.name === definition.name);
if (existing) {
if (this.isBarrelReexport(existing, relPath, key, definition)) {
if (existing.path === relPath && source) {
existing.source = source;
}
existing.instance = expandedExports[key];
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "DUPLICATE_PRIMITIVE",
severity: "warning",
message: `Duplicate custom component definition found: ${filename} -> ${key} (already defined in ${existing.path} -> ${existing.export})`,
file: relPath
});
continue;
}
this._customComponents.push({
definition,
export: key,
path: relPath,
source: source ?? "",
instance: expandedExports[key]
});
} else if (Primitives.Definitions.isTableDefinition(definition)) {
const existing = this._tables.find((p) => p.definition.name === definition.name);
if (existing) {
if (this.isBarrelReexport(existing, relPath, key, definition)) {
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "DUPLICATE_PRIMITIVE",
severity: "warning",
message: `Duplicate table definition found: ${filename} -> ${key} (already defined in ${existing.path} -> ${existing.export})`,
file: relPath
});
continue;
}
this._tables.push({
definition,
export: key,
path: relPath
});
} else {
const toolDefinition = getToolDefinition(expandedExports[key]);
if (!toolDefinition) {
continue;
}
const existing = this._tools.find((tool) => tool.definition.name === toolDefinition.name);
if (existing) {
if (this.isToolBarrelReexport(existing.path, relPath)) {
if (this.shouldPreferToolDefinition(existing.path, relPath)) {
existing.path = relPath;
existing.export = key;
existing.definition = toolDefinition;
}
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "DUPLICATE_PRIMITIVE",
severity: "warning",
message: `Duplicate tool definition found: ${filename} -> ${key} (already defined in ${existing.path} -> ${existing.export})`,
file: relPath
});
continue;
}
this._tools.push({
definition: toolDefinition,
export: key,
path: relPath
});
}
}
} catch (error) {
if (Errors.isAdkError(error)) {
continue;
}
this._warnings.push({
$type: "ValidationError",
code: "IMPORT_ERROR",
severity: "warning",
message: `Failed to import primitive from ${relPath}: ${error instanceof Error ? error.message : String(error)}`,
file: relPath,
hint: "Ensure the file exports valid primitives and has no syntax errors"
});
}
}
const unresolved = this._customComponents.filter((c) => !c.source);
if (unresolved.length > 0) {
this._customComponents = this._customComponents.filter((c) => c.source);
for (const comp of unresolved) {
this._errors.push({
$type: "ValidationError",
code: "INVALID_PRIMITIVE_DEFINITION",
severity: "error",
message: `Cannot resolve .bp.tsx source for custom component "${comp.definition.name}" exported from ${comp.path} as "${comp.export}".`,
file: comp.path,
hint: "Each CustomComponent must be constructed from a default-imported .bp.tsx component, e.g. `import Foo from './Foo.bp.tsx'; export const FooComponent = new CustomComponent(Foo, ...)`."
});
}
}
}
};
});
async function getFormat() {
if (!_formatLoaded) {
_formatLoaded = true;
try {
const oxfmt = await import("oxfmt");
_format = oxfmt.format;
} catch {}
}
return _format;
}
var _format = null;
var _formatLoaded = false;
var formatCode = async (code, filepath) => {
try {
if (!code || code.length > 1e6) {
return code;
}
const format = await getFormat();
if (!format)
return code;
const fileName = filepath || "file.ts";
const result = await format(fileName, code);
return result.code;
} catch (err) {
console.warn("Failed to format code with oxfmt:", err);
console.warn(code.slice(0, 1000).split(`
`).map((l, i) => ` ${i.toString().padStart(2, "0")} | ${l}`).join(`
`));
return code;
}
};
var init_utils = () => {};
var exports_dependencies = {};
__export(exports_dependencies, {
sortKeysDeep: () => sortKeysDeep,
resolveDependencyStatuses: () => resolveDependencyStatuses,
migrateFromConfig: () => migrateFromConfig,
jsonEqual: () => jsonEqual,
isCallable: () => isCallable,
emptyDependencySnapshot: () => emptyDependencySnapshot,
dependencyStateSchema: () => dependencyStateSchema,
dependencySnapshotFromBot: () => dependencySnapshotFromBot,
computePluginStatus: () => computePluginStatus,
computeIntegrationStatus: () => computeIntegrationStatus,
PluginResolver: () => PluginResolver,
PluginRegistry: () => PluginRegistry,
InterfaceRegistry: () => InterfaceRegistry,
IntegrationResolver: () => IntegrationResolver,
IntegrationRegistry: () => IntegrationRegistry,
DependencySnapshotStore: () => DependencySnapshotStore,
DependencyMigrationManager: () => DependencyMigrationManager,
DependencyManager: () => DependencyManager,
DependencyError: () => DependencyError,
DEPENDENCY_WARNING_CODES: () => DEPENDENCY_WARNING_CODES,
DEPENDENCY_ERROR_CODES: () => DEPENDENCY_ERROR_CODES
});
init_types();
init_errors2();
init_errors2();
class IntegrationResolver {
registry;
client;
constructor(opts) {
this.registry = opts.registry;
this.client = opts.client;
}
toDependencyEntry(cloud) {
const entry = {
name: cloud.name ?? "",
version: cloud.version ?? "0.0.0",
enabled: Boolean(cloud.enabled),
config: cloud.configuration ?? {}
};
if (typeof cloud.configurationType === "string" && cloud.configurationType && cloud.configurationType !== "default") {
entry.configurationType = cloud.configurationType;
}
return entry;
}
async applyToCloud(opts) {
const spec = await this.registry.getSpec(opts.entry.name, opts.entry.version);
const integrationId = spec.id;
if (!integrationId) {
throw new DependencyError({
code: "INTEGRATION_NOT_FOUND",
message: `Could not resolve integrationId for ${opts.entry.name}@${opts.entry.version}`
});
}
await this.client.updateBot({
id: opts.botId,
integrations: {
[opts.alias]: {
integrationId,
enabled: opts.entry.enabled,
configuration: opts.entry.config
}
}
});
}
async removeFromCloud(opts) {
await this.client.updateBot({
id: opts.botId,
integrations: { [opts.alias]: null }
});
}
}
init_errors2();
class PluginResolver {
registry;
integrationRegistry;
client;
constructor(opts) {
this.registry = opts.registry;
this.integrationRegistry = opts.integrationRegistry;
this.client = opts.client;
}
toDependencyEntry(cloud) {
const dependencies = {};
for (const [ifaceAlias, dep] of Object.entries(cloud.interfaces ?? {})) {
if (dep.integrationAlias) {
dependencies[ifaceAlias] = { integrationAlias: dep.integrationAlias };
}
}
return {
name: cloud.name ?? "",
version: cloud.version ?? "0.0.0",
enabled: cloud.enabled ?? true,
config: cloud.configuration ?? {},
dependencies
};
}
async applyToCloud(opts) {
const pluginSpec = await this.registry.getSpec(opts.entry.name, opts.entry.version);
const requiredInterfaces = pluginSpec.dependencies?.interfaces ?? {};
const resolvedInterfaces = {};
for (const [pluginIfaceAlias, requirement] of Object.entries(requiredInterfaces)) {
const dep = opts.entry.dependencies[pluginIfaceAlias];
if (!dep) {
throw new DependencyError({
code: "MISSING_DEPENDENCY",
message: `Plugin '${opts.alias}' is missing dependency '${pluginIfaceAlias}' (needs an integration implementing '${requirement.name}')`,
details: { plugin: opts.alias, pluginInterfaceAlias: pluginIfaceAlias, interfaceName: requirement.name }
});
}
const integration = opts.state.integrations[dep.integrationAlias];
if (!integration) {
throw new DependencyError({
code: "MISSING_DEPENDENCY",
message: `Plugin '${opts.alias}' references integration alias '${dep.integrationAlias}' which is not installed`,
details: { plugin: opts.alias, integrationAlias: dep.integrationAlias },
suggestion: `Run: adk integrations add ${dep.integrationAlias}`
});
}
const integrationSpec = await this.integrationRegistry.getSpec(integration.name, integration.version);
const integrationIfaceAlias = Object.entries(integrationSpec.interfaces ?? {}).find(([, def]) => def.name === requirement.name)?.[0];
if (!integrationIfaceAlias) {
throw new DependencyError({
code: "INTERFACE_NOT_IMPLEMENTED",
message: `Integration '${integration.name}' does not implement interface '${requirement.name}' required by plugin '${opts.alias}'`,
details: { integration: integration.name, interfaceName: requirement.name }
});
}
resolvedInterfaces[pluginIfaceAlias] = {
integrationId: integrationSpec.id,
integrationAlias: dep.integrationAlias,
integrationInterfaceAlias: integrationIfaceAlias
};
}
const pluginId = pluginSpec.id;
if (!pluginId) {
throw new DependencyError({
code: "PLUGIN_NOT_FOUND",
message: `Could not resolve plugin id for ${opts.entry.name}@${opts.entry.version}`
});
}
await this.client.updateBot({
id: opts.botId,
plugins: {
[opts.alias]: {
id: pluginId,
enabled: opts.entry.enabled,
configuration: opts.entry.config,
interfaces: resolvedInterfaces
}
}
});
}
async removeFromCloud(opts) {
await this.client.updateBot({ id: opts.botId, plugins: { [opts.alias]: null } });
}
}
init_manager();
class IntegrationRegistry {
manager;
constructor(opts = {}) {
this.manager = opts.manager ?? new IntegrationManager(opts.managerOptions);
}
async getSpec(name, version) {
const ref = {
name,
version: version ?? "latest",
fullName: name
};
return this.manager.fetchIntegration(ref);
}
async search(_query) {
return this.manager.searchIntegrations(_query);
}
async findImplementersOfInterface(interfaceName, limit) {
return this.manager.findImplementersOfInterface(interfaceName, limit);
}
async listVersions(name) {
return this.manager.listIntegrationVersions(name);
}
}
init_dependencies_parser();
init_validation_errors();
init_client_factory();
init_catalog_service();
init_src();
init_search_ranking();
class PluginCatalogSource {
clientFactory;
cacheConfig = { cacheType: "plugins", idField: "pluginId" };
constructor(clientFactory) {
this.clientFactory = clientFactory;
}
async fetchByRef(ref) {
const client = await this.clientFactory.getClient();
const plugin = await this._findPublicPlugin(client, ref);
if (!plugin) {
throw new AdkError({
code: "PLUGIN_NOT_FOUND",
expected: true,
message: await this._buildPluginNotFoundMessage(client, ref)
});
}
return { id: plugin.id, updatedAt: plugin.updatedAt, definition: plugin };
}
async search(query, limit = 20) {
const client = await this.clientFactory.getClient();
const results = new Map;
for await (const plugin of client.list.publicPlugins({})) {
collectCatalogSearchResult(results, plugin, query);
}
return getSortedCatalogSearchResults(results, limit);
}
async listVersions(name) {
const client = await this.clientFactory.getClient();
const versions = new Set;
for await (const plugin of client.list.publicPlugins({ name })) {
versions.add(plugin.version);
}
return getSortedVersions(versions);
}
async _buildPluginNotFoundMessage(client, ref) {
if (ref.version !== "latest") {
const latest = await this._findPublicPlugin(client, { ...ref, version: "latest" });
if (latest) {
return `Plugin "${ref.name}" version "${ref.version}" not found on the Botpress Hub (latest is ${latest.version}).`;
}
}
return `Plugin "${ref.name}" not found on the Botpress Hub`;
}
async _findPublicPlugin(client, ref) {
try {
const response = await client.getPublicPlugin({
name: ref.name,
version: ref.version
});
return response.plugin;
} catch (error) {
if (this._isResourceNotFoundError(error)) {
return;
}
throw error;
}
}
_isResourceNotFoundError(error) {
if (error && typeof error === "object" && "type" in error) {
return error.type === "ResourceNotFound";
}
return false;
}
}
class PluginManager {
source;
service;
constructor(options = {}) {
const { noCache, ...clientOptions } = options;
this.source = new PluginCatalogSource(new CatalogClientFactory(clientOptions));
this.service = new CatalogService(this.source, noCache || false);
}
async loadPlugins(dependencies) {
const errors = [];
const warnings = [];
const parseResult = PluginParser.parsePlugins(dependencies);
const plugins = parseResult.plugins;
errors.push(...parseResult.errors);
const duplicateWarnings = PluginParser.checkDuplicates(plugins);
warnings.push(...duplicateWarnings);
const fetchPromises = plugins.map(async (plugin) => {
try {
plugin.definition = await this.fetchPlugin(plugin.ref);
} catch {
errors.push(ValidationErrors.unknownPlugin(plugin.alias, plugin.ref.fullName));
}
});
await Promise.all(fetchPromises);
return { plugins, errors, warnings };
}
async fetchPlugin(ref) {
return this.service.getDefinition(ref);
}
async searchPlugins(query, limit = 20) {
return this.source.search(query, limit);
}
async listPluginVersions(name) {
return this.source.listVersions(name);
}
async getCacheStats() {
return this.service.getCacheStats();
}
async clearCache() {
await this.service.clearCache();
}
}
class PluginRegistry {
manager;
constructor(opts = {}) {
this.manager = opts.manager ?? new PluginManager(opts.managerOptions);
}
async getSpec(name, version) {
const ref = {
name,
version: version ?? "latest",
fullName: name
};
return this.manager.fetchPlugin(ref);
}
async search(_query) {
return this.manager.searchPlugins(_query);
}
async listVersions(name) {
return this.manager.listPluginVersions(name);
}
}
init_agent_project();
init_snapshot_store();
class DependencyManager {
snapshotStore;
env;
client;
projectPath;
botId;
integrationRegistry;
pluginRegistry;
integrationResolver;
pluginResolver;
constructor(opts) {
this.projectPath = opts.projectPath;
this.env = opts.env;
this.client = opts.client;
this.botId = opts.botId;
this.snapshotStore = new DependencySnapshotStore({ projectPath: opts.projectPath });
this.integrationRegistry = opts.integrationRegistry ?? new IntegrationRegistry;
this.pluginRegistry = opts.pluginRegistry ?? new PluginRegistry;
this.integrationResolver = opts.integrationResolver ?? new IntegrationResolver({ registry: this.integrationRegistry, client: this.client });
this.pluginResolver = opts.pluginResolver ?? new PluginResolver({
registry: this.pluginRegistry,
integrationRegistry: this.integrationRegistry,
client: this.client
});
}
static async fromProject(opts) {
const project = await AgentProject.load(opts.projectPath);
const botId = opts.botId ?? DependencyManager.getProjectBotId(project, opts.env);
if (!botId) {
throw new DependencyError({
code: "BOT_NOT_FOUND",
message: `No ${opts.env} bot ID found in ${opts.projectPath}. Run 'adk link' to link the project to a bot.`
});
}
return new DependencyManager({
projectPath: project.path,
env: opts.env,
client: opts.client,
botId,
integrationRegistry: opts.integrationRegistry,
pluginRegistry: opts.pluginRegistry,
integrationResolver: opts.integrationResolver,
pluginResolver: opts.pluginResolver
});
}
static getProjectBotId(project, env) {
const info = project.agentInfo;
if (!info)
return;
return env === "dev" ? info.devId ?? info.botId : info.botId;
}
async getProjectBotId(env) {
const project = await AgentProject.load(this.projectPath);
const botId = DependencyManager.getProjectBotId(project, env);
if (!botId) {
throw new DependencyError({
code: "BOT_NOT_FOUND",
message: `No ${env} bot ID found in ${this.projectPath}. Run 'adk link' to link the project to a bot.`
});
}
return botId;
}
async readSnapshot(options) {
return this.snapshotStore.readOrEmpty(this.env, {
tolerant: options?.tolerant,
botId: this.botId
});
}
async writeSnapshot(snapshot) {
await this.snapshotStore.write({
...snapshot,
botId: snapshot.botId || this.botId,
fetchedAt: new Date().toISOString()
});
}
async readCloudSnapshot(previous) {
const { bot } = await this.client.getBot({ id: this.botId });
return dependencySnapshotFromBot({
bot,
botId: this.botId,
env: this.env,
fetchedAt: new Date,
previous
});
}
async snapshotStateFromCloud() {
const previous = await this.readSnapshot({ tolerant: true });
const cloud = await this.readCloudSnapshot(previous);
return dependencySnapshotToState(cloud);
}
async applyState(state, opts) {
const parsed = dependencyStateSchema.parse({
...state,
env: this.env
});
await this.writeSnapshot({
version: 1,
env: this.env,
botId: this.botId,
fetchedAt: new Date().toISOString(),
integrations: parsed.integrations,
plugins: parsed.plugins
});
const result = await this.apply(opts);
if (!result.dryRun && result.applied.length === 0 && result.skipped.length === 0 && result.errors.length === 0) {
await this.refreshSnapshotFromCloud();
}
return result;
}
async list(type) {
const data = await this.readSnapshot({ tolerant: true });
const out = [];
if (!type || type === "integration") {
for (const [alias, e] of Object.entries(data.integrations)) {
out.push({ type: "integration", alias, name: e.name, version: e.version, enabled: e.enabled });
}
}
if (!type || type === "plugin") {
for (const [alias, e] of Object.entries(data.plugins)) {
out.push({ type: "plugin", alias, name: e.name, version: e.version, enabled: e.enabled });
}
}
return out;
}
async get(type, alias) {
const all = await this.list(type);
return all.find((e) => e.alias === alias);
}
async waitForIntegrationWebhook(name, opts) {
const deadline = Date.now() + opts.timeoutMs;
for (;; ) {
try {
const { bot } = await this.client.getBot({ id: this.botId });
const integrations = Object.values(bot.integrations ?? {});
if (integrations.some((i) => i?.name === name && !!i?.webhookId)) {
return true;
}
} catch {}
if (Date.now() >= deadline) {
return false;
}
await new Promise((resolve2) => setTimeout(resolve2, opts.intervalMs));
}
}
async add(type, spec) {
if (type === "interface") {
throw new DependencyError({
code: "BUILTIN_INTERFACE_IMMUTABLE",
message: "Interfaces are built-in platform constants and cannot be added."
});
}
const alias = spec.alias ?? spec.name;
const version = spec.version ?? "latest";
const snapshot = await this.readSnapshot();
if (type === "integration" && snapshot.integrations[alias]) {
const existing = snapshot.integrations[alias];
const versionMatch = version === "latest" || existing.version === version;
if (existing.enabled && versionMatch && jsonEqual(existing.config, spec.config ?? {})) {
return {
ok: true,
noop: true,
resource: { type, alias, name: spec.name, version: existing.version }
};
}
}
if (type === "plugin" && snapshot.plugins[alias]) {
const existing = snapshot.plugins[alias];
const versionMatch = version === "latest" || existing.version === version;
if (existing.enabled && versionMatch && jsonEqual(existing.config, spec.config ?? {})) {
return {
ok: true,
noop: true,
resource: { type, alias, name: spec.name, version: existing.version }
};
}
}
if (type === "integration") {
const desired = {
name: spec.name,
version,
enabled: true,
config: spec.config ?? {}
};
let installedDisabled;
let authorizationPending = false;
try {
await this.integrationResolver.applyToCloud({ botId: this.botId, alias, entry: desired });
} catch (err) {
const missingFields = extractMissingRequiredFields(err);
if (missingFields) {
await this.integrationResolver.applyToCloud({
botId: this.botId,
alias,
entry: { ...desired, enabled: false }
});
installedDisabled = { missingFields };
} else if (await this.specRequiresAuthorization(spec.name, spec.version)) {
await this.integrationResolver.applyToCloud({
botId: this.botId,
alias,
entry: { ...desired, enabled: false }
});
authorizationPending = true;
} else {
throw err;
}
}
const refreshed = await this.refreshSnapshotFromCloud();
if (installedDisabled || authorizationPending) {
const snapshotAlias = refreshed.integrations[alias] ? alias : Object.keys(refreshed.integrations).find((k) => !snapshot.integrations[k] && !refreshed.integrations[k].enabled && refreshed.integrations[k].name === spec.name);
if (snapshotAlias && refreshed.integrations[snapshotAlias]) {
refreshed.integrations[snapshotAlias] = {
...refreshed.integrations[snapshotAlias],
...installedDisabled ? { missingFields: installedDisabled.missingFields } : {},
...authorizationPending ? { authorizationPending: true } : {}
};
await this.writeSnapshot(refreshed);
}
}
const resolvedVersion = refreshed.integrations[alias]?.version ?? version;
return {
ok: true,
resource: { type, alias, name: spec.name, version: resolvedVersion },
installedDisabled,
...authorizationPending ? { installedAwaitingAuthorization: true } : {}
};
} else {
const { dependencies: resolvedDeps, autoResolved } = await this.resolvePluginDependencies({
pluginName: spec.name,
pluginVersion: version,
userDeps: spec.dependencies ?? {},
state: snapshot
});
const desired = {
name: spec.name,
version,
enabled: true,
config: spec.config ?? {},
dependencies: resolvedDeps
};
let installedDisabled;
try {
await this.pluginResolver.applyToCloud({ botId: this.botId, alias, entry: desired, state: snapshot });
} catch (err) {
const missingFields = extractMissingRequiredFields(err);
if (!missingFields)
throw err;
await this.pluginResolver.applyToCloud({
botId: this.botId,
alias,
entry: { ...desired, enabled: false },
state: snapshot
});
installedDisabled = { missingFields };
}
const refreshed = await this.refreshSnapshotFromCloud();
if (installedDisabled) {
const snapshotAlias = refreshed.plugins[alias] ? alias : Object.keys(refreshed.plugins).find((k) => !snapshot.plugins[k] && !refreshed.plugins[k].enabled && refreshed.plugins[k].name === spec.name);
if (snapshotAlias && refreshed.plugins[snapshotAlias]) {
refreshed.plugins[snapshotAlias] = {
...refreshed.plugins[snapshotAlias],
missingFields: installedDisabled.missingFields
};
await this.writeSnapshot(refreshed);
}
}
const resolvedVersion = refreshed.plugins[alias]?.version ?? version;
return {
ok: true,
resource: { type, alias, name: spec.name, version: resolvedVersion },
autoResolved,
installedDisabled
};
}
}
async remove(type, alias) {
if (type === "interface") {
throw new DependencyError({ code: "BUILTIN_INTERFACE_IMMUTABLE", message: "Interfaces cannot be removed." });
}
const snapshot = await this.readSnapshot();
const exists = type === "integration" ? !!snapshot.integrations[alias] : !!snapshot.plugins[alias];
if (!exists)
return { ok: true, noop: true };
if (type === "integration") {
await this.integrationResolver.removeFromCloud({ botId: this.botId, alias });
} else {
await this.pluginResolver.removeFromCloud({ botId: this.botId, alias });
}
await this.refreshSnapshotFromCloud();
return { ok: true };
}
async upgrade(type, alias, version) {
if (type === "interface") {
throw new DependencyError({ code: "BUILTIN_INTERFACE_IMMUTABLE", message: "Interfaces cannot be upgraded." });
}
const snapshot = await this.readSnapshot();
const existing = type === "integration" ? snapshot.integrations[alias] : snapshot.plugins[alias];
if (!existing) {
throw new DependencyError({
code: type === "integration" ? "INTEGRATION_NOT_FOUND" : "PLUGIN_NOT_FOUND",
message: `'${alias}' is not installed in ${this.env}`
});
}
const targetVersion = version ?? "latest";
if (existing.version === targetVersion) {
return { ok: true, noop: true, resource: { type, alias, name: existing.name, version: existing.version } };
}
const next = { ...existing, version: targetVersion };
await this.applyEntry(type, alias, next, snapshot);
await this.refreshSnapshotFromCloud();
return { ok: true, resource: { type, alias, name: existing.name, version: next.version } };
}
async enable(type, alias) {
return this.toggleEnabled(type, alias, true);
}
async disable(type, alias) {
return this.toggleEnabled(type, alias, false);
}
async toggleEnabled(type, alias, enabled) {
if (type === "interface") {
throw new DependencyError({
code: "BUILTIN_INTERFACE_IMMUTABLE",
message: "Interfaces cannot be enabled/disabled."
});
}
const snapshot = await this.readSnapshot();
const existing = type === "integration" ? snapshot.integrations[alias] : snapshot.plugins[alias];
if (!existing) {
throw new DependencyError({
code: type === "integration" ? "INTEGRATION_NOT_FOUND" : "PLUGIN_NOT_FOUND",
message: `'${alias}' is not installed in ${this.env}`
});
}
if (existing.enabled === enabled)
return { ok: true, noop: true };
await this.applyEntry(type, alias, { ...existing, enabled }, snapshot);
const refreshedSnapshot = await this.refreshSnapshotFromCloud();
const refreshedEntry = type === "integration" ? refreshedSnapshot.integrations[alias] : refreshedSnapshot.plugins[alias];
if (!refreshedEntry || refreshedEntry.enabled !== enabled) {
throw new DependencyError({
code: "SNAPSHOT_DRIFT",
message: `Cloud did not persist ${type} '${alias}' as ${enabled ? "enabled" : "disabled"}.`,
details: {
type,
alias,
expected: { enabled },
actual: refreshedEntry ? { enabled: refreshedEntry.enabled } : null
}
});
}
return { ok: true, resource: { type, alias, name: existing.name, version: existing.version } };
}
async configure(type, alias, patch) {
if (type === "interface") {
throw new DependencyError({ code: "BUILTIN_INTERFACE_IMMUTABLE", message: "Interfaces cannot be configured." });
}
const snapshot = await this.readSnapshot();
const existing = type === "integration" ? snapshot.integrations[alias] : snapshot.plugins[alias];
if (!existing) {
throw new DependencyError({
code: type === "integration" ? "INTEGRATION_NOT_FOUND" : "PLUGIN_NOT_FOUND",
message: `'${alias}' is not installed in ${this.env}`
});
}
const nextConfig = { ...existing.config, ...patch.set ?? {} };
for (const key of patch.unset ?? [])
delete nextConfig[key];
const next = { ...existing, config: nextConfig };
if (type === "plugin" && patch.map && "dependencies" in next) {
next.dependencies = { ...next.dependencies, ...patch.map };
}
if (jsonEqual(next, existing)) {
return { ok: true, noop: true, resource: { type, alias, name: existing.name, version: existing.version } };
}
await this.applyEntry(type, alias, next, snapshot);
await this.refreshSnapshotFromCloud();
return { ok: true, resource: { type, alias, name: existing.name, version: existing.version } };
}
async applyEntry(type, alias, entry, state) {
if (type === "integration") {
await this.integrationResolver.applyToCloud({
botId: this.botId,
alias,
entry
});
} else {
await this.pluginResolver.applyToCloud({
botId: this.botId,
alias,
entry,
state
});
}
}
async resolvePluginDependencies(opts) {
const pluginSpec = await this.pluginRegistry.getSpec(opts.pluginName, opts.pluginVersion);
const requiredInterfaces = pluginSpec.dependencies?.interfaces ?? {};
const resolved = { ...opts.userDeps };
const autoResolved = [];
for (const [pluginIfaceAlias, requirement] of Object.entries(requiredInterfaces)) {
if (resolved[pluginIfaceAlias]) {
continue;
}
const candidates = [];
for (const [integrationAlias, integrationEntry] of Object.entries(opts.state.integrations)) {
let integrationSpec;
try {
integrationSpec = await this.integrationRegistry.getSpec(integrationEntry.name, integrationEntry.version);
} catch {
continue;
}
const implements_ = Object.values(integrationSpec.interfaces ?? {}).some((iface) => iface.name === requirement.name);
if (implements_) {
candidates.push({ alias: integrationAlias, integrationName: integrationEntry.name });
}
}
if (candidates.length === 1) {
const match = candidates[0];
resolved[pluginIfaceAlias] = { integrationAlias: match.alias };
autoResolved.push({ pluginInterfaceAlias: pluginIfaceAlias, integrationAlias: match.alias });
} else if (candidates.length > 1) {
throw new DependencyError({
code: "AMBIGUOUS_DEPENDENCY",
message: `Multiple installed integrations implement interface '${requirement.name}' required by plugin '${opts.pluginName}': ${candidates.map((c) => c.alias).join(", ")}. Pass --dep ${pluginIfaceAlias}=<alias> to disambiguate.`,
details: {
plugin: opts.pluginName,
pluginInterfaceAlias: pluginIfaceAlias,
interfaceName: requirement.name,
candidates: candidates.map((c) => ({ alias: c.alias, name: c.integrationName }))
},
suggestion: `Pass --dep ${pluginIfaceAlias}=<alias> where <alias> is one of: ${candidates.map((c) => c.alias).join(", ")}`
});
} else {
const implementers = await this.findHubImplementersSafely(requirement.name);
const suggestion = formatMissingDependencySuggestion({
interfaceName: requirement.name,
pluginInterfaceAlias: pluginIfaceAlias,
implementers
});
throw new DependencyError({
code: "MISSING_DEPENDENCY",
message: `Plugin '${opts.pluginName}' requires interface '${requirement.name}', but no installed integration implements it.
` + suggestion,
details: {
plugin: opts.pluginName,
pluginInterfaceAlias: pluginIfaceAlias,
interfaceName: requirement.name,
implementers: implementers.map((i) => ({ name: i.name, version: i.version, title: i.title }))
},
suggestion
});
}
}
return { dependencies: resolved, autoResolved };
}
async findHubImplementersSafely(interfaceName) {
try {
const results = await this.integrationRegistry.findImplementersOfInterface(interfaceName, 5);
return results;
} catch {
return [];
}
}
async specRequiresAuthorization(name, version) {
try {
const spec = await this.integrationRegistry.getSpec(name, version);
return integrationRequiresAuthorization(spec);
} catch {
return false;
}
}
async refreshSnapshotFromCloud() {
return this.snapshotStore.refreshFromCloud({
client: this.client,
botId: this.botId,
env: this.env,
integrationRegistry: this.integrationRegistry
});
}
integrationEntriesMatch(a, b) {
return jsonEqual(stripSnapshotMetadata(a), stripSnapshotMetadata(b));
}
pluginEntriesMatch(a, b) {
return jsonEqual(stripSnapshotMetadata(a), stripSnapshotMetadata(b));
}
async diff() {
const snapshot = await this.readSnapshot();
const cloud = await this.readCloudSnapshot(snapshot);
const delta = { addedInSnapshot: [], removedInSnapshot: [], changedInSnapshot: [] };
for (const [alias, entry] of Object.entries(snapshot.integrations)) {
const cloudEntry = cloud.integrations[alias];
if (!cloudEntry) {
delta.addedInSnapshot.push({ type: "integration", alias, name: entry.name, version: entry.version });
} else if (!this.integrationEntriesMatch(entry, cloudEntry)) {
delta.changedInSnapshot.push({ type: "integration", alias, field: "unspecified" });
}
}
for (const [alias, entry] of Object.entries(cloud.integrations)) {
if (!snapshot.integrations[alias]) {
delta.removedInSnapshot.push({ type: "integration", alias, name: entry.name, version: entry.version });
}
}
for (const [alias, entry] of Object.entries(snapshot.plugins)) {
const cloudEntry = cloud.plugins[alias];
if (!cloudEntry) {
delta.addedInSnapshot.push({ type: "plugin", alias, name: entry.name, version: entry.version });
} else if (!this.pluginEntriesMatch(entry, cloudEntry)) {
delta.changedInSnapshot.push({ type: "plugin", alias, field: "unspecified" });
}
}
for (const [alias, entry] of Object.entries(cloud.plugins)) {
if (!snapshot.plugins[alias]) {
delta.removedInSnapshot.push({ type: "plugin", alias, name: entry.name, version: entry.version });
}
}
const snapshotReflectsCloud = delta.addedInSnapshot.length === 0 && delta.removedInSnapshot.length === 0 && delta.changedInSnapshot.length === 0;
return { target: this.env, snapshotReflectsCloud, delta };
}
async apply(opts) {
const snapshot = await this.readSnapshot();
const cloud = await this.readCloudSnapshot(snapshot);
const actions = [];
const errors = [];
for (const [alias, entry] of Object.entries(snapshot.integrations)) {
const c = cloud.integrations[alias];
if (!c) {
actions.push({
type: "integration",
alias,
action: "install",
details: { name: entry.name, version: entry.version }
});
} else if (c.version !== entry.version) {
const isDowngrade = semver2.valid(c.version) && semver2.valid(entry.version) && semver2.gt(c.version, entry.version);
actions.push({
type: "integration",
alias,
action: isDowngrade ? "downgrade" : "upgrade",
details: { name: entry.name, fromVersion: c.version, toVersion: entry.version }
});
} else {
if (c.enabled !== entry.enabled)
actions.push({
type: "integration",
alias,
action: entry.enabled ? "enable" : "disable",
details: { name: entry.name, version: entry.version, previous: c.enabled }
});
if (!jsonEqual(c.config, entry.config))
actions.push({
type: "integration",
alias,
action: "reconfigure",
details: { name: entry.name, version: entry.version, changedFields: ["config"] }
});
}
}
for (const alias of Object.keys(cloud.integrations)) {
if (!snapshot.integrations[alias]) {
const c = cloud.integrations[alias];
actions.push({
type: "integration",
alias,
action: "uninstall",
details: { name: c.name, version: c.version }
});
}
}
for (const [alias, entry] of Object.entries(snapshot.plugins)) {
const c = cloud.plugins[alias];
if (!c) {
actions.push({
type: "plugin",
alias,
action: "install",
details: { name: entry.name, version: entry.version }
});
} else if (c.version !== entry.version) {
const isDowngrade = semver2.valid(c.version) && semver2.valid(entry.version) && semver2.gt(c.version, entry.version);
actions.push({
type: "plugin",
alias,
action: isDowngrade ? "downgrade" : "upgrade",
details: { name: entry.name, fromVersion: c.version, toVersion: entry.version }
});
} else {
if (c.enabled !== entry.enabled)
actions.push({
type: "plugin",
alias,
action: entry.enabled ? "enable" : "disable",
details: { name: entry.name, version: entry.version, previous: c.enabled }
});
const configChanged = !jsonEqual(c.config, entry.config);
const depsChanged = !jsonEqual(c.dependencies, entry.dependencies);
if (configChanged || depsChanged) {
const changedFields = [];
if (configChanged)
changedFields.push("config");
if (depsChanged)
changedFields.push("dependencies");
actions.push({
type: "plugin",
alias,
action: "reconfigure",
details: { name: entry.name, version: entry.version, changedFields }
});
}
}
}
for (const alias of Object.keys(cloud.plugins)) {
if (!snapshot.plugins[alias]) {
const c = cloud.plugins[alias];
actions.push({
type: "plugin",
alias,
action: "uninstall",
details: { name: c.name, version: c.version }
});
}
}
if (opts?.dryRun) {
return { target: this.env, applied: [], errors: [], skipped: actions, dryRun: true };
}
if (actions.length === 0) {
return { target: this.env, applied: [], skipped: [], errors, dryRun: false };
}
if (!opts?.yes && this.env === "prod") {
throw new DependencyError({
code: "PROD_CONFIRMATION_REQUIRED",
message: "Apply targets production. Pass yes: true to confirm.",
details: { planned: actions }
});
}
if (!opts?.yes && actions.some((a) => a.action === "uninstall")) {
throw new DependencyError({
code: "UNINSTALL_REQUIRES_CONFIRMATION",
message: "Apply would uninstall resources from cloud. Pass yes: true to confirm.",
details: { destructive: actions.filter((a) => a.action === "uninstall") }
});
}
const order = ["uninstall", "upgrade", "install", "reconfigure", "enable", "disable"];
function typeWeight(action, type) {
if (action === "uninstall")
return type === "plugin" ? 0 : 1;
return type === "integration" ? 0 : 1;
}
const sorted = [...actions].sort((a, b) => {
const verbDiff = order.indexOf(a.action) - order.indexOf(b.action);
if (verbDiff !== 0)
return verbDiff;
return typeWeight(a.action, a.type) - typeWeight(b.action, b.type);
});
const applied = [];
const appliedKeys = new Set;
for (const a of sorted) {
try {
const key = `${a.type}:${a.alias}`;
if (a.action === "uninstall") {
if (a.type === "integration")
await this.integrationResolver.removeFromCloud({ botId: this.botId, alias: a.alias });
else
await this.pluginResolver.removeFromCloud({ botId: this.botId, alias: a.alias });
} else if (!appliedKeys.has(key)) {
const entry = a.type === "integration" ? snapshot.integrations[a.alias] : snapshot.plugins[a.alias];
await this.applyEntry(a.type, a.alias, entry, snapshot);
appliedKeys.add(key);
}
applied.push(a);
} catch (err) {
errors.push({
action: a,
code: err.code ?? "INVALID_CONFIG",
message: err.message,
suggestion: err.suggestion
});
}
}
await this.refreshSnapshotFromCloud();
return { target: this.env, applied, skipped: [], errors, dryRun: false };
}
async copy(opts) {
if (opts.from === opts.to) {
throw new DependencyError({ code: "SAME_SOURCE_TARGET", message: "--from and --to must be different" });
}
if (this.env !== opts.to) {
throw new DependencyError({
code: "INVALID_CONFIG",
message: `DependencyManager constructed with env='${this.env}' but copy targets '${opts.to}'`
});
}
const sourceBotId = opts.sourceBotId?.trim() || await this.getProjectBotId(opts.from);
if (sourceBotId === this.botId) {
throw new DependencyError({
code: "SAME_SOURCE_TARGET",
message: `${opts.from} and ${opts.to} resolve to the same Cloud bot (${sourceBotId}).`
});
}
const sourceStore = new DependencySnapshotStore({ projectPath: this.projectPath });
const sourceData = await sourceStore.refreshFromCloud({
client: this.client,
botId: sourceBotId,
env: opts.from,
integrationRegistry: this.integrationRegistry
});
const targetSnapshotExists = await this.snapshotStore.exists(this.env);
const targetSnapshot = await this.readSnapshot();
const originalSnapshot = JSON.stringify(targetSnapshot);
const merged = { ...targetSnapshot, integrations: sourceData.integrations, plugins: sourceData.plugins };
await this.writeSnapshot(merged);
try {
const result = await this.apply({ dryRun: opts.dryRun, yes: opts.yes });
if (opts.dryRun) {
if (targetSnapshotExists) {
await this.writeSnapshot(JSON.parse(originalSnapshot));
} else {
await this.snapshotStore.delete(this.env);
}
}
return result;
} catch (err) {
if (targetSnapshotExists) {
await this.writeSnapshot(JSON.parse(originalSnapshot)).catch(() => {});
} else {
await this.snapshotStore.delete(this.env).catch(() => {});
}
throw err;
}
}
}
function formatMissingDependencySuggestion(opts) {
if (opts.implementers.length === 0) {
return `Install an integration that implements '${opts.interfaceName}' first, ` + `then retry \u2014 or pass --dep ${opts.pluginInterfaceAlias}=<alias> if you already have one.`;
}
const lines = [`Hub integrations that implement '${opts.interfaceName}':`];
for (const i of opts.implementers) {
const title = i.title && i.title !== i.name ? ` \u2014 ${i.title}` : "";
lines.push(` \u2022 ${i.name}@${i.version}${title}`);
}
lines.push("");
lines.push(`Install one and retry: adk integrations add ${opts.implementers[0].name}`);
lines.push(`Or, if you have an installed integration that implements this: --dep ${opts.pluginInterfaceAlias}=<alias>`);
return lines.join(`
`);
}
function stripSnapshotMetadata(entry) {
const {
cloudAlias: _cloudAlias,
cloudId: _cloudId,
updatedAt: _updatedAt,
...semantic
} = entry;
return semantic;
}
function dependencySnapshotToState(snapshot) {
const integrations = {};
for (const [alias, entry] of Object.entries(snapshot.integrations)) {
integrations[alias] = stripSnapshotMetadata(entry);
}
const plugins = {};
for (const [alias, entry] of Object.entries(snapshot.plugins)) {
plugins[alias] = stripSnapshotMetadata(entry);
}
return dependencyStateSchema.parse({
version: 1,
env: snapshot.env,
integrations,
plugins
});
}
init_types();
function getIntegrationAlias(integrationName) {
return integrationName.replace(/\//g, "__").replace(/-/g, "_").toLowerCase();
}
function bpModuleDirName(kind, alias) {
return kind === "integration" ? `integration_${getIntegrationAlias(alias)}` : `plugin_${alias}`;
}
async function resolveDependencyStatuses(input) {
const out = [];
const integrationVerdicts = new Map;
for (const [alias, entry] of Object.entries(input.snapshot.integrations)) {
const installed = input.bpModulesDir ? existsSync(path11.join(input.bpModulesDir, bpModuleDirName("integration", alias))) : true;
const spec = await tryGetSpec(input.integrationRegistry, entry.name, entry.version);
const verdict = spec ? computeIntegrationStatus({
installed,
spec,
enabled: entry.enabled,
config: entry.config,
...entry.configurationType ? { configurationType: entry.configurationType } : {},
...entry.missingFields !== undefined ? { persistedMissingFields: entry.missingFields } : {},
...entry.authorizationPending !== undefined ? { authorizationPending: entry.authorizationPending } : {}
}) : computeSnapshotOnlyStatus({
installed,
enabled: entry.enabled,
...entry.missingFields !== undefined ? { missingFields: entry.missingFields } : {},
...entry.authorizationPending !== undefined ? { authorizationPending: entry.authorizationPending } : {}
});
integrationVerdicts.set(alias, verdict);
out.push({
type: "integration",
alias,
name: entry.name,
version: entry.version,
enabled: entry.enabled,
...verdict
});
}
for (const [alias, entry] of Object.entries(input.snapshot.plugins)) {
const installed = input.bpModulesDir ? existsSync(path11.join(input.bpModulesDir, bpModuleDirName("plugin", alias))) : true;
const spec = await tryGetSpec(input.pluginRegistry, entry.name, entry.version);
const dependencyStatuses = mapPluginDependencyStatuses(entry.dependencies, (intAlias) => integrationVerdicts.get(intAlias));
let verdict;
if (spec) {
verdict = computePluginStatus({
installed,
spec,
enabled: entry.enabled,
config: entry.config,
dependencyStatuses,
...entry.missingFields !== undefined ? { persistedMissingFields: entry.missingFields } : {}
});
} else if (!installed) {
verdict = { state: "not_installed" };
} else {
verdict = transitiveDependencyVerdict(dependencyStatuses) ?? computeSnapshotOnlyStatus({
installed: true,
enabled: entry.enabled,
...entry.missingFields !== undefined ? { missingFields: entry.missingFields } : {}
});
}
out.push({ type: "plugin", alias, name: entry.name, version: entry.version, enabled: entry.enabled, ...verdict });
}
return out;
}
async function tryGetSpec(source, name, version) {
if (!source)
return null;
try {
return await source.getSpec(name, version);
} catch {
return null;
}
}
init_errors2();
init_constants();
class InterfaceRegistry {
async list() {
return Object.entries(BUILTIN_INTERFACES).map(([alias, versionString]) => {
const [name, version] = versionString.split("@");
return { alias, name: name || alias, version: version || "latest", builtin: true };
});
}
async getInfo(alias) {
const all = await this.list();
return all.find((i) => i.alias === alias);
}
}
init_snapshot_store();
init_types();
init_errors2();
class LegacyDependencyLockFile {
filePath;
env;
constructor(opts) {
this.env = opts.env;
this.filePath = path12.join(opts.projectPath, `dependencies.${opts.env}.lock.json`);
}
async exists() {
try {
await fs10.access(this.filePath);
return true;
} catch {
return false;
}
}
async read(options) {
let raw;
try {
raw = await fs10.readFile(this.filePath, "utf8");
} catch (err) {
if (err.code === "ENOENT") {
return dependencyStateSchema.parse({ version: 1, env: this.env });
}
throw err;
}
try {
return dependencyStateSchema.parse(JSON.parse(raw));
} catch (err) {
if (options?.tolerant) {
return dependencyStateSchema.parse({ version: 1, env: this.env });
}
throw new DependencyError({
code: "INVALID_CONFIG",
message: `Legacy dependency lock at ${this.filePath} failed schema validation`,
details: { issues: err.issues ?? String(err) }
});
}
}
async delete() {
try {
await fs10.unlink(this.filePath);
} catch (err) {
if (err.code !== "ENOENT")
throw err;
}
}
}
init_utils();
init_src();
class ConfigWriter {
configPath;
constructor(projectPath) {
this.configPath = path14.join(projectPath, "agent.config.ts");
}
loadConfig() {
const project = new import_ts_morph3.Project;
const sourceFile = project.createSourceFile(this.configPath, readFileSync2(this.configPath, "utf-8"), {
overwrite: true
});
const defineConfigCall = sourceFile.getDescendantsOfKind(import_ts_morph3.SyntaxKind.CallExpression).find((call) => {
return call.getExpression().getText() === "defineConfig";
});
if (!defineConfigCall) {
throw new AdkError({
code: "CONFIG_AST_INVALID",
expected: true,
message: "Could not find defineConfig() call in agent.config.ts"
});
}
const configArg = defineConfigCall.getArguments()[0];
if (!configArg || !configArg.isKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression)) {
throw new AdkError({
code: "CONFIG_AST_INVALID",
expected: true,
message: "defineConfig() must have an object literal as its first argument"
});
}
return { sourceFile, configObject: configArg };
}
async saveConfig(sourceFile) {
sourceFile.formatText();
const content = sourceFile.getFullText();
const formatted = await formatCode(content, this.configPath);
sourceFile.replaceWithText(formatted);
await sourceFile.save();
}
serializeDefaultModelSelection(value) {
if (Array.isArray(value)) {
return `[${value.map((entry) => `'${entry.replace(/'/g, "\\'")}'`).join(", ")}]`;
}
return `'${value.replace(/'/g, "\\'")}'`;
}
getIntegrationConfigMigrationCandidates() {
const { sourceFile, configObject } = this.loadConfig();
const dependenciesProp = configObject.getProperty("dependencies");
if (!dependenciesProp) {
return { sourceFile, candidates: [] };
}
const depsInit = dependenciesProp.getInitializerIfKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression);
if (!depsInit) {
return { sourceFile, candidates: [] };
}
const integrationsProp = depsInit.getProperty("integrations");
if (!integrationsProp) {
return { sourceFile, candidates: [] };
}
const integrationsInit = integrationsProp.getInitializerIfKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression);
if (!integrationsInit) {
return { sourceFile, candidates: [] };
}
const candidates = integrationsInit.getProperties().flatMap((prop) => {
if (!prop.isKind(import_ts_morph3.SyntaxKind.PropertyAssignment)) {
return [];
}
const assignment = prop;
const initializer = assignment.getInitializerIfKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression);
if (!initializer) {
return [];
}
const hasConfigProperty = initializer.getProperty("config") !== undefined;
const versionProp = initializer.getProperty("version");
const versionInitializer = versionProp?.getInitializer();
const versionInitializerText = versionInitializer && (versionInitializer.isKind(import_ts_morph3.SyntaxKind.StringLiteral) || versionInitializer.isKind(import_ts_morph3.SyntaxKind.NoSubstitutionTemplateLiteral)) ? versionInitializer.getText() : undefined;
return [{ alias: assignment.getName(), assignment, hasConfigProperty, versionInitializerText }];
});
return { sourceFile, candidates };
}
async updateDependencies(dependencies) {
const { sourceFile, configObject } = this.loadConfig();
let dependenciesProperty = configObject.getProperty("dependencies");
if (dependenciesProperty) {
dependenciesProperty.setInitializer(JSON.stringify(dependencies, null, 2));
} else {
dependenciesProperty = configObject.addPropertyAssignment({
name: "dependencies",
initializer: JSON.stringify(dependencies)
});
}
await this.saveConfig(sourceFile);
}
async updateName(name) {
const { sourceFile, configObject } = this.loadConfig();
const nameProperty = configObject.getProperty("name");
if (nameProperty) {
nameProperty.setInitializer(`'${name.replace(/'/g, "\\'")}'`);
}
await this.saveConfig(sourceFile);
}
async updateDefaultModels(updates) {
const requestedEntries = Object.entries(updates).filter(([, value]) => value !== undefined);
if (requestedEntries.length === 0) {
return;
}
const { sourceFile, configObject } = this.loadConfig();
let defaultModelsProperty = configObject.getProperty("defaultModels");
if (!defaultModelsProperty) {
defaultModelsProperty = configObject.addPropertyAssignment({
name: "defaultModels",
initializer: "{}"
});
}
const defaultModelsObject = defaultModelsProperty.getInitializerIfKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression);
if (!defaultModelsObject) {
throw new AdkError({
code: "CONFIG_AST_INVALID",
expected: true,
message: "defaultModels must be an object literal in agent.config.ts"
});
}
for (const [key, value] of requestedEntries) {
const existingProperty = defaultModelsObject.getProperty(key);
const initializer = this.serializeDefaultModelSelection(value);
if (existingProperty) {
existingProperty.setInitializer(initializer);
} else {
defaultModelsObject.addPropertyAssignment({
name: key,
initializer
});
}
}
await this.saveConfig(sourceFile);
}
async migrateIntegrationsToStringFormat() {
const { sourceFile, candidates } = this.getIntegrationConfigMigrationCandidates();
const migrated = [];
for (const candidate of candidates) {
if (!candidate.versionInitializerText || candidate.hasConfigProperty) {
continue;
}
candidate.assignment.setInitializer(candidate.versionInitializerText);
migrated.push(candidate.alias);
}
if (migrated.length > 0) {
await this.saveConfig(sourceFile);
}
return migrated;
}
getIntegrationConfigMigrationState() {
try {
const { candidates } = this.getIntegrationConfigMigrationCandidates();
const deprecatedAliases = candidates.map((candidate) => candidate.alias);
const migratableAliases = candidates.filter((candidate) => candidate.versionInitializerText && !candidate.hasConfigProperty).map((candidate) => candidate.alias);
return { deprecatedAliases, migratableAliases };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Configuration update failed with error: ${message} \u2014 please verify that there are no syntax errors in your agent.config.ts`);
return { deprecatedAliases: [], migratableAliases: [] };
}
}
getObjectFormatIntegrations() {
return this.getIntegrationConfigMigrationState().deprecatedAliases;
}
serializeStringLiteral(value) {
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
}
buildSecretInitializer(update) {
const parts = [];
if (update.description !== undefined) {
parts.push(`description: ${this.serializeStringLiteral(update.description)}`);
}
if (update.optional !== undefined) {
parts.push(`optional: ${update.optional ? "true" : "false"}`);
}
return `{ ${parts.join(", ")} }`;
}
async updateSecrets(updates) {
const { sourceFile, configObject } = this.loadConfig();
const hasAdds = updates.some((u) => u.action === "add");
let secretsProp = configObject.getProperty("secrets");
if (!secretsProp) {
if (!hasAdds)
return;
secretsProp = configObject.addPropertyAssignment({
name: "secrets",
initializer: "{}"
});
}
const secretsObject = secretsProp.getInitializerIfKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression);
if (!secretsObject)
return;
for (const update of updates) {
const existing = secretsObject.getProperty(update.field);
switch (update.action) {
case "add":
if (!existing) {
secretsObject.addPropertyAssignment({
name: update.field,
initializer: this.buildSecretInitializer(update)
});
}
break;
case "update": {
if (!existing)
break;
const existingInit = existing.getInitializerIfKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression);
if (!existingInit) {
existing.setInitializer(this.buildSecretInitializer(update));
break;
}
if (update.description !== undefined) {
const descProp = existingInit.getProperty("description");
const initializer = this.serializeStringLiteral(update.description);
if (descProp) {
descProp.setInitializer(initializer);
} else {
existingInit.addPropertyAssignment({ name: "description", initializer });
}
}
if (update.optional !== undefined) {
const optionalProp = existingInit.getProperty("optional");
const initializer = update.optional ? "true" : "false";
if (optionalProp) {
optionalProp.setInitializer(initializer);
} else {
existingInit.addPropertyAssignment({ name: "optional", initializer });
}
}
break;
}
case "remove":
if (existing) {
existing.remove();
}
break;
}
}
await this.saveConfig(sourceFile);
}
async updateConfiguration(updates) {
const { sourceFile, configObject } = this.loadConfig();
const hasAdds = updates.some((u) => u.action === "add");
let configProp = configObject.getProperty("configuration");
if (!configProp) {
if (!hasAdds)
return;
configProp = configObject.addPropertyAssignment({
name: "configuration",
initializer: "{ schema: z.object({}) }"
});
}
const configInit = configProp.getInitializerIfKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression);
if (!configInit)
return;
let schemaProp = configInit.getProperty("schema");
if (!schemaProp) {
if (!hasAdds)
return;
schemaProp = configInit.addPropertyAssignment({
name: "schema",
initializer: "z.object({})"
});
}
const schemaCall = schemaProp.getInitializerIfKind(import_ts_morph3.SyntaxKind.CallExpression);
if (!schemaCall)
return;
const schemaArg = schemaCall.getArguments()[0];
if (!schemaArg || !schemaArg.isKind(import_ts_morph3.SyntaxKind.ObjectLiteralExpression))
return;
const schemaObject = schemaArg;
for (const update of updates) {
const existing = schemaObject.getProperty(update.field);
switch (update.action) {
case "add":
if (!existing && update.definition) {
schemaObject.addPropertyAssignment({
name: update.field,
initializer: update.definition
});
}
break;
case "update":
if (existing && update.definition) {
existing.setInitializer(update.definition);
}
break;
case "remove":
if (existing) {
existing.remove();
}
break;
}
}
await this.saveConfig(sourceFile);
}
async removeDependenciesField() {
const { sourceFile, configObject } = this.loadConfig();
const dependenciesProp = configObject.getProperty("dependencies");
if (dependenciesProp) {
dependenciesProp.remove();
await this.saveConfig(sourceFile);
}
}
}
init_agent_project();
init_snapshot_store();
class DependencyMigrationManager {
projectPath;
client;
snapshotStore;
integrationRegistry;
pluginRegistry;
integrationResolver;
pluginResolver;
constructor(opts) {
this.projectPath = opts.projectPath;
this.client = opts.client;
this.snapshotStore = new DependencySnapshotStore({ projectPath: opts.projectPath });
this.integrationRegistry = new IntegrationRegistry;
this.pluginRegistry = new PluginRegistry;
this.integrationResolver = opts.integrationResolver ?? new IntegrationResolver({ registry: this.integrationRegistry, client: this.client });
this.pluginResolver = opts.pluginResolver ?? new PluginResolver({
registry: this.pluginRegistry,
integrationRegistry: this.integrationRegistry,
client: this.client
});
}
async run() {
const result = {
migrated: [],
warnings: [],
skipped: [],
legacySources: [],
snapshotWrites: [],
cloudWrites: []
};
if (await this.snapshotStore.hasMigrationMarker()) {
for (const env of ["dev", "prod"]) {
await this.deleteLegacyLock(env, result);
result.skipped.push({ env, reason: "migration already completed" });
}
return result;
}
const project = await AgentProject.load(this.projectPath, { noCache: true });
const info = project.agentInfo;
const agentConfigDependencies = await readDependenciesFromConfig(this.projectPath);
const hasAgentConfigDependencies = hasDependencies(agentConfigDependencies);
if (hasAgentConfigDependencies) {
result.legacySources?.push("agentConfig");
}
if (!info) {
result.warnings.push({
code: "CLOUD_FETCH_PARTIAL",
message: "No agent.json found; cannot migrate dependencies to Cloud-backed snapshots."
});
for (const env of ["dev", "prod"]) {
result.skipped.push({ env, reason: "no agent.json" });
}
return result;
}
const markerSources = new Set;
for (const env of ["dev", "prod"]) {
const botId = env === "dev" ? info.devId ?? info.botId : info.botId;
if (!botId) {
result.warnings.push({
code: "NO_PROD_BOT",
message: "No prod bot configured in agent.json. Prod dependency snapshot was not written."
});
result.skipped.push({ env, reason: "no prod bot configured" });
continue;
}
const legacy = await this.readLegacyState(env, agentConfigDependencies);
if (legacy?.source === "lock" && !result.legacySources?.includes("lock")) {
result.legacySources?.push("lock");
}
let bot = await this.fetchBot(botId, env, result);
if (!bot)
continue;
if (!cloudHasDependencies(bot) && legacy) {
await this.importLegacyToCloud({ botId, legacy: legacy.data });
result.cloudWrites?.push(env);
markerSources.add(legacy.source);
bot = await this.fetchBot(botId, env, result);
if (!bot)
continue;
} else {
markerSources.add("cloud");
}
await this.snapshotStore.write(dependencySnapshotFromBot({
bot,
botId,
env,
fetchedAt: new Date,
previous: await this.snapshotStore.read(env, { tolerant: true })
}));
result.snapshotWrites?.push(env);
result.migrated.push(env);
await this.deleteLegacyLock(env, result);
}
if (hasAgentConfigDependencies && result.migrated.length > 0) {
const writer = new ConfigWriter(this.projectPath);
await writer.removeDependenciesField();
}
if (result.migrated.length > 0) {
await this.snapshotStore.writeMigrationMarker({
version: 1,
migratedAt: new Date().toISOString(),
sources: [...markerSources]
});
result.warnings.push({
code: "MIGRATED_DEPENDENCIES",
message: `Migrated dependencies to .adk snapshots for ${result.migrated.join(", ")}. Cloud is now the source of truth.`
});
}
return result;
}
async deleteLegacyLock(env, result) {
try {
await new LegacyDependencyLockFile({ projectPath: this.projectPath, env }).delete();
} catch (err) {
result.warnings.push({
code: "LEGACY_LOCK_DELETE_FAILED",
message: `Migrated ${env} dependencies, but could not delete dependencies.${env}.lock.json: ${err.message}`
});
}
}
async readLegacyState(env, agentConfigDependencies) {
const legacyLock = new LegacyDependencyLockFile({ projectPath: this.projectPath, env });
if (await legacyLock.exists()) {
return { source: "lock", data: await legacyLock.read() };
}
if (hasDependencies(agentConfigDependencies)) {
return { source: "agentConfig", data: { ...agentConfigDependencies, env } };
}
return null;
}
async fetchBot(botId, env, result) {
try {
const { bot } = await this.client.getBot({ id: botId });
return bot;
} catch (err) {
result.warnings.push({
code: "CLOUD_FETCH_PARTIAL",
message: `Could not fetch cloud state for ${env}: ${err.message}`
});
result.skipped.push({ env, reason: "cloud fetch failed" });
return null;
}
}
async importLegacyToCloud(opts) {
for (const [alias, entry] of Object.entries(opts.legacy.integrations)) {
await this.integrationResolver.applyToCloud({ botId: opts.botId, alias, entry });
}
for (const [alias, entry] of Object.entries(opts.legacy.plugins)) {
await this.pluginResolver.applyToCloud({ botId: opts.botId, alias, entry, state: opts.legacy });
}
}
}
async function migrateFromConfig(opts) {
return new DependencyMigrationManager(opts).run();
}
async function readDependenciesFromConfig(projectPath) {
const configPath = path15.join(projectPath, "agent.config.ts");
const empty = { version: 1, env: "dev", integrations: {}, plugins: {} };
try {
await fs11.access(configPath);
} catch {
return empty;
}
try {
const project = new import_ts_morph2.Project({ useInMemoryFileSystem: false });
const sourceFile = project.addSourceFileAtPath(configPath);
const callExpr = sourceFile.getDescendantsOfKind(import_ts_morph2.SyntaxKind.CallExpression).find((c) => c.getExpression().getText() === "defineConfig");
const obj = callExpr?.getArguments()[0]?.asKind(import_ts_morph2.SyntaxKind.ObjectLiteralExpression);
if (!obj)
return empty;
const depsProp = obj.getProperty("dependencies");
if (!depsProp?.isKind(import_ts_morph2.SyntaxKind.PropertyAssignment))
return empty;
const depsObj = depsProp.getInitializer()?.asKind(import_ts_morph2.SyntaxKind.ObjectLiteralExpression);
if (!depsObj)
return empty;
readDependencyField(depsObj, "integrations", empty);
readDependencyField(depsObj, "plugins", empty);
} catch {
return empty;
}
return empty;
}
function readDependencyField(depsObj, field, data) {
const innerProp = depsObj.getProperty(field);
if (!innerProp?.isKind(import_ts_morph2.SyntaxKind.PropertyAssignment))
return;
const inner = innerProp.getInitializer()?.asKind(import_ts_morph2.SyntaxKind.ObjectLiteralExpression);
if (!inner)
return;
for (const aliasProp of inner.getProperties()) {
if (!aliasProp.isKind(import_ts_morph2.SyntaxKind.PropertyAssignment))
continue;
const alias = aliasProp.getName().replace(/['"]/g, "");
const version = readVersionLiteral(aliasProp.getInitializer());
if (!version)
continue;
const at = version.indexOf("@");
if (at < 0)
continue;
const name = version.slice(0, at);
const semver3 = version.slice(at + 1);
if (field === "integrations") {
data.integrations[alias] = { name, version: semver3, enabled: true, config: {} };
} else {
data.plugins[alias] = { name, version: semver3, enabled: true, config: {}, dependencies: {} };
}
}
}
function readVersionLiteral(node) {
if (node?.isKind(import_ts_morph2.SyntaxKind.StringLiteral)) {
return node.getLiteralText();
}
if (node?.isKind(import_ts_morph2.SyntaxKind.ObjectLiteralExpression)) {
const versionProp = node.getProperty("version");
if (versionProp?.isKind(import_ts_morph2.SyntaxKind.PropertyAssignment)) {
const value = versionProp.getInitializer();
if (value?.isKind(import_ts_morph2.SyntaxKind.StringLiteral))
return value.getLiteralText();
}
}
return null;
}
function hasDependencies(data) {
return Object.keys(data.integrations).length > 0 || Object.keys(data.plugins).length > 0;
}
function cloudHasDependencies(bot) {
return Object.keys(bot.integrations ?? {}).length > 0 || Object.keys(bot.plugins ?? {}).length > 0;
}
export { sortKeysDeep, jsonEqual, dependencyStateSchema, DEPENDENCY_ERROR_CODES, DependencyError, DEPENDENCY_WARNING_CODES, computeIntegrationStatus, computePluginStatus, isCallable, DependencySnapshotStore, emptyDependencySnapshot, dependencySnapshotFromBot, IntegrationResolver, PluginResolver, IntegrationRegistry, PluginRegistry, DependencyManager, resolveDependencyStatuses, InterfaceRegistry, DependencyMigrationManager, migrateFromConfig };