| 1 | // Tools that use `exec.core.call` and report exactly what came back, so a test |
| 2 | // can see every outcome the core can produce: a result, or a typed refusal. |
| 3 | export const name = 'core-call' |
| 4 | export const inject = ['tools', 'commands'] |
| 5 | |
| 6 | const outcome = async (promise) => { |
| 7 | try { |
| 8 | return { ok: await promise } |
| 9 | } catch (error) { |
| 10 | return { failed: { name: error.name, code: error.code ?? null, message: error.message } } |
| 11 | } |
| 12 | } |
| 13 | |
| 14 | export function apply(ctx) { |
| 15 | // One core call: `{name, input}` -> `{ok: {content, isError, structured?}}` or `{failed: {code, message}}`. |
| 16 | ctx.tools.register({ |
| 17 | name: 'cc_call', |
| 18 | description: 'Ask the core to run a tool and report the outcome.', |
| 19 | parameters: { |
| 20 | type: 'object', |
| 21 | properties: { name: { type: 'string' }, input: { type: 'object' } }, |
| 22 | required: ['name'], |
| 23 | additionalProperties: false, |
| 24 | }, |
| 25 | execute: async ({ name, input }, exec) => { |
| 26 | if (exec.core === undefined) return { noCore: true } |
| 27 | return outcome(exec.core.call(name, input ?? {})) |
| 28 | }, |
| 29 | }) |
| 30 | |
| 31 | // `count` calls of the same tool, one after another or all at once. |
| 32 | ctx.tools.register({ |
| 33 | name: 'cc_many', |
| 34 | description: 'Ask the core to run a tool several times and report every outcome.', |
| 35 | parameters: { |
| 36 | type: 'object', |
| 37 | properties: { name: { type: 'string' }, input: { type: 'object' }, count: { type: 'number' }, parallel: { type: 'boolean' } }, |
| 38 | required: ['name', 'count'], |
| 39 | additionalProperties: false, |
| 40 | }, |
| 41 | execute: async ({ name, input, count, parallel }, exec) => { |
| 42 | if (exec.core === undefined) return { noCore: true } |
| 43 | const one = () => outcome(exec.core.call(name, input ?? {})) |
| 44 | if (parallel) return { outcomes: await Promise.all(Array.from({ length: count }, one)) } |
| 45 | const outcomes = [] |
| 46 | for (let i = 0; i < count; i++) outcomes.push(await one()) |
| 47 | return { outcomes } |
| 48 | }, |
| 49 | }) |
| 50 | |
| 51 | ctx.tools.register({ |
| 52 | name: 'cc_probe', |
| 53 | description: 'Report whether this call has exec.core, and what exec holds.', |
| 54 | parameters: { type: 'object', properties: {}, additionalProperties: false }, |
| 55 | execute: (_input, exec) => ({ hasCore: 'core' in exec, keys: Object.keys(exec).sort(), coreKeys: exec.core ? Object.keys(exec.core).sort() : null }), |
| 56 | }) |
| 57 | |
| 58 | // A bad request the host refuses before sending anything. |
| 59 | ctx.tools.register({ |
| 60 | name: 'cc_local', |
| 61 | description: 'Make core calls the host itself refuses to send.', |
| 62 | parameters: { type: 'object', properties: {}, additionalProperties: false }, |
| 63 | execute: async (_input, exec) => ({ |
| 64 | emptyName: await outcome(exec.core.call('')), |
| 65 | longName: await outcome(exec.core.call('x'.repeat(200))), |
| 66 | notJson: await outcome(exec.core.call('read', { fn: () => 1 })), |
| 67 | cyclic: await outcome(exec.core.call('read', (() => { const a = {}; a.a = a; return a })())), |
| 68 | }), |
| 69 | }) |
| 70 | |
| 71 | // Sleeps after (or without) a core call; for call-deadline tests. |
| 72 | ctx.tools.register({ |
| 73 | name: 'cc_then_sleep', |
| 74 | description: 'Make one core call, then wait `ms` milliseconds or until cancelled.', |
| 75 | parameters: { |
| 76 | type: 'object', |
| 77 | properties: { name: { type: 'string' }, input: { type: 'object' }, ms: { type: 'number' } }, |
| 78 | required: ['name', 'ms'], |
| 79 | additionalProperties: false, |
| 80 | }, |
| 81 | execute: async ({ name, input, ms }, exec) => { |
| 82 | const first = await outcome(exec.core.call(name, input ?? {})) |
| 83 | await new Promise((resolve, reject) => { |
| 84 | const timer = setTimeout(resolve, ms) |
| 85 | exec.signal.addEventListener('abort', () => { |
| 86 | clearTimeout(timer) |
| 87 | reject(new Error('aborted')) |
| 88 | }) |
| 89 | }) |
| 90 | return { first } |
| 91 | }, |
| 92 | }) |
| 93 | |
| 94 | ctx.commands.register({ |
| 95 | name: 'cc-probe', |
| 96 | description: 'Report whether a command invocation has core.', |
| 97 | handler: (invocation) => ({ kind: 'success', text: JSON.stringify({ hasCore: 'core' in invocation, keys: Object.keys(invocation).sort() }) }), |
| 98 | }) |
| 99 | } |
| 100 |