mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 21:31:46 +00:00
getSectionContent - allow multiple sectionids
This commit is contained in:
@@ -14,9 +14,18 @@ const sectionContentSchema = z.object({
|
|||||||
partIndex: z.number().optional(),
|
partIndex: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSectionContentOutputSchema = z.object({
|
const sectionDataSchema = z.object({
|
||||||
|
sectionId: z.string(),
|
||||||
content: 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
|
// #endregion
|
||||||
@@ -25,20 +34,21 @@ const getSectionContentOutputSchema = z.object({
|
|||||||
|
|
||||||
export const toolMetadata = {
|
export const toolMetadata = {
|
||||||
description:
|
description:
|
||||||
"Retrieves all text chunks from a specific specification section by sectionid. " +
|
"Retrieves all text chunks from one or more specification sections by section IDs. " +
|
||||||
"Supports recursive fetching - if recursive=true and the section has children, it will fetch all descendants. " +
|
"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.",
|
"Use this to get complete content when you see 'Subsection available' or 'partial section' references.",
|
||||||
args: {
|
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:
|
recursive:
|
||||||
"If true, recursively fetches content from all child sections and their descendants. " +
|
"If true, recursively fetches content from all child sections and their descendants. " +
|
||||||
"If false, only returns content from the specified section itself. " +
|
"If false, only returns content from the specified sections themselves. " +
|
||||||
"Use false when you only need the specific section's content without subsections.",
|
"Use false when you only need the specific sections' content without subsections.",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const inputSchema = z.object({
|
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),
|
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) {
|
export function createGetSectionContentTool(table: Table) {
|
||||||
return async ({
|
return async ({
|
||||||
sectionId,
|
sectionIds,
|
||||||
recursive,
|
recursive,
|
||||||
}: GetSectionContentInput): Promise<GetSectionContentOutput> => {
|
}: GetSectionContentInput): Promise<GetSectionContentOutput> => {
|
||||||
const allDocs: string[] = [];
|
const sectionsData = new Map<
|
||||||
const queue: string[] = [sectionId];
|
string,
|
||||||
|
{
|
||||||
|
content: string[];
|
||||||
|
title?: string;
|
||||||
|
partIndex?: number;
|
||||||
|
totalParts?: number;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
const queue: string[] = [...sectionIds];
|
||||||
const visited = new Set<string>();
|
const visited = new Set<string>();
|
||||||
|
|
||||||
while (queue.length > 0) {
|
while (queue.length > 0) {
|
||||||
@@ -84,7 +102,7 @@ export function createGetSectionContentTool(table: Table) {
|
|||||||
const results = await table
|
const results = await table
|
||||||
.query()
|
.query()
|
||||||
.where(`sectionid = '${currentId}'`)
|
.where(`sectionid = '${currentId}'`)
|
||||||
.limit(100)
|
.limit(10)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
// Sort by partindex to maintain order (nulls last for single-part sections)
|
// Sort by partindex to maintain order (nulls last for single-part sections)
|
||||||
@@ -99,10 +117,23 @@ export function createGetSectionContentTool(table: Table) {
|
|||||||
text?: string;
|
text?: string;
|
||||||
childrensectionids?: string[];
|
childrensectionids?: string[];
|
||||||
sectiontitle?: string;
|
sectiontitle?: string;
|
||||||
|
partindex?: number;
|
||||||
|
totalparts?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typedResult.text) {
|
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
|
// 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 {
|
return {
|
||||||
content: allDocs.join("\n\n---\n\n"),
|
sectionId: id,
|
||||||
sectionCount: visited.size,
|
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 {
|
||||||
|
sections,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -113,13 +113,17 @@ async function createMcpServer() {
|
|||||||
openWorldHint: false,
|
openWorldHint: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ sectionId, recursive }) => {
|
async ({ sectionIds, recursive }) => {
|
||||||
console.log(
|
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(
|
console.log(
|
||||||
`[TOOL] ${sectionContentToolName}: ${result.content.length} chars, ${result.sectionCount} sections`,
|
`[TOOL] ${sectionContentToolName}: ${totalContentLength} chars, ${result.sections.length} sections`,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
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:
|
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.
|
- Use ask262Debug.startImportant() and ask262Debug.stopImportant() in the code to mark only important sections.
|
||||||
- STEP 1: ask262_evaluate_in_engine262(code: markedCode)
|
- 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
|
- Explain which spec sections were hit and why
|
||||||
|
|
||||||
PATTERN 2 - For "How does X work?" questions (e.g., "${question}"):
|
PATTERN 2 - For "How does X work?" questions (e.g., "${question}"):
|
||||||
- Flow A: Generate a specific code example and follow Pattern 1
|
- Flow A: Generate a specific code example and follow Pattern 1
|
||||||
- Flow B: If no code example possible, search broadly:
|
- Flow B: If no code example possible, search broadly:
|
||||||
* STEP 1: ask262_search_spec_sections(query: relevant keywords from "${question}")
|
* 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.
|
I prefer Pattern 1 when possible as it provides exact spec sections through execution.
|
||||||
|
|
||||||
|
|||||||
@@ -147,10 +147,10 @@ export async function main() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({
|
async ({
|
||||||
sectionId,
|
sectionIds,
|
||||||
recursive,
|
recursive,
|
||||||
}: GetSectionContentMCPInput): Promise<GetSectionContentMCPOutput> => {
|
}: GetSectionContentMCPInput): Promise<GetSectionContentMCPOutput> => {
|
||||||
const result = await getSectionContentTool({ sectionId, recursive });
|
const result = await getSectionContentTool({ sectionIds, recursive });
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
structuredContent: result,
|
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:
|
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.
|
- Use ask262Debug.startImportant() and ask262Debug.stopImportant() in the code to mark only important sections.
|
||||||
- STEP 1: ask262_evaluate_in_engine262(code: markedCode)
|
- 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
|
- Explain which spec sections were hit and why
|
||||||
|
|
||||||
PATTERN 2 - For "How does X work?" questions (e.g., "${question}"):
|
PATTERN 2 - For "How does X work?" questions (e.g., "${question}"):
|
||||||
- Flow A: Generate a specific code example and follow Pattern 1
|
- Flow A: Generate a specific code example and follow Pattern 1
|
||||||
- Flow B: If no code example possible, search broadly:
|
- Flow B: If no code example possible, search broadly:
|
||||||
* STEP 1: ask262_search_spec_sections(query: relevant keywords from "${question}")
|
* 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.
|
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({
|
const contentResult = (await client.callTool({
|
||||||
name: "ask262_get_section_content",
|
name: "ask262_get_section_content",
|
||||||
arguments: {
|
arguments: {
|
||||||
sectionId: "sec-array.prototype.map",
|
sectionIds: ["sec-array.prototype.map"],
|
||||||
recursive: false,
|
recursive: false,
|
||||||
},
|
},
|
||||||
})) as GetSectionContentMCPOutput;
|
})) as GetSectionContentMCPOutput;
|
||||||
@@ -79,10 +79,13 @@ async function testMCPServer() {
|
|||||||
throw new Error(`Get section failed: ${contentResult.content[0]?.text}`);
|
throw new Error(`Get section failed: ${contentResult.content[0]?.text}`);
|
||||||
}
|
}
|
||||||
const contentData = contentResult.structuredContent;
|
const contentData = contentResult.structuredContent;
|
||||||
console.log(
|
const totalContentLength =
|
||||||
`Content length: ${contentData.content?.length ?? 0} characters`,
|
contentData.sections?.reduce(
|
||||||
);
|
(sum: number, s: { content: string }) => sum + s.content.length,
|
||||||
console.log(`Sections visited: ${contentData.sectionCount ?? 0}`);
|
0,
|
||||||
|
) ?? 0;
|
||||||
|
console.log(`Content length: ${totalContentLength} characters`);
|
||||||
|
console.log(`Sections visited: ${contentData.sections?.length ?? 0}`);
|
||||||
console.log("✓ Section content retrieved (isError: false)\n");
|
console.log("✓ Section content retrieved (isError: false)\n");
|
||||||
|
|
||||||
// Test 4: Evaluate in engine262 - success case
|
// 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