@mesh-tech/mesh-cli
Version:
CLI for Mesh platform development utilities
113 lines (112 loc) • 5.41 kB
JavaScript
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})`;
}