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 (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 1.0.0-rc.1 and mounts on eve 1.0.0-rc.1 or later:

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
})

Automatic presets and approval

For an agent that takes open-ended requests, let the extension pick the tools and decide which writes need a human. Both modes need ai 7.0.105 or later.

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

export default githubExtension({
  preset: 'auto',
  requireApproval: 'auto',
})
  • preset: 'auto' asks the evaluation model which presets the latest user message needs and registers only their tools for that turn. It combines at most two presets and never exposes the full catalog. Tools the agent already called in the session stay registered, so a parked approval resumes even when routing changes.
  • requireApproval: 'auto' lets low-risk write tools (AUTO_APPROVAL_TOOLS: labels, assignees, reactions, comments, review-thread replies, reviewer requests, notification reads, workflow re-runs) run without a prompt when the model rates the call low-risk and the user's request asked for it. Other write tools keep always(). Mix it per tool: requireApproval: { addLabels: 'auto', mergePullRequest: true }.
  • evaluation tunes both: minPresetProbability and maxPresets for routing, maxRisk and minIntent for approval, and model to replace the default evaluation model.

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 | ((ctx, call) => record)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. App tokens target the installation owning each call's repository by default, see Multiple GitHub App installations; a resolver picks other params per tool call
presetpreset name, array, or 'auto'code-review, issue-triage, ci-ops, repo-explorer, security-audit, release-manager, discussion-moderator, notification-inbox, pr-author, maintainer, see Presets. 'auto' routes each user message, see Automatic presets and approval
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 | 'auto' | recordGlobal or per-tool; per-tool values may be 'once', 'always', 'never', 'auto', or predicate functions
evaluation{ model?, maxRisk?, minIntent?, minPresetProbability?, maxPresets? }?Tunes the 'auto' modes, see Automatic presets and approval
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's tool file is a defineDynamic function that eve runs before every model call. It rebuilds each tool's execute, toModelOutput, approval, and schemas from the extension config with @github-tools/sdk/eve-runtime, so the same tools and approval policies come back in every process, including after Workflow replay and redeploys. With preset: 'auto', the routed presets are kept for the turn with ctx.memo, so every model call in a turn sees the same tools. Prefer this mount over the deprecated createGithubTools / connectGithubTools paths.

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. For failures the SDK can classify, error is a structured { code, message, why, fix, link } object from the error catalog — e.g. a 404 explains that GitHub also masks no-access private repos as 404, so the model does not hallucinate a cause. Unclassified failures keep the plain message string.

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)
'auto'evaluation modelLow-risk write tools run when the call matches the user's request; otherwise approval is requested

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.

Approval requests name the action and its target instead of eve's default Approve <tool>?, for example Merge pull request on vercel-labs/github-tools#176?. owner, repo, and the PR, issue, or discussion number fall back to context when the call omits them. Slack also shows the full tool input in a collapsed block below the card.

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.

Multiple GitHub App installations

Nothing to configure. Each app token is minted for the GitHub App installation that owns the call's target repository, so one App installed on several orgs or users just works:

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

export default githubExtension({
  connector: 'github/my-connector',
  preset: 'pr-author',
  context: { owner: 'evloghq', repo: 'evlog' },
})

The target is the tool's owner / repo input after context defaults are applied, so a call that omits them uses the configured home repository. Tools without a repository target (searchCode, gist and notification tools) get the connector's default installation. Static connect params (validityBufferMs, scopes, ...) merge in. Connect caches tokens per connector and params, so repeated calls on the same repository reuse one token. When the App is not installed on the target account, the tool returns CONNECT_INSTALLATION_REQUIRED naming that account instead of a GitHub 403.

To pin one installation instead, set installationId, authorizationDetails or repositories in connect; an explicit choice is never overridden. User-subject tokens already span installations and are left untouched.

For other per-call rules, pass your own resolver. It receives the eve tool execution context and the call ({ toolName, input, owner?, repo? }), and may return a subject resolver too:

agent/extensions/github.ts
export default githubExtension({
  connector: 'github/my-connector',
  connect: (ctx, call) => ({
    installationId: call.owner === 'hugorcd' ? 'inst_personal' : undefined,
  }),
})

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