Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eve-durable-schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@github-tools/eve-extension": patch
---

Rebuild the extension against eve 0.62 and preserve dynamic input and output validation across durable replay. Tool schemas now use `defineDurableSchema` with a serializable tool-name closure, preventing eve 0.59 and newer from rejecting GitHub tools whose Zod schemas were captured through resolver-local descriptors.
4 changes: 2 additions & 2 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ Every tool splits into a **core** function (pure logic) and a **tool factory** (
pnpm build && pnpm lint && pnpm typecheck && pnpm test
```

### eve extension durable callbacks
### eve extension durable callbacks and schemas

`packages/github-tools-eve-extension/extension/tools/github.ts` must set `execute`, `toModelOutput`, and `approval` as **direct** `defineTool` properties with inline functions. Spreading those keys, or passing `resolveEveApproval(...)` / `always()` as the property value, leaves them without a durable descriptor, and eve rejects a dynamic tool whose callback is missing one. The same rule covers `approvalKey` and the `label` callbacks if they are ever authored here. `test/durable-define-tool.test.ts` fails CI if that pattern returns.
`packages/github-tools-eve-extension/extension/tools/github.ts` must set `execute`, `toModelOutput`, and `approval` as **direct** `defineTool` properties with inline functions. Spreading those keys, or passing `resolveEveApproval(...)` / `always()` as the property value, leaves them without a durable descriptor, and eve rejects a dynamic tool whose callback is missing one. The same rule covers `approvalKey` and the `label` callbacks if they are ever authored here. Live `inputSchema` and `outputSchema` values must use `defineDurableSchema` with a JSON-serializable closure. `test/durable-define-tool.test.ts` fails CI if either contract regresses.

## Pull requests

Expand Down
30 changes: 26 additions & 4 deletions .github/workflows/eve-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,35 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Bump eve to latest everywhere
- name: Bump eve and AI SDK to a coherent latest pair
run: |
pnpm -r up 'eve@latest'
pnpm ls -r --depth -1 eve || true
AI_VERSION="$(npm view ai version)"
export AI_VERSION
node <<'NODE'
const fs = require('node:fs')
const path = 'pnpm-workspace.yaml'
const source = fs.readFileSync(path, 'utf8')
const updated = source.replace(
/('@ai-sdk\/workflow>ai': )\S+/,
(_, prefix) => `${prefix}${process.env.AI_VERSION}`,
)
if (updated === source) throw new Error('AI SDK workflow override was not updated')
fs.writeFileSync(path, updated)
NODE
pnpm -r up \
'eve@latest' \
"ai@$AI_VERSION" \
'@ai-sdk/gateway@latest' \
'@ai-sdk/vue@latest' \
'@ai-sdk/provider@latest' \
'@ai-sdk/provider-utils@latest'
pnpm ls -r --depth -1 eve ai || true

- name: Build
run: pnpm exec turbo run build --filter=@github-tools/sdk --filter=@github-tools/eve-extension
run: pnpm exec turbo run build --filter=@github-tools/eve

- name: Verify durable schema transform
run: node scripts/verify-eve-schema-transform.mjs examples/eve/.output/server/index.mjs

- name: Typecheck
run: pnpm exec turbo run typecheck --filter=@github-tools/sdk --filter=@github-tools/eve-extension
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ export const myTool = (token: GithubTokenInput, { needsApproval = true }: ToolOp

Ten presets (`code-review`, `issue-triage`, `repo-explorer`, `ci-ops`, `security-audit`, `release-manager`, `discussion-moderator`, `notification-inbox`, `pr-author`, `maintainer`) defined in `src/core/presets.ts` as tool name arrays, with matching system prompts in `src/agents.ts`. Composable via arrays.

## eve extension durable callbacks (`packages/github-tools-eve-extension`)
## eve extension durable callbacks and schemas (`packages/github-tools-eve-extension`)

eve rejects a dynamic tool whose callback has no durable descriptor. It stamps one per callback phase (`execute`, `toModelOutput`, `approval` / `approvalRequest`, `approvalKey`, and the `label` callbacks), and `execute` is the only required phase. In `extension/tools/github.ts`, every authored callback must be a **direct** `defineTool` property with an inline function (or identifier). Conditional spreads and call expressions (`resolveEveApproval(...)`, `always()`) are invisible to eve's stamp. Callbacks may only close over a serializable tool `name` and re-read config via `buildSessionOptions()`. CI enforces this via `test/durable-define-tool.test.ts`. A scheduled canary (`.github/workflows/eve-canary.yml`) additionally builds and tests against `eve@latest` daily to catch upstream drift the static guard cannot see.
eve rejects a dynamic tool whose callback or live validation schema has no durable descriptor. It stamps one per callback phase (`execute`, `toModelOutput`, `approval` / `approvalRequest`, `approvalKey`, and the `label` callbacks), and `execute` is the only required callback. In `extension/tools/github.ts`, every authored callback must be a **direct** `defineTool` property with an inline function (or identifier). Input and output schemas from the SDK or extension config must use `defineDurableSchema`. Conditional spreads and call expressions (`resolveEveApproval(...)`, `always()`) are invisible to eve's callback transform. Durable callbacks and schema factories may only close over a serializable tool `name` and re-read config via `buildSessionOptions()`. CI enforces this via `test/durable-define-tool.test.ts`. A scheduled canary (`.github/workflows/eve-canary.yml`) additionally builds the consumer example against a coherent `eve@latest` / `ai@latest` pair and inspects the durable schema transform.

## Chat App Architecture (`apps/chat`)

Expand Down
6 changes: 3 additions & 3 deletions apps/chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"db:migrate": "nuxt db migrate"
},
"dependencies": {
"@ai-sdk/gateway": "^4.0.78",
"@ai-sdk/vue": "^4.0.97",
"@ai-sdk/gateway": "^4.0.87",
"@ai-sdk/vue": "^4.0.107",
"@ai-sdk/workflow": "^1.0.70",
"@github-tools/sdk": "workspace:*",
"@iconify-json/logos": "^1.2.14",
Expand All @@ -31,7 +31,7 @@
"@workflow/ai": "^4.2.1",
"@workflow/nitro": "4.1.6",
"@workflow/nuxt": "^4.0.22",
"ai": "^7.0.97",
"ai": "^7.0.107",
"date-fns": "^4.4.0",
"drizzle-orm": "^0.45.2",
"h3": "^1.15.11",
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/1.getting-started/2.installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ What you install next depends on the [framework](/frameworks/ai-sdk) you build o
| Framework | Import path | Peer dependencies |
|---|---|---|
| [AI SDK](/frameworks/ai-sdk) | `@github-tools/sdk` | `ai` (v6 or v7), `zod` |
| [eve extension](/frameworks/eve-extension) | `@github-tools/eve-extension` | `eve` (declared `*`, built against 0.53, **`ai` v7** transitively) |
| [eve extension](/frameworks/eve-extension) | `@github-tools/eve-extension` | `eve` (declared `*`, built against 0.62, **`ai` v7** transitively) |
| [eve (direct import, deprecated)](/deprecated/eve) | `@github-tools/sdk/eve` | `eve`, `ai` **v7**, `zod` |
| [Vercel Workflow](/frameworks/vercel-workflow) | `@github-tools/sdk/workflow` | `workflow`, `@ai-sdk/workflow`, `ai`, `zod` |
| [Chat SDK](/frameworks/chat-sdk) | `@github-tools/sdk` | `chat`, `@chat-adapter/github`, `ai`, `zod` |
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/docs/2.frameworks/1.eve-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ bun add @github-tools/eve-extension
```
:::

`eve` is a required peer dependency (which itself requires **`ai` v7**). The extension declares it as `*`: your agent provides the runtime copy of eve, and eve validates the extension's generated capability metadata at build time instead of an npm range. This release is built against eve 0.53:
`eve` is a required peer dependency (which itself requires **`ai` v7**). The extension declares it as `*`: your agent provides the runtime copy of eve, and eve validates the extension's generated capability metadata at build time instead of an npm range. This release is built against eve 0.62:

:::code-group
```bash [pnpm]
Expand Down Expand Up @@ -208,7 +208,7 @@ export default githubExtension({

## Durable multi-turn sessions

The extension registers each tool with an **authored inline** `execute`, `toModelOutput`, and `approval` as **direct** `defineTool` properties that only close over a serializable tool `name`, then rebuilds session options from the extension config on every call via `@github-tools/sdk/eve-runtime`. Tools resolve on `step.started` so registration stays fresh across durable steps. That pattern survives multi-turn eve Workflow replay (see [#51](https://github.com/vercel-labs/github-tools/issues/51), [#99](https://github.com/vercel-labs/github-tools/issues/99)). A spread or call-expression callback (`resolveEveApproval(...)`, a ternary-wrapped `toModelOutput`) has no durable descriptor, and eve rejects a dynamic tool whose callback is missing one. Prefer this mount over the deprecated [`createGithubTools`](/deprecated/eve) / [`connectGithubTools`](/deprecated/eve) paths for Slack / multi-turn durable agents — those register tools from inside `node_modules` and are skipped on replay. Author `overrides.toModelOutput` inline in the agent; a function imported from a library will not get a durable descriptor.
The extension registers each tool with an **authored inline** `execute`, `toModelOutput`, and `approval` as **direct** `defineTool` properties that only close over a serializable tool `name`, then rebuilds session options from the extension config on every call via `@github-tools/sdk/eve-runtime`. Input schemas and optional `overrides.outputSchema` values use `defineDurableSchema` with the same name-only closure, preserving Zod refinements and transformations across Workflow replay. Tools resolve on `step.started` so registration stays fresh across durable steps. That pattern survives multi-turn eve Workflow replay (see [#51](https://github.com/vercel-labs/github-tools/issues/51), [#99](https://github.com/vercel-labs/github-tools/issues/99)). A spread or call-expression callback (`resolveEveApproval(...)`, a ternary-wrapped `toModelOutput`) has no durable descriptor, and eve rejects a dynamic tool whose callback is missing one. Prefer this mount over the deprecated [`createGithubTools`](/deprecated/eve) / [`connectGithubTools`](/deprecated/eve) paths for Slack / multi-turn durable agents — those register tools from inside `node_modules` and are skipped on replay. Author `overrides.toModelOutput` inline in the agent; a function imported from a library will not get a durable descriptor.

Object-shaped execute results include `rateLimit` (`remaining`, `limit`, `reset`, `resource`). `toModelOutput` strips it so the model never sees the remaining count; `toolResultFrom` and channels still do. See [Rate-limit metadata](/api/reference#rate-limit-metadata).

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/skills/github-tools-agents/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export async function run(messages: ModelMessage[], token: string) {

### eve extension (recommended for eve agents)

Requires `eve` as a peer, declared `*` and checked through the extension's generated capability metadata (transitively **`ai` v7**); built against eve 0.53. Mount from `@github-tools/eve-extension` under `agent/extensions/`.
Requires `eve` as a peer, declared `*` and checked through the extension's generated capability metadata (transitively **`ai` v7**); built against eve 0.62. Mount from `@github-tools/eve-extension` under `agent/extensions/`.

```ts
// agent/extensions/github.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Use `@github-tools/eve-extension` when the user builds an [eve](https://eve.dev)
pnpm add @github-tools/eve-extension eve
```

- **`ai` v7** required (transitive `eve` peer); the extension declares `eve` as `*` and eve checks its generated capability metadata, not an npm range. Built against eve 0.53
- **`ai` v7** required (transitive `eve` peer); the extension declares `eve` as `*` and eve checks its generated capability metadata, not an npm range. Built against eve 0.62
- `GITHUB_TOKEN`, explicit `token`, or a Vercel Connect `connector`

## Mount under `agent/extensions/`
Expand Down Expand Up @@ -55,7 +55,7 @@ export default githubExtension({
})
```

`execute`, `toModelOutput`, and `approval` are direct `defineTool` properties whose callbacks only close over the tool name (a spread or `resolveEveApproval(...)` call is not stamped). `toModelOutput` also strips `rateLimit` from the model-facing payload. Author `overrides.toModelOutput` inline in the agent — a library function will not get a durable descriptor, and eve rejects a dynamic tool whose callback is missing one. Execute failures return `{ error }` so the model still receives a `tool_result` — a structured `{ code, message, why, fix, link }` object for catalog errors (e.g. `github_tools.NOT_FOUND` explains GitHub masks no-access private repos as 404), a plain string otherwise.
`execute`, `toModelOutput`, and `approval` are direct `defineTool` properties whose callbacks only close over the tool name (a spread or `resolveEveApproval(...)` call is not stamped). Input schemas and optional output-schema overrides use `defineDurableSchema` with the same name-only closure, preserving validation across replay. `toModelOutput` also strips `rateLimit` from the model-facing payload. Author `overrides.toModelOutput` inline in the agent — a library function will not get a durable descriptor, and eve rejects a dynamic tool whose callback is missing one. Execute failures return `{ error }` so the model still receives a `tool_result` — a structured `{ code, message, why, fix, link }` object for catalog errors (e.g. `github_tools.NOT_FOUND` explains GitHub masks no-access private repos as 404), a plain string otherwise.

## Approval

Expand Down
4 changes: 2 additions & 2 deletions examples/eve/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"dependencies": {
"@github-tools/eve-extension": "workspace:*",
"@vercel/connect": "^2.0.4",
"ai": "^7.0.97",
"eve": "^0.53.1",
"ai": "^7.0.107",
"eve": "^0.62.0",
"zod": "^4.5.4"
}
}
2 changes: 1 addition & 1 deletion examples/pr-review-agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@chat-adapter/state-memory": "latest",
"@github-tools/sdk": "workspace:*",
"@workflow/ai": "latest",
"ai": "^7.0.97",
"ai": "^7.0.107",
"chat": "latest",
"evlog": "latest",
"workflow": "latest",
Expand Down
4 changes: 2 additions & 2 deletions packages/github-tools-eve-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This is **the recommended way** to add GitHub tools to an eve agent. The legacy
pnpm add @github-tools/eve-extension
```

`eve` is a required peer dependency, declared as `*`: the consuming agent provides the runtime copy and eve checks the extension's generated capability metadata rather than an npm range. This release is built against eve 0.53. `@vercel/connect` is optional (install it only when using `connector`):
`eve` is a required peer dependency, declared as `*`: the consuming agent provides the runtime copy and eve checks the extension's generated capability metadata rather than an npm range. This release is built against eve 0.62. `@vercel/connect` is optional (install it only when using `connector`):

```sh
pnpm add eve
Expand All @@ -43,7 +43,7 @@ export default githubExtension({

> `code-review` pairs cleanly with a Connect `connector`. `maintainer` and `repo-explorer` include gist tools, and GitHub only grants gist access to user access tokens, never the installation tokens Connect mints, so gist calls 403 over Connect. Write tools already require approval via `always()` by default, so a plain `{ someTool: true }` is a no-op, use a predicate (as above) when you actually want to narrow or loosen the default.

Tools are registered with **inline** `execute`, `toModelOutput`, and `approval` as direct `defineTool` properties so they survive multi-turn durable eve Workflow replay (see [#51](https://github.com/vercel-labs/github-tools/issues/51), [#99](https://github.com/vercel-labs/github-tools/issues/99)). A spread or call-expression callback has no durable descriptor, and eve rejects a dynamic tool whose callback is missing one. `toModelOutput` strips `rateLimit` from the model-facing payload; the execute result still carries it for hooks and channels. Do not use the deprecated `@github-tools/sdk/connect/eve` one-liner for durable Slack/multi-turn agents.
Tools are registered with **inline** `execute`, `toModelOutput`, and `approval` as direct `defineTool` properties so they survive multi-turn durable eve Workflow replay (see [#51](https://github.com/vercel-labs/github-tools/issues/51), [#99](https://github.com/vercel-labs/github-tools/issues/99)). Input schemas and optional output-schema overrides use `defineDurableSchema`; every durable closure contains only the tool name and reconstructs the live schema from extension configuration. A spread or call-expression callback has no durable descriptor, and eve rejects a dynamic tool whose callback is missing one. `toModelOutput` strips `rateLimit` from the model-facing payload; the execute result still carries it for hooks and channels. Do not use the deprecated `@github-tools/sdk/connect/eve` one-liner for durable Slack/multi-turn agents.

`connector` also accepts a `() => string | Promise<string>` resolver, so the same config can pick a connector dynamically (e.g. by environment):

Expand Down
40 changes: 33 additions & 7 deletions packages/github-tools-eve-extension/extension/tools/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ import {
type GithubWriteToolName,
} from '@github-tools/sdk/eve-runtime'
import type { ApprovalContext } from 'eve/tools/approval'
import { defineDynamic, defineTool, type ToolContext, type ToolDefinition } from 'eve/tools'
import { defineDurableSchema, defineDynamic, defineTool, type ToolContext, type ToolDefinition } from 'eve/tools'
import extension from '../extension'

/**
* Rebuild options from extension config on every call.
* Durable `execute` / `toModelOutput` / `approval` only close over a serializable
* tool `name` (#51, #99). Those must be direct `defineTool` properties — a spread
* or call expression is invisible to eve's stamp, and eve rejects a dynamic tool
* whose callback has no durable descriptor.
* Durable callbacks and schemas only close over a serializable tool `name`
* (#51, #99). Callbacks must be direct `defineTool` properties, while live
* schemas use `defineDurableSchema`; eve rejects either without a descriptor.
*/
function buildSessionOptions(ctx?: ToolContext): EveGithubToolsOptions {
const {
Expand Down Expand Up @@ -128,6 +127,27 @@ function runGithubEveApproval(name: GithubToolName, ctx: ApprovalContext) {
return policy(ctx)
}

function buildGithubEveInputSchema({ name }: { name: GithubToolName }) {
const descriptor = listEveToolDescriptors({
...buildSessionOptions(),
preset: undefined,
include: [name],
exclude: undefined,
})[0]
if (!descriptor) {
throw new Error(`GitHub tool descriptor "${name}" is not available`)
}
return descriptor.inputSchema
}

function buildGithubEveOutputSchema({ name }: { name: GithubToolName }) {
const outputSchema = buildSessionOptions().overrides?.[name]?.outputSchema
if (!outputSchema) {
throw new Error(`GitHub tool output schema override "${name}" is not available`)
}
return outputSchema
}

export default defineDynamic({
events: {
// Re-resolve each model step (not once per session) so tool registration
Expand All @@ -144,11 +164,17 @@ export default defineDynamic({

tools[name] = defineTool({
description: override?.description ?? entry.description,
inputSchema: entry.inputSchema,
inputSchema: defineDurableSchema({
closure: { name },
schema: buildGithubEveInputSchema,
}),
approval: (ctx) => runGithubEveApproval(name, ctx),
toModelOutput: (output: unknown) => runGithubEveToModelOutput(name, output),
...(override?.outputSchema !== undefined && {
outputSchema: override.outputSchema,
outputSchema: defineDurableSchema({
closure: { name },
schema: buildGithubEveOutputSchema,
}),
}),
execute: async (input, ctx) => runGithubEveTool(name, input, ctx),
})
Expand Down
2 changes: 1 addition & 1 deletion packages/github-tools-eve-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"@types/node": "^26.5.0",
"@vercel/connect": "^2.0.4",
"eslint": "^10.10.0",
"eve": "0.53.1",
"eve": "0.62.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.70.0"
},
Expand Down
Loading
Loading