UNPKG

@botpress/adk-cli

Version:

Command-line interface for the Botpress Agent Development Kit (ADK)

343 lines (339 loc) 12.1 kB
// @bun import { findAgentRoot } from "./chunk-kk3h6qaj.js"; import { AdkError, AgentProject, auth, getProjectClient } from "./chunk-p0hjqn4r.js"; import { Uk } from "./chunk-3xrpxgq4.js"; // src/utils/command-context.ts import fs from "fs/promises"; import path from "path"; function normalizeApiUrl(apiUrl) { return apiUrl.replace(/\/+$/, ""); } function sameApiUrl(a, b) { return !!a && !!b && normalizeApiUrl(a) === normalizeApiUrl(b); } function createCommandCredentials(credentials, apiUrl, scope = {}) { return { token: credentials.token, apiUrl, ...scope.workspaceId ? { workspaceId: scope.workspaceId } : {}, ...scope.botId ? { botId: scope.botId } : {} }; } function apiUrlSourceReason(source, apiUrl) { switch (source.type) { case "option": return `${source.option} was set to ${apiUrl}`; case "project-local": return `${source.file} overrides this project to ${apiUrl}`; case "project": return `${source.file} links this project to ${apiUrl}`; case "selected-profile": return `no linked project was found, so this command uses selected profile "${source.profileName}"`; } } function apiUrlSourceLabel(source) { switch (source.type) { case "option": return source.option; case "project-local": case "project": return source.file; case "selected-profile": return `selected profile "${source.profileName}"`; } } function profileApiUrlMismatchError(details) { const profileApiUrlDisplay = details.profileApiUrl ?? "<missing>"; const sourceLabel = apiUrlSourceLabel(details.apiUrlSource); const targetLabel = details.apiUrlSource.type === "option" ? "This command targets:" : "This project targets:"; const suggestion = `Use --profile <profile-name>, run \`adk profiles set <profile-name>\`, or login with \`adk login --api-url ${details.targetApiUrl} --profile <profile-name>\`.`; const summary = `Selected profile "${details.profileName}" targets ${profileApiUrlDisplay}, but this command targets ${details.targetApiUrl}.`; const message = [ "Profile/API URL mismatch", "", targetLabel, ` ${details.targetApiUrl}`, ` from ${sourceLabel}`, "", `But selected profile "${details.profileName}" targets:`, ` ${profileApiUrlDisplay}`, "", `Use a profile for ${details.targetApiUrl}:`, " adk profiles set <profile-name>", ` adk login --api-url ${details.targetApiUrl} --profile <profile-name>`, "", "Or re-run this command with:", " --profile <profile-name>" ].join(` `); const errorDetails = { profileName: details.profileName, profileApiUrl: details.profileApiUrl, targetApiUrl: details.targetApiUrl, targetReason: details.targetReason, apiUrlSource: details.apiUrlSource }; const error = new AdkError({ code: "PROFILE_API_URL_MISMATCH", message, expected: true, details: errorDetails }); error.json = { success: false, error: { code: "PROFILE_API_URL_MISMATCH", message: summary, suggestion, details: errorDetails } }; return error; } function missingTargetBotError(target) { if (target === "dev") { return new AdkError({ code: "NO_DEV_BOT", message: "No dev bot ID found in agent.local.json. Run `adk dev` first to deploy a development bot.", expected: true, suggestion: "Run `adk dev` first." }); } return new AdkError({ code: "BOT_NOT_LINKED", message: "No botId found in agent.json. Please run `adk link` to link your agent to a bot.", expected: true, suggestion: "Run `adk link` first." }); } function hasRequirement(requirements, requirement) { return requirements.has(requirement); } function resolveTargetBotId(project, target) { if (target === "dev") { return project.agentInfo?.devId; } return project.agentInfo?.botId; } function profileMatchesCredentials(profile, credentials) { const profileApiUrl = profile.credentials.apiUrl; const credentialsApiUrl = credentials.apiUrl; if (profile.credentials.token !== credentials.token) { return false; } if (profileApiUrl || credentialsApiUrl) { return sameApiUrl(profileApiUrl, credentialsApiUrl); } return true; } async function resolveSelectedProfile() { const credentials = await auth.getActiveCredentials(); const profiles = await auth.listProfiles().catch(() => []); const profile = profiles.find((candidate) => profileMatchesCredentials(candidate, credentials)) ?? profiles.find((candidate) => candidate.credentials.token === credentials.token); const profileName = profile?.name ?? await auth.getCurrentProfile().catch(() => "current"); return { name: profileName, credentials, ...credentials.apiUrl ? { apiUrl: credentials.apiUrl } : {}, ...profile?.email ? { email: profile.email } : {}, ...profile?.displayName ? { displayName: profile.displayName } : {}, ...profile?.accountId ? { accountId: profile.accountId } : {} }; } async function readJsonObject(filePath) { try { const parsed = JSON.parse(await fs.readFile(filePath, "utf8")); return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}; } catch { return; } } async function projectApiUrlSource(agentRoot, apiUrl) { const localPath = path.join(agentRoot, "agent.local.json"); const local = await readJsonObject(localPath); if (sameApiUrl(typeof local?.apiUrl === "string" ? local.apiUrl : undefined, apiUrl)) { return { type: "project-local", file: "agent.local.json", path: localPath }; } return { type: "project", file: "agent.json", path: path.join(agentRoot, "agent.json") }; } function validateProfileApiUrl(options) { if (sameApiUrl(options.selectedProfile.credentials.apiUrl, options.apiUrl)) { return; } throw profileApiUrlMismatchError({ profileName: options.selectedProfile.name, profileApiUrl: options.selectedProfile.credentials.apiUrl ?? null, targetApiUrl: options.apiUrl, targetReason: options.targetReason, apiUrlSource: options.apiUrlSource }); } function projectNotFoundError() { return new AdkError({ code: "PROJECT_NOT_FOUND", message: `No ADK agent root found in the current directory or its parents. ` + "Run `adk help` to see all commands\n" + "Run `adk init` to create a new agent", expected: true }); } async function resolveCommandContext(options = {}) { const cwd = options.cwd ?? process.cwd(); const target = options.target ?? "dev"; const requirements = new Set(options.require ?? ["project", "credentials"]); const agentRoot = await findAgentRoot(cwd); if (!agentRoot) { throw projectNotFoundError(); } const project = await AgentProject.load(agentRoot, options.projectLoadOptions); const selectedProfile = await resolveSelectedProfile(); const apiUrl = options.apiUrl ?? project.agentInfo?.apiUrl; if (!apiUrl) { throw new AdkError({ code: "PROJECT_API_URL_MISSING", message: "No API URL found for this project. Run `adk link` to link the project first.", expected: true, suggestion: "Run `adk link` first." }); } const apiUrlSource = options.apiUrl ? { type: "option", option: "--api-url" } : await projectApiUrlSource(agentRoot, apiUrl); const targetReason = apiUrlSourceReason(apiUrlSource, apiUrl); validateProfileApiUrl({ selectedProfile, apiUrl, apiUrlSource, targetReason }); const workspaceId = project.agentInfo?.workspaceId; const botId = resolveTargetBotId(project, target); if (hasRequirement(requirements, "workspace") && !workspaceId) { throw new AdkError({ code: "WORKSPACE_ID_MISSING", message: "No workspace ID found in agent.json. Please run `adk link` to link your agent first.", expected: true, suggestion: "Run `adk link` first." }); } if (hasRequirement(requirements, "bot") && !botId) { throw missingTargetBotError(target); } const credentials = createCommandCredentials(selectedProfile.credentials, apiUrl, { ...workspaceId ? { workspaceId } : {}, ...botId ? { botId } : {} }); const shouldCreateClient = options.createClient ?? !!workspaceId; const client = shouldCreateClient && workspaceId ? await getProjectClient({ project, credentials, workspaceId, ...botId ? { botId } : {} }) : undefined; return { cwd, target, agentRoot, project, apiUrl, apiUrlSource, targetReason, selectedProfile, credentials, ...workspaceId ? { workspaceId } : {}, ...botId ? { botId } : {}, ...client ? { client } : {} }; } async function resolveLinkContext(options = {}) { const cwd = options.cwd ?? process.cwd(); const agentRoot = await findAgentRoot(cwd); if (!agentRoot) { throw projectNotFoundError(); } const project = await AgentProject.load(agentRoot, options.projectLoadOptions); const selectedProfile = await resolveSelectedProfile(); const projectApiUrl = project.agentInfo?.apiUrl; const apiUrl = options.apiUrl ?? projectApiUrl ?? selectedProfile.credentials.apiUrl; if (!apiUrl) { throw new AdkError({ code: "LINK_API_URL_MISSING", message: "No API URL found for adk link. Pass --api-url or login with a profile that has an API URL before linking.", expected: true, suggestion: "Run `adk login --api-url <api-url> --profile <profile-name>` or pass `--api-url <api-url>`." }); } const apiUrlSource = options.apiUrl ? { type: "option", option: "--api-url" } : projectApiUrl ? await projectApiUrlSource(agentRoot, projectApiUrl) : { type: "selected-profile", profileName: selectedProfile.name }; const targetReason = apiUrlSource.type === "selected-profile" ? `agent.json has no apiUrl, so adk link uses selected profile "${selectedProfile.name}"` : apiUrlSourceReason(apiUrlSource, apiUrl); validateProfileApiUrl({ selectedProfile, apiUrl, apiUrlSource, targetReason }); const credentials = createCommandCredentials(selectedProfile.credentials, apiUrl); return { cwd, agentRoot, project, apiUrl, apiUrlSource, targetReason, selectedProfile, credentials }; } async function resolveEnvironmentRemoteContext(options = {}) { const cwd = options.cwd ?? process.cwd(); const agentRoot = await findAgentRoot(cwd); const project = agentRoot ? await AgentProject.load(agentRoot, options.projectLoadOptions) : undefined; const selectedProfile = await resolveSelectedProfile(); const projectApiUrl = project?.agentInfo?.apiUrl; const apiUrl = projectApiUrl ?? selectedProfile.credentials.apiUrl; if (!apiUrl) { throw new AdkError({ code: "PROFILE_API_URL_MISSING", message: `Selected profile "${selectedProfile.name}" is missing an API URL.`, expected: true, suggestion: `Run \`adk login --profile ${selectedProfile.name}\` again.` }); } const apiUrlSource = projectApiUrl ? await projectApiUrlSource(agentRoot, projectApiUrl) : { type: "selected-profile", profileName: selectedProfile.name }; const targetReason = apiUrlSourceReason(apiUrlSource, apiUrl); validateProfileApiUrl({ selectedProfile, apiUrl, apiUrlSource, targetReason }); const profileWorkspaceId = selectedProfile.credentials.workspaceId; const credentials = createCommandCredentials(selectedProfile.credentials, apiUrl, { ...profileWorkspaceId ? { workspaceId: profileWorkspaceId } : {} }); const client = options.createClient ? new Uk({ token: credentials.token, apiUrl: credentials.apiUrl, ...credentials.workspaceId ? { workspaceId: credentials.workspaceId } : {}, headers: { "x-multiple-integrations": "true" } }) : undefined; return { cwd, ...agentRoot ? { agentRoot } : {}, ...project ? { project } : {}, apiUrl, apiUrlSource, targetReason, selectedProfile, credentials, ...client ? { client } : {} }; } export { profileApiUrlMismatchError, resolveCommandContext, resolveLinkContext, resolveEnvironmentRemoteContext };