providerGuides.ts•5.99 kB
import { ProviderGuidesInput, ResponseContent } from "../types/index.js";
import { createStandardResponse, addStandardContext } from "../utils/responseUtils.js";
import { handleToolError } from "../utils/responseUtils.js";
import { REGISTRY_API_BASE } from "../../config.js";
import logger from "../utils/logger.js";
interface GuideDoc {
id: string;
title: string;
slug: string;
description?: string;
content?: string;
}
interface GuideDataItem {
id: string;
attributes: {
title: string;
slug: string;
content?: string;
};
}
/**
* Handles the providerGuides tool request
*/
export async function handleProviderGuides(params: ProviderGuidesInput): Promise<ResponseContent> {
try {
logger.debug("Processing providerGuides request", params);
const { provider, namespace = "hashicorp", guide, search } = params;
if (!provider) {
throw new Error("Provider is required");
}
// For testing, we'll use a known version ID that works
const versionId = "67250"; // This is a recent AWS provider version
// Build the URL for fetching guides
const guidesUrl = `${REGISTRY_API_BASE}/v2/provider-docs?filter[provider-version]=${versionId}&filter[category]=guides&filter[language]=hcl`;
logger.info("Fetching guides from:", guidesUrl);
const guidesResponse = await fetch(guidesUrl);
if (!guidesResponse.ok) {
throw new Error(`Failed to fetch guides: ${guidesResponse.status} ${guidesResponse.statusText}`);
}
const guidesData = await guidesResponse.json();
if (!guidesData.data || !Array.isArray(guidesData.data)) {
throw new Error("Invalid guides response format");
}
// Convert guides to more usable format
const guides: GuideDoc[] = guidesData.data.map((item: GuideDataItem) => ({
id: item.id,
title: item.attributes.title,
slug: item.attributes.slug
}));
// If a specific guide is requested, fetch its content
if (guide) {
const targetGuide = guides.find((g) => g.slug === guide || g.title.toLowerCase().includes(guide.toLowerCase()));
if (!targetGuide) {
throw new Error(`Guide '${guide}' not found`);
}
// Fetch the specific guide content
const guideUrl = `${REGISTRY_API_BASE}/v2/provider-docs/${targetGuide.id}`;
logger.info("Fetching guide content from:", guideUrl);
const guideResponse = await fetch(guideUrl);
if (!guideResponse.ok) {
throw new Error(`Failed to fetch guide content: ${guideResponse.status} ${guideResponse.statusText}`);
}
const guideData = await guideResponse.json();
if (!guideData.data?.attributes?.content) {
throw new Error("Guide content is empty or malformed");
}
// Extract description from the content
const descriptionMatch = guideData.data.attributes.content.match(/description: |-\n(.*?)\n---/s);
const description = descriptionMatch ? descriptionMatch[1].trim() : "";
// Format the response for a single guide
let markdownResponse = `# ${targetGuide.title}\n\n`;
if (description) {
markdownResponse += `${description}\n\n`;
}
markdownResponse += `${guideData.data.attributes.content.split("---")[2]?.trim() || ""}\n\n`;
const metadata = {
provider,
namespace,
guide: {
title: targetGuide.title,
slug: targetGuide.slug,
id: targetGuide.id
}
};
addStandardContext(metadata);
return createStandardResponse("success", markdownResponse, metadata);
}
// If search is provided, filter guides
let filteredGuides = guides;
if (search) {
const searchLower = search.toLowerCase();
filteredGuides = guides.filter(
(guide) => guide.title.toLowerCase().includes(searchLower) || guide.slug.toLowerCase().includes(searchLower)
);
}
// Format the response for guide listing
const sections = [
{
title: "Available Guides",
guides: filteredGuides.map((guide) => ({
title: guide.title,
slug: guide.slug
}))
}
];
// Group guides by type if we can identify patterns
const upgradeGuides = filteredGuides.filter((g) => g.slug.includes("version-") && g.slug.includes("-upgrade"));
const featureGuides = filteredGuides.filter((g) => !g.slug.includes("version-") || !g.slug.includes("-upgrade"));
if (upgradeGuides.length > 0) {
sections.push({
title: "Version Upgrade Guides",
guides: upgradeGuides
});
}
if (featureGuides.length > 0) {
sections.push({
title: "Feature & Integration Guides",
guides: featureGuides
});
}
let markdownResponse = `# ${namespace}/${provider} Provider Guides\n\n`;
if (search) {
markdownResponse += `Search results for: "${search}"\n\n`;
}
if (filteredGuides.length === 0) {
markdownResponse += "No guides found.\n\n";
} else {
sections.forEach((section) => {
if (section.guides.length > 0) {
markdownResponse += `## ${section.title}\n\n`;
section.guides.forEach((guide) => {
markdownResponse += `- [${guide.title}](${REGISTRY_API_BASE}/providers/${namespace}/${provider}/latest/docs/guides/${guide.slug})\n`;
});
markdownResponse += "\n";
}
});
}
const metadata = {
provider,
namespace,
summary: {
total: filteredGuides.length,
upgradeGuides: upgradeGuides.length,
featureGuides: featureGuides.length
}
};
addStandardContext(metadata);
return createStandardResponse("success", markdownResponse, metadata);
} catch (error) {
return handleToolError("providerGuides", error, {
inputParams: params,
context: {
message: error instanceof Error ? error.message : "Unknown error occurred",
timestamp: new Date().toISOString()
}
});
}
}