@finalai/final-agent-provider 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/HOSTING.md +3 -3
  2. package/README.md +7 -91
  3. package/dist/modules/channel-factories.d.mts +5 -6
  4. package/dist/modules/channel-factories.mjs +7 -11
  5. package/dist/modules/channel-factories.mjs.map +3 -3
  6. package/dist/modules/channels/inbound.mjs +2 -2
  7. package/dist/modules/channels/inbound.mjs.map +2 -2
  8. package/dist/modules/channels/index.d.mts +0 -7
  9. package/dist/modules/channels/index.mjs +0 -7
  10. package/dist/modules/channels/index.mjs.map +2 -2
  11. package/dist/modules/channels/prompt.d.mts +2 -2
  12. package/dist/modules/channels/prompt.mjs +3 -3
  13. package/dist/modules/channels/prompt.mjs.map +2 -2
  14. package/dist/modules/channels/routing.d.mts +1 -1
  15. package/dist/modules/channels/routing.mjs +1 -1
  16. package/dist/modules/channels/routing.mjs.map +1 -1
  17. package/package.json +4 -40
  18. package/dist/modules/channels/channel-utils.d.mts +0 -132
  19. package/dist/modules/channels/channel-utils.mjs +0 -445
  20. package/dist/modules/channels/channel-utils.mjs.map +0 -7
  21. package/dist/modules/channels/discord-dispatch.d.mts +0 -17
  22. package/dist/modules/channels/discord-dispatch.mjs +0 -139
  23. package/dist/modules/channels/discord-dispatch.mjs.map +0 -7
  24. package/dist/modules/channels/discord-gateway.d.mts +0 -19
  25. package/dist/modules/channels/discord-gateway.mjs +0 -233
  26. package/dist/modules/channels/discord-gateway.mjs.map +0 -7
  27. package/dist/modules/channels/discord.d.mts +0 -9
  28. package/dist/modules/channels/discord.mjs +0 -904
  29. package/dist/modules/channels/discord.mjs.map +0 -7
  30. package/dist/modules/channels/github.d.mts +0 -5
  31. package/dist/modules/channels/github.mjs +0 -720
  32. package/dist/modules/channels/github.mjs.map +0 -7
  33. package/dist/modules/channels/linear.d.mts +0 -4
  34. package/dist/modules/channels/linear.mjs +0 -621
  35. package/dist/modules/channels/linear.mjs.map +0 -7
  36. package/dist/modules/channels/phone.d.mts +0 -8
  37. package/dist/modules/channels/phone.mjs +0 -382
  38. package/dist/modules/channels/phone.mjs.map +0 -7
  39. package/dist/modules/channels/reply-receipts.d.mts +0 -31
  40. package/dist/modules/channels/reply-receipts.mjs +0 -63
  41. package/dist/modules/channels/reply-receipts.mjs.map +0 -7
  42. package/dist/modules/channels/slack.d.mts +0 -7
  43. package/dist/modules/channels/slack.mjs +0 -1134
  44. package/dist/modules/channels/slack.mjs.map +0 -7
  45. package/dist/modules/channels/voice-recording.d.mts +0 -7
  46. package/dist/modules/channels/voice-recording.mjs +0 -49
  47. package/dist/modules/channels/voice-recording.mjs.map +0 -7
  48. package/dist/modules/channels/voice.d.mts +0 -33
  49. package/dist/modules/channels/voice.mjs +0 -851
  50. package/dist/modules/channels/voice.mjs.map +0 -7
  51. package/dist/modules/channels/webhook.d.mts +0 -5
  52. package/dist/modules/channels/webhook.mjs +0 -76
  53. package/dist/modules/channels/webhook.mjs.map +0 -7
