mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
getSectionContent - allow multiple sectionids
This commit is contained in:
@@ -14,9 +14,18 @@ const sectionContentSchema = z.object({
|
||||
partIndex: z.number().optional(),
|
||||
});
|
||||
|
||||
const getSectionContentOutputSchema = z.object({
|
||||
const sectionDataSchema = z.object({
|
||||
sectionId: z.string(),
|
||||
content: z.string(),
|
||||
sectionCount: z.number(),
|
||||
found: z.boolean(),
|
||||
error: z.string().optional(),
|
||||
sectionTitle: z.string().optional(),
|
||||
partIndex: z.number().optional(),
|
||||
totalParts: z.number().optional(),
|
||||
});
|
||||
|
||||
const getSectionContentOutputSchema = z.object({
|
||||
sections: z.array(sectionDataSchema),
|
||||
});
|
||||
|
||||
// #endregion
|
||||
@@ -25,20 +34,21 @@ const getSectionContentOutputSchema = z.object({
|
||||
|
||||
export const toolMetadata = {
|
||||
description:
|
||||
"Retrieves all text chunks from a specific specification section by sectionid. " +
|
||||
"Supports recursive fetching - if recursive=true and the section has children, it will fetch all descendants. " +
|
||||
"Retrieves all text chunks from one or more specification sections by section IDs. " +
|
||||
"Supports recursive fetching - if recursive=true and a section has children, it will fetch all descendants. " +
|
||||
"Use this to get complete content when you see 'Subsection available' or 'partial section' references.",
|
||||
args: {
|
||||
sectionId: "The section ID (e.g., 'sec-if-statement') to fetch chunks for",
|
||||
sectionIds:
|
||||
"Array of section IDs (e.g., ['sec-if-statement', 'sec-for-statement']) to fetch chunks for",
|
||||
recursive:
|
||||
"If true, recursively fetches content from all child sections and their descendants. " +
|
||||
"If false, only returns content from the specified section itself. " +
|
||||
"Use false when you only need the specific section's content without subsections.",
|
||||
"If false, only returns content from the specified sections themselves. " +
|
||||
"Use false when you only need the specific sections' content without subsections.",
|
||||
},
|
||||
};
|
||||
|
||||
export const inputSchema = z.object({
|
||||
sectionId: z.string().describe(toolMetadata.args.sectionId),
|
||||
sectionIds: z.array(z.string()).describe(toolMetadata.args.sectionIds),
|
||||
recursive: z.boolean().default(true).describe(toolMetadata.args.recursive),
|
||||
});
|
||||
|
||||
@@ -69,11 +79,19 @@ export type GetSectionContentInput = z.infer<typeof inputSchema>;
|
||||
*/
|
||||
export function createGetSectionContentTool(table: Table) {
|
||||
return async ({
|
||||
sectionId,
|
||||
sectionIds,
|
||||
recursive,
|
||||
}: GetSectionContentInput): Promise<GetSectionContentOutput> => {
|
||||
const allDocs: string[] = [];
|
||||
const queue: string[] = [sectionId];
|
||||
const sectionsData = new Map<
|
||||
string,
|
||||
{
|
||||
content: string[];
|
||||
title?: string;
|
||||
partIndex?: number;
|
||||
totalParts?: number;
|
||||
}
|
||||
>();
|
||||
const queue: string[] = [...sectionIds];
|
||||
const visited = new Set<string>();
|
||||
|
||||
while (queue.length > 0) {
|
||||
@@ -84,7 +102,7 @@ export function createGetSectionContentTool(table: Table) {
|
||||
const results = await table
|
||||
.query()
|
||||
.where(`sectionid = '${currentId}'`)
|
||||
.limit(100)
|
||||
.limit(10)
|
||||
.toArray();
|
||||
|
||||
// Sort by partindex to maintain order (nulls last for single-part sections)
|
||||
@@ -99,10 +117,23 @@ export function createGetSectionContentTool(table: Table) {
|
||||
text?: string;
|
||||
childrensectionids?: string[];
|
||||
sectiontitle?: string;
|
||||
partindex?: number;
|
||||
totalparts?: number;
|
||||
};
|
||||
|
||||
if (typedResult.text) {
|
||||
allDocs.push(typedResult.text);
|
||||
// Get or create section data
|
||||
let section = sectionsData.get(currentId);
|
||||
if (!section) {
|
||||
section = {
|
||||
content: [],
|
||||
title: typedResult.sectiontitle,
|
||||
partIndex: typedResult.partindex ?? undefined,
|
||||
totalParts: typedResult.totalparts ?? undefined,
|
||||
};
|
||||
sectionsData.set(currentId, section);
|
||||
}
|
||||
section.content.push(typedResult.text);
|
||||
}
|
||||
|
||||
// Add children to queue for recursive fetching only if recursive is true
|
||||
@@ -116,9 +147,30 @@ export function createGetSectionContentTool(table: Table) {
|
||||
}
|
||||
}
|
||||
|
||||
// Build output array from all requested sections
|
||||
// Missing sections are included with found: false and error message
|
||||
const sections = sectionIds.map((id) => {
|
||||
const data = sectionsData.get(id);
|
||||
if (data) {
|
||||
return {
|
||||
sectionId: id,
|
||||
content: data.content.join("\n\n"),
|
||||
found: true,
|
||||
sectionTitle: data.title,
|
||||
partIndex: data.partIndex,
|
||||
totalParts: data.totalParts,
|
||||
};
|
||||
}
|
||||
return {
|
||||
sectionId: id,
|
||||
content: "",
|
||||
found: false,
|
||||
error: `Section '${id}' not found in database`,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
content: allDocs.join("\n\n---\n\n"),
|
||||
sectionCount: visited.size,
|
||||
sections,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
+10
-6
@@ -113,13 +113,17 @@ async function createMcpServer() {
|
||||
openWorldHint: false,
|
||||
},
|
||||
},
|
||||
async ({ sectionId, recursive }) => {
|
||||
async ({ sectionIds, recursive }) => {
|
||||
console.log(
|
||||
`[TOOL] ${sectionContentToolName}: sectionId="${sectionId}" recursive=${recursive}`,
|
||||
`[TOOL] ${sectionContentToolName}: sectionIds=[${sectionIds.map((id: string) => `"${id}"`).join(", ")}] recursive=${recursive}`,
|
||||
);
|
||||
const result = await getSectionContentTool({ sectionIds, recursive });
|
||||
const totalContentLength = result.sections.reduce(
|
||||
(sum: number, s: { content: string }) => sum + s.content.length,
|
||||
0,
|
||||
);
|
||||
const result = await getSectionContentTool({ sectionId, recursive });
|
||||
console.log(
|
||||
`[TOOL] ${sectionContentToolName}: ${result.content.length} chars, ${result.sectionCount} sections`,
|
||||
`[TOOL] ${sectionContentToolName}: ${totalContentLength} chars, ${result.sections.length} sections`,
|
||||
);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||
@@ -200,14 +204,14 @@ I'll use one of these orchestration patterns:
|
||||
PATTERN 1 - For "What happens when I run this code?" questions:
|
||||
- Use ask262Debug.startImportant() and ask262Debug.stopImportant() in the code to mark only important sections.
|
||||
- STEP 1: ask262_evaluate_in_engine262(code: markedCode)
|
||||
- STEP 2: ask262_get_section_content(sectionId: importantSections[0], recursive: true)
|
||||
- STEP 2: ask262_get_section_content(sectionIds: ["sec-1", "sec-2"], recursive: true)
|
||||
- Explain which spec sections were hit and why
|
||||
|
||||
PATTERN 2 - For "How does X work?" questions (e.g., "${question}"):
|
||||
- Flow A: Generate a specific code example and follow Pattern 1
|
||||
- Flow B: If no code example possible, search broadly:
|
||||
* STEP 1: ask262_search_spec_sections(query: relevant keywords from "${question}")
|
||||
* STEP 2: ask262_get_section_content(sectionId: foundSectionId, recursive: true)
|
||||
* STEP 2: ask262_get_section_content(sectionIds: ["sec-1", "sec-2"], recursive: true)
|
||||
|
||||
I prefer Pattern 1 when possible as it provides exact spec sections through execution.
|
||||
|
||||
|
||||
@@ -147,10 +147,10 @@ export async function main() {
|
||||
},
|
||||
},
|
||||
async ({
|
||||
sectionId,
|
||||
sectionIds,
|
||||
recursive,
|
||||
}: GetSectionContentMCPInput): Promise<GetSectionContentMCPOutput> => {
|
||||
const result = await getSectionContentTool({ sectionId, recursive });
|
||||
const result = await getSectionContentTool({ sectionIds, recursive });
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||
structuredContent: result,
|
||||
@@ -222,14 +222,14 @@ I'll use one of these orchestration patterns:
|
||||
PATTERN 1 - For "What happens when I run this code?" questions:
|
||||
- Use ask262Debug.startImportant() and ask262Debug.stopImportant() in the code to mark only important sections.
|
||||
- STEP 1: ask262_evaluate_in_engine262(code: markedCode)
|
||||
- STEP 2: ask262_get_section_content(sectionId: importantSections[0], recursive: true)
|
||||
- STEP 2: ask262_get_section_content(sectionIds: [importantSections[0]], recursive: true)
|
||||
- Explain which spec sections were hit and why
|
||||
|
||||
PATTERN 2 - For "How does X work?" questions (e.g., "${question}"):
|
||||
- Flow A: Generate a specific code example and follow Pattern 1
|
||||
- Flow B: If no code example possible, search broadly:
|
||||
* STEP 1: ask262_search_spec_sections(query: relevant keywords from "${question}")
|
||||
* STEP 2: ask262_get_section_content(sectionId: foundSectionId, recursive: true)
|
||||
* STEP 2: ask262_get_section_content(sectionIds: [foundSectionId], recursive: true)
|
||||
|
||||
I prefer Pattern 1 when possible as it provides exact spec sections through execution.
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ async function testMCPServer() {
|
||||
const contentResult = (await client.callTool({
|
||||
name: "ask262_get_section_content",
|
||||
arguments: {
|
||||
sectionId: "sec-array.prototype.map",
|
||||
sectionIds: ["sec-array.prototype.map"],
|
||||
recursive: false,
|
||||
},
|
||||
})) as GetSectionContentMCPOutput;
|
||||
@@ -79,10 +79,13 @@ async function testMCPServer() {
|
||||
throw new Error(`Get section failed: ${contentResult.content[0]?.text}`);
|
||||
}
|
||||
const contentData = contentResult.structuredContent;
|
||||
console.log(
|
||||
`Content length: ${contentData.content?.length ?? 0} characters`,
|
||||
);
|
||||
console.log(`Sections visited: ${contentData.sectionCount ?? 0}`);
|
||||
const totalContentLength =
|
||||
contentData.sections?.reduce(
|
||||
(sum: number, s: { content: string }) => sum + s.content.length,
|
||||
0,
|
||||
) ?? 0;
|
||||
console.log(`Content length: ${totalContentLength} characters`);
|
||||
console.log(`Sections visited: ${contentData.sections?.length ?? 0}`);
|
||||
console.log("✓ Section content retrieved (isError: false)\n");
|
||||
|
||||
// Test 4: Evaluate in engine262 - success case
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Test to verify non-existing section IDs return found: false
|
||||
*/
|
||||
|
||||
import * as lancedbSdk from "@lancedb/lancedb";
|
||||
import { createGetSectionContentTool } from "../../agent-tools/getSectionContent.js";
|
||||
import { STORAGE_DIR } from "../../constants.js";
|
||||
|
||||
async function main() {
|
||||
console.log("Testing getSectionContent with non-existing section IDs...\n");
|
||||
|
||||
try {
|
||||
const db = await lancedbSdk.connect(STORAGE_DIR);
|
||||
const table = await db.openTable("spec_vectors");
|
||||
|
||||
const getSectionContentTool = createGetSectionContentTool(table);
|
||||
|
||||
// Test with mix of existing and non-existing section IDs
|
||||
const result = await getSectionContentTool({
|
||||
sectionIds: [
|
||||
"sec-non-existent-12345", // Should not exist
|
||||
"sec-also-fake-99999", // Should not exist
|
||||
],
|
||||
recursive: false,
|
||||
});
|
||||
|
||||
console.log("=== RESULT ===");
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
console.log("\n=== VERIFICATION ===");
|
||||
|
||||
// Verify all sections are returned
|
||||
if (result.sections.length !== 2) {
|
||||
console.error(`❌ Expected 2 sections, got ${result.sections.length}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Verify non-existing sections have found: false
|
||||
for (const section of result.sections) {
|
||||
if (section.found !== false) {
|
||||
console.error(
|
||||
`❌ Section ${section.sectionId} should have found: false`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (!section.error) {
|
||||
console.error(
|
||||
`❌ Section ${section.sectionId} should have error message`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (section.content !== "") {
|
||||
console.error(
|
||||
`❌ Section ${section.sectionId} should have empty content`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(
|
||||
`✅ ${section.sectionId}: found=false, error="${section.error}"`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log("\n✅ All non-existing sections correctly return found: false");
|
||||
} catch (error) {
|
||||
console.error("❌ Test failed:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user