fix(wiki): preserve knowledge base ids in tool inputs

This commit is contained in:
matevip 2026-08-13 04:04:30 -04:00
parent c4751ea69c
commit bd35f28dad
4 changed files with 114 additions and 87 deletions

View File

@ -201,15 +201,15 @@ public class WikiTool {
@ToolParam(description = "Max characters to return (null = full page)", required = false) Integer maxChars,
@ToolParam(description = "Section heading to extract (null = all sections)", required = false) String sectionHeading,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (slug == null || slug.isBlank()) {
return error("slug is required");
}
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
WikiPageEntity page = pageService.getBySlug(kbId, slug);
if (page == null) {
@ -251,11 +251,11 @@ public class WikiTool {
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Title keyword filter (optional)", required = false) String query,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
WikiPageTypePermissionService.Access access = pageTypeAccess(agentId, kbId);
List<WikiPageLite> pages;
@ -314,15 +314,15 @@ public class WikiTool {
@ToolParam(description = "Max results (default 5, max 20)", required = false) Integer topK,
@ToolParam(description = "Knowledge layer filter: fact | experience | all (default all). 'fact' = factual pages (and unlayered pages); 'experience' = synthesis/analysis pages.", required = false) String layer,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (query == null || query.isBlank()) {
return error("query is required");
}
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
int k = (topK != null && topK > 0) ? Math.min(topK, 20) : 5;
List<PageSearchResult> results = hybridRetriever.search(kbId, query, mode, k);
@ -379,15 +379,15 @@ public class WikiTool {
@ToolParam(description = "Natural language query") String query,
@ToolParam(description = "Max results (default 5)", required = false) Integer topK,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (query == null || query.isBlank()) {
return error("query is required");
}
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
int k = (topK != null && topK > 0) ? Math.min(topK, 20) : 5;
List<HybridRetriever.ChunkHit> hits = hybridRetriever.searchChunks(kbId, query, k);
@ -445,15 +445,15 @@ public class WikiTool {
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (slug == null || slug.isBlank()) {
return error("slug is required");
}
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
WikiPageEntity page = pageService.getBySlug(kbId, slug);
if (page == null) {
@ -479,7 +479,7 @@ public class WikiTool {
@ToolParam(description = "Page title") String title,
@ToolParam(description = "Page content (Markdown)") String content,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (title == null || title.isBlank()) {
return error("title is required");
@ -488,9 +488,9 @@ public class WikiTool {
return error("content is required");
}
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
// wiki_create_page does not take an explicit pageType, so creation is
// governed by the agent's wildcard ('*') write rule for this KB.
@ -567,14 +567,14 @@ public class WikiTool {
@ToolParam(description = "Optional explicit slug for the page", required = false) String slug,
@ToolParam(description = "Max evidence chunks (default 8, max 20)", required = false) Integer maxEvidenceChunks,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (topic == null || topic.isBlank()) {
return error("topic is required");
}
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
if (compileService == null) return error("Compile service not available");
String compileErr = checkWrite(agentId, kbId, null,
@ -623,12 +623,12 @@ public class WikiTool {
@ToolParam(description = "Comma-separated slugs (max 10)") String slugs,
@ToolParam(description = "Max chars returned per page (default 2000, max 8000)", required = false) Integer maxCharsPerPage,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (slugs == null || slugs.isBlank()) return error("slugs is required");
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
int cap = (maxCharsPerPage == null || maxCharsPerPage <= 0) ? 2000 : Math.min(8000, maxCharsPerPage);
List<String> slugList = Arrays.stream(slugs.split(","))
@ -674,8 +674,8 @@ public class WikiTool {
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug to archive") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
return setArchivedTool(agentId, slug, true, "archived", kbName, kbId);
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
return setArchivedTool(agentId, slug, true, "archived", kbName, kbIdParam);
}
@Tool(description = """
@ -686,15 +686,15 @@ public class WikiTool {
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug to unarchive") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
return setArchivedTool(agentId, slug, false, "unarchived", kbName, kbId);
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
return setArchivedTool(agentId, slug, false, "unarchived", kbName, kbIdParam);
}
private String setArchivedTool(String agentId, String slug, boolean archive, String verb, String kbName, Long kbId) {
private String setArchivedTool(String agentId, String slug, boolean archive, String verb, String kbName, String kbIdParam) {
if (slug == null || slug.isBlank()) return error("slug is required");
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
// Archiving toggles visibility gate it as an update, and hide pages
// whose type the agent cannot read.
WikiPageEntity target = pageService.getBySlug(kbId, slug);
@ -729,15 +729,15 @@ public class WikiTool {
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug to delete") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (slug == null || slug.isBlank()) {
return error("slug is required");
}
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
WikiPageEntity page = pageService.getBySlug(kbId, slug);
if (page == null) {
@ -786,11 +786,11 @@ public class WikiTool {
@ToolParam(description = "Page slug") String slug,
@ToolParam(description = "Max results (default 5, max 10)", required = false) Integer topK,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
if (relationService == null) return error("Relation service not available");
int k = (topK != null && topK > 0) ? Math.min(topK, 10) : 5;
@ -824,11 +824,11 @@ public class WikiTool {
@ToolParam(description = "First page slug") String slugA,
@ToolParam(description = "Second page slug") String slugB,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
if (relationService == null) return error("Relation service not available");
WikiPageTypePermissionService.Access relAccess = pageTypeAccess(agentId, kbId);
@ -858,11 +858,11 @@ public class WikiTool {
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
if (jobService == null || eventPublisher == null) return error("Job service not available");
WikiPageEntity page = pageService.getBySlug(kbId, slug);
@ -904,7 +904,7 @@ public class WikiTool {
@ToolParam(description = "New full Markdown content for the page body") String content,
@ToolParam(description = "New one-line summary (optional; omit to auto-derive from content)", required = false) String summary,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (slug == null || slug.isBlank()) {
return error("slug is required");
@ -912,9 +912,9 @@ public class WikiTool {
if (content == null || content.isBlank()) {
return error("content is required");
}
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
WikiPageEntity page = pageService.getBySlug(kbId, slug);
if (page == null) {
@ -947,11 +947,11 @@ public class WikiTool {
public String wiki_stale_pages(
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
WikiPageTypePermissionService.Access access = pageTypeAccess(agentId, kbId);
JSONArray arr = new JSONArray();
@ -983,10 +983,10 @@ public class WikiTool {
public String wiki_list_transformations(
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
if (transformationService == null) return error("Transformations not available");
WikiKnowledgeBaseEntity kb = kbService.getById(kbId);
@ -1013,14 +1013,14 @@ public class WikiTool {
public String wiki_apply_transformation(
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Transformation name (from wiki_list_transformations)") String name,
@ToolParam(description = "Raw material ID to run the transformation against") Long rawId,
@ToolParam(description = "Raw material ID to run the transformation against. Must be passed as a string to preserve large integer precision") String rawIdParam,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (name == null || name.isBlank()) return error("name is required");
if (rawId == null) return error("rawId is required");
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
Long rawId = parseRequiredId(rawIdParam, "rawId");
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
if (transformationService == null || transformationExecutor == null) {
return error("Transformations not available");
}
@ -1070,12 +1070,12 @@ public class WikiTool {
@ToolParam(description = "Transformation name (from wiki_list_transformations)") String name,
@ToolParam(description = "Source wiki page slug to run the transformation against") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (name == null || name.isBlank()) return error("name is required");
if (slug == null || slug.isBlank()) return error("slug is required");
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
if (transformationService == null || transformationExecutor == null) {
return error("Transformations not available");
}
@ -1131,11 +1131,11 @@ public class WikiTool {
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Transformation name (from wiki_list_transformations)") String name,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Must be passed as a string to preserve large integer precision. Use when `kbName` returns an ambiguous-name error.", required = false) String kbIdParam) {
if (name == null || name.isBlank()) return error("name is required");
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
KbResolution kbRes = resolveKb(agentId, kbName, kbIdParam);
if (kbRes.hasError()) return kbRes.errorJson();
kbId = kbRes.kbId();
Long kbId = kbRes.kbId();
if (transformationService == null || transformationAggregator == null) {
return error("Transformations not available");
}
@ -1196,6 +1196,26 @@ public class WikiTool {
}
}
private Long parseOptionalId(String value, String name) {
String trimmed = value != null ? value.trim() : "";
if (trimmed.isEmpty()) {
return null;
}
try {
return Long.parseLong(trimmed);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(name + " must be a numeric string");
}
}
private Long parseRequiredId(String value, String name) {
Long parsed = parseOptionalId(value, name);
if (parsed == null) {
throw new IllegalArgumentException(name + " is required");
}
return parsed;
}
/**
* Outcome of resolving a KB for a tool call. Exactly one of
* {@code kbId} / {@code errorJson} is non-null:
@ -1412,8 +1432,8 @@ public class WikiTool {
return KbResolution.ok(primary.getId());
}
private KbResolution resolveKb(String agentId, String kbName, Long kbId) {
return resolveKb(parseAgentId(agentId), kbName, kbId);
private KbResolution resolveKb(String agentId, String kbName, String kbIdParam) {
return resolveKb(parseAgentId(agentId), kbName, parseOptionalId(kbIdParam, "kbId"));
}
/**

View File

@ -33,15 +33,22 @@ class WikiToolIdSchemaTest {
int checked = 0;
for (ToolCallback callback : ToolCallbacks.from(tool)) {
JsonNode root = MAPPER.readTree(callback.getToolDefinition().inputSchema());
JsonNode agentId = root.at("/properties/agentId/type");
if (!agentId.isMissingNode()) {
assertThat(agentId.asText())
.as(callback.getToolDefinition().name())
.isEqualTo("string");
checked++;
}
checked += assertIdParamIsString(root, callback, "agentId");
checked += assertIdParamIsString(root, callback, "kbId");
checked += assertIdParamIsString(root, callback, "rawId");
}
assertThat(checked).isGreaterThan(0);
}
private static int assertIdParamIsString(JsonNode root, ToolCallback callback, String property) {
JsonNode type = root.at("/properties/" + property + "/type");
if (!type.isMissingNode()) {
assertThat(type.asText())
.as(callback.getToolDefinition().name())
.isEqualTo("string");
return 1;
}
return 0;
}
}

View File

@ -142,7 +142,7 @@ class WikiToolKbNameRoutingTest {
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
when(kbService.findVisibleById(AGENT, OTHER_KB)).thenReturn(kb(OTHER_KB, "Other", null));
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, OTHER_KB);
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, String.valueOf(OTHER_KB));
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getJSONArray("pages").getJSONObject(0).getStr("slug"))
@ -157,7 +157,7 @@ class WikiToolKbNameRoutingTest {
// Deliberately do NOT stub findAllByName if the tool consulted
// kbName at all (or fell back to primary), the call would NPE.
String json = tool.wiki_list_pages(AGENT_PARAM, null, "anything", OTHER_KB);
String json = tool.wiki_list_pages(AGENT_PARAM, null, "anything", String.valueOf(OTHER_KB));
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getJSONArray("pages").getJSONObject(0).getStr("slug"))
.isEqualTo("other-only-slug");
@ -218,7 +218,7 @@ class WikiToolKbNameRoutingTest {
// would return null and surface a spurious "kbId=0 not visible" error,
// which is exactly the production regression this test prevents.
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, 0L);
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, "0");
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getStr("error"))
@ -235,7 +235,7 @@ class WikiToolKbNameRoutingTest {
when(kbService.findVisibleById(AGENT, 99999L)).thenReturn(null);
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, 99999L);
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, "99999");
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getStr("error"))

View File

@ -91,7 +91,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("*", 0, 0, 0, 0, "deny")));
when(h.pageService().getBySlug(KB, "secret")).thenReturn(page("secret", "analysis"));
String out = h.tool().wiki_read_page(AGENT_PARAM, "secret", null, null, null, KB);
String out = h.tool().wiki_read_page(AGENT_PARAM, "secret", null, null, null, String.valueOf(KB));
assertTrue(out.contains("Page not found"), out);
}
@ -101,7 +101,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("*", 1, 0, 0, 0, "deny")));
when(h.pageService().getBySlug(KB, "ok")).thenReturn(page("ok", "concept"));
String out = h.tool().wiki_read_page(AGENT_PARAM, "ok", null, null, null, KB);
String out = h.tool().wiki_read_page(AGENT_PARAM, "ok", null, null, null, String.valueOf(KB));
assertTrue(out.contains("\"content\""), out);
assertFalse(out.contains("Page not found"), out);
@ -113,7 +113,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("concept", 1, 0, 0, 0, "deny")));
when(h.pageService().getBySlug(KB, "p")).thenReturn(page("p", "concept"));
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, KB);
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, String.valueOf(KB));
assertTrue(out.contains("Not permitted"), out);
verify(h.pageService(), never()).delete(anyLong(), any());
@ -124,7 +124,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("concept", 1, 0, 0, 1, "approval_required")));
when(h.pageService().getBySlug(KB, "p")).thenReturn(page("p", "concept"));
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, KB);
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, String.valueOf(KB));
assertTrue(out.contains("Approval required"), out);
verify(h.pageService(), never()).delete(anyLong(), any());
@ -135,7 +135,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("concept", 1, 1, 1, 1, "allow")));
when(h.pageService().getBySlug(KB, "p")).thenReturn(page("p", "concept"));
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, KB);
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, String.valueOf(KB));
assertTrue(out.contains("\"ok\":true"), out);
verify(h.pageService(), times(1)).delete(eq(KB), eq("p"));
@ -146,7 +146,7 @@ class WikiToolPermissionTest {
// a row exists for 'episode' only KB is gated, wildcard create not granted
Harness h = harness(List.of(row("episode", 1, 1, 1, 1, "allow")));
String out = h.tool().wiki_create_page(AGENT_PARAM, "New Page", "content here", null, KB);
String out = h.tool().wiki_create_page(AGENT_PARAM, "New Page", "content here", null, String.valueOf(KB));
assertTrue(out.contains("Not permitted"), out);
verify(h.pageService(), never()).createPage(anyLong(), any(), any(), any(), any(), any());
@ -162,7 +162,7 @@ class WikiToolPermissionTest {
updated.setVersion(2);
when(h.pageService().updatePageManually(eq(KB), eq("p"), any(), any())).thenReturn(updated);
String out = h.tool().wiki_update_page(AGENT_PARAM, "p", "new body", null, null, KB);
String out = h.tool().wiki_update_page(AGENT_PARAM, "p", "new body", null, null, String.valueOf(KB));
assertTrue(out.contains("\"ok\":true"), out);
assertTrue(out.contains("updated in place"), out);
@ -178,7 +178,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("concept", 1, 1, 0, 0, "allow")));
when(h.pageService().getBySlug(KB, "p")).thenReturn(page("p", "concept"));
String out = h.tool().wiki_update_page(AGENT_PARAM, "p", "new body", null, null, KB);
String out = h.tool().wiki_update_page(AGENT_PARAM, "p", "new body", null, null, String.valueOf(KB));
assertTrue(out.contains("Not permitted"), out);
verify(h.pageService(), never()).updatePageManually(anyLong(), any(), any(), any());
@ -189,7 +189,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("*", 1, 1, 1, 1, "allow")));
when(h.pageService().getBySlug(KB, "ghost")).thenReturn(null);
String out = h.tool().wiki_update_page(AGENT_PARAM, "ghost", "body", null, null, KB);
String out = h.tool().wiki_update_page(AGENT_PARAM, "ghost", "body", null, null, String.valueOf(KB));
assertTrue(out.contains("Page not found"), out);
verify(h.pageService(), never()).updatePageManually(anyLong(), any(), any(), any());
@ -213,7 +213,7 @@ class WikiToolPermissionTest {
WikiPageEntity staleHidden = stalePage("classified", "secret", "{\"reason\":\"x\"}");
when(h.pageService().listByKbId(KB)).thenReturn(List.of(fresh, staleOk, staleHidden));
String out = h.tool().wiki_stale_pages(AGENT_PARAM, null, KB);
String out = h.tool().wiki_stale_pages(AGENT_PARAM, null, String.valueOf(KB));
assertTrue(out.contains("\"staleCount\":1"), out);
assertTrue(out.contains("aged"), out);
@ -226,7 +226,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of());
when(h.pageService().listByKbId(KB)).thenReturn(List.of(page("a", "concept"), page("b", "episode")));
String out = h.tool().wiki_stale_pages(AGENT_PARAM, null, KB);
String out = h.tool().wiki_stale_pages(AGENT_PARAM, null, String.valueOf(KB));
assertTrue(out.contains("\"staleCount\":0"), out);
}