@@ -1,720 +0,0 @@
1
- import {
2
- defineChannel,
3
- packageRef,
4
- jsonValueSchema
5
- } from "@finalai/agent-provider";
6
- import * as z from "zod";
7
- import {
8
- deletedMirrorReceiptSchema,
9
- mirrorReceiptKey,
10
- settleMirrorReaction,
11
- shareReplyReceipt
12
- } from "./reply-receipts.mjs";
13
- import {
14
- channelBaseUrl,
15
- providerPlaceKey,
16
- confirmOAuth,
17
- deliveryRetention,
18
- externalKey,
19
- inTenant,
20
- parseJson,
21
- personFor,
22
- receivePersonMessage,
23
- refString,
24
- responseJson,
25
- startOAuth,
26
- stateKey,
27
- takeOAuth,
28
- textOf,
29
- completionText,
30
- verifyHmac
31
- } from "./channel-utils.mjs";
32
- const configSchema = z.object({
33
- baseUrl: z.string().url().optional(),
34
- mention: z.string().default("final")
35
- });
36
- const missingTarget = /* @__PURE__ */ Symbol("missing GitHub target");
37
- const api = async (ctx, path, body, method) => {
38
- const app = await ctx.credentials.app({
39
- tenant: refString(ctx.origin?.ref, "tenant") ?? ctx.origin?.place.tenant
40
- });
41
- const response = await ctx.credentials.fetch(`https://api.github.com${path}`, {
42
- method: method ?? (body == null ? "GET" : "POST"),
43
- headers: {
44
- authorization: `Bearer ${app.accessToken}`,
45
- accept: "application/vnd.github+json",
46
- "x-github-api-version": "2022-11-28",
47
- "content-type": "application/json"
48
- },
49
- ...body == null ? {} : { body: JSON.stringify(body) }
50
- });
51
- if (response.status === 404 && path.includes("/collaborators/")) return { permission: "none" };
52
- if (response.status === 404 && method === "DELETE") {
53
- await response.text();
54
- return missingTarget;
55
- }
56
- if (response.status === 403) {
57
- const text = await response.text();
58
- throw new Error(`Channel request failed (403): ${text.slice(0, 500)}`, {
59
- cause: {
60
- status: 403,
61
- rateLimited: response.headers.get("x-ratelimit-remaining") === "0" || response.headers.has("retry-after") || /rate limit/i.test(text)
62
- }
63
- });
64
- }
65
- return responseJson(response);
66
- };
67
- const classifyMirrorReaction = async (args) => {
68
- const failure = (cause) => z.object({ status: z.number(), rateLimited: z.boolean().optional() }).safeParse(cause instanceof Error ? cause.cause : null);
69
- const error = failure(args.cause);
70
- if (!error.success) return "retry";
71
- if (error.data.status === 403) return error.data.rateLimited === false ? "unavailable" : "retry";
72
- if (error.data.status !== 404) return "retry";
73
- try {
74
- await api(args.ctx, `/repos/${args.repo}`);
75
- } catch (cause) {
76
- const denied = failure(cause);
77
- return denied.success && (denied.data.status === 404 || denied.data.status === 403 && denied.data.rateLimited === false) ? "unavailable" : "retry";
78
- }
79
- try {
80
- await api(
81
- args.ctx,
82
- `/repos/${args.repo}/${args.ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${args.target}`
83
- );
84
- return "unavailable";
85
- } catch (cause) {
86
- const missing = failure(cause);
87
- return missing.success && missing.data.status === 404 ? "missing" : missing.success && missing.data.status === 403 && missing.data.rateLimited === false ? "unavailable" : "retry";
88
- }
89
- };
90
- const commentText = (body) => body.length > 6e4 ? `${body.slice(0, 59500)}
91
-
92
- Continue in Final \u2192` : body;
93
- const commentSchema = z.object({
94
- action: z.string(),
95
- sender: z.object({ id: z.number(), login: z.string(), type: z.string().optional() }),
96
- repository: z.object({
97
- full_name: z.string(),
98
- owner: z.object({ login: z.string() }),
99
- name: z.string()
100
- }),
101
- comment: z.object({
102
- id: z.number(),
103
- body: z.string(),
104
- html_url: z.string(),
105
- user: z.object({ id: z.number(), login: z.string() }),
106
- in_reply_to_id: z.number().optional()
107
- }),
108
- issue: z.object({
109
- number: z.number(),
110
- title: z.string(),
111
- pull_request: z.object({ url: z.string() }).optional()
112
- }).optional(),
113
- pull_request: z.object({ number: z.number(), title: z.string() }).optional()
114
- });
115
- const visibleMentionText = (text) => {
116
- let fence = null;
117
- const visible = [];
118
- for (const line of text.replace(/<!--[\s\S]*?(?:-->|$)/g, "").split("\n")) {
119
- const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1];
120
- if (marker != null) {
121
- if (fence == null) fence = { marker: marker[0] ?? "`", length: marker.length };
122
- else if (marker[0] === fence.marker && marker.length >= fence.length) fence = null;
123
- continue;
124
- }
125
- if (fence != null || /^\s*>|^(?: {4}|\t)/.test(line)) continue;
126
- visible.push(line.replace(/(`+).*?\1/g, ""));
127
- }
128
- return visible.join("\n");
129
- };
130
- const reactionReceiptSchema = z.object({
131
- id: z.number(),
132
- principals: z.array(z.string()),
133
- overflow: z.boolean(),
134
- owned: z.boolean().default(false)
135
- });
136
- const replyKey = (ctx, replyOf) => stateKey(
137
- "github-reply",
138
- replyOf?.externalKey ?? refString(ctx.origin.ref, "externalKey") ?? ctx.origin.place.id,
139
- replyOf?.turnId ?? ctx.turn.id
140
- );
141
- const comment = async (ctx, body, key = replyKey(ctx)) => {
142
- const resource = ctx.origin.place.kind === "review-thread" ? ctx.origin.place.parent : ctx.origin.place;
143
- const repo = refString(ctx.origin.ref, "repository") ?? resource?.id.split("#")[0];
144
- const issue = refString(ctx.origin.ref, "number") ?? resource?.id.split("#")[1];
145
- if (repo == null || issue == null || body.length === 0) return;
146
- const receipt = await ctx.store.get(key);
147
- if (deletedMirrorReceiptSchema.safeParse(receipt).success) return;
148
- const saved = z.object({ id: z.number() }).safeParse(receipt);
149
- if (saved.success) return;
150
- const result = z.object({ id: z.number() }).parse(
151
- await api(
152
- ctx,
153
- ctx.origin.place.kind === "review-thread" ? `/repos/${repo}/pulls/${issue}/comments/${refString(ctx.origin.ref, "commentId") ?? ctx.origin.place.id.split(":").at(-1)}/replies` : `/repos/${repo}/issues/${issue}/comments`,
154
- {
155
- body: commentText(body)
156
- }
157
- )
158
- );
159
- await ctx.store.set(key, { id: result.id }, deliveryRetention);
160
- };
161
- const githubReaction = (emoji) => {
162
- const reactions = {
163
- "\u{1F44D}": "+1",
164
- "\u{1F44E}": "-1",
165
- "\u{1F604}": "laugh",
166
- "\u{1F389}": "hooray",
167
- "\u{1F615}": "confused",
168
- "\u2764\uFE0F": "heart",
169
- "\u2764": "heart",
170
- "\u{1F680}": "rocket",
171
- "\u{1F440}": "eyes"
172
- };
173
- return reactions[emoji] ?? (["+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes"].includes(emoji) ? emoji : null);
174
- };
175
- const reactionTarget = async (ctx, data) => {
176
- if (data.replyOf != null) {
177
- const saved = z.object({ id: z.number() }).safeParse(await ctx.store.get(replyKey(ctx, data.replyOf)));
178
- return saved.success ? saved.data.id : null;
179
- }
180
- if (data.externalKey.startsWith("final:")) {
181
- const key2 = mirrorReceiptKey({
182
- provider: "github",
183
- place: ctx.origin.place,
184
- externalKey: data.externalKey
185
- });
186
- if (key2 == null) return null;
187
- const saved = z.object({ id: z.number() }).safeParse(await ctx.store.get(key2));
188
- return saved.success ? saved.data.id : null;
189
- }
190
- const key = z.tuple([z.string().nullable(), z.string(), z.string()]).safeParse(parseJson(data.externalKey));
191
- if (!key.success || !/^\d+$/.test(key.data[2])) return null;
192
- return Number(key.data[2]);
193
- };
194
- const githubChannel = (options = {}) => {
195
- const config = configSchema.parse(options);
196
- return defineChannel({
197
- name: "github",
198
- config,
199
- installed: packageRef("@finalai/final-agent-provider/channels/github"),
200
- credentials: { app: "github" },
201
- identity: {
202
- kind: "redirect",
203
- start: async (args, ctx) => {
204
- const app = await ctx.credentials.appInfo();
205
- return startOAuth(
206
- {
207
- ...args,
208
- authorizeUrl: "https://github.com/login/oauth/authorize",
209
- clientId: app.clientId,
210
- redirectUri: `${channelBaseUrl(config)}/b/${ctx.binding.id}/oauth/callback`,
211
- scope: "read:user"
212
- },
213
- ctx
214
- );
215
- }
216
- },
217
- places: { kinds: ["pr", "issue", "review-thread"] },
218
- routes: [
219
- {
220
- method: "GET",
221
- path: "/oauth/callback",
222
- handler: async (ctx) => {
223
- const saved = await takeOAuth(ctx.request, ctx);
224
- if (saved == null)
225
- return new Response("Invalid or expired link request", { status: 400 });
226
- const app = await ctx.credentials.appInfo();
227
- const token = z.object({ access_token: z.string() }).parse(
228
- await responseJson(
229
- await ctx.credentials.fetch("https://github.com/login/oauth/access_token", {
230
- method: "POST",
231
- headers: { accept: "application/json" },
232
- body: new URLSearchParams({
233
- client_id: app.clientId,
234
- client_secret: app.clientSecret,
235
- code: saved.code,
236
- redirect_uri: saved.redirectUri
237
- })
238
- })
239
- )
240
- );
241
- const user = z.object({ id: z.number(), login: z.string() }).parse(
242
- await responseJson(
243
- await ctx.credentials.fetch("https://api.github.com/user", {
244
- headers: {
245
- authorization: `Bearer ${token.access_token}`,
246
- accept: "application/vnd.github+json"
247
- }
248
- })
249
- )
250
- );
251
- return confirmOAuth(
252
- {
253
- ticket: saved.ticket,
254
- expected: saved.principal,
255
- actual: {
256
- id: String(user.id),
257
- displayName: user.login,
258
- ...saved.principal.tenant == null ? {} : { tenant: saved.principal.tenant }
259
- }
260
- },
261
- ctx
262
- );
263
- }
264
- },
265
- {
266
- method: "POST",
267
- path: "/webhook",
268
- handler: async (ctx) => {
269
- const body = await ctx.request.text();
270
- const app = await ctx.credentials.appInfo();
271
- const signature = ctx.request.headers.get("x-hub-signature-256");
272
- if (app.signingSecret == null || signature == null || !signature.startsWith("sha256=") || !await verifyHmac({ secret: app.signingSecret, body, signature: signature.slice(7) }))
273
- return new Response(null, { status: 401 });
274
- const event = ctx.request.headers.get("x-github-event");
275
- if (event !== "issue_comment" && event !== "pull_request_review_comment" && event !== "pull_request" && event !== "pull_request_review" && event !== "pull_request_review_thread" && event !== "issues" && event !== "installation")
276
- return new Response(null, { status: 204 });
277
- const result = await ctx.receive({
278
- event,
279
- body: jsonValueSchema.parse(parseJson(body)),
280
- delivery: ctx.request.headers.get("x-github-delivery")
281
- });
282
- return Response.json(result, { status: 202 });
283
- }
284
- }
285
- ],
286
- receive: async (input, baseContext) => {
287
- const installation = z.object({ body: z.object({ installation: z.object({ id: z.number() }).optional() }) }).safeParse(input);
288
- const ctx = installation.success && installation.data.body.installation != null ? inTenant(baseContext, String(installation.data.body.installation.id)) : baseContext;
289
- const wrapped = z.object({ event: z.string(), body: jsonValueSchema }).safeParse(input);
290
- if (!wrapped.success) return { ignored: true, reason: "invalid-event" };
291
- const lifecycle = z.object({ action: z.string() }).safeParse(wrapped.data.body);
292
- const tenant = installation.success && installation.data.body.installation != null ? String(installation.data.body.installation.id) : null;
293
- if (wrapped.data.event === "installation" && lifecycle.success && ["deleted", "suspend", "unsuspend"].includes(lifecycle.data.action)) {
294
- if (tenant == null) return { ignored: true, reason: "missing-tenant" };
295
- const key2 = stateKey("github-suspended", tenant);
296
- if (lifecycle.data.action === "deleted") {
297
- await ctx.people.revoke({ tenant, reason: "uninstalled" });
298
- await ctx.store.set(key2, null);
299
- } else await ctx.store.set(key2, lifecycle.data.action === "suspend" ? true : null);
300
- return { accepted: true };
301
- }
302
- if (tenant != null && await ctx.store.get(stateKey("github-suspended", tenant)) === true)
303
- return { ignored: true, reason: "installation-suspended" };
304
- if (wrapped.data.event !== "issue_comment" && wrapped.data.event !== "pull_request_review_comment") {
305
- const resource = z.object({
306
- action: z.string(),
307
- review: z.object({
308
- state: z.string(),
309
- body: z.string().nullish(),
310
- html_url: z.string().optional(),
311
- user: z.object({ login: z.string() }).optional()
312
- }).optional(),
313
- sender: z.object({ login: z.string() }).optional(),
314
- repository: z.object({ full_name: z.string() }),
315
- pull_request: z.object({
316
- number: z.number(),
317
- title: z.string(),
318
- state: z.string().optional(),
319
- html_url: z.string().optional()
320
- }).optional(),
321
- issue: z.object({
322
- number: z.number(),
323
- title: z.string(),
324
- state: z.string().optional(),
325
- html_url: z.string().optional()
326
- }).optional()
327
- }).safeParse(wrapped.data.body);
328
- if (!resource.success) return { ignored: true, reason: "unsupported-resource-event" };
329
- const item = resource.data.pull_request ?? resource.data.issue;
330
- if (item == null) return { ignored: true, reason: "no-resource" };
331
- const place2 = {
332
- kind: resource.data.pull_request == null ? "issue" : "pr",
333
- id: `${resource.data.repository.full_name.toLowerCase()}#${item.number}`,
334
- ...installation.success && installation.data.body.installation != null ? { tenant: String(installation.data.body.installation.id) } : {},
335
- title: item.title
336
- };
337
- const observers = await ctx.places.observers(place2);
338
- const delivery = z.object({ delivery: z.string().nullable().optional() }).parse(input).delivery;
339
- if (delivery == null) return { ignored: true, reason: "missing-delivery-id" };
340
- let accepted = false;
341
- let refused = null;
342
- for (const conversation of observers) {
343
- const result2 = await ctx.conversation(conversation).input({
344
- label: `GitHub ${place2.kind} ${resource.data.action}`,
345
- content: [
346
- {
347
- type: "text",
348
- text: resource.data.review == null ? `${item.title}: ${resource.data.action}${item.state == null ? "" : ` (${item.state})`}${item.html_url == null ? "" : ` ${item.html_url}`}` : `${item.title}: review ${resource.data.action} (${resource.data.review.state})${(resource.data.review.user?.login ?? resource.data.sender?.login) == null ? "" : ` by ${resource.data.review.user?.login ?? resource.data.sender?.login}`}${resource.data.review.html_url == null ? "" : ` ${resource.data.review.html_url}`}${resource.data.review.body == null ? "" : `
349
- ${resource.data.review.body}`}`
350
- }
351
- ],
352
- origin: { place: place2 },
353
- idempotencyKey: delivery
354
- });
355
- if ("refused" in result2) {
356
- refused ??= result2;
357
- ctx.log("GitHub observer input refused.", { reason: result2.refused });
358
- } else accepted = true;
359
- }
360
- if (!accepted && refused != null) return { accepted: false, refusal: refused };
361
- return observers.length === 0 ? { ignored: true, reason: "no-observers" } : { accepted: true };
362
- }
363
- const parsed = commentSchema.safeParse(wrapped.data.body);
364
- if (!parsed.success) return { ignored: true, reason: "not-a-comment" };
365
- const event = parsed.data;
366
- if (event.sender.type === "Bot") return { ignored: true, reason: "bot" };
367
- const target = event.issue ?? event.pull_request;
368
- if (target == null) return { ignored: true, reason: "no-issue" };
369
- const isPr = event.pull_request != null || event.issue?.pull_request != null;
370
- const parent = {
371
- kind: isPr ? "pr" : "issue",
372
- id: `${event.repository.full_name.toLowerCase()}#${target.number}`,
373
- ...installation.success && installation.data.body.installation != null ? { tenant: String(installation.data.body.installation.id) } : {},
374
- title: target.title
375
- };
376
- const place = wrapped.data.event === "pull_request_review_comment" ? {
377
- kind: "review-thread",
378
- id: `${event.repository.full_name.toLowerCase()}:${event.comment.in_reply_to_id ?? event.comment.id}`,
379
- ...parent.tenant == null ? {} : { tenant: parent.tenant },
380
- parent,
381
- title: target.title
382
- } : parent;
383
- const principal = {
384
- id: String(event.sender.id),
385
- displayName: event.sender.login,
386
- ...parent.tenant == null ? {} : { tenant: parent.tenant }
387
- };
388
- const key = externalKey(parent, String(event.comment.id));
389
- if (event.action !== "edited" && event.action !== "deleted" && (event.action !== "created" || !new RegExp(
390
- `(?:^|[^A-Za-z0-9_@/-])@${config.mention.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?=$|[^A-Za-z0-9_/-])`,
391
- "i"
392
- ).test(visibleMentionText(event.comment.body))))
393
- return { ignored: true, reason: "not-mentioned" };
394
- if ((event.action === "edited" || event.action === "deleted") && await ctx.store.get(stateKey("github-origin", key)) == null)
395
- return { ignored: true, reason: "unknown-origin" };
396
- const permission = z.object({
397
- permission: z.string(),
398
- user: z.object({
399
- permissions: z.object({
400
- push: z.boolean().optional(),
401
- maintain: z.boolean().optional(),
402
- admin: z.boolean().optional()
403
- }).optional()
404
- }).optional()
405
- }).safeParse(
406
- await api(
407
- ctx,
408
- `/repos/${event.repository.full_name.toLowerCase()}/collaborators/${encodeURIComponent(event.sender.login)}/permission`
409
- )
410
- );
411
- if (!permission.success || !["write", "maintain", "admin"].includes(permission.data.permission))
412
- return { ignored: true, reason: "not-a-collaborator" };
413
- if (event.action === "edited" || event.action === "deleted") {
414
- const by = await personFor(ctx, principal);
415
- if (typeof by !== "string") return { accepted: false, refusal: by };
416
- const route = await ctx.places.resolve(place);
417
- if (route.kind !== "connected") return { ignored: true, reason: "unconnected" };
418
- const result2 = event.action === "edited" ? await ctx.conversation(route.conversation).edit({
419
- externalKey: key,
420
- by,
421
- content: [{ type: "text", text: event.comment.body }]
422
- }) : await ctx.conversation(route.conversation).remove({ externalKey: key, by });
423
- return "refused" in result2 ? { accepted: false, refusal: result2 } : { accepted: true };
424
- }
425
- await api(
426
- ctx,
427
- `/repos/${event.repository.full_name.toLowerCase()}/${wrapped.data.event === "pull_request_review_comment" ? "pulls" : "issues"}/comments/${event.comment.id}/reactions`,
428
- { content: "eyes" }
429
- ).catch(() => ctx.log("GitHub acknowledgement unavailable."));
430
- const result = await receivePersonMessage({
431
- ctx,
432
- principal,
433
- place,
434
- text: event.comment.body,
435
- externalKey: key,
436
- ref: {
437
- externalKey: key,
438
- repository: event.repository.full_name.toLowerCase(),
439
- number: String(target.number),
440
- commentId: String(event.comment.in_reply_to_id ?? event.comment.id),
441
- requester: event.sender.login,
442
- tenant: installation.success && installation.data.body.installation != null ? String(installation.data.body.installation.id) : null,
443
- url: event.comment.html_url
444
- }
445
- });
446
- if ("accepted" in result && !result.accepted) {
447
- const noticeKey = stateKey("github-refusal", key);
448
- if (await ctx.store.get(noticeKey) == null) {
449
- const posted = z.object({ id: z.number() }).parse(
450
- await api(
451
- ctx,
452
- `/repos/${event.repository.full_name.toLowerCase()}/issues/${target.number}/comments`,
453
- {
454
- body: `@${event.sender.login} ${result.refusal.message} ${result.refusal.linkUrl ?? result.refusal.connectUrl ?? ""}`
455
- }
456
- )
457
- );
458
- await ctx.store.set(noticeKey, posted.id, deliveryRetention);
459
- }
460
- }
461
- if ("accepted" in result && result.accepted)
462
- await ctx.store.set(stateKey("github-origin", key), true, deliveryRetention);
463
- return result;
464
- },
465
- events: {
466
- "message.posted": async (data, ctx) => {
467
- await comment(
468
- ctx,
469
- `From ${data.author.displayName} in Final
470
-
471
- ${textOf(data.message.content)}`,
472
- stateKey("github-mirror", providerPlaceKey(ctx.origin.place), data.message.id)
473
- );
474
- },
475
- "turn.completed": async (data, ctx) => {
476
- const resource = ctx.origin.place.kind === "review-thread" ? ctx.origin.place.parent : ctx.origin.place;
477
- const repo = refString(ctx.origin.ref, "repository") ?? resource?.id.split("#")[0];
478
- const issue = refString(ctx.origin.ref, "number") ?? resource?.id.split("#")[1];
479
- const thread = ctx.origin.place.kind === "review-thread" ? refString(ctx.origin.ref, "commentId") ?? ctx.origin.place.id.split(":").at(-1) : null;
480
- await shareReplyReceipt({
481
- bindingId: ctx.binding.id,
482
- store: ctx.store,
483
- destination: stateKey(
484
- "github",
485
- ctx.origin.place.tenant ?? null,
486
- repo?.toLowerCase() ?? null,
487
- issue ?? null,
488
- thread ?? null
489
- ),
490
- messageIds: data.messages?.map((message) => message.id) ?? [],
491
- originKey: replyKey(ctx),
492
- send: (key) => comment(ctx, completionText(data), key)
493
- });
494
- },
495
- "decision.created": async (data, ctx) => {
496
- await comment(
497
- ctx,
498
- `Waiting on @${refString(ctx.origin.ref, "requester") ?? "the requester"} in Final.`,
499
- stateKey("github-decision", providerPlaceKey(ctx.origin.place), data.decisionId)
500
- );
501
- },
502
- "decision.resolved": async (data, ctx) => {
503
- const repo = refString(ctx.origin.ref, "repository");
504
- const saved = z.object({ id: z.number() }).safeParse(
505
- await ctx.store.get(
506
- stateKey("github-decision", providerPlaceKey(ctx.origin.place), data.decisionId)
507
- )
508
- );
509
- if (repo != null && saved.success)
510
- await api(
511
- ctx,
512
- `/repos/${repo}/${ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${saved.data.id}`,
513
- { body: "Decision answered in Final." },
514
- "PATCH"
515
- );
516
- },
517
- "message.edited": async (data, ctx) => {
518
- if (data.replyOf == null && data.externalKey.startsWith("final:")) {
519
- const key = mirrorReceiptKey({
520
- provider: "github",
521
- place: ctx.origin.place,
522
- externalKey: data.externalKey,
523
- messageId: data.message.id
524
- });
525
- if (key == null) return;
526
- const saved2 = z.object({ id: z.number() }).safeParse(await ctx.store.get(key));
527
- const resource = ctx.origin.place.kind === "review-thread" ? ctx.origin.place.parent : ctx.origin.place;
528
- const repo2 = refString(ctx.origin.ref, "repository") ?? resource?.id.split("#")[0];
529
- if (!saved2.success || repo2 == null) return;
530
- try {
531
- await api(
532
- ctx,
533
- `/repos/${repo2}/${ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${saved2.data.id}`,
534
- {
535
- body: commentText(
536
- `From ${data.author.displayName} in Final
537
-
538
- ${textOf(data.message.content)}`
539
- )
540
- },
541
- "PATCH"
542
- );
543
- } catch (cause) {
544
- if (!(cause instanceof Error) || !z.object({ status: z.literal(404) }).safeParse(cause.cause).success)
545
- throw cause;
546
- await api(ctx, `/repos/${repo2}`);
547
- await ctx.store.set(key, { deleted: true }, deliveryRetention);
548
- }
549
- return;
550
- }
551
- if (data.replyOf == null) return;
552
- const repo = refString(ctx.origin.ref, "repository");
553
- const saved = z.object({ id: z.number() }).safeParse(await ctx.store.get(replyKey(ctx, data.replyOf)));
554
- if (repo != null && saved.success)
555
- await api(
556
- ctx,
557
- `/repos/${repo}/${ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${saved.data.id}`,
558
- { body: textOf(data.message.content).slice(0, 6e4) },
559
- "PATCH"
560
- );
561
- },
562
- "message.removed": async (data, ctx) => {
563
- if (data.replyOf == null && data.externalKey.startsWith("final:")) {
564
- const key = mirrorReceiptKey({
565
- provider: "github",
566
- place: ctx.origin.place,
567
- externalKey: data.externalKey,
568
- messageId: data.message.id
569
- });
570
- if (key == null) return;
571
- const saved2 = z.object({ id: z.number() }).safeParse(await ctx.store.get(key));
572
- const resource = ctx.origin.place.kind === "review-thread" ? ctx.origin.place.parent : ctx.origin.place;
573
- const repo2 = refString(ctx.origin.ref, "repository") ?? resource?.id.split("#")[0];
574
- if (!saved2.success || repo2 == null) return;
575
- const result = await api(
576
- ctx,
577
- `/repos/${repo2}/${ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${saved2.data.id}`,
578
- void 0,
579
- "DELETE"
580
- );
581
- if (result === missingTarget) await api(ctx, `/repos/${repo2}`);
582
- await ctx.store.set(key, { deleted: true }, deliveryRetention);
583
- return;
584
- }
585
- if (data.replyOf == null) return;
586
- const repo = refString(ctx.origin.ref, "repository");
587
- const saved = z.object({ id: z.number() }).safeParse(await ctx.store.get(replyKey(ctx, data.replyOf)));
588
- if (repo != null && saved.success)
589
- await api(
590
- ctx,
591
- `/repos/${repo}/${ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${saved.data.id}`,
592
- void 0,
593
- "DELETE"
594
- );
595
- },
596
- "reaction.added": async (data, ctx) => {
597
- const resource = ctx.origin.place.kind === "review-thread" ? ctx.origin.place.parent : ctx.origin.place;
598
- const repo = refString(ctx.origin.ref, "repository") ?? (data.replyOf == null && data.externalKey.startsWith("final:") ? resource?.id.split("#")[0] : null);
599
- const target = await reactionTarget(ctx, data);
600
- const content = githubReaction(data.emoji);
601
- if (repo == null || target == null || content == null) return;
602
- const mirrorKey = data.replyOf == null ? mirrorReceiptKey({
603
- provider: "github",
604
- place: ctx.origin.place,
605
- externalKey: data.externalKey
606
- }) : null;
607
- await settleMirrorReaction({
608
- ctx,
609
- key: mirrorKey,
610
- classify: (cause) => classifyMirrorReaction({ ctx, repo, target, cause }),
611
- work: async () => {
612
- const key = stateKey(
613
- "github-reaction",
614
- repo,
615
- ctx.origin.place.kind === "review-thread" ? "review-comment" : "issue-comment",
616
- String(target),
617
- content
618
- );
619
- const principal = JSON.stringify([data.principal.tenant ?? null, data.principal.id]);
620
- const saved = reactionReceiptSchema.safeParse(await ctx.store.get(key));
621
- if (saved.success) {
622
- if (saved.data.principals.includes(principal) || saved.data.overflow) return;
623
- await ctx.store.set(
624
- key,
625
- saved.data.principals.length >= 256 ? { ...saved.data, overflow: true } : { ...saved.data, principals: [...saved.data.principals, principal] },
626
- deliveryRetention
627
- );
628
- return;
629
- }
630
- let owned = false;
631
- const reactionContext = {
632
- ...ctx,
633
- credentials: {
634
- ...ctx.credentials,
635
- fetch: async (request, init) => {
636
- const response = await ctx.credentials.fetch(request, init);
637
- owned = response.status === 201;
638
- return response;
639
- }
640
- }
641
- };
642
- const result = z.object({ id: z.number() }).parse(
643
- await api(
644
- reactionContext,
645
- `/repos/${repo}/${ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${target}/reactions`,
646
- { content }
647
- )
648
- );
649
- await ctx.store.set(
650
- key,
651
- { id: result.id, principals: [principal], overflow: false, owned },
652
- deliveryRetention
653
- );
654
- }
655
- });
656
- },
657
- "reaction.removed": async (data, ctx) => {
658
- const resource = ctx.origin.place.kind === "review-thread" ? ctx.origin.place.parent : ctx.origin.place;
659
- const repo = refString(ctx.origin.ref, "repository") ?? (data.replyOf == null && data.externalKey.startsWith("final:") ? resource?.id.split("#")[0] : null);
660
- const target = await reactionTarget(ctx, data);
661
- const content = githubReaction(data.emoji);
662
- if (repo == null || target == null || content == null) return;
663
- const mirrorKey = data.replyOf == null ? mirrorReceiptKey({
664
- provider: "github",
665
- place: ctx.origin.place,
666
- externalKey: data.externalKey
667
- }) : null;
668
- await settleMirrorReaction({
669
- ctx,
670
- key: mirrorKey,
671
- classify: (cause) => classifyMirrorReaction({ ctx, repo, target, cause }),
672
- work: async () => {
673
- const key = stateKey(
674
- "github-reaction",
675
- repo,
676
- ctx.origin.place.kind === "review-thread" ? "review-comment" : "issue-comment",
677
- String(target),
678
- content
679
- );
680
- const saved = reactionReceiptSchema.safeParse(await ctx.store.get(key));
681
- if (!saved.success || saved.data.overflow) return;
682
- const principal = JSON.stringify([data.principal.tenant ?? null, data.principal.id]);
683
- if (!saved.data.principals.includes(principal)) return;
684
- const remaining = saved.data.principals.filter((value) => value !== principal);
685
- if (remaining.length > 0) {
686
- await ctx.store.set(key, { ...saved.data, principals: remaining }, deliveryRetention);
687
- return;
688
- }
689
- if (saved.data.owned) {
690
- const result = await api(
691
- ctx,
692
- `/repos/${repo}/${ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${target}/reactions/${saved.data.id}`,
693
- void 0,
694
- "DELETE"
695
- );
696
- if (mirrorKey != null && result === missingTarget) {
697
- await api(ctx, `/repos/${repo}`);
698
- try {
699
- await api(
700
- ctx,
701
- `/repos/${repo}/${ctx.origin.place.kind === "review-thread" ? "pulls" : "issues"}/comments/${target}`
702
- );
703
- } catch (cause) {
704
- if (!(cause instanceof Error) || !z.object({ status: z.literal(404) }).safeParse(cause.cause).success)
705
- throw cause;
706
- await ctx.store.set(mirrorKey, { deleted: true }, deliveryRetention);
707
- }
708
- }
709
- }
710
- await ctx.store.set(key, null);
711
- }
712
- });
713
- }
714
- }
715
- });
716
- };
717
- export {
718
- githubChannel
719
- };
720
- //# sourceMappingURL=github.mjs.map