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
8 changes: 4 additions & 4 deletions examples/alchemy-cloudflare/src/DiscussionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
type GitHubApi,
type GitHubApiError,
type GitHubIssue,
type GitHubIssueComment,
type GitHubIssueCommentRecord,
type GitHubParticipant,
type GitHubPullRequest,
type GitHubReview,
type GitHubReviewComment,
type GitHubReviewCommentRecord,
} from '@humanlayer/channels-github'
import { Array as Arr, Effect, Match, Option, Predicate, Schema } from 'effect'

Expand Down Expand Up @@ -55,7 +55,7 @@ const clip = (body: string) =>
? body
: `${body.slice(0, MAX_BODY_LENGTH)}\n[Cut off at ${MAX_BODY_LENGTH} of ${body.length} characters.]`

const commentEntry = (comment: GitHubIssueComment): Entry => ({
const commentEntry = (comment: GitHubIssueCommentRecord): Entry => ({
id: comment.ref.id,
text: `${author(comment.author)} commented (comment ${comment.ref.id}):\n${clip(comment.body)}`,
})
Expand All @@ -65,7 +65,7 @@ const reviewEntry = (review: GitHubReview): Entry => ({
text: `${author(review.author)} reviewed (${review.state.replace('_', ' ')})${review.body ? `:\n${clip(review.body)}` : '.'}`,
})

const reviewCommentEntry = (comment: GitHubReviewComment): Entry => {
const reviewCommentEntry = (comment: GitHubReviewCommentRecord): Entry => {
const line = comment.line ?? comment.startLine
const where = line === null || line === undefined ? comment.path : `${comment.path}:${line}`
const thread = comment.inReplyToId ?? comment.ref.id
Expand Down
8 changes: 4 additions & 4 deletions examples/alchemy-cloudflare/src/GitHubTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
GitHubApi,
GitHubRepositoryRef,
type GitHubIssue,
type GitHubIssueComment,
type GitHubIssueCommentRecord,
type GitHubPullRequest,
type GitHubReviewComment,
type GitHubReviewCommentRecord,
} from '@humanlayer/channels-github'
import { defineTool, ToolResultFailure, ToolResultText, type FoldTool } from '@humanlayer/fold-core'
import { Data, Effect, Match, Predicate, Schema } from 'effect'
Expand All @@ -36,15 +36,15 @@ const POST_COMMENT_DESCRIPTION =
'Post a GitHub Markdown comment. Your final answer is posted for you when you finish, so use this only for something else, such as a reply in a review thread or an update while you work.'

/** A comment as the agent sees it: what it needs to read it, link to it, and answer it. */
const commentSummary = (comment: GitHubIssueComment) => ({
const commentSummary = (comment: GitHubIssueCommentRecord) => ({
id: comment.ref.id,
author: comment.author?.login ?? null,
url: comment.url,
body: comment.body,
})

/** A line comment as the agent sees it, with the thread to reply in: GitHub threads replies under the first comment. */
const reviewCommentSummary = (comment: GitHubReviewComment) => ({
const reviewCommentSummary = (comment: GitHubReviewCommentRecord) => ({
id: comment.ref.id,
thread: comment.inReplyToId ?? comment.ref.id,
author: comment.author?.login ?? null,
Expand Down
8 changes: 4 additions & 4 deletions examples/alchemy-cloudflare/src/GithubBot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ import {
type GitHubApiError,
type GitHubCallbackHandlers,
type GitHubCheckConclusion,
type GitHubIssueComment,
type GitHubIssueCommentRecord,
type GitHubIssueCreated,
type GitHubMentioned,
type GitHubParticipant,
type GitHubPrCheckCompleted,
type GitHubPrCreated,
type GitHubPrEvent,
type GitHubRepositoryRef,
type GitHubReviewComment,
type GitHubReviewCommentRecord,
} from '@humanlayer/channels-github'
import type { RuntimeContext } from 'alchemy/RuntimeContext'
import { Array as Arr, Config, Effect, Match, Predicate, Schema } from 'effect'
Expand Down Expand Up @@ -114,14 +114,14 @@ interface RequestItem {

const authorOf = (author: GitHubParticipant | null) => (author === null ? 'a deleted user' : `@${author.login}`)

const commentItem = (comment: GitHubIssueComment): RequestItem => ({
const commentItem = (comment: GitHubIssueCommentRecord): RequestItem => ({
comment: RequestComment.cases.Comment.make({ id: comment.ref.id }),
body: comment.body,
text: `${authorOf(comment.author)} commented (comment ${comment.ref.id}):\n${comment.body}`,
})

/** A line comment with where it is in the diff and the thread to reply in: GitHub threads replies under the first. */
const lineCommentItem = (comment: GitHubReviewComment): RequestItem => {
const lineCommentItem = (comment: GitHubReviewCommentRecord): RequestItem => {
const line = comment.line ?? comment.startLine
const where = Predicate.isNullish(line) ? comment.path : `${comment.path}:${line}`
const thread = comment.inReplyToId ?? comment.ref.id
Expand Down
8 changes: 4 additions & 4 deletions examples/alchemy-cloudflare/test/check-tools.vi.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { describe, it } from '@effect/vitest'
import {
GitHubActionsJob,
GitHubActionsJobRecord,
GitHubApi,
GitHubCheckRun,
GitHubCheckRunRecord,
GitHubCheckRunInfo,
GitHubEventId,
GitHubId,
Expand Down Expand Up @@ -86,7 +86,7 @@ const apiWithLog = (log: string) =>
baseSha: 'def',
}),
),
listCheckRunsForRef: () => Effect.succeed(infos.map((info) => GitHubCheckRun.make({ ref: info.ref }))),
listCheckRunsForRef: () => Effect.succeed(infos.map((info) => GitHubCheckRunRecord.make({ ref: info.ref }))),
fetchCheckRun: ({ checkRun }) => Effect.succeed(infos.find((info) => info.ref.id === checkRun.id) ?? infos[0]!),
listCheckRunAnnotations: () =>
Effect.succeed([
Expand All @@ -104,7 +104,7 @@ const apiWithLog = (log: string) =>
},
]),
resolveActionsJob: () =>
Effect.succeed(GitHubActionsJob.make({ ref: { ...repository, id: GitHubId.make(900) } })),
Effect.succeed(GitHubActionsJobRecord.make({ ref: { ...repository, id: GitHubId.make(900) } })),
fetchActionsJob: () =>
Effect.succeed({
ref: { ...repository, id: GitHubId.make(900) },
Expand Down
10 changes: 5 additions & 5 deletions examples/alchemy-cloudflare/test/discussion-context.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import {
GitHubDiscussionRef,
GitHubId,
GitHubIssue,
GitHubIssueComment,
GitHubIssueCommentRecord,
GitHubIssueInfo,
GitHubIssueRef,
GitHubParticipant,
GitHubPullRequest,
GitHubPullRequestInfo,
GitHubPullRequestRef,
GitHubReviewComment,
GitHubReviewCommentRecord,
} from '@humanlayer/channels-github'
import { Effect, Layer, Option } from 'effect'

Expand All @@ -37,7 +37,7 @@ const bob = GitHubParticipant.make({ id: GitHubId.make(2), login: 'bob', type: '
const bot = GitHubParticipant.make({ id: GitHubId.make(BOT_USER_ID), login: 'agent[bot]', type: 'Bot' })

const comment = (discussion: GitHubDiscussionRef, id: number, author: GitHubParticipant, body: string) =>
GitHubIssueComment.make({
GitHubIssueCommentRecord.make({
ref: { discussion, id: GitHubId.make(id) },
body,
url: `https://github.com/humanlayer/effect-channels/issues/42#issuecomment-${id}`,
Expand Down Expand Up @@ -96,7 +96,7 @@ const api = Layer.mock(GitHubApi, {
]),
listPullRequestReviewComments: () =>
Effect.succeed([
GitHubReviewComment.make({
GitHubReviewCommentRecord.make({
ref: { pullRequest: pullRequest.ref, id: GitHubId.make(40) },
nodeId: 'review-comment-40',
body: 'This can be null.',
Expand All @@ -109,7 +109,7 @@ const api = Layer.mock(GitHubApi, {
diffHunk: '@@ -1 +1 @@',
line: 12,
}),
GitHubReviewComment.make({
GitHubReviewCommentRecord.make({
ref: { pullRequest: pullRequest.ref, id: GitHubId.make(41) },
nodeId: 'review-comment-41',
body: '@agent fix this',
Expand Down
8 changes: 4 additions & 4 deletions examples/alchemy-cloudflare/test/github-tools.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
GitHubApi,
GitHubDiscussionRef,
GitHubId,
GitHubIssueComment,
GitHubIssueCommentRecord,
GitHubPullRequest,
GitHubPullRequestRef,
GitHubReviewComment,
GitHubReviewCommentRecord,
type GitHubReplyToReviewComment,
} from '@humanlayer/channels-github'
import { ToolResultText } from '@humanlayer/fold-core'
Expand All @@ -26,7 +26,7 @@ const pullRequest = GitHubPullRequest.make({
})

const lineComment = (id: number, inReplyToId?: number) =>
GitHubReviewComment.make({
GitHubReviewCommentRecord.make({
ref: { pullRequest: pullRequest.ref, id: GitHubId.make(id) },
nodeId: `review-comment-${id}`,
body: 'This can be null.',
Expand Down Expand Up @@ -54,7 +54,7 @@ const post = (request: { readonly markdown: string; readonly replyTo?: number })
postPullRequestComment: ({ content }) =>
Ref.update(posts, (all) => [...all, content.markdown]).pipe(
Effect.as(
GitHubIssueComment.make({
GitHubIssueCommentRecord.make({
ref: {
discussion: GitHubDiscussionRef.cases.PullRequest.make({
ref: pullRequest.ref,
Expand Down
11 changes: 6 additions & 5 deletions examples/alchemy-cloudflare/test/mention-access.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
GitHubIssue,
GitHubIssueComment,
GitHubIssueCommentCreated,
GitHubIssueCommentRecord,
GitHubIssueMentioned,
GitHubIssueOpened,
GitHubIssueRef,
Expand Down Expand Up @@ -59,7 +60,7 @@ const discussionComment = (
body = maintainerOnlyNotice,
id = 500,
) =>
GitHubIssueComment.make({
GitHubIssueCommentRecord.make({
ref: { discussion, id: GitHubId.make(id) },
body,
url: 'https://github.com/humanlayer/effect-channels/issues/42#issuecomment-500',
Expand All @@ -74,13 +75,13 @@ const fixtures = (actor = person(1)) => {
issue,
actor,
eventId,
comment: discussionComment(issueDiscussion, actor, '@agent help', actor.id),
comment: GitHubIssueComment.make(discussionComment(issueDiscussion, actor, '@agent help', actor.id)),
})
const prComment = GitHubPrCommentCreated.make({
pullRequest,
actor,
eventId,
comment: discussionComment(prDiscussion, actor, '@agent help', actor.id),
comment: GitHubIssueComment.make(discussionComment(prDiscussion, actor, '@agent help', actor.id)),
})
const reviewComment = GitHubPrReviewCommentCreated.make({
pullRequest,
Expand Down Expand Up @@ -150,13 +151,13 @@ interface ApiState {
readonly reactions: ReadonlyArray<GitHubReactionRequest>
readonly lists: ReadonlyArray<GitHubDiscussionRef>
readonly posts: ReadonlyArray<{ readonly discussion: GitHubDiscussionRef; readonly content: GitHubContent }>
readonly comments: ReadonlyArray<GitHubIssueComment>
readonly comments: ReadonlyArray<GitHubIssueCommentRecord>
}

const makeApi = (
access: Effect.Effect<GitHubAccessLevel, GitHubApiError>,
options: {
readonly comments?: ReadonlyArray<GitHubIssueComment>
readonly comments?: ReadonlyArray<GitHubIssueCommentRecord>
readonly lookupFailure?: GitHubApiError
readonly ambiguousPostFailure?: GitHubApiError
} = {},
Expand Down
11 changes: 11 additions & 0 deletions packages/github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ Add `github` to the providers passed to `Channels.make` or the host-specific wra

Callbacks provide `GitHubIssue` and `GitHubPullRequest` resources. Their methods require `GitHubApi`, which the application provides, usually with `GitHubApiLive`; the bot builds no GitHub client of its own. List methods follow GitHub pagination automatically with 100 items per request.

`GitHubApi` itself returns plain records with no methods: `GitHubIssueCommentRecord`, `GitHubReviewCommentRecord`, `GitHubCheckRunRecord`, `GitHubActionsJobRecord`, and lists of them. Resource methods wrap these in the `GitHubIssueComment`, `GitHubReviewComment`, `GitHubCheckRun`, and `GitHubActionsJob` objects, so `issue.postComment(...)` returns a comment with `addReaction()`. Code that calls `GitHubApi` directly and needs those methods wraps the record, as in `GitHubIssueComment.make(record)`, or `commentFromRecord(record)` for an `updateComment` result.

```ts
const comment = yield * issue.postComment(content) // GitHubIssueComment
yield * comment.addReaction('eyes')

const api = yield * GitHubApi
const record = yield * api.postIssueComment({ issue: issue.ref, content }) // GitHubIssueCommentRecord
yield * GitHubIssueComment.make(record).addReaction('eyes')
```

### Issues

In addition to `fetchInfo()`, comment operations, and subscription operations, an issue supports:
Expand Down
52 changes: 28 additions & 24 deletions packages/github/src/GitHubApiContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,35 @@ import {
type GitHubAccessLevel,
GitHubActionsJobRef,
type GitHubActionsJobInfo,
type GitHubActionsJobRecord,
type GitHubCheckAnnotations,
GitHubCheckRunRef,
type GitHubCheckRunInfo,
type GitHubCheckRunRecords,
GitHubCommentRef,
type GitHubCommentRecord,
type GitHubCommits,
GitHubContent,
GitHubIssueCommentRef,
type GitHubIssueCommentRecord,
type GitHubIssueCommentRecords,
type GitHubIssueInfo,
type GitHubLabelsResult,
GitHubIssueRef,
GitHubMergeMethod,
type GitHubMergeResult,
type GitHubPullRequestFiles,
type GitHubPullRequestInfo,
GitHubPullRequestRef,
GitHubReaction,
GitHubReactionTarget,
GitHubReviewCommentLocation,
GitHubRepositoryRef,
GitHubReviewCommentRef,
type GitHubReviewCommentRecord,
type GitHubReviewCommentRecords,
type GitHubReviews,
} from './GitHubModels'
import type {
GitHubActionsJob,
GitHubCheckAnnotations,
GitHubCheckRuns,
GitHubComment,
GitHubCommits,
GitHubIssueComment,
GitHubIssueComments,
GitHubLabelsResult,
GitHubPullRequestFiles,
GitHubReviewComment,
GitHubReviewComments,
GitHubReviews,
} from './GitHubResources'

export const GitHubIssueRequest = Schema.Struct({ issue: GitHubIssueRef })
export interface GitHubIssueRequest extends Schema.Schema.Type<typeof GitHubIssueRequest> {}
Expand Down Expand Up @@ -269,14 +267,14 @@ export class GitHubApiError extends Schema.TaggedError<GitHubApiError>()('GitHub
export type GitHubApiOperations = {
readonly fetchIssue: (input: GitHubIssueRequest) => Effect.Effect<GitHubIssueInfo, GitHubApiError>
readonly fetchPullRequest: (input: GitHubPullRequestRequest) => Effect.Effect<GitHubPullRequestInfo, GitHubApiError>
readonly listIssueComments: (input: GitHubIssueRequest) => Effect.Effect<GitHubIssueComments, GitHubApiError>
readonly listIssueComments: (input: GitHubIssueRequest) => Effect.Effect<GitHubIssueCommentRecords, GitHubApiError>
readonly listPullRequestComments: (
input: GitHubPullRequestRequest,
) => Effect.Effect<GitHubIssueComments, GitHubApiError>
) => Effect.Effect<GitHubIssueCommentRecords, GitHubApiError>
readonly listPullRequestReviews: (input: GitHubPullRequestRequest) => Effect.Effect<GitHubReviews, GitHubApiError>
readonly listPullRequestReviewComments: (
input: GitHubPullRequestRequest,
) => Effect.Effect<GitHubReviewComments, GitHubApiError>
) => Effect.Effect<GitHubReviewCommentRecords, GitHubApiError>
readonly listPullRequestFiles: (
input: GitHubPullRequestRequest,
) => Effect.Effect<GitHubPullRequestFiles, GitHubApiError>
Expand All @@ -301,17 +299,19 @@ export type GitHubApiOperations = {
) => Effect.Effect<GitHubLabelsResult, GitHubApiError>
readonly removeAllIssueLabels: (input: GitHubIssueRequest) => Effect.Effect<void, GitHubApiError>
readonly removeAllPullRequestLabels: (input: GitHubPullRequestRequest) => Effect.Effect<void, GitHubApiError>
readonly postIssueComment: (input: GitHubPostIssueComment) => Effect.Effect<GitHubIssueComment, GitHubApiError>
readonly postIssueComment: (
input: GitHubPostIssueComment,
) => Effect.Effect<GitHubIssueCommentRecord, GitHubApiError>
readonly postPullRequestComment: (
input: GitHubPostPullRequestComment,
) => Effect.Effect<GitHubIssueComment, GitHubApiError>
) => Effect.Effect<GitHubIssueCommentRecord, GitHubApiError>
readonly postPullRequestReviewComment: (
input: GitHubPostPullRequestReviewComment,
) => Effect.Effect<GitHubReviewComment, GitHubApiError>
) => Effect.Effect<GitHubReviewCommentRecord, GitHubApiError>
readonly replyToReviewComment: (
input: GitHubReplyToReviewComment,
) => Effect.Effect<GitHubReviewComment, GitHubApiError>
readonly updateComment: (input: GitHubUpdateComment) => Effect.Effect<GitHubComment, GitHubApiError>
) => Effect.Effect<GitHubReviewCommentRecord, GitHubApiError>
readonly updateComment: (input: GitHubUpdateComment) => Effect.Effect<GitHubCommentRecord, GitHubApiError>
readonly deleteComment: (input: GitHubDeleteComment) => Effect.Effect<void, GitHubApiError>
/** Add the bot's reaction to a comment, issue, or pull request. Already present counts as added. */
readonly addReaction: (input: GitHubReactionRequest) => Effect.Effect<void, GitHubApiError>
Expand All @@ -329,12 +329,16 @@ export type GitHubApiOperations = {
input: GitHubListPullRequestsForBranch,
) => Effect.Effect<ReadonlyArray<GitHubPullRequestInfo>, GitHubApiError>
readonly createPullRequest: (input: GitHubCreatePullRequest) => Effect.Effect<GitHubPullRequestInfo, GitHubApiError>
readonly listCheckRunsForRef: (input: GitHubListCheckRunsForRef) => Effect.Effect<GitHubCheckRuns, GitHubApiError>
readonly listCheckRunsForRef: (
input: GitHubListCheckRunsForRef,
) => Effect.Effect<GitHubCheckRunRecords, GitHubApiError>
readonly fetchCheckRun: (input: GitHubCheckRunRequest) => Effect.Effect<GitHubCheckRunInfo, GitHubApiError>
readonly listCheckRunAnnotations: (
input: GitHubCheckRunRequest,
) => Effect.Effect<GitHubCheckAnnotations, GitHubApiError>
readonly resolveActionsJob: (input: GitHubCheckRunRequest) => Effect.Effect<GitHubActionsJob | null, GitHubApiError>
readonly resolveActionsJob: (
input: GitHubCheckRunRequest,
) => Effect.Effect<GitHubActionsJobRecord | null, GitHubApiError>
readonly fetchActionsJob: (input: GitHubActionsJobRequest) => Effect.Effect<GitHubActionsJobInfo, GitHubApiError>
readonly downloadActionsJobLog: (input: GitHubActionsJobRequest) => Effect.Effect<string, GitHubApiError>
/** A user's access to a repository. An account that is not a GitHub user fails with `not_found`. */
Expand Down
4 changes: 2 additions & 2 deletions packages/github/src/GitHubEventProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ import {
GitHubReviewThread,
GitHubTeam,
} from './GitHubModels'
import { GitHubIssue, GitHubIssueComment, GitHubPullRequest } from './GitHubResources'
import { GitHubIssue, GitHubIssueComment, GitHubPullRequest, GitHubReviewComment } from './GitHubResources'
import type {
GitHubIssueCommentWebhook,
GitHubIssuesWebhook,
Expand Down Expand Up @@ -505,7 +505,7 @@ const normalizeWebhook = (
const ref = pullRequestRef(payload)
const pullRequest = GitHubPullRequest.make({ ref, mailboxKey })
const actor = participant(payload.sender)
const comment = reviewComment(ref, payload.comment)
const comment = GitHubReviewComment.make(reviewComment(ref, payload.comment))
const event = Match.value(payload.action).pipe(
Match.when('created', () =>
GitHubPrReviewCommentCreated.make({ eventId, pullRequest, actor, comment }),
Expand Down
Loading
Loading