/** Shared tool primitives and schemas for the main and executor Sessions. */ import { z } from 'zod' import { IM_WORDS, wordCount } from '../../infra/text.ts' import { resourceGrammar } from '../resources.ts' export interface Tool { name: string description: string schema: S execute: (args: z.infer, signal: AbortSignal) => Promise } export type RegisterTool = (name: string, description: string, schema: S, execute: Tool['execute']) => void export const tool = (definition: Tool): Tool => definition as unknown as Tool /** Human-facing acknowledgement text, constrained to `IM_WORDS` regardless of which channel it is posted to. */ export const AcknowledgementSchema = z.string().trim().min(1) .refine(text => wordCount(text) <= IM_WORDS, `An acknowledgement has at most ${IM_WORDS} words (Chinese uses word segmentation); write one short sentence.`) /** * Builds the `orchestrator_read` tool, scoping allowed resource patterns to the main Session or to an * Executor Session (which may read only its own ticket and messages that reached it). */ export function readTool(description: string, executor: boolean, read: (resource: string, offset: number, limit: number | undefined, signal: AbortSignal) => Promise): Tool { return tool({ name: 'orchestrator_read', description, schema: z.object({ resource: z.string().min(1).describe(`The resource, one of: ${resourceGrammar(executor)}.`), offset: z.number().int().min(0).default(0).describe('Copy next_offset from the previous page; it counts characters of a text and items of a list.'), limit: z.number().int().min(1).max(8000).optional() .describe('Characters of a text, 4000 by default and 8000 at most, or items of a list, 20 by default and 50 at most.'), }).strict(), execute: async (args, signal) => read(args.resource, args.offset, args.limit, signal), }) }