@mikode13/harness 10.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"defineTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/defineTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAIpE,OAAO,EAAa,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,4FAA4F;AAC5F,iFAAiF;AACjF,MAAM,mBAAmB,GAAG;IAC3B,SAAS;IACT,SAAS;IACT,kBAAkB;IAClB,kBAAkB;IAClB,YAAY;IACZ,WAAW;IACX,WAAW;CACX,CAAC;AAEF,2FAA2F;AAC3F,uEAAuE;AACvE,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,sBAAsB,CAAC,CAAC;AAChE,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AACtE,MAAM,iBAAiB,GAAG,CAAC,YAAY,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AAEjE,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,MAAe,EAAE,QAAgB;IACtD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACvB,OAAO;IACR,CAAC;IAED,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC;IAC3E,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,uBAAuB,CAChC,SAAS,QAAQ,WAAW,OAAO,oEAAoE,CACvG,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,4EAA4E;QAC5E,IAAI,MAAM,CAAC,oBAAoB,KAAK,KAAK,EAAE,CAAC;YAC3C,MAAM,IAAI,uBAAuB,CAChC,SAAS,QAAQ,uEAAuE,CACxF,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAa,CAAC;QACrD,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,CAC3D,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CACxC,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,uBAAuB,CAChC,SAAS,QAAQ,0BAA0B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,kCAAkC,CAChG,CAAC;QACH,CAAC;IACF,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,cAAc;QAAE,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IACtE,KAAK,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,KAAK,MAAM,IAAI,IAAI,IAAI;gBAAE,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAChF,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,QAAQ,CAAC,GAAG,CAAC;YAAE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;gBAAE,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxF,CAAC;AACF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAA6B,EACtD,IAAI,EACJ,WAAW,EACX,KAAK,EACL,IAAI,EACJ,SAAS,EACT,OAAO,GAQP;IACA,OAAO;QACN,IAAI;QACJ,WAAW;QACX,WAAW,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC;QACtC,GAAG,CAAC,SAAS;YACZ,CAAC,CAAC;gBACA,SAAS,EAAE,mBAAmB,CAAC,KAAK,EAAE,SAAS,CAAC;aAChD;YACF,CAAC,CAAC,EAAE,CAAC;QACN,IAAI,EAAE,GAAG,CAAC,EAAE;YACX,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACpC,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACpD,CAAC;QACD,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACxB,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACrB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjE,CAAC;YAED,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACrC,CAAC;KACD,CAAC;AACH,CAAC;AAED,6EAA6E;AAC7E,SAAS,YAAY,CAAC,KAAkB,EAAE,QAAgB;IACzD,MAAM,WAAW,GAA4B,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1E,4CAA4C;IAC5C,OAAO,WAAW,CAAC,OAAO,CAAC;IAC3B,YAAY,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACpC,OAAO,WAAyB,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAA6B,EAC/D,IAAI,EACJ,WAAW,EACX,KAAK,EACL,SAAS,EACT,OAAO,GAWP;IACA,OAAO;QACN,IAAI;QACJ,WAAW;QACX,WAAW,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC;QACtC,GAAG,CAAC,SAAS;YACZ,CAAC,CAAC;gBACA,SAAS,EAAE,mBAAmB,CAAC,KAAK,EAAE,SAAS,CAAC;aAChD;YACF,CAAC,CAAC,EAAE,CAAC;QACN,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;YAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACrF,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC3C,CAAC;KACD,CAAC;AACH,CAAC","sourcesContent":["import { z } from 'zod';\nimport type { ToolRisk } from '#src/agent/domain/approval';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport type { JSONSchema } from '#src/llm/domain/tool';\nimport type { PreparedCall, PreparingTool } from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport { type Tool } from '../domain/tool.ts';\nimport { validatedSummarizer } from './validatedSummarizer.ts';\n\n// Both providers take tools in strict mode, which supports only part of JSON Schema. Claude\n// rejects these bounds; a tool that needs one checks it in its own code instead.\nconst unsupportedKeywords = [\n\t'minimum',\n\t'maximum',\n\t'exclusiveMinimum',\n\t'exclusiveMaximum',\n\t'multipleOf',\n\t'minLength',\n\t'maxLength',\n];\n\n// Keywords whose value is one schema, or a list or map of them. Anything else, such as the\n// keys of `properties`, is a name or a value, never a schema to check.\nconst schemaKeywords = ['items', 'not', 'additionalProperties'];\nconst schemaListKeywords = ['anyOf', 'oneOf', 'allOf', 'prefixItems'];\nconst schemaMapKeywords = ['properties', '$defs', 'definitions'];\n\nfunction isSchema(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Walks the whole schema, nested objects included, and visits only schemas: a property named\n * `maxLength` is a name, not the keyword. A schema the providers reject would fail every\n * request with a 400, so it fails here, when the tool is defined.\n */\nfunction assertStrict(schema: unknown, toolName: string): void {\n\tif (!isSchema(schema)) {\n\t\treturn;\n\t}\n\n\tconst keyword = unsupportedKeywords.find(candidate => candidate in schema);\n\tif (keyword) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`Tool \"${toolName}\" uses \"${keyword}\", which strict mode does not support; check it in the tool's code`,\n\t\t);\n\t}\n\n\tif (schema.type === 'object') {\n\t\t// A record or a loose object takes keys strict mode cannot know in advance.\n\t\tif (schema.additionalProperties !== false) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`Tool \"${toolName}\" has an object open to any key; strict mode needs every key declared`,\n\t\t\t);\n\t\t}\n\n\t\tconst required = (schema.required ?? []) as string[];\n\t\t// OpenAI requires every property: an optional one is a union with `null`.\n\t\tconst optional = Object.keys(schema.properties ?? {}).filter(\n\t\t\tproperty => !required.includes(property),\n\t\t);\n\t\tif (optional.length > 0) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`Tool \"${toolName}\" has optional fields (${optional.join(', ')}); make them .nullable() instead`,\n\t\t\t);\n\t\t}\n\t}\n\n\tfor (const key of schemaKeywords) assertStrict(schema[key], toolName);\n\tfor (const key of schemaListKeywords) {\n\t\tconst list = schema[key];\n\t\tif (Array.isArray(list)) for (const item of list) assertStrict(item, toolName);\n\t}\n\tfor (const key of schemaMapKeywords) {\n\t\tconst map = schema[key];\n\t\tif (isSchema(map)) for (const item of Object.values(map)) assertStrict(item, toolName);\n\t}\n}\n\n/**\n * Builds a `Tool` from one Zod schema, which gives the model its JSON Schema, validates what the\n * model sends, and types the input `execute` receives. Invalid input is rejected with a message\n * naming what is wrong, which the agent hands back to the model as an error result.\n *\n * `risk` is one level for every call, or a function that judges each call from its validated\n * input, such as `destructive` only when a file already exists. Input that fails validation\n * counts as `safe`, because `execute` rejects it before doing anything.\n *\n * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.\n */\nexport function defineTool<Schema extends z.ZodObject>({\n\tname,\n\tdescription,\n\tinput,\n\trisk,\n\tsummarize,\n\texecute,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\trisk: ToolRisk | ((input: z.infer<Schema>) => ToolRisk);\n\tsummarize?: (input: z.infer<Schema>) => string;\n\texecute: (input: z.infer<Schema>, signal: AbortSignal) => Promise<string>;\n}): Tool {\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\tinputSchema: strictSchema(input, name),\n\t\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\n\t\trisk: raw => {\n\t\t\tif (typeof risk === 'string') return risk;\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\treturn parsed.success ? risk(parsed.data) : 'safe';\n\t\t},\n\t\texecute: (raw, signal) => {\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\tif (!parsed.success) {\n\t\t\t\treturn Promise.reject(new Error(z.prettifyError(parsed.error)));\n\t\t\t}\n\n\t\t\treturn execute(parsed.data, signal);\n\t\t},\n\t};\n}\n\n/** The JSON schema of `input`, checked against what strict mode supports. */\nfunction strictSchema(input: z.ZodObject, toolName: string): JSONSchema {\n\tconst inputSchema: Record<string, unknown> = { ...z.toJSONSchema(input) };\n\t// The dialect marker is noise to both APIs.\n\tdelete inputSchema.$schema;\n\tassertStrict(inputSchema, toolName);\n\treturn inputSchema as JSONSchema;\n}\n\n/**\n * Builds a harness tool that prepares each call, from a Zod schema, as `defineTool` builds a\n * consumer's: input that fails validation is refused before anything is prepared. Internal.\n *\n * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.\n */\nexport function definePreparingTool<Schema extends z.ZodObject>({\n\tname,\n\tdescription,\n\tinput,\n\tsummarize,\n\tprepare,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\tsummarize?: (input: z.infer<Schema>) => string;\n\tprepare: (\n\t\tinput: z.infer<Schema>,\n\t\tsignal: AbortSignal,\n\t\tcall: ToolCallContext,\n\t) => Promise<PreparedCall>;\n}): PreparingTool {\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\tinputSchema: strictSchema(input, name),\n\t\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\n\t\tprepare: (raw, signal, call) => {\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\tif (!parsed.success) return Promise.reject(new Error(z.prettifyError(parsed.error)));\n\t\t\treturn prepare(parsed.data, signal, call);\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"defineTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/defineTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { ToolRisk } from '#src/agent/domain/approval';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport type { JSONSchema } from '#src/llm/domain/tool';\nimport type {\n\tDelegatingTool,\n\tPreparedCall,\n\tPreparedDelegation,\n\tPreparingTool,\n} from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport { type Tool } from '../domain/tool.ts';\nimport { validatedSummarizer } from './validatedSummarizer.ts';\n\n// Both providers take tools in strict mode, which supports only part of JSON Schema. Claude\n// rejects these bounds; a tool that needs one checks it in its own code instead.\nconst unsupportedKeywords = [\n\t'minimum',\n\t'maximum',\n\t'exclusiveMinimum',\n\t'exclusiveMaximum',\n\t'multipleOf',\n\t'minLength',\n\t'maxLength',\n];\n\n// Keywords whose value is one schema, or a list or map of them. Anything else, such as the\n// keys of `properties`, is a name or a value, never a schema to check.\nconst schemaKeywords = ['items', 'not', 'additionalProperties'];\nconst schemaListKeywords = ['anyOf', 'oneOf', 'allOf', 'prefixItems'];\nconst schemaMapKeywords = ['properties', '$defs', 'definitions'];\n\nfunction isSchema(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Walks the whole schema, nested objects included, and visits only schemas: a property named\n * `maxLength` is a name, not the keyword. A schema the providers reject would fail every\n * request with a 400, so it fails here, when the tool is defined.\n */\nfunction assertStrict(schema: unknown, toolName: string): void {\n\tif (!isSchema(schema)) {\n\t\treturn;\n\t}\n\n\tconst keyword = unsupportedKeywords.find(candidate => candidate in schema);\n\tif (keyword) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`Tool \"${toolName}\" uses \"${keyword}\", which strict mode does not support; check it in the tool's code`,\n\t\t);\n\t}\n\n\tif (schema.type === 'object') {\n\t\t// A record or a loose object takes keys strict mode cannot know in advance.\n\t\tif (schema.additionalProperties !== false) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`Tool \"${toolName}\" has an object open to any key; strict mode needs every key declared`,\n\t\t\t);\n\t\t}\n\n\t\tconst required = (schema.required ?? []) as string[];\n\t\t// OpenAI requires every property: an optional one is a union with `null`.\n\t\tconst optional = Object.keys(schema.properties ?? {}).filter(\n\t\t\tproperty => !required.includes(property),\n\t\t);\n\t\tif (optional.length > 0) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`Tool \"${toolName}\" has optional fields (${optional.join(', ')}); make them .nullable() instead`,\n\t\t\t);\n\t\t}\n\t}\n\n\tfor (const key of schemaKeywords) assertStrict(schema[key], toolName);\n\tfor (const key of schemaListKeywords) {\n\t\tconst list = schema[key];\n\t\tif (Array.isArray(list)) for (const item of list) assertStrict(item, toolName);\n\t}\n\tfor (const key of schemaMapKeywords) {\n\t\tconst map = schema[key];\n\t\tif (isSchema(map)) for (const item of Object.values(map)) assertStrict(item, toolName);\n\t}\n}\n\n/**\n * Builds a `Tool` from one Zod schema, which gives the model its JSON Schema, validates what the\n * model sends, and types the input `execute` receives. Invalid input is rejected with a message\n * naming what is wrong, which the agent hands back to the model as an error result.\n *\n * `risk` is one level for every call, or a function that judges each call from its validated\n * input, such as `destructive` only when a file already exists. Input that fails validation\n * counts as `safe`, because `execute` rejects it before doing anything.\n *\n * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.\n */\nexport function defineTool<Schema extends z.ZodObject>({\n\tname,\n\tdescription,\n\tinput,\n\trisk,\n\tsummarize,\n\texecute,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\trisk: ToolRisk | ((input: z.infer<Schema>) => ToolRisk);\n\tsummarize?: (input: z.infer<Schema>) => string;\n\texecute: (input: z.infer<Schema>, signal: AbortSignal) => Promise<string>;\n}): Tool {\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\tinputSchema: strictSchema(input, name),\n\t\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\n\t\trisk: raw => {\n\t\t\tif (typeof risk === 'string') return risk;\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\treturn parsed.success ? risk(parsed.data) : 'safe';\n\t\t},\n\t\texecute: (raw, signal) => {\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\tif (!parsed.success) {\n\t\t\t\treturn Promise.reject(new Error(z.prettifyError(parsed.error)));\n\t\t\t}\n\n\t\t\treturn execute(parsed.data, signal);\n\t\t},\n\t};\n}\n\n/** The JSON schema of `input`, checked against what strict mode supports. */\nfunction strictSchema(input: z.ZodObject, toolName: string): JSONSchema {\n\tconst inputSchema: Record<string, unknown> = { ...z.toJSONSchema(input) };\n\t// The dialect marker is noise to both APIs.\n\tdelete inputSchema.$schema;\n\tassertStrict(inputSchema, toolName);\n\treturn inputSchema as JSONSchema;\n}\n\n/**\n * Builds a harness tool that prepares each call, from a Zod schema, as `defineTool` builds a\n * consumer's: input that fails validation is refused before anything is prepared. Internal.\n *\n * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.\n */\nexport function definePreparingTool<Schema extends z.ZodObject>({\n\tname,\n\tdescription,\n\tinput,\n\tsummarize,\n\tprepare,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\tsummarize?: (input: z.infer<Schema>) => string;\n\tprepare: (\n\t\tinput: z.infer<Schema>,\n\t\tsignal: AbortSignal,\n\t\tcall: ToolCallContext,\n\t) => Promise<PreparedCall>;\n}): PreparingTool {\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\tinputSchema: strictSchema(input, name),\n\t\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\n\t\tprepare: (raw, signal, call) => {\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\tif (!parsed.success) return Promise.reject(new Error(z.prettifyError(parsed.error)));\n\t\t\treturn prepare(parsed.data, signal, call);\n\t\t},\n\t};\n}\n\n/**\n * Builds a harness tool whose calls hand tasks to agents working in the background, from a Zod\n * schema: input that fails validation is refused before any task is handed over. Internal.\n *\n * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.\n */\nexport function defineDelegatingTool<Schema extends z.ZodObject>({\n\tname,\n\tdescription,\n\tinput,\n\tsummarize,\n\tdelegate,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\tsummarize?: (input: z.infer<Schema>) => string;\n\tdelegate: (\n\t\tinput: z.infer<Schema>,\n\t\tsignal: AbortSignal,\n\t\tcall: ToolCallContext,\n\t) => Promise<PreparedDelegation>;\n}): DelegatingTool {\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\tinputSchema: strictSchema(input, name),\n\t\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\n\t\tdelegate: (raw, signal, call) => {\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\tif (!parsed.success) return Promise.reject(new Error(z.prettifyError(parsed.error)));\n\t\t\treturn delegate(parsed.data, signal, call);\n\t\t},\n\t};\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { Agent } from '#src/agent/domain/agent';
2
- import type { PreparingTool } from '../domain/preparedCall.ts';
2
+ import type { DelegatingTool } from '../domain/preparedCall.ts';
3
3
  /** The most explorers one call sends, so a single call cannot fan out without bound. */
4
4
  export declare const maxExplorers = 4;
5
5
  /**
@@ -7,4 +7,4 @@ export declare const maxExplorers = 4;
7
7
  * `explorer`, to find what an agent needs in the repository, so the agent that called it reads
8
8
  * their reports instead of filling its own context with the files.
9
9
  */
10
- export declare function createExploreTool(explorer: () => Agent): PreparingTool;
10
+ export declare function createExploreTool(explorer: () => Agent): DelegatingTool;
@@ -1,48 +1,36 @@
1
1
  import { z } from 'zod';
2
2
  import { RecoverableError, UnrecoverableError } from '#src/shared/domain/errors';
3
- import { definePreparingTool } from "./defineTool.js";
3
+ import { defineDelegatingTool } from "./defineTool.js";
4
4
  import { cutAnswer, failureOf, subagentOptions } from "./subagents.js";
5
5
  /** The most explorers one call sends, so a single call cannot fan out without bound. */
6
6
  export const maxExplorers = 4;
7
7
  /**
8
- * Runs one explorer per question, all at once, each a new agent that starts from its question
9
- * alone. Every explorer's usage counts in the run's, whether it answered or failed; a failure
10
- * becomes that explorer's answer, so the others' are not lost. A cancellation escapes.
8
+ * Sends one explorer, a new agent that starts from its question alone. What it spends counts in
9
+ * the run's usage, whether it answered or failed; a failure becomes its answer. A cancellation
10
+ * escapes.
11
11
  */
12
- async function explore(questions, explorer, signal, call) {
13
- const answers = await Promise.allSettled(questions.map(async (question, index) => {
14
- const subagent = index + 1;
15
- const heading = `Explorer ${String(subagent)}: ${question}`;
16
- // Within the run that made the call, so the explorers' reads belong to it.
17
- const options = subagentOptions(call, {
18
- tool: 'explore',
19
- name: 'explorer',
20
- subagent,
21
- task: question,
22
- options: { signal },
23
- });
24
- try {
25
- const { response, tokens } = await explorer().run(question, options);
26
- call.spend(tokens, tokens === undefined);
27
- return `${heading}\n${cutAnswer(response || 'It found nothing to report.')}`;
28
- }
29
- catch (error) {
30
- if (!(error instanceof RecoverableError || error instanceof UnrecoverableError)) {
31
- throw error;
32
- }
33
- call.spend(error.tokens, error.usageUnreported);
34
- return `${heading}\nThe explorer failed: ${failureOf(error)}`;
35
- }
36
- }));
37
- // Whatever each explorer threw once the run was cancelled, the call ends as cancelled.
38
- signal.throwIfAborted();
39
- return answers
40
- .map(answer => {
41
- if (answer.status === 'rejected')
42
- throw answer.reason;
43
- return answer.value;
44
- })
45
- .join('\n\n');
12
+ async function explore(question, subagent, explorer, signal, call) {
13
+ // Within the run that made the call, so the explorer's reads belong to it.
14
+ const options = subagentOptions(call, {
15
+ tool: 'explore',
16
+ name: 'explorer',
17
+ subagent,
18
+ task: question,
19
+ options: { signal },
20
+ });
21
+ try {
22
+ const { response, tokens } = await explorer().run(question, options);
23
+ call.spend(tokens, tokens === undefined);
24
+ return { text: cutAnswer(response || 'It found nothing to report.') };
25
+ }
26
+ catch (error) {
27
+ // Whatever it threw once the run was cancelled, it ends as cancelled.
28
+ signal.throwIfAborted();
29
+ if (!(error instanceof RecoverableError || error instanceof UnrecoverableError))
30
+ throw error;
31
+ call.spend(error.tokens, error.usageUnreported);
32
+ return { text: `The explorer failed: ${failureOf(error)}`, failed: true };
33
+ }
46
34
  }
47
35
  /**
48
36
  * `explore` for the planner and the reviewer: it sends cheaper, read-only explorers, created by
@@ -50,14 +38,14 @@ async function explore(questions, explorer, signal, call) {
50
38
  * their reports instead of filling its own context with the files.
51
39
  */
52
40
  export function createExploreTool(explorer) {
53
- return definePreparingTool({
41
+ return defineDelegatingTool({
54
42
  name: 'explore',
55
- description: `Sends read-only explorers to find things in the repository for you, one for each question, all at once. Each explorer starts from nothing but its question, so make each one complete: what to find, and what to report about it, such as which files and lines implement something and how. Ask up to ${String(maxExplorers)} independent questions in one call. Each answer is an explorer's report, not the files: read a file yourself when you need its exact text.`,
43
+ description: `Sends read-only explorers to find things in the repository for you, one for each question, all at once. They work in the background: keep working meanwhile, and each explorer's report reaches you in a message of its own once it finishes. Each explorer starts from nothing but its question, so make each one complete: what to find, and what to report about it, such as which files and lines implement something and how. Ask up to ${String(maxExplorers)} independent questions in one call. Each answer is an explorer's report, not the files: read a file yourself when you need its exact text.`,
56
44
  input: z.object({
57
45
  questions: z.array(z.string()).describe('One complete, independent question per explorer.'),
58
46
  }),
59
47
  summarize: ({ questions }) => questions.length === 1 ? (questions[0] ?? '') : `${String(questions.length)} questions`,
60
- prepare: ({ questions }, _, call) => {
48
+ delegate: ({ questions }, _, call) => {
61
49
  const asked = questions.map(question => question.trim());
62
50
  if (asked.length === 0 || asked.length > maxExplorers) {
63
51
  return Promise.reject(new Error(`Ask between 1 and ${String(maxExplorers)} questions in one call.`));
@@ -67,7 +55,12 @@ export function createExploreTool(explorer) {
67
55
  }
68
56
  return Promise.resolve({
69
57
  risk: 'safe',
70
- run: signal => explore(asked, explorer, signal, call),
58
+ start: signal => asked.map((question, index) => ({
59
+ name: 'explorer',
60
+ subagent: index + 1,
61
+ task: question,
62
+ answer: explore(question, index + 1, explorer, signal, call),
63
+ })),
71
64
  });
72
65
  },
73
66
  });
@@ -1 +1 @@
1
- {"version":3,"file":"exploreTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/exploreTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Agent } from '#src/agent/domain/agent';\nimport { RecoverableError, UnrecoverableError } from '#src/shared/domain/errors';\nimport type { PreparingTool } from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport { definePreparingTool } from './defineTool.ts';\nimport { cutAnswer, failureOf, subagentOptions } from './subagents.ts';\n\n/** The most explorers one call sends, so a single call cannot fan out without bound. */\nexport const maxExplorers = 4;\n\n/**\n * Runs one explorer per question, all at once, each a new agent that starts from its question\n * alone. Every explorer's usage counts in the run's, whether it answered or failed; a failure\n * becomes that explorer's answer, so the others' are not lost. A cancellation escapes.\n */\nasync function explore(\n\tquestions: readonly string[],\n\texplorer: () => Agent,\n\tsignal: AbortSignal,\n\tcall: ToolCallContext,\n): Promise<string> {\n\tconst answers = await Promise.allSettled(\n\t\tquestions.map(async (question, index) => {\n\t\t\tconst subagent = index + 1;\n\t\t\tconst heading = `Explorer ${String(subagent)}: ${question}`;\n\t\t\t// Within the run that made the call, so the explorers' reads belong to it.\n\t\t\tconst options = subagentOptions(call, {\n\t\t\t\ttool: 'explore',\n\t\t\t\tname: 'explorer',\n\t\t\t\tsubagent,\n\t\t\t\ttask: question,\n\t\t\t\toptions: { signal },\n\t\t\t});\n\t\t\ttry {\n\t\t\t\tconst { response, tokens } = await explorer().run(question, options);\n\t\t\t\tcall.spend(tokens, tokens === undefined);\n\t\t\t\treturn `${heading}\\n${cutAnswer(response || 'It found nothing to report.')}`;\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError || error instanceof UnrecoverableError)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tcall.spend(error.tokens, error.usageUnreported);\n\t\t\t\treturn `${heading}\\nThe explorer failed: ${failureOf(error)}`;\n\t\t\t}\n\t\t}),\n\t);\n\t// Whatever each explorer threw once the run was cancelled, the call ends as cancelled.\n\tsignal.throwIfAborted();\n\treturn answers\n\t\t.map(answer => {\n\t\t\tif (answer.status === 'rejected') throw answer.reason;\n\t\t\treturn answer.value;\n\t\t})\n\t\t.join('\\n\\n');\n}\n\n/**\n * `explore` for the planner and the reviewer: it sends cheaper, read-only explorers, created by\n * `explorer`, to find what an agent needs in the repository, so the agent that called it reads\n * their reports instead of filling its own context with the files.\n */\nexport function createExploreTool(explorer: () => Agent): PreparingTool {\n\treturn definePreparingTool({\n\t\tname: 'explore',\n\t\tdescription: `Sends read-only explorers to find things in the repository for you, one for each question, all at once. Each explorer starts from nothing but its question, so make each one complete: what to find, and what to report about it, such as which files and lines implement something and how. Ask up to ${String(maxExplorers)} independent questions in one call. Each answer is an explorer's report, not the files: read a file yourself when you need its exact text.`,\n\t\tinput: z.object({\n\t\t\tquestions: z.array(z.string()).describe('One complete, independent question per explorer.'),\n\t\t}),\n\t\tsummarize: ({ questions }) =>\n\t\t\tquestions.length === 1 ? (questions[0] ?? '') : `${String(questions.length)} questions`,\n\t\tprepare: ({ questions }, _, call) => {\n\t\t\tconst asked = questions.map(question => question.trim());\n\t\t\tif (asked.length === 0 || asked.length > maxExplorers) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(`Ask between 1 and ${String(maxExplorers)} questions in one call.`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (asked.some(question => question === '')) {\n\t\t\t\treturn Promise.reject(new Error('Every question needs some text.'));\n\t\t\t}\n\t\t\treturn Promise.resolve({\n\t\t\t\trisk: 'safe',\n\t\t\t\trun: signal => explore(asked, explorer, signal, call),\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"exploreTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/exploreTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Agent } from '#src/agent/domain/agent';\nimport { RecoverableError, UnrecoverableError } from '#src/shared/domain/errors';\nimport type { DelegatedAnswer, DelegatingTool } from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport { defineDelegatingTool } from './defineTool.ts';\nimport { cutAnswer, failureOf, subagentOptions } from './subagents.ts';\n\n/** The most explorers one call sends, so a single call cannot fan out without bound. */\nexport const maxExplorers = 4;\n\n/**\n * Sends one explorer, a new agent that starts from its question alone. What it spends counts in\n * the run's usage, whether it answered or failed; a failure becomes its answer. A cancellation\n * escapes.\n */\nasync function explore(\n\tquestion: string,\n\tsubagent: number,\n\texplorer: () => Agent,\n\tsignal: AbortSignal,\n\tcall: ToolCallContext,\n): Promise<DelegatedAnswer> {\n\t// Within the run that made the call, so the explorer's reads belong to it.\n\tconst options = subagentOptions(call, {\n\t\ttool: 'explore',\n\t\tname: 'explorer',\n\t\tsubagent,\n\t\ttask: question,\n\t\toptions: { signal },\n\t});\n\ttry {\n\t\tconst { response, tokens } = await explorer().run(question, options);\n\t\tcall.spend(tokens, tokens === undefined);\n\t\treturn { text: cutAnswer(response || 'It found nothing to report.') };\n\t} catch (error) {\n\t\t// Whatever it threw once the run was cancelled, it ends as cancelled.\n\t\tsignal.throwIfAborted();\n\t\tif (!(error instanceof RecoverableError || error instanceof UnrecoverableError)) throw error;\n\t\tcall.spend(error.tokens, error.usageUnreported);\n\t\treturn { text: `The explorer failed: ${failureOf(error)}`, failed: true };\n\t}\n}\n\n/**\n * `explore` for the planner and the reviewer: it sends cheaper, read-only explorers, created by\n * `explorer`, to find what an agent needs in the repository, so the agent that called it reads\n * their reports instead of filling its own context with the files.\n */\nexport function createExploreTool(explorer: () => Agent): DelegatingTool {\n\treturn defineDelegatingTool({\n\t\tname: 'explore',\n\t\tdescription: `Sends read-only explorers to find things in the repository for you, one for each question, all at once. They work in the background: keep working meanwhile, and each explorer's report reaches you in a message of its own once it finishes. Each explorer starts from nothing but its question, so make each one complete: what to find, and what to report about it, such as which files and lines implement something and how. Ask up to ${String(maxExplorers)} independent questions in one call. Each answer is an explorer's report, not the files: read a file yourself when you need its exact text.`,\n\t\tinput: z.object({\n\t\t\tquestions: z.array(z.string()).describe('One complete, independent question per explorer.'),\n\t\t}),\n\t\tsummarize: ({ questions }) =>\n\t\t\tquestions.length === 1 ? (questions[0] ?? '') : `${String(questions.length)} questions`,\n\t\tdelegate: ({ questions }, _, call) => {\n\t\t\tconst asked = questions.map(question => question.trim());\n\t\t\tif (asked.length === 0 || asked.length > maxExplorers) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(`Ask between 1 and ${String(maxExplorers)} questions in one call.`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (asked.some(question => question === '')) {\n\t\t\t\treturn Promise.reject(new Error('Every question needs some text.'));\n\t\t\t}\n\t\t\treturn Promise.resolve({\n\t\t\t\trisk: 'safe',\n\t\t\t\tstart: signal =>\n\t\t\t\t\tasked.map((question, index) => ({\n\t\t\t\t\t\tname: 'explorer',\n\t\t\t\t\t\tsubagent: index + 1,\n\t\t\t\t\t\ttask: question,\n\t\t\t\t\t\tanswer: explore(question, index + 1, explorer, signal, call),\n\t\t\t\t\t})),\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikode13/harness",
3
- "version": "10.0.0",
3
+ "version": "11.0.0",
4
4
  "description": "Test/agent harness utility",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",