2026-01-04 05:07:37 +01:00
import { Type } from "@sinclair/typebox" ;
2026-01-14 14:31:43 +00:00
import { normalizeCronJobCreate , normalizeCronJobPatch } from "../../cron/normalize.js" ;
2026-01-13 06:28:09 +00:00
import { optionalStringEnum , stringEnum } from "../schema/typebox.js" ;
2026-01-06 03:25:21 +01:00
import { type AnyAgentTool , jsonResult , readStringParam } from "./common.js" ;
import { callGatewayTool , type GatewayCallOptions } from "./gateway.js" ;
2026-01-05 23:09:48 -03:00
2026-01-06 13:43:09 +05:30
// NOTE: We use Type.Object({}, { additionalProperties: true }) for job/patch
2026-01-13 06:28:09 +00:00
// instead of CronAddParamsSchema/CronJobPatchSchema because the gateway schemas
// contain nested unions. Tool schemas need to stay provider-friendly, so we
// accept "any object" here and validate at runtime.
2026-01-04 05:07:37 +01:00
2026-01-14 14:31:43 +00:00
const CRON_ACTIONS = [ "status" , "list" , "add" , "update" , "remove" , "run" , "runs" , "wake" ] as const ;
2026-01-13 06:28:09 +00:00
const CRON_WAKE_MODES = [ "now" , "next-heartbeat" ] as const ;
// Flattened schema: runtime validates per-action requirements.
const CronToolSchema = Type . Object ( {
action : stringEnum ( CRON_ACTIONS ) ,
gatewayUrl : Type.Optional ( Type . String ( ) ) ,
gatewayToken : Type.Optional ( Type . String ( ) ) ,
timeoutMs : Type.Optional ( Type . Number ( ) ) ,
includeDisabled : Type.Optional ( Type . Boolean ( ) ) ,
job : Type.Optional ( Type . Object ( { } , { additionalProperties : true } ) ) ,
jobId : Type.Optional ( Type . String ( ) ) ,
id : Type.Optional ( Type . String ( ) ) ,
patch : Type.Optional ( Type . Object ( { } , { additionalProperties : true } ) ) ,
text : Type.Optional ( Type . String ( ) ) ,
mode : optionalStringEnum ( CRON_WAKE_MODES ) ,
} ) ;
2026-01-04 05:07:37 +01:00
export function createCronTool ( ) : AnyAgentTool {
return {
label : "Cron" ,
name : "cron" ,
description :
2026-01-08 20:46:58 +01:00
"Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use `jobId` as the canonical identifier; `id` is accepted for compatibility." ,
2026-01-04 05:07:37 +01:00
parameters : CronToolSchema ,
execute : async ( _toolCallId , args ) = > {
const params = args as Record < string , unknown > ;
const action = readStringParam ( params , "action" , { required : true } ) ;
const gatewayOpts : GatewayCallOptions = {
gatewayUrl : readStringParam ( params , "gatewayUrl" , { trim : false } ) ,
gatewayToken : readStringParam ( params , "gatewayToken" , { trim : false } ) ,
2026-01-14 14:31:43 +00:00
timeoutMs : typeof params . timeoutMs === "number" ? params.timeoutMs : undefined ,
2026-01-04 05:07:37 +01:00
} ;
switch ( action ) {
case "status" :
2026-01-14 14:31:43 +00:00
return jsonResult ( await callGatewayTool ( "cron.status" , gatewayOpts , { } ) ) ;
2026-01-04 05:07:37 +01:00
case "list" :
return jsonResult (
await callGatewayTool ( "cron.list" , gatewayOpts , {
includeDisabled : Boolean ( params . includeDisabled ) ,
} ) ,
) ;
case "add" : {
if ( ! params . job || typeof params . job !== "object" ) {
throw new Error ( "job required" ) ;
}
2026-01-05 23:09:48 -03:00
const job = normalizeCronJobCreate ( params . job ) ? ? params . job ;
2026-01-14 14:31:43 +00:00
return jsonResult ( await callGatewayTool ( "cron.add" , gatewayOpts , job ) ) ;
2026-01-04 05:07:37 +01:00
}
case "update" : {
2026-01-14 14:31:43 +00:00
const id = readStringParam ( params , "jobId" ) ? ? readStringParam ( params , "id" ) ;
2026-01-08 20:46:58 +01:00
if ( ! id ) {
2026-01-14 14:31:43 +00:00
throw new Error ( "jobId required (id accepted for backward compatibility)" ) ;
2026-01-08 20:46:58 +01:00
}
2026-01-04 05:07:37 +01:00
if ( ! params . patch || typeof params . patch !== "object" ) {
throw new Error ( "patch required" ) ;
}
2026-01-05 23:09:48 -03:00
const patch = normalizeCronJobPatch ( params . patch ) ? ? params . patch ;
2026-01-04 05:07:37 +01:00
return jsonResult (
await callGatewayTool ( "cron.update" , gatewayOpts , {
2026-01-04 14:57:26 +00:00
id ,
2026-01-05 23:09:48 -03:00
patch ,
2026-01-04 05:07:37 +01:00
} ) ,
) ;
}
case "remove" : {
2026-01-14 14:31:43 +00:00
const id = readStringParam ( params , "jobId" ) ? ? readStringParam ( params , "id" ) ;
2026-01-08 20:46:58 +01:00
if ( ! id ) {
2026-01-14 14:31:43 +00:00
throw new Error ( "jobId required (id accepted for backward compatibility)" ) ;
2026-01-08 20:46:58 +01:00
}
2026-01-14 14:31:43 +00:00
return jsonResult ( await callGatewayTool ( "cron.remove" , gatewayOpts , { id } ) ) ;
2026-01-04 05:07:37 +01:00
}
case "run" : {
2026-01-14 14:31:43 +00:00
const id = readStringParam ( params , "jobId" ) ? ? readStringParam ( params , "id" ) ;
2026-01-08 20:46:58 +01:00
if ( ! id ) {
2026-01-14 14:31:43 +00:00
throw new Error ( "jobId required (id accepted for backward compatibility)" ) ;
2026-01-08 20:46:58 +01:00
}
2026-01-14 14:31:43 +00:00
return jsonResult ( await callGatewayTool ( "cron.run" , gatewayOpts , { id } ) ) ;
2026-01-04 05:07:37 +01:00
}
case "runs" : {
2026-01-14 14:31:43 +00:00
const id = readStringParam ( params , "jobId" ) ? ? readStringParam ( params , "id" ) ;
2026-01-08 20:46:58 +01:00
if ( ! id ) {
2026-01-14 14:31:43 +00:00
throw new Error ( "jobId required (id accepted for backward compatibility)" ) ;
2026-01-08 20:46:58 +01:00
}
2026-01-14 14:31:43 +00:00
return jsonResult ( await callGatewayTool ( "cron.runs" , gatewayOpts , { id } ) ) ;
2026-01-04 05:07:37 +01:00
}
case "wake" : {
const text = readStringParam ( params , "text" , { required : true } ) ;
const mode =
params . mode === "now" || params . mode === "next-heartbeat"
? params . mode
: "next-heartbeat" ;
return jsonResult (
2026-01-14 14:31:43 +00:00
await callGatewayTool ( "wake" , gatewayOpts , { mode , text } , { expectFinal : false } ) ,
2026-01-04 05:07:37 +01:00
) ;
}
default :
throw new Error ( ` Unknown action: ${ action } ` ) ;
}
} ,
} ;
}