UNPKG

@mesh-tech/mesh-cli

Version:

CLI for Mesh platform development utilities

113 lines (112 loc) 5.41 kB
import { loadTargetsFile, stripSlash } from "@mesh-tech/agent-targets"; import { getValidToken } from "./login.js"; import { logWarn } from "../utils/log.js"; const DEFAULT_AUTH_CONTEXT = "mesh.dev"; const LOCAL_URL_RE = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|\/|$)/i; export class TargetResolutionError extends Error { } const NO_REGISTRY_GUIDANCE = "no registry — add `mesh agent-targets add <name>` or pass --api-url"; export function resolveTarget(opts) { if (opts.apiUrl) { return { apiBaseUrl: stripSlash(opts.apiUrl), loginContext: opts.context || DEFAULT_AUTH_CONTEXT }; } const res = loadTargetsFile(); if (opts.target) { if (res.status === "invalid") { throw new TargetResolutionError(`Registry at ${res.path} is invalid: ${res.message}. Fix it, or pass --api-url <url>.`); } if (res.status === "absent") { throw new TargetResolutionError(`No agent target given and ${NO_REGISTRY_GUIDANCE}.`); } const entry = res.targets[opts.target]; if (!entry) { throw new TargetResolutionError(`Unknown target "${opts.target}". Available: ${Object.keys(res.targets).join(", ") || "(none)"}. ` + `Add it with 'mesh agent-targets add ${opts.target}' or pass --api-url.`); } return { apiBaseUrl: entry.apiBaseUrl, loginContext: entry.loginContext, conversationPathPrefix: entry.conversationPathPrefix, hubBaseUrl: entry.hubBaseUrl, token: entry.token, }; } if (res.status === "loaded") { const entry = res.targets[res.defaultTarget]; return { apiBaseUrl: entry.apiBaseUrl, loginContext: entry.loginContext, conversationPathPrefix: entry.conversationPathPrefix, hubBaseUrl: entry.hubBaseUrl, token: entry.token, }; } if (res.status === "invalid") { logWarn(`Agent-targets registry at ${res.path} is invalid: ${res.message}. Falling back to env/localhost.`); } const fallbackUrl = process.env.AGENT_API_URL || process.env.API_URL || "http://localhost:8787"; return { apiBaseUrl: stripSlash(fallbackUrl), loginContext: opts.context || DEFAULT_AUTH_CONTEXT }; } async function authHeaders(target) { if (target.token) return { Authorization: `Bearer ${target.token}` }; const ctx = target.loginContext || DEFAULT_AUTH_CONTEXT; const token = await getValidToken(ctx); if (token) return { Authorization: `Bearer ${token}` }; if (LOCAL_URL_RE.test(target.apiBaseUrl)) return { "X-Forwarded-User": "dev-user", "X-Forwarded-Email": "dev@localhost" }; throw new TargetResolutionError(`No valid credentials for context "${ctx}". Run: mesh login ${ctx}`); } export async function agentApiFetch(target, apiPath) { const headers = await authHeaders(target); return fetch(`${target.apiBaseUrl}${apiPath}`, { headers, redirect: "manual" }); } export async function agentApiSend(target, apiPath, init) { const headers = await authHeaders(target); const body = init.json !== undefined ? JSON.stringify(init.json) : init.body; const contentType = init.json !== undefined ? "application/json" : (init.contentType ?? "application/octet-stream"); return fetch(`${target.apiBaseUrl}${apiPath}`, { method: init.method, headers: { ...headers, ...(body === undefined ? {} : { "content-type": contentType }) }, ...(body === undefined ? {} : { body: body }), redirect: "manual", }); } const NETWORK_ERROR_PATTERN = /ECONNREFUSED|fetch failed|ENOTFOUND|EAI_AGAIN/; export function describeNetworkError(err, target) { const message = err instanceof Error ? err.message : String(err); if (!NETWORK_ERROR_PATTERN.test(message)) return message; if (LOCAL_URL_RE.test(target.apiBaseUrl)) { return "Is the agent API running? Start it with: mesh dev"; } const loginContext = target.loginContext ?? DEFAULT_AUTH_CONTEXT; return (`Couldn't reach ${target.apiBaseUrl} — check the URL/VPN/tailscale, ` + `or \`mesh login ${loginContext}\` if it's an auth redirect.`); } export async function describeHttpError(res, target, ctx) { const isRedirect = res.redirected || res.type === "opaqueredirect" || res.status === 302; if (res.status === 401 || res.status === 403 || isRedirect) { const loginContext = target.loginContext ?? DEFAULT_AUTH_CONTEXT; return `auth did not reach the agent — run \`mesh login ${loginContext}\``; } if (res.status === 404) { const idPart = ctx?.id ? ` (${ctx.id})` : ""; return `conversation/artifact not found${idPart}, or not owned by this identity — run \`mesh conversations list\` to see valid ids.`; } const bodyText = await res.text(); if (res.status === 503) { try { const parsed = JSON.parse(bodyText); if (parsed.error === "conversation_unavailable") { const message = parsed.detail ?? bodyText; const recoverHint = ctx?.id ? `\ntry: mesh temporal recover-conversation ${ctx.id}` : ""; return `${message}${recoverHint}`; } } catch { } } return `agent-api error ${res.status}: ${bodyText || res.statusText} (${target.apiBaseUrl})`; }