Build a GitHub agent with the eve extension
eve is Vercel's filesystem-first agent framework: an agent is a folder with instructions, a model config, and tools. @github-tools/eve-extension packages all 79 GitHub tools as a mountable eve extension: a single pnpm add and a one-line mount under agent/extensions/, no CLI setup, and no direct SDK import in agent/tools/.
createGithubTools and per-tool factories from @github-tools/sdk/eve) are deprecated in its favor. They keep working for existing agent/tools/ setups, but new agents should mount the extension instead. Shared runtime helpers used by this extension live on @github-tools/sdk/eve-runtime (not deprecated).Add GitHub tools to an eve agent via the extension
The whole agent
Three files. That's the entire thing:
You are a GitHub assistant. Use the GitHub tools to inspect repos, PRs, and issues.
Ask before merging or closing anything destructive.
import { defineAgent } from 'eve'
export default defineAgent({
model: 'anthropic/claude-sonnet-5',
})
import githubExtension from '@github-tools/eve-extension'
export default githubExtension({
preset: 'maintainer',
})
Run it:
npx eve dev
You now have a GitHub agent that can read repos, review PRs, triage issues, and manage CI, with every write operation gated behind durable approval by default. The full example lives in examples/eve-extension-agent/ (or pnpm dev:eve-extension-agent from the monorepo root).
Install
pnpm add @github-tools/eve-extension
npm install @github-tools/eve-extension
yarn add @github-tools/eve-extension
bun add @github-tools/eve-extension
eve is a required peer dependency (which itself requires ai v7):
pnpm add eve
npm install eve
yarn add eve
bun add eve
You still need GITHUB_TOKEN (or a Vercel Connect connector, below). See Installation.
Mount it
Drop a file under agent/extensions/. The filename becomes the tool namespace:
import githubExtension from '@github-tools/eve-extension'
export default githubExtension({
connector: 'github/my-connector', // or token: process.env.GITHUB_TOKEN
preset: 'code-review',
requireApproval: {
addPullRequestComment: ({ toolInput }) => toolInput?.owner !== 'vercel-labs',
},
})
Tools are exposed to the model as <namespace>__<toolName>, where <namespace> comes from the mount file's name: agent/extensions/github.ts yields github__listPullRequests, github__createIssue, and so on.
code-review is used above (rather than maintainer) because it 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 (see Tokens & Auth). The requireApproval predicate above is a real gate, not a no-op: write tools already require approval by always() by default, so { mergePullRequest: true } would change nothing. A predicate is what actually narrows or loosens the default.
Pick exact tools
preset scopes to one of five predefined groups. To hand-pick tools instead, standalone or layered on top of a preset, use include and exclude:
import githubExtension from '@github-tools/eve-extension'
export default githubExtension({
include: ['getRepository', 'listPullRequests', 'mergePullRequest'],
})
include adds to preset. The effective set is the union of both, so you can pull in a tool a preset is missing without switching to a bigger preset. When connector is set and connect.scopes is omitted, Connect scopes follow that same resolved tool set — a standalone include does not mint the full admin union:
export default githubExtension({
preset: 'code-review', // read-only PR review tools
include: ['createIssue'], // + one write tool code-review doesn't include
})
exclude removes tool names from whatever preset + include resolved to. Useful for dropping a couple of tools you don't want exposed from a larger preset:
export default githubExtension({
preset: 'maintainer', // all 79 tools
exclude: ['createRepository', 'deleteGist'], // minus these two
})
Config schema
| Field | Type | Notes |
|---|---|---|
token | string? | Falls back to GITHUB_TOKEN when omitted and connector is not set |
connector | string | (() => string | Promise<string>) | Vercel Connect connector name, or a resolver to pick one dynamically (e.g. per environment/tenant); takes priority over token |
connect | record? | Passed through to getToken when connector is set |
preset | preset name or array | code-review, issue-triage, ci-ops, repo-explorer, security-audit, release-manager, discussion-moderator, notification-inbox, pr-author, maintainer, see Presets |
include | string[]? | Tool names to add on top of preset (union), or the full set standalone, see Pick exact tools |
exclude | string[]? | Tool names to remove from the resolved preset + include set |
context | { owner?, repo?, pullNumber?, issueNumber?, ref? }? | Default owner/repo/number/ref for tool inputs — matching fields become optional and fill from context when omitted, see Working context |
requireApproval | boolean | record | Global or per-tool; per-tool values may be 'once', 'always', 'never', or predicate functions |
overrides | record | Per-tool description / approval / toModelOutput / outputSchema |
author / committer / coAuthors | commit identity | Attribution for commit-creating tools, see Commit Attribution |
Durable multi-turn sessions
The extension registers each tool with an authored inline execute that only closes 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). Prefer this mount over the deprecated createGithubTools / connectGithubTools paths for Slack / multi-turn durable agents — those register tools from inside node_modules and are skipped on replay.
Durable approval, done right
Approval pauses the session durably until a human responds, and policies are expressive:
| Value | Maps to | Behavior |
|---|---|---|
true / 'always' | always() | Require approval on every call |
false / 'never' | omit approval | Skip approval (eve default) |
'once' | once() | Approve once per session, then auto-allow |
| predicate | custom Approval | Input-dependent gate (toolInput, session context) |
Default (no requireApproval): all write tools → always(). Unlisted write tools keep the always() fail-safe default. Read tools never require approval. Details: Control write safety.
Vercel Connect
Skip GITHUB_TOKEN entirely and mint the token from a Connect connector. Pass connector directly in the mount config, no separate connectGithubTools import needed:
import githubExtension from '@github-tools/eve-extension'
export default githubExtension({
connector: 'github/my-connector',
preset: 'code-review',
})
Unlike the deprecated direct import, no build.externalDependencies workaround is needed in agent/agent.ts. The extension is pre-built via eve extension build and loaded through eve's extension mechanism rather than inlined from a workspace-linked source import.
@vercel/connect is an optional peer dependency of the extension, install it only when using connector. connector also accepts a () => string | Promise<string> resolver for picking a connector per environment or tenant, see dynamic connector selection. See Vercel Connect for the connector setup checklist and multi-tenant scoping.
Idempotency
eve replays completed steps but re-runs steps interrupted mid-execution:
| Tool | Idempotency |
|---|---|
createOrUpdateFile | Natural when content + sha unchanged |
closeIssue | Natural when already closed |
createBranch | Natural when branch exists at same SHA |
addIssueComment, createIssue, mergePullRequest, … | Not idempotent |
Gate non-idempotent writes behind always() or once() where replay safety matters.
Migrating from the direct import
If you have an existing agent using @github-tools/sdk/eve directly in agent/tools/, move to the extension in three steps:
pnpm add @github-tools/eve-extensionand remove the now-unused directeve/ai/zodinstall if nothing else in the agent needs them.- Delete
agent/tools/github.tsand createagent/extensions/github.tsexportinggithubExtension({ ...same options... })instead ofcreateGithubTools({ ...same options... }). Options (preset,include,exclude,context,requireApproval,overrides,author/committer/coAuthors,token) are unchanged. - If you used
connectGithubToolsfrom@github-tools/sdk/connect/eve, drop it. Passconnectordirectly togithubExtensioninstead. - If you cherry-picked single-tool factories (e.g.
listPullRequests()exported alone from its own file), replace eachagent/tools/*.tsfile with aninclude: [...]list in the singleagent/extensions/github.tsmount, see Pick exact tools.
One behavior change to be aware of: tool names gain the <namespace>__ prefix described above, so update any code that references tool names by their bare string (requireApproval and include keys stay bare, only the exposed model-facing tool name gets the prefix).
eve extension vs direct import vs AI SDK vs Workflow SDK
| eve extension | eve (direct import) | AI SDK | Workflow SDK | |
|---|---|---|---|---|
| Import | @github-tools/eve-extension | @github-tools/sdk/eve (createGithubTools) | @github-tools/sdk | @github-tools/sdk/workflow |
| Mount point | agent/extensions/ | agent/tools/ | anywhere | workflow function |
| Status | Recommended | Deprecated | Active | Active |
| Tool registration | githubExtension() mount | defineDynamic in agent/tools/ | createGithubTools() object | createGithubTools() in workflow |
| Tool naming | <namespace>__<toolName> | bare tool name | bare tool name | bare tool name |
| Approval | always / once / predicates | always / once / predicates | boolean needsApproval | boolean needsApproval (durable pause) |
| Durability | eve session (filesystem-first) | eve session (filesystem-first) | in-process | "use workflow" steps |
External references
- How to build a GitHub agent with eve and GitHub tools
- eve documentation
- eve extensions
- Dynamic capabilities (bundled with the
evepackage) - Human-in-the-loop