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
3 changes: 1 addition & 2 deletions .changeset/connect-per-call-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
"@github-tools/eve-extension": minor
---

Mint Vercel Connect tokens per tool call, for the call's target repository. This covers GitHub Apps installed on several accounts.
Mint Vercel Connect tokens per tool call.

- `connect` on `githubExtension` accepts a `(ctx, call) => params` resolver, where `call` is `{ toolName, input, owner?, repo? }`. `owner` / `repo` are the tool's inputs after `context` defaults, and are undefined for tools without a repository target (search, gists, notifications). The static shape and the `connect.subject` resolver keep working unchanged.
- New `perRepository(params?)` in `@github-tools/sdk/connect`: `connect: perRepository()` mints each token with `authorizationDetails: [{ type: 'github_app_installation', org: owner, repositories: [repo] }]`, and falls back to the static `params` for calls without a repository target. It works with `githubExtension`, `connectGithubTools`, and `connectGithubToken`. Using it from an eve agent requires `@github-tools/sdk` as a direct dependency.
- `connectGithubTools` / `connectGithubToken` accept a `(call) => params` resolver as `connect` / `params`. Scopes still derive from `preset` / `include` / `exclude` unless the resolved params set `scopes`. Connect caches tokens per connector and params, so calls on the same repository reuse one token.
- Token providers (`GithubTokenInput`) now receive an optional `GithubTokenCall` argument on every tool call, from both `createGithubTools` and the eve runtime. Existing `() => Promise<string>` providers are unaffected.
- `CONNECT_INSTALLATION_REQUIRED` names the target account ("The connector's GitHub App is not installed on <owner>") when the token targets an org or repository owner.
10 changes: 10 additions & 0 deletions .changeset/connect-per-repository-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@github-tools/sdk": minor
"@github-tools/eve-extension": minor
---

Target the GitHub App installation that owns each tool call's repository by default. A GitHub App installed on several accounts now works with `githubExtension({ connector })`, `connectGithubTools` and `connectGithubToken` as they are, with nothing to configure.

