mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 11:58:34 +08:00
fix(tools): preserve cron job id tool inputs
This commit is contained in:
parent
04a61fda19
commit
c4751ea69c
@ -217,17 +217,18 @@ public class CronJobTool {
|
|||||||
@Tool(description = "Enable or disable a scheduled task by its job ID. "
|
@Tool(description = "Enable or disable a scheduled task by its job ID. "
|
||||||
+ "Use list_cron_jobs first to find the job ID.")
|
+ "Use list_cron_jobs first to find the job ID.")
|
||||||
public String toggle_cron_job(
|
public String toggle_cron_job(
|
||||||
@ToolParam(description = "Job ID (number)") Long jobId,
|
@ToolParam(description = "Job ID. Must be passed as a string to preserve large integer precision") String jobId,
|
||||||
@ToolParam(description = "true to enable, false to disable") Boolean enabled,
|
@ToolParam(description = "true to enable, false to disable") Boolean enabled,
|
||||||
@Nullable ToolContext ctx) {
|
@Nullable ToolContext ctx) {
|
||||||
try {
|
try {
|
||||||
|
Long parsedJobId = parseJobId(jobId);
|
||||||
// RFC-083: scope toggle to the originating workspace.
|
// RFC-083: scope toggle to the originating workspace.
|
||||||
Long workspaceId = workspaceFromContext(ctx);
|
Long workspaceId = workspaceFromContext(ctx);
|
||||||
cronJobService.toggle(jobId, enabled, workspaceId);
|
cronJobService.toggle(parsedJobId, enabled, workspaceId);
|
||||||
CronJobDTO updated = cronJobService.getById(jobId, workspaceId);
|
CronJobDTO updated = cronJobService.getById(parsedJobId, workspaceId);
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
result.put("success", true);
|
result.put("success", true);
|
||||||
result.put("jobId", jobId);
|
result.put("jobId", parsedJobId);
|
||||||
result.put("name", updated.getName());
|
result.put("name", updated.getName());
|
||||||
result.put("enabled", updated.getEnabled());
|
result.put("enabled", updated.getEnabled());
|
||||||
result.put("nextRunTime", updated.getNextRunTime() != null ? updated.getNextRunTime().toString() : "");
|
result.put("nextRunTime", updated.getNextRunTime() != null ? updated.getNextRunTime().toString() : "");
|
||||||
@ -242,14 +243,15 @@ public class CronJobTool {
|
|||||||
@Tool(description = "Delete a scheduled task by its job ID. This action requires user approval. "
|
@Tool(description = "Delete a scheduled task by its job ID. This action requires user approval. "
|
||||||
+ "Use list_cron_jobs first to find the job ID.")
|
+ "Use list_cron_jobs first to find the job ID.")
|
||||||
public String delete_cron_job(
|
public String delete_cron_job(
|
||||||
@ToolParam(description = "Job ID (number) to delete") Long jobId,
|
@ToolParam(description = "Job ID to delete. Must be passed as a string to preserve large integer precision") String jobId,
|
||||||
@Nullable ToolContext ctx) {
|
@Nullable ToolContext ctx) {
|
||||||
try {
|
try {
|
||||||
|
Long parsedJobId = parseJobId(jobId);
|
||||||
// RFC-083: scope delete to the originating workspace.
|
// RFC-083: scope delete to the originating workspace.
|
||||||
Long workspaceId = workspaceFromContext(ctx);
|
Long workspaceId = workspaceFromContext(ctx);
|
||||||
CronJobDTO job = cronJobService.getById(jobId, workspaceId);
|
CronJobDTO job = cronJobService.getById(parsedJobId, workspaceId);
|
||||||
String jobName = job.getName();
|
String jobName = job.getName();
|
||||||
cronJobService.delete(jobId, workspaceId);
|
cronJobService.delete(parsedJobId, workspaceId);
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
result.put("success", true);
|
result.put("success", true);
|
||||||
result.put("deleted", jobName);
|
result.put("deleted", jobName);
|
||||||
@ -267,6 +269,18 @@ public class CronJobTool {
|
|||||||
return writeJson(result);
|
return writeJson(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Long parseJobId(String jobId) {
|
||||||
|
String trimmed = jobId != null ? jobId.trim() : "";
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("jobId is required");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(trimmed);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("jobId must be a numeric string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize tool output through the id-safe application ObjectMapper so every
|
* Serialize tool output through the id-safe application ObjectMapper so every
|
||||||
* {@code Long} (notably {@code jobId}) is rendered as a string. Falls back to
|
* {@code Long} (notably {@code jobId}) is rendered as a string. Falls back to
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
package vip.mate.tool.builtin;
|
package vip.mate.tool.builtin;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.ai.support.ToolCallbacks;
|
||||||
|
import org.springframework.ai.tool.ToolCallback;
|
||||||
import vip.mate.cron.model.CronJobDTO;
|
import vip.mate.cron.model.CronJobDTO;
|
||||||
import vip.mate.cron.service.CronJobService;
|
import vip.mate.cron.service.CronJobService;
|
||||||
|
|
||||||
@ -52,4 +55,29 @@ class CronJobToolIdPrecisionTest {
|
|||||||
assertFalse(out.contains(": " + bigId) || out.contains(":" + bigId),
|
assertFalse(out.contains(": " + bigId) || out.contains(":" + bigId),
|
||||||
"jobId must NOT appear as a bare JSON number");
|
"jobId must NOT appear as a bare JSON number");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("cron mutating tools publish jobId as a string parameter so LLM tool calls preserve precision")
|
||||||
|
void cronJobIdSchemasAreString() throws Exception {
|
||||||
|
CronJobTool tool = new CronJobTool(mock(CronJobService.class), idSafeMapper());
|
||||||
|
|
||||||
|
assertJobIdIsString(tool, "toggle_cron_job");
|
||||||
|
assertJobIdIsString(tool, "delete_cron_job");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertJobIdIsString(Object tool, String name) throws Exception {
|
||||||
|
JsonNode root = idSafeMapper().readTree(callback(tool, name).getToolDefinition().inputSchema());
|
||||||
|
|
||||||
|
assertTrue("string".equals(root.at("/properties/jobId/type").asText()),
|
||||||
|
name + " jobId must be a string schema");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ToolCallback callback(Object tool, String name) {
|
||||||
|
for (ToolCallback callback : ToolCallbacks.from(tool)) {
|
||||||
|
if (name.equals(callback.getToolDefinition().name())) {
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError("Missing tool callback: " + name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user