diff --git a/.changeset/connect-per-user-subject.md b/.changeset/connect-per-user-subject.md deleted file mode 100644 index 4f837f2..0000000 --- a/.changeset/connect-per-user-subject.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@github-tools/sdk": minor -"@github-tools/eve-extension": minor ---- - -Support per-user Vercel Connect subjects. `GithubConnectParams` now accepts a `subject` (default stays `{ type: 'app' }`, the project's GitHub App installation), so multi-user apps can mint each caller's own connection token with `subject: { type: 'user', id }`. In the eve extension, `connect.subject` also accepts a per-caller resolver called with the tool execution context on every call — e.g. `(ctx) => ({ type: 'user', id: ctx.session.auth.current!.principalId })` — so each signed-in user reaches GitHub through their own connection instead of the shared app installation. diff --git a/apps/docs/content/docs/2.frameworks/1.eve-extension.md b/apps/docs/content/docs/2.frameworks/1.eve-extension.md index cf65203..0698168 100644 --- a/apps/docs/content/docs/2.frameworks/1.eve-extension.md +++ b/apps/docs/content/docs/2.frameworks/1.eve-extension.md @@ -186,7 +186,7 @@ export default githubExtension({ |---|---|---| | `token` | `string \| (() => Promise)` | 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)` | 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; `connect.subject` defaults to `{ type: 'app' }` and also accepts a per-caller resolver, see [Per-user tokens](#per-user-tokens) | +| `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](/guide/presets) | | `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 | @@ -244,28 +244,6 @@ Unlike the deprecated direct import, no `build.externalDependencies` workaround `@vercel/connect` is an optional peer dependency of the extension, install it only when using `connector`. `connector` also accepts a `() => string | Promise` resolver for picking a connector per environment or tenant, see [dynamic connector selection](/guide/vercel-connect#dynamic-connector-selection). See [Vercel Connect](/guide/vercel-connect#eve-extension) 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: - -```ts [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: diff --git a/apps/docs/content/docs/4.guide/5.vercel-connect.md b/apps/docs/content/docs/4.guide/5.vercel-connect.md index e1c1a16..c08b088 100644 --- a/apps/docs/content/docs/4.guide/5.vercel-connect.md +++ b/apps/docs/content/docs/4.guide/5.vercel-connect.md @@ -134,24 +134,7 @@ const tools = connectGithubTools('github/my-connector', { }) ``` -`subject` defaults to `{ type: 'app' }`, same as `connectGitHubAdapter` from `@vercel/connect`. - -## Per-user tokens - -The default `{ type: 'app' }` subject mints the project's GitHub App **installation** token — one identity shared by every caller. In multi-user apps where each user connects their own GitHub account (an integrations panel, a personal agent template), that default silently gives every signed-in user the project-level access. Pass a `{ type: 'user' }` subject to mint the caller's own connection token instead: - -```ts [connect-per-user.ts] -import { connectGithubTools } from '@github-tools/sdk/connect' - -const tools = connectGithubTools('github/my-connector', { - preset: 'issue-triage', - connect: { - subject: { type: 'user', id: currentUserId }, - }, -}) -``` - -A user without an active connection gets a `UserAuthorizationRequiredError` from Connect instead of falling back to the app installation. In the eve extension, `connect.subject` also accepts a per-caller resolver — see [the extension's Vercel Connect section](/frameworks/eve-extension#vercel-connect). +`subject` is always `{ type: 'app' }`, same as `connectGitHubAdapter` from `@vercel/connect`. ## Dynamic connector selection diff --git a/apps/docs/content/docs/5.api/2.reference.md b/apps/docs/content/docs/5.api/2.reference.md index 9be374c..2763938 100644 --- a/apps/docs/content/docs/5.api/2.reference.md +++ b/apps/docs/content/docs/5.api/2.reference.md @@ -364,14 +364,13 @@ type ConnectGithubToolsOptions = GithubToolsOptions & { } type GithubConnectParams = Omit & { - subject?: ConnectTokenSubject repositories?: string[] } type GithubConnectorInput = string | (() => string | Promise) ``` -`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. +`subject` is always `{ type: 'app' }`. See [Vercel Connect guide](/guide/vercel-connect#dynamic-connector-selection) for the dynamic connector example. ## `connectGithubTools(connector, options?)`: eve (deprecated) diff --git a/apps/docs/skills/github-tools-agents/references/eve-extension.md b/apps/docs/skills/github-tools-agents/references/eve-extension.md index 7d61886..aefbb0c 100644 --- a/apps/docs/skills/github-tools-agents/references/eve-extension.md +++ b/apps/docs/skills/github-tools-agents/references/eve-extension.md @@ -74,18 +74,6 @@ export default githubExtension({ No separate `connectGithubTools` import needed. `connector` is a mount-config field. -`connect.subject` defaults to `{ type: 'app' }` (the project's GitHub App installation — one identity shared by every caller). For multi-user apps where each user connects their own GitHub account, pass a per-caller resolver so each caller gets their own connection token: - -```ts -export default githubExtension({ - connector: 'github/my-connector', - preset: 'issue-triage', - connect: { - subject: (ctx) => ({ type: 'user', id: ctx.session.auth.current!.principalId }), - }, -}) -``` - ## Docs - `/frameworks/eve-extension` diff --git a/packages/github-tools-eve-extension/README.md b/packages/github-tools-eve-extension/README.md index 6682e1e..3138b58 100644 --- a/packages/github-tools-eve-extension/README.md +++ b/packages/github-tools-eve-extension/README.md @@ -90,7 +90,7 @@ extension/ |---|---|---| | `token` | `string \| (() => Promise)` (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)` (optional) | 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. `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 | +| `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` | | `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 | diff --git a/packages/github-tools-eve-extension/extension/extension.ts b/packages/github-tools-eve-extension/extension/extension.ts index 63ba887..3de83a4 100644 --- a/packages/github-tools-eve-extension/extension/extension.ts +++ b/packages/github-tools-eve-extension/extension/extension.ts @@ -1,6 +1,5 @@ import type { GithubTokenInput } from '@github-tools/sdk' -import type { ConnectTokenSubject, GithubConnectorInput, GithubConnectParams } from '@github-tools/sdk/connect' -import type { ToolContext } from 'eve/tools' +import type { GithubConnectorInput } from '@github-tools/sdk/connect' import { GITHUB_TOOL_NAMES, GITHUB_WRITE_TOOLS, @@ -31,24 +30,6 @@ export interface GithubExtensionContext { ref?: string } -/** - * Connect token subject for the extension: a static value, or a resolver - * called with the eve tool execution context on every tool call — e.g. - * `(ctx) => ({ type: 'user', id: ctx.session.auth.current!.principalId })` - * to mint each caller's own GitHub connection token in multi-user apps. - */ -export type GithubConnectSubjectInput = - | ConnectTokenSubject - | ((ctx: ToolContext) => ConnectTokenSubject | Promise) - -/** - * Connect token params for the extension. Same as the SDK's - * `GithubConnectParams`, except `subject` may also be a per-caller resolver. - */ -export type GithubExtensionConnectParams = Omit & { - subject?: GithubConnectSubjectInput -} - /** * Config passed to `githubExtension({ ... })` at the agent mount site. * Declared as an interface (not only a Zod schema) so IDE hovers show JSDoc. @@ -67,13 +48,8 @@ export interface GithubExtensionConfig { * (e.g. per environment or tenant). Takes priority over `token`. */ connector?: GithubConnectorInput - /** - * Vercel Connect token params passed through to `getToken` when `connector` - * is set. `subject` defaults to `{ type: 'app' }` (the project's GitHub App - * installation, shared by every caller); pass a value or a per-caller - * resolver to mint per-user tokens instead. - */ - connect?: GithubExtensionConnectParams + /** Vercel Connect token params passed through to `getToken` when `connector` is set. */ + connect?: Record /** Restrict tools to a preset (or array of presets). Prefer a focused preset; omit or use `maintainer` for the full catalog. */ preset?: GithubToolPreset | GithubToolPreset[] /** diff --git a/packages/github-tools-eve-extension/extension/tools/github.ts b/packages/github-tools-eve-extension/extension/tools/github.ts index a87cb4b..aadcaea 100644 --- a/packages/github-tools-eve-extension/extension/tools/github.ts +++ b/packages/github-tools-eve-extension/extension/tools/github.ts @@ -7,7 +7,6 @@ import { listEveToolDescriptors, mapEveApprovalValue, resolveEveApproval, - resolveGithubToken, type EveApprovalConfig, type EveApprovalValue, type EveGithubToolsOptions, @@ -16,7 +15,7 @@ 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 { defineDynamic, defineTool, type ToolDefinition } from 'eve/tools' import extension from '../extension' /** @@ -26,7 +25,7 @@ import extension from '../extension' * a spread or call expression is invisible to eve's stamp, and 0.44+ then * drops the whole toolset. */ -function buildSessionOptions(ctx?: ToolContext): EveGithubToolsOptions { +function buildSessionOptions(): EveGithubToolsOptions { const { token, connector, @@ -45,21 +44,13 @@ function buildSessionOptions(ctx?: ToolContext): EveGithubToolsOptions { const includeNames = include as GithubToolName[] | undefined const excludeNames = exclude as GithubToolName[] | undefined - // `connect.subject` may be a per-caller resolver; it needs the execution - // context, so the token is minted lazily, per tool call. const resolvedToken = connector - ? async () => { - const { subject, ...params } = connect ?? {} - const resolvedSubject = typeof subject === 'function' - ? await subject(requireToolContext(ctx)) - : subject - return resolveGithubToken(connectGithubToken(connector, { - preset, - include: includeNames, - exclude: excludeNames, - params: { ...params, ...(resolvedSubject && { subject: resolvedSubject }) }, - })) - } + ? connectGithubToken(connector, { + preset, + include: includeNames, + exclude: excludeNames, + params: connect, + }) : token return { @@ -95,15 +86,8 @@ function writeToolName(name: GithubToolName): GithubWriteToolName | undefined { return GITHUB_WRITE_TOOLS[name as keyof typeof GITHUB_WRITE_TOOLS] } -function requireToolContext(ctx: ToolContext | undefined): ToolContext { - if (!ctx) { - throw new Error('connect.subject resolver needs the tool execution context — it is only available while a tool call executes') - } - return ctx -} - -async function runGithubEveTool(name: GithubToolName, input: unknown, ctx: ToolContext) { - return executeGithubEveTool(name, input as Record, buildSessionOptions(ctx)) +async function runGithubEveTool(name: GithubToolName, input: unknown) { + return executeGithubEveTool(name, input as Record, buildSessionOptions()) } function runGithubEveToModelOutput(name: GithubToolName, output: unknown) { @@ -149,7 +133,7 @@ export default defineDynamic({ ...(override?.outputSchema !== undefined && { outputSchema: override.outputSchema, }), - execute: async (input, ctx) => runGithubEveTool(name, input, ctx), + execute: async (input) => runGithubEveTool(name, input), }) } diff --git a/packages/github-tools/src/connect/index.ts b/packages/github-tools/src/connect/index.ts index 9e21a18..2c9aee3 100644 --- a/packages/github-tools/src/connect/index.ts +++ b/packages/github-tools/src/connect/index.ts @@ -16,4 +16,3 @@ export type { ConnectGithubToolsOptions, GithubConnectParams, } from './types' -export type { ConnectTokenSubject } from '@vercel/connect' diff --git a/packages/github-tools/src/connect/params.ts b/packages/github-tools/src/connect/params.ts index d08baea..675e406 100644 --- a/packages/github-tools/src/connect/params.ts +++ b/packages/github-tools/src/connect/params.ts @@ -14,7 +14,7 @@ function buildConnectTokenParams( scopes: string[], params?: GithubConnectParams, ): ConnectTokenParams { - const { repositories, subject, ...rest } = params ?? {} + const { repositories, ...rest } = params ?? {} const authorizationDetails = rest.authorizationDetails ?? (repositories?.length @@ -22,7 +22,7 @@ function buildConnectTokenParams( : undefined) return { - subject: subject ?? { type: 'app' }, + subject: { type: 'app' }, ...rest, scopes, ...(authorizationDetails && { authorizationDetails }), diff --git a/packages/github-tools/src/connect/scopes.ts b/packages/github-tools/src/connect/scopes.ts index e42e5f2..f03b0ca 100644 --- a/packages/github-tools/src/connect/scopes.ts +++ b/packages/github-tools/src/connect/scopes.ts @@ -13,10 +13,10 @@ import { ALL_GITHUB_TOOL_NAMES, type GithubToolName } from '../core/tool-names' * * Gist tools in `repo-explorer` and `maintainer` are intentionally left * unscoped: the Gists API only accepts GitHub App *user* access tokens, never - * installation tokens, and Connect mints `subject: { type: 'app' }` - * installation tokens by default. Gist calls made with an app-subject token - * 403 regardless of requested scopes — use a `{ type: 'user' }` subject or a - * fine-grained PAT with the "Gists" account permission for those tools instead. + * installation tokens, and Connect always mints `subject: { type: 'app' }` + * installation tokens. Gist calls made with a Connect-derived token 403 + * regardless of requested scopes — use a fine-grained PAT with the "Gists" + * account permission for those tools instead. * * Notification tools in `maintainer` and `notification-inbox` are unscoped for * the same reason: `notifications` is an account-level GitHub App permission diff --git a/packages/github-tools/src/connect/token.test.ts b/packages/github-tools/src/connect/token.test.ts index cc91e0b..333a8a3 100644 --- a/packages/github-tools/src/connect/token.test.ts +++ b/packages/github-tools/src/connect/token.test.ts @@ -76,22 +76,6 @@ describe('connectGithubToken', () => { }, undefined) }) - it('passes an explicit user subject through instead of the app default', async () => { - const resolve = resolveConnectToken('github/my-connector', { - preset: 'issue-triage', - params: { subject: { type: 'user', id: 'user_123', issuer: 'https://auth.example.com' } }, - }) - - await resolve() - expect(getToken).toHaveBeenCalledWith( - 'github/my-connector', - expect.objectContaining({ - subject: { type: 'user', id: 'user_123', issuer: 'https://auth.example.com' }, - }), - undefined, - ) - }) - it('maps repositories to github_app_installation authorization details', async () => { const resolve = resolveConnectToken('github/my-connector', { preset: 'issue-triage', diff --git a/packages/github-tools/src/connect/types.ts b/packages/github-tools/src/connect/types.ts index d4584d5..59737c7 100644 --- a/packages/github-tools/src/connect/types.ts +++ b/packages/github-tools/src/connect/types.ts @@ -6,18 +6,9 @@ import type { EveGithubToolsOptions } from '../eve/types' /** * Token parameters for Vercel Connect GitHub connectors. - * `subject` defaults to `{ type: 'app' }` — the project's GitHub App - * installation, same as `connectGitHubAdapter`. + * `subject` is pinned to `{ type: 'app' }` by the SDK — same as `connectGitHubAdapter`. */ export type GithubConnectParams = Omit & { - /** - * Connect token subject. Defaults to `{ type: 'app' }` (the project's GitHub - * App installation — one identity shared by every caller). Pass - * `{ type: 'user', id }` to mint a token for that user's own GitHub - * connection instead, e.g. in multi-user apps where each user connects - * their account from an integrations panel. - */ - subject?: ConnectTokenParams['subject'] /** Restrict the token to specific repositories via GitHub authorization details. */ repositories?: string[] } diff --git a/packages/github-tools/src/eve-runtime.ts b/packages/github-tools/src/eve-runtime.ts index 8936678..5e4364b 100644 --- a/packages/github-tools/src/eve-runtime.ts +++ b/packages/github-tools/src/eve-runtime.ts @@ -16,7 +16,6 @@ export { hasGithubEveToolModelOutput, } from './eve/build' export { mapEveApprovalValue, resolveEveApproval, resolveEveToolApproval, isEveApprovalDisabled } from './eve/approval' -export { resolveGithubToken } from './core/token' export type { EveApprovalConfig, EveApprovalValue,