Frameworks

Build a GitHub agent with the eve extension

Mount @github-tools/eve-extension under agent/extensions/ to add all 84 GitHub tools to an eve agent, the recommended way to wire GitHub into eve, with durable approval and Vercel Connect support.

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 84 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/.

This is the recommended way to add GitHub tools to an eve agent. The legacy direct registration APIs (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.

Run it:

Terminal
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/ (or pnpm dev:eve from the monorepo root).


For complete production implementations, see the Software Factory and Incident Response Agent. Both use the eve extension with Vercel Connect, while scoping GitHub access for different operational needs.


Install

pnpm add @github-tools/eve-extension

eve is a required peer dependency (>=0.44, which itself requires ai v7):

pnpm 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:

agent/extensions/github.ts
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 the ten predefined presets. To hand-pick tools instead, standalone or layered on top of a preset, use include and exclude:

agent/extensions/github.ts
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:

agent/extensions/github.ts
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:

agent/extensions/github.ts
export default githubExtension({
  preset: 'maintainer', // all 84 tools
  exclude: ['createRepository', 'deleteGist'], // minus these two
})

Config schema

FieldTypeNotes
tokenstring | (() => Promise<string>)PAT string, or an async provider for rotating tokens (e.g. a GitHub App installation token) — the same GithubTokenInput the SDK accepts; falls back to GITHUB_TOKEN when omitted and connector is not set
connectorstring | (() => string | Promise<string>)Vercel Connect connector name, or a resolver to pick one dynamically (e.g. per environment/tenant); takes priority over token
connectrecord?Passed through to getToken when connector is set; connect.subject defaults to { type: 'app' } and also accepts a per-caller resolver, see Per-user tokens
presetpreset name or arraycode-review, issue-triage, ci-ops, repo-explorer, security-audit, release-manager, discussion-moderator, notification-inbox, pr-author, maintainer, see Presets
includestring[]?Tool names to add on top of preset (union), or the full set standalone, see Pick exact tools
excludestring[]?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
requireApprovalboolean | recordGlobal or per-tool; per-tool values may be 'once', 'always', 'never', or predicate functions
overridesrecordPer-tool description / approval / toModelOutput / outputSchema
author / committer / coAuthorscommit identityAttribution for commit-creating tools, see Commit Attribution

The provider runs on every tool call, so short-lived tokens stay fresh:

agent/extensions/github.ts
import githubExtension from '@github-tools/eve-extension'

export default githubExtension({
  token: () => mintInstallationToken(),
  preset: 'issue-triage',
})

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, #99). A spread or call-expression callback (resolveEveApproval(...), a ternary-wrapped toModelOutput) has no durable descriptor: on eve 0.44+ the resolver discards the entire GitHub toolset. 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. 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.

If execute fails (token mint, GitHub 403/429, …), the tool returns { error } instead of throwing so the model always receives a tool_result. A thrown error in eve's tool-loop can leave a tool_use unpaired and kill the turn.

Durable approval, done right

Approval pauses the session durably until a human responds, and policies are expressive:

ValueMaps toBehavior
true / 'always'always()Require approval on every call
false / 'never'omit approvalSkip approval (eve default)
'once'once()Approve once per session, then auto-allow
predicatecustom ApprovalInput-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:

agent/extensions/github.ts
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.

Per-user tokens

By default Connect mints the project's GitHub App installation token (subject: { type: 'app' }) — one identity shared by every caller. That is right for single-tenant agents, but in multi-user apps where each user connects their own GitHub account, it silently gives every signed-in user the project-level access. Set connect.subject to a per-caller resolver to mint each caller's own connection token instead. The resolver receives the eve tool execution context on every tool call:

agent/extensions/github.ts
import githubExtension from '@github-tools/eve-extension'

export default githubExtension({
  connector: 'github/my-connector',
  preset: 'issue-triage',
  connect: {
    subject: (ctx) => {
      const caller = ctx.session.auth.current
      if (!caller) throw new Error('GitHub tools require an authenticated caller')
      return { type: 'user', id: caller.principalId }
    },
  },
})

ctx.session.auth.current is the authenticated caller of the active turn — eve restores it durably across workflow replay, so the resolver stays correct in multi-turn sessions. A caller without an active GitHub connection gets a UserAuthorizationRequiredError from Connect instead of silently falling back to the app installation. A static subject value (e.g. { type: 'user', id } fixed at mount time) also works when the agent serves a single known user.

Idempotency

eve replays completed steps but re-runs steps interrupted mid-execution:

ToolIdempotency
createOrUpdateFileNatural when content + sha unchanged
closeIssueNatural when already closed
createBranchNatural 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:

  1. pnpm add @github-tools/eve-extension and remove the now-unused direct eve/ai/zod install if nothing else in the agent needs them.
  2. Delete agent/tools/github.ts and create agent/extensions/github.ts exporting githubExtension({ ...same options... }) instead of createGithubTools({ ...same options... }). Options (preset, include, exclude, context, requireApproval, overrides, author/committer/coAuthors, token) are unchanged.
  3. If you used connectGithubTools from @github-tools/sdk/connect/eve, drop it. Pass connector directly to githubExtension instead.
  4. If you cherry-picked single-tool factories (e.g. listPullRequests() exported alone from its own file), replace each agent/tools/*.ts file with an include: [...] list in the single agent/extensions/github.ts mount, 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 extensioneve (direct import)AI SDKWorkflow SDK
Import@github-tools/eve-extension@github-tools/sdk/eve (createGithubTools)@github-tools/sdk@github-tools/sdk/workflow
Mount pointagent/extensions/agent/tools/anywhereworkflow function
StatusRecommendedDeprecatedActiveActive
Tool registrationgithubExtension() mountdefineDynamic in agent/tools/createGithubTools() objectcreateGithubTools() in workflow
Tool naming<namespace>__<toolName>bare tool namebare tool namebare tool name
Approvalalways / once / predicatesalways / once / predicatesboolean needsApprovalboolean needsApproval (durable pause)
Durabilityeve session (filesystem-first)eve session (filesystem-first)in-process"use workflow" steps

External references