- App-subject tokens for a call with `owner` / `repo` (after `context` defaults) get `authorizationDetails: [{ type: 'github_app_installation', org: owner, repositories: [repo] }]`. Static `connect` params merge in. Calls without a repository target and calls outside a tool use the connector's default installation, as before.
- An explicit `installationId`, `authorizationDetails` or `repositories` in the resolved params pins the installation and is never overridden. User subjects (`{ type: 'user' }`) are not targeted: a user token already spans installations.
- Scopes still derive from `preset` / `include` / `exclude`. Connect caches tokens per connector and params, so calls on one repository reuse the token; single-installation connectors resolve to the same installation they used before.
25 changes: 4 additions & 21 deletions apps/docs/content/docs/2.frameworks/1.eve-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export default githubExtension({
|---|---|---|
| `token` | `string \| (() => 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 |
| `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 \| ((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](#per-user-tokens). A resolver picks params per tool call, see [Multiple GitHub App installations](#multiple-github-app-installations) |
| `connect` | `record \| ((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](#per-user-tokens). App tokens target the installation owning each call's repository by default, see [Multiple GitHub App installations](#multiple-github-app-installations); a resolver picks other params per tool call |
| `preset` | preset 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](/guide/presets). `'auto'` routes each user message, see [Automatic presets and approval](#automatic-presets-and-approval) |
| `include` | `string[]?` | Tool names to add on top of `preset` (union), or the full set standalone, see [Pick exact tools](#pick-exact-tools) |
| `exclude` | `string[]?` | Tool names to remove from the resolved `preset` + `include` set |
Expand Down Expand Up @@ -287,38 +287,21 @@ export default githubExtension({

### Multiple GitHub App installations

Connect picks the GitHub App installation from the token's `authorizationDetails`. It checks an explicit `installationId` first, then the `org` (or the owner of a qualified repository), then the connector default. With static `connect` params, every tool call gets a token for one installation. When the App is installed on several accounts, a call to a repository on another account then fails with a 403. Pass `perRepository()` as `connect` to mint each token for the installation that owns the call's target repository:
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:

```ts [agent/extensions/github.ts]
import githubExtension from '@github-tools/eve-extension'
import { perRepository } from '@github-tools/sdk/connect'

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

`perRepository` is exported by the SDK, so add `@github-tools/sdk` to the agent's dependencies:
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.

:::code-group
```bash [pnpm]
pnpm add @github-tools/sdk
```
```bash [npm]
npm install @github-tools/sdk
```
```bash [yarn]
yarn add @github-tools/sdk
```
```bash [bun]
bun add @github-tools/sdk
```
:::

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 static params instead. Pass extra static params to merge them, e.g. `perRepository({ validityBufferMs: 60_000 })`. Scopes still derive from `preset` / `include` / `exclude` unless you set `scopes`. 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:

Expand Down
13 changes: 1 addition & 12 deletions apps/docs/content/docs/4.guide/5.vercel-connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,7 @@ const tools = connectGithubTools('github/my-connector', {

### One App installed on several accounts

Static `connect` params pin every token to one installation. When the connector's GitHub App is installed on several orgs or users, pass `perRepository()` to mint each token for the installation that owns the tool call's target repository:

```ts [connect-per-repository.ts]
import { connectGithubTools, perRepository } from '@github-tools/sdk/connect'

const tools = connectGithubTools('github/my-connector', {
preset: 'pr-author',
connect: perRepository(),
})
```

Calls that target `owner/repo` (after `context` defaults) get `authorizationDetails: [{ type: 'github_app_installation', org: owner, repositories: [repo] }]`. Calls without a repository target get the static params passed to `perRepository({ ... })`. `connect` also accepts any `(call) => params` resolver. Connect caches tokens per connector and params, so repeated calls on one repository reuse the token. For the eve extension, see [Multiple GitHub App installations](/frameworks/eve-extension#multiple-github-app-installations).
Nothing to configure: app tokens are minted for the installation that owns the tool call's target repository. Calls that target `owner/repo` (after `context` defaults) get `authorizationDetails: [{ type: 'github_app_installation', org: owner, repositories: [repo] }]`; calls without a repository target use the connector's default installation. Static `connect` params merge in, and an explicit `installationId`, `authorizationDetails` or `repositories` pins one installation instead. User subjects are left untouched. `connect` also accepts any `(call) => params` resolver. Connect caches tokens per connector and params, so repeated calls on one repository reuse the token. For the eve extension, see [Multiple GitHub App installations](/frameworks/eve-extension#multiple-github-app-installations).

## Per-user tokens

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/4.guide/7.errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The SDK maps every failure it can classify to a structured error from an [evlog]
| `OIDC_TOKEN_EXPIRED` | `VERCEL_OIDC_TOKEN` is past its `exp` claim — thrown before any request is made, with the exact expiry time. Run `vercel env pull` locally |
| `CONNECT_NOT_AUTHORIZED` | Connect rejected the calling process's identity (403). The request never reached GitHub — this is not a GitHub permission problem |
| `CONNECT_USER_NOT_CONNECTED` | A `{ type: 'user' }` subject was requested but that user has no active GitHub connection |
| `CONNECT_INSTALLATION_REQUIRED` | The connector's GitHub App is not installed on the target account — named in the message when the token targets an `org` or repository owner (e.g. with `perRepository()`) |
| `CONNECT_INSTALLATION_REQUIRED` | The connector's GitHub App is not installed on the target account — named in the message when the token targets an `org` or repository owner, which every tool call with a repository target does |
| `SUBJECT_CONTEXT_REQUIRED` | A `connect` or `connect.subject` resolver ran outside a tool execution (no eve context) |

### GitHub API
Expand Down
15 changes: 1 addition & 14 deletions apps/docs/content/docs/5.api/2.reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,20 +398,7 @@ type GithubConnectorInput = string | (() => string | Promise<string>)

`subject` defaults to `{ type: 'app' }` (the project's GitHub App installation). Pass `{ type: 'user', id }` to mint a token for that user's own connection — see [per-user tokens](/guide/vercel-connect#per-user-tokens). See the [Vercel Connect guide](/guide/vercel-connect#dynamic-connector-selection) for the dynamic connector example.

A `connect` resolver runs on every tool call with its `GithubTokenCall`, so the token can target the call's repository. Scopes still derive from `preset` unless the resolved params set `scopes`.

## `perRepository(params?)`

Import from `@github-tools/sdk/connect`. Returns a `connect` resolver that mints each token for the GitHub App installation owning the call's target repository: `{ ...params, authorizationDetails: [{ type: 'github_app_installation', org: owner, repositories: [repo] }] }` when the call has `owner` / `repo`, and `params` unchanged otherwise. Works as `connect` for `connectGithubTools`, `connectGithubToken` (`params`), and [`githubExtension`](/frameworks/eve-extension#multiple-github-app-installations):

```ts [connect-per-repository.ts]
import { connectGithubTools, perRepository } from '@github-tools/sdk/connect'

const tools = connectGithubTools('github/my-connector', {
preset: 'pr-author',
connect: perRepository({ validityBufferMs: 60_000 }),
})
```
App-subject tokens target the GitHub App installation that owns the call's repository: when the call has `owner` / `repo` and the params set none of `installationId`, `authorizationDetails` or `repositories`, `authorizationDetails: [{ type: 'github_app_installation', org: owner, repositories: [repo] }]` is added. See [One App installed on several accounts](/guide/vercel-connect#one-app-installed-on-several-accounts). A `connect` resolver runs on every tool call with its `GithubTokenCall` for other per-call rules. Scopes still derive from `preset` unless the resolved params set `scopes`.

## `connectGithubTools(connector, options?)`: eve (deprecated)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default githubExtension({
})
```

When one GitHub App is installed on several accounts, static `connect` params pin every token to one installation, so calls to repos on other accounts 403. Pass `connect: perRepository()` (import from `@github-tools/sdk/connect`; the agent must depend on `@github-tools/sdk`). Each token then targets the installation owning the call's `owner/repo`, after `context` defaults. Tools without a repository target (search, gists, notifications) use the static params passed to `perRepository({ ... })`. `connect` also accepts a custom `(ctx, call) => params` resolver, where `call` is `{ toolName, input, owner?, repo? }`. A missing installation surfaces as `CONNECT_INSTALLATION_REQUIRED` naming the account.
One GitHub App installed on several accounts needs no configuration: each app token targets the installation owning the call's `owner/repo`, after `context` defaults. Tools without a repository target (search, gists, notifications) use the connector's default installation. Set `installationId`, `authorizationDetails` or `repositories` in `connect` to pin one installation; user subjects are left untouched. `connect` also accepts a custom `(ctx, call) => params` resolver, where `call` is `{ toolName, input, owner?, repo? }`. A missing installation surfaces as `CONNECT_INSTALLATION_REQUIRED` naming the account.

## Docs

Expand Down
15 changes: 2 additions & 13 deletions packages/github-tools-eve-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,7 @@ export default githubExtension({
})
```

When the connector's GitHub App is installed on several accounts, pass `perRepository()` (from `@github-tools/sdk/connect`, so add `@github-tools/sdk` to the agent) as `connect`. Each token is then minted for the installation that owns the call's target `owner/repo`, after `context` defaults. Tools without a repository target use the static params you pass to `perRepository({ ... })`. If the App is not installed on the target account, the tool returns `CONNECT_INSTALLATION_REQUIRED` naming that account:

```ts
import { perRepository } from '@github-tools/sdk/connect'

export default githubExtension({
connector: 'github/my-connector',
preset: 'pr-author',
context: { owner: 'evloghq', repo: 'evlog' },
connect: perRepository(),
})
```
A GitHub App installed on several accounts needs no configuration: each token is minted for the installation that owns the call's target `owner/repo`, after `context` defaults. Tools without a repository target use the connector's default installation. Set `installationId`, `authorizationDetails` or `repositories` in `connect` to pin one installation. If the App is not installed on the target account, the tool returns `CONNECT_INSTALLATION_REQUIRED` naming that account.

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.

Expand Down Expand Up @@ -103,7 +92,7 @@ extension/
|---|---|---|
| `token` | `string \| (() => Promise<string>)` (optional) | 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 |
| `connector` | `string \| (() => string \| Promise<string>)` (optional) | Vercel Connect connector name, or a resolver to pick one dynamically (e.g. per environment/tenant); takes priority over `token` |
| `connect` | `record \| ((ctx, call) => record)` (optional) | Passed through to `getToken` when `connector` is set. `connect.subject` defaults to `{ type: 'app' }` (the project's GitHub App installation); pass `{ type: 'user', id }` or a per-caller resolver `(ctx) => subject` to mint each caller's own connection token in multi-user apps. Pass a resolver (e.g. `perRepository()`) to pick params per tool call |
| `connect` | `record \| ((ctx, call) => record)` (optional) | Passed through to `getToken` when `connector` is set. `connect.subject` defaults to `{ type: 'app' }` (the project's GitHub App installation); pass `{ type: 'user', id }` or a per-caller resolver `(ctx) => subject` to mint each caller's own connection token in multi-user apps. App tokens target the installation owning each call's repository unless `installationId`, `authorizationDetails` or `repositories` pins one; pass a `(ctx, call) => record` resolver for other per-call rules |
| `preset` | preset name, array, or `'auto'` | `code-review`, `issue-triage`, `ci-ops`, `repo-explorer`, `security-audit`, `release-manager`, `discussion-moderator`, `notification-inbox`, `pr-author`, `maintainer`; `'auto'` picks at most two presets per user message |
| `include` | `string[]?` | Tool names to add on top of `preset` (union), or the full set standalone |
| `exclude` | `string[]?` | Tool names to remove from the resolved `preset` + `include` set |
Expand Down
13 changes: 7 additions & 6 deletions packages/github-tools-eve-extension/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export type GithubExtensionConnectParams = Omit<GithubConnectParams, 'subject'>
* Resolves Connect token params for each tool call. Receives the eve tool
* execution context and the call — `owner` / `repo` are the tool's resolved
* inputs after `context` defaults, undefined for tools without a repository
* target. Use `perRepository()` from `@github-tools/sdk/connect` for the
* common case of selecting the GitHub App installation per target repository.
* target. Selecting the installation per target repository is the default and
* needs no resolver.
*/
export type GithubExtensionConnectResolver = (
ctx: ToolContext,
Expand Down Expand Up @@ -101,10 +101,11 @@ export interface GithubExtensionConfig {
* installation, shared by every caller); pass a value or a per-caller
* resolver to mint per-user tokens instead.
*
* Pass a resolver to pick params per tool call, e.g. `perRepository()` from
* `@github-tools/sdk/connect` when the GitHub App is installed on several
* accounts. Scopes still derive from `preset` / `include` / `exclude` unless
* the resolved params set `scopes`.
* App tokens target the GitHub App installation owning each call's
* repository, so an App installed on several accounts needs nothing here;
* `installationId`, `authorizationDetails` or `repositories` pins one. Pass
* a resolver for other per-call rules. Scopes still derive from `preset` /
* `include` / `exclude` unless the resolved params set `scopes`.
*/
connect?: GithubExtensionConnectParams | GithubExtensionConnectResolver
/**
Expand Down
Loading
Loading