diff --git a/docs/use/execute.md b/docs/use/execute.md index 779abe57e2..c252d0ca52 100644 --- a/docs/use/execute.md +++ b/docs/use/execute.md @@ -19,6 +19,9 @@ Import runtime APIs from **`kody:runtime`** when you need Kody helpers. These helpers are runtime exports: - use **`import { codemode } from 'kody:runtime'`** to call builtin capabilities + discovered by **search** as **`await codemode.capability_id(input)`** for + valid identifier names, or **`await codemode["capability-id"](input)`** for + non-identifier capability ids - use **`import { refreshAccessToken, createAuthenticatedFetch } from 'kody:runtime'`** for integration OAuth helpers @@ -94,6 +97,8 @@ user input, confirmation, or a result that changes the plan. To read field shapes while coding, use **search** with **`entity: "{name}:capability"`** for builtin capability type definitions, or inspect the relevant saved package with **`entity: "{kody_id}:package"`**. +Capability detail includes a complete **execute** module snippet; the runtime +call itself is always through the imported `codemode` object. ## Saved packages diff --git a/docs/use/search.md b/docs/use/search.md index 70771f3c05..8e0c3b2c0b 100644 --- a/docs/use/search.md +++ b/docs/use/search.md @@ -26,9 +26,10 @@ response must stay small. ## Single-entity detail To get **full markdown and call shapes for one hit** (for example a capability’s -`inputTypeDefinition` / `outputTypeDefinition`), call **search** again with -**`entity`** set to `"{id}:{type}"` where **`type`** is `capability`, `value`, -`integration`, `package`, or `secret`. +ready-to-run **execute** snippet plus `inputTypeDefinition` / +`outputTypeDefinition`), call **search** again with **`entity`** set to +`"{id}:{type}"` where **`type`** is `capability`, `value`, `integration`, +`package`, or `secret`. Examples: @@ -46,6 +47,23 @@ Top-level ranked result cards include an explicit entity ref for each hit when applicable, using that same `"{id}:{type}"` format, so you can immediately copy the ref into a follow-up `entity` lookup when needed. +Capability detail shows the exact runtime pattern for **execute**: + +```ts +import { codemode } from 'kody:runtime' + +export default async function main(input = {}) { + return await codemode.kody_official_guide(input) +} +``` + +Use the capability id returned by **search** as the `codemode` method name, and +pass an object matching the displayed input type. Valid JavaScript identifier +ids use dot notation such as `codemode.kody_official_guide(input)`. If a +capability id is not a valid JavaScript identifier, use bracket notation such as +`codemode["capability-id"](input)`. Capability detail emits the exact form to +copy. Use `{}` when the capability has no required fields. + ## When results look thin If ranked search misses what you need, **rephrase the query** or use diff --git a/packages/worker/src/mcp/server-instructions.ts b/packages/worker/src/mcp/server-instructions.ts index 99742dcb0b..5f99964c8e 100644 --- a/packages/worker/src/mcp/server-instructions.ts +++ b/packages/worker/src/mcp/server-instructions.ts @@ -83,14 +83,14 @@ What shows up in \`search\` (before you search) search - \`query\`: natural language; results are ranked (order matters). Optional \`limit\`, \`maxResponseSize\`. -- \`entity: "{id}:{type}"\` (\`capability\` | \`package\` | \`value\` | \`integration\` | \`secret\`) for one entity’s detail (TypeScript call shapes, usage). If a \`query\` returns no useful hits, rephrase or call \`meta_list_capabilities\` — \`entity\` does not repair an empty ranked list. +- \`entity: "{id}:{type}"\` (\`capability\` | \`package\` | \`value\` | \`integration\` | \`secret\`) for one entity’s detail. Capability detail includes an exact execute snippet and TypeScript call shapes; other entity details include usage. If a \`query\` returns no useful hits, rephrase or call \`meta_list_capabilities\` — \`entity\` does not repair an empty ranked list. - Examples: - search({ query: 'saved package for github automation' }) - search({ query: 'Cloudflare API zones dns workers d1' }) - search({ entity: 'kody_official_guide:capability' }) execute -- Single ESM module string with a default export such as \`export default async function main(input = {}) { ... }\`; \`params\` are passed as the first argument. Import runtime APIs from \`kody:runtime\`. Example: \`import { codemode, refreshAccessToken, createAuthenticatedFetch, workflows } from 'kody:runtime'\`. \`workflows.create\` can queue either inline \`code\` or a saved-package \`exportName\` through the shared Workflow hub. Prefer one \`execute\` when the plan is clear. Full rules for \`fetch\`, placeholders, \`secret_list\` / \`value_get\`, and \`x-kody-secret\`: see the \`execute\` tool description. +- Single ESM module string with a default export such as \`export default async function main(input = {}) { ... }\`; \`params\` are passed as the first argument. Import runtime APIs from \`kody:runtime\`. Example: \`import { codemode, refreshAccessToken, createAuthenticatedFetch, workflows } from 'kody:runtime'\`. Built-in capabilities returned by \`search\` are available through \`codemode\`: use \`await codemode.capability_id(input)\` for valid identifier names or \`await codemode["capability-id"](input)\` for non-identifier ids. \`workflows.create\` can queue either inline \`code\` or a saved-package \`exportName\` through the shared Workflow hub. Prefer one \`execute\` when the plan is clear. Full rules for \`fetch\`, placeholders, \`secret_list\` / \`value_get\`, and \`x-kody-secret\`: see the \`execute\` tool description. - Cross-package imports use specifiers such as \`kody:@scope/my-package/export-name\`. For dynamic current-version calls inside package runtime code, prefer \`packages.invokeChecked({ kodyId, exportName, params })\` or \`packages.check(...)\` followed by \`packages.invoke(check.invoke)\`; use static imports for library-like bundled snapshots. Saved package names must be scoped (\`@scope/\`) and the leaf segment must match \`kody.id\`. Package jobs are owned by packages, ad hoc jobs can be scheduled with \`job_schedule\`, and package apps are optional package surfaces. - Official how-to guides from the Kody repo: if a requested package or workflow depends on a third-party integration, secrets, or OAuth, call \`kody_official_guide\` with \`guide: "integration_bootstrap"\` before building the package. Then load the relevant setup guide: \`oauth\` for standard third-party OAuth (\`/connect/oauth\`), \`connect_secret\` for secret collection, and \`secret_backed_integration\` for the default non-OAuth secret-backed recipe after bootstrap. If unsure, \`search\` for this capability and load the right guide before implementing. - Do not save or present an auth-dependent package as complete until \`search\` shows the required integration or secret reference exists and a minimal authenticated \`execute\` smoke test succeeds. diff --git a/packages/worker/src/mcp/tools/execute.ts b/packages/worker/src/mcp/tools/execute.ts index 023bff9c12..916341e696 100644 --- a/packages/worker/src/mcp/tools/execute.ts +++ b/packages/worker/src/mcp/tools/execute.ts @@ -35,7 +35,7 @@ const executeTool = { name: 'execute', title: 'Execute Capabilities', description: ` -Run one ephemeral ESM module string with a default export. Imports may be arbitrary npm packages compatible with the Cloudflare Workers runtime (e.g. \`p-retry\`, \`mailparser\`, \`remark\`); prefer existing packages over rewriting helpers. Discover capability names with \`search\`; for one capability’s TypeScript call shape, call \`search\` with \`entity: "{name}:capability"\` or use \`meta_list_capabilities\`. +Run one ephemeral ESM module string with a default export. Imports may be arbitrary npm packages compatible with the Cloudflare Workers runtime (e.g. \`p-retry\`, \`mailparser\`, \`remark\`); prefer existing packages over rewriting helpers. Discover capability names with \`search\`; for one capability’s executable snippet and TypeScript call shape, call \`search\` with \`entity: "{name}:capability"\` or use \`meta_list_capabilities\`. Projection rule: you write the code -- if a call returns a large response, project the fields you need before returning. Never return raw API responses; extract a slim shape (e.g. \`{ id, subject, snippet }\`) or a summary. @@ -47,7 +47,7 @@ Saved package surface: Sandbox surface: - Import runtime helpers from \`kody:runtime\`. -- \`import { codemode } from 'kody:runtime'\` for builtin capabilities. +- \`import { codemode } from 'kody:runtime'\` for builtin capabilities discovered by \`search\`; call valid identifier names as \`await codemode.capability_id(input)\`. If a capability id is not a valid JavaScript identifier, use bracket notation: \`await codemode["capability-id"](input)\`. Capability detail from \`search({ entity: "{name}:capability" })\` includes the exact snippet. - \`import { storage } from 'kody:runtime'\` for durable storage helpers on the bound \`storageId\`, including \`storage.sql(query, params?)\`. \`storage.sql\` returns \`{ columns, rows, rowCount, rowsRead, rowsWritten }\`; read query rows from \`.rows\`. - \`import { refreshAccessToken, createAuthenticatedFetch } from 'kody:runtime'\` for OAuth integrations. Integration \`name\` may be account-specific (e.g. \`google-personal\`, \`google-business\`); call \`integration_list\` first when the task involves a provider that may have multiple accounts connected. - \`import { workflows } from 'kody:runtime'\` for durable Cloudflare Workflows. \`workflows.create\` accepts either inline \`code\` or a saved-package \`exportName\`; use \`workflow_list\` to inspect recent runs. diff --git a/packages/worker/src/mcp/tools/search-format.node.test.ts b/packages/worker/src/mcp/tools/search-format.node.test.ts index c5d69fca23..71761c6c18 100644 --- a/packages/worker/src/mcp/tools/search-format.node.test.ts +++ b/packages/worker/src/mcp/tools/search-format.node.test.ts @@ -217,13 +217,69 @@ test('capability entity detail keeps type definitions stable without schema fiel expect(detail.structured).toMatchObject({ type: 'capability', + usage: 'execute with codemode.github_create_issue(args)', + executeExample: expect.stringContaining( + 'return await codemode.github_create_issue(input)', + ), inputTypeDefinition: expect.any(String), outputTypeDefinition: expect.any(String), }) + expect(detail.markdown).toContain('## Execute from `execute`') + expect(detail.markdown).toContain("import { codemode } from 'kody:runtime'") + expect(detail.markdown).toContain( + 'return await codemode.github_create_issue(input)', + ) expect(detail.structured).not.toHaveProperty('inputSchema') expect(detail.structured).not.toHaveProperty('outputSchema') }) +test('capability usage uses bracket notation for non-identifier ids', () => { + const [match] = toSlimStructuredMatches({ + baseUrl: 'http://localhost', + matches: [ + { + type: 'capability', + name: 'foo-bar', + description: 'Capability with a non-identifier id.', + }, + ], + }) + expect(match).toMatchObject({ + type: 'capability', + usage: 'execute with codemode["foo-bar"](args)', + }) + + const detail = formatEntityDetailMarkdown({ + type: 'capability', + id: 'foo-bar', + title: 'foo-bar', + description: 'Capability with a non-identifier id.', + spec: { + name: 'foo-bar', + domain: 'meta', + description: 'Capability with a non-identifier id.', + keywords: [], + readOnly: true, + idempotent: true, + destructive: false, + inputFields: [], + requiredInputFields: [], + outputFields: [], + inputSchema: { type: 'object', properties: {} }, + inputTypeDefinition: 'type FooBarInput = Record', + }, + }) + + expect(detail.structured).toMatchObject({ + type: 'capability', + usage: 'execute with codemode["foo-bar"](args)', + executeExample: expect.stringContaining( + 'return await codemode["foo-bar"](input)', + ), + }) + expect(detail.markdown).toContain('return await codemode["foo-bar"](input)') +}) + test('repo_run_commands capability detail keeps the structured capability contract', () => { const registry = buildCapabilityRegistry(builtinDomains) const repoRunCommands = registry.capabilitySpecs.repo_run_commands @@ -247,6 +303,9 @@ test('repo_run_commands capability detail keeps the structured capability contra title: 'repo_run_commands', description: repoRunCommands.description, usage: 'execute with codemode.repo_run_commands(args)', + executeExample: expect.stringContaining( + 'return await codemode.repo_run_commands(input)', + ), requiredInputFields: ['commands'], readOnly: expect.any(Boolean), idempotent: expect.any(Boolean), diff --git a/packages/worker/src/mcp/tools/search-format.ts b/packages/worker/src/mcp/tools/search-format.ts index 6324beba0a..41bbb616b6 100644 --- a/packages/worker/src/mcp/tools/search-format.ts +++ b/packages/worker/src/mcp/tools/search-format.ts @@ -206,6 +206,7 @@ export type SearchEntityDetailStructured = title: string description: string usage: string + executeExample: string requiredInputFields: Array readOnly: boolean idempotent: boolean @@ -413,7 +414,22 @@ function buildEntityRef(id: string, type: SearchEntityType) { } function buildCapabilityUsage(name: string) { - return `execute with codemode.${name}(args)` + return `execute with ${buildCodemodeCapabilityAccessor(name)}(args)` +} + +function buildCodemodeCapabilityAccessor(name: string) { + if (/^[A-Za-z_$][\w$]*$/.test(name)) { + return `codemode.${name}` + } + return `codemode[${JSON.stringify(name)}]` +} + +function buildCapabilityExecuteExample(name: string) { + return `import { codemode } from 'kody:runtime' + +export default async function main(input = {}) { +\treturn await ${buildCodemodeCapabilityAccessor(name)}(input) +}` } function buildPackageRootImportUsage(packageName: string) { @@ -756,6 +772,16 @@ export function formatEntityDetailMarkdown(detail: SearchEntityDetail) { `- Idempotent: ${detail.spec.idempotent ? 'yes' : 'no'}`, `- Destructive: ${detail.spec.destructive ? 'yes' : 'no'}`, '', + '## Execute from `execute`', + '', + 'Built-in capabilities returned by `search` are available inside `execute` as methods on the imported `codemode` object.', + '', + '```ts', + buildCapabilityExecuteExample(detail.spec.name), + '```', + '', + 'Pass concrete arguments that satisfy the input type below; use `{}` when there are no required fields.', + '', '## Type definitions', '', '```ts', @@ -775,6 +801,7 @@ export function formatEntityDetailMarkdown(detail: SearchEntityDetail) { title: detail.title, description: detail.description, usage: buildCapabilityUsage(detail.spec.name), + executeExample: buildCapabilityExecuteExample(detail.spec.name), requiredInputFields: detail.spec.requiredInputFields, readOnly: detail.spec.readOnly, idempotent: detail.spec.idempotent, diff --git a/packages/worker/src/mcp/tools/search.ts b/packages/worker/src/mcp/tools/search.ts index a7655ec806..be6c04944c 100644 --- a/packages/worker/src/mcp/tools/search.ts +++ b/packages/worker/src/mcp/tools/search.ts @@ -1416,8 +1416,9 @@ If nothing useful returns, rephrase or call \`meta_list_capabilities\`; \`entity does not fix an empty ranked list. **entity: "{id}:{type}"** — detail for one hit (\`capability\` | \`value\` -| \`integration\` | \`package\` | \`secret\`). Capability detail includes -TypeScript call-shape definitions by default. +| \`integration\` | \`package\` | \`secret\`). Capability detail includes an +exact \`execute\` module snippet plus TypeScript call-shape definitions by +default. Packages: \`package_list\`, \`package_get\`, and \`repo_*\` for editing/publishing. Open package apps with \`open_generated_ui({ kody_id })\` or use hosted package URLs.