@opentag/dispatcher 0.1.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.
- package/README.md +53 -0
- package/dist/callbacks.d.ts +12 -0
- package/dist/callbacks.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +295 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @opentag/dispatcher
|
|
2
|
+
|
|
3
|
+
Embeddable dispatcher service for OpenTag.
|
|
4
|
+
|
|
5
|
+
Use this package when you want to host the OpenTag dispatcher inside another Node or Hono-compatible service instead of running `@opentag/dispatcher-app`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @opentag/dispatcher
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Exports
|
|
14
|
+
|
|
15
|
+
- `createDispatcherApp`: creates the Hono app that exposes the OpenTag dispatcher API.
|
|
16
|
+
- `createGitHubCallbackSink`: posts callback messages to GitHub issue or PR comments.
|
|
17
|
+
- `createSlackCallbackSink`: posts callback messages to Slack threads through `chat.postMessage`.
|
|
18
|
+
- `createCompositeCallbackSink`: fans callback delivery out to multiple sinks.
|
|
19
|
+
- `CallbackMessage`, `CallbackSink`: callback delivery contracts.
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
createCompositeCallbackSink,
|
|
26
|
+
createDispatcherApp,
|
|
27
|
+
createGitHubCallbackSink,
|
|
28
|
+
createSlackCallbackSink
|
|
29
|
+
} from "@opentag/dispatcher";
|
|
30
|
+
|
|
31
|
+
export const dispatcher = createDispatcherApp({
|
|
32
|
+
databasePath: "opentag.db",
|
|
33
|
+
pairingToken: process.env.OPENTAG_PAIRING_TOKEN,
|
|
34
|
+
callbackSink: createCompositeCallbackSink([
|
|
35
|
+
createGitHubCallbackSink({ token: process.env.OPENTAG_GITHUB_TOKEN }),
|
|
36
|
+
createSlackCallbackSink({ botToken: process.env.OPENTAG_SLACK_BOT_TOKEN })
|
|
37
|
+
])
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API Shape
|
|
42
|
+
|
|
43
|
+
The app exposes `/healthz` and `/v1/*` dispatcher endpoints for runners, repository bindings, Slack channel bindings, runs, progress, heartbeats, completion, and audit event lookup.
|
|
44
|
+
|
|
45
|
+
When `pairingToken` is set, every `/v1/*` endpoint requires:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Authorization: Bearer <pairingToken>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Stability
|
|
52
|
+
|
|
53
|
+
The Hono app factory and callback sink interfaces are public API. Individual HTTP endpoint semantics should remain backward compatible within a major version.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CallbackSink } from "./server.js";
|
|
2
|
+
export type FetchLike = typeof fetch;
|
|
3
|
+
export declare function createGitHubCallbackSink(input: {
|
|
4
|
+
token?: string;
|
|
5
|
+
fetchImpl?: FetchLike;
|
|
6
|
+
}): CallbackSink;
|
|
7
|
+
export declare function createSlackCallbackSink(input: {
|
|
8
|
+
botToken?: string;
|
|
9
|
+
fetchImpl?: FetchLike;
|
|
10
|
+
}): CallbackSink;
|
|
11
|
+
export declare function createCompositeCallbackSink(sinks: CallbackSink[]): CallbackSink;
|
|
12
|
+
//# sourceMappingURL=callbacks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callbacks.d.ts","sourceRoot":"","sources":["../src/callbacks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,YAAY,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC;AAErC,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG,YAAY,CAwBvG;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG,YAAY,CA+BzG;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,YAAY,CAQ/E"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// src/callbacks.ts
|
|
2
|
+
import { parseSlackThreadKey } from "@opentag/slack";
|
|
3
|
+
function createGitHubCallbackSink(input) {
|
|
4
|
+
const fetchImpl = input.fetchImpl ?? fetch;
|
|
5
|
+
return {
|
|
6
|
+
async deliver(message) {
|
|
7
|
+
if (message.provider !== "github") return;
|
|
8
|
+
if (!input.token) return;
|
|
9
|
+
const response = await fetchImpl(message.uri, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
accept: "application/vnd.github+json",
|
|
13
|
+
authorization: `Bearer ${input.token}`,
|
|
14
|
+
"content-type": "application/json",
|
|
15
|
+
"x-github-api-version": "2022-11-28"
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify({ body: message.body })
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new Error(`deliver GitHub callback failed: ${response.status} ${await response.text()}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createSlackCallbackSink(input) {
|
|
26
|
+
const fetchImpl = input.fetchImpl ?? fetch;
|
|
27
|
+
return {
|
|
28
|
+
async deliver(message) {
|
|
29
|
+
if (message.provider !== "slack") return;
|
|
30
|
+
if (!input.botToken) return;
|
|
31
|
+
const thread = parseSlackThreadKey(message.threadKey ?? "");
|
|
32
|
+
const response = await fetchImpl(message.uri, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
authorization: `Bearer ${input.botToken}`,
|
|
36
|
+
"content-type": "application/json"
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
channel: thread.channelId,
|
|
40
|
+
text: message.body,
|
|
41
|
+
thread_ts: thread.threadTs
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`deliver Slack callback failed: ${response.status} ${await response.text()}`);
|
|
46
|
+
}
|
|
47
|
+
const body = await response.json();
|
|
48
|
+
if (body.ok === false) {
|
|
49
|
+
throw new Error(`deliver Slack callback failed: ${body.error ?? "unknown_error"}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function createCompositeCallbackSink(sinks) {
|
|
55
|
+
return {
|
|
56
|
+
async deliver(message) {
|
|
57
|
+
for (const sink of sinks) {
|
|
58
|
+
await sink.deliver(message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/server.ts
|
|
65
|
+
import { OpenTagEventSchema, OpenTagRunResultSchema } from "@opentag/core";
|
|
66
|
+
import { renderAcknowledgement, renderFinalResult, renderProgress } from "@opentag/github";
|
|
67
|
+
import { createOpenTagRepository, migrateSchema } from "@opentag/store";
|
|
68
|
+
import Database from "better-sqlite3";
|
|
69
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
70
|
+
import { Hono } from "hono";
|
|
71
|
+
import { z } from "zod";
|
|
72
|
+
var CreateRunnerSchema = z.object({
|
|
73
|
+
runnerId: z.string().min(1),
|
|
74
|
+
name: z.string().min(1)
|
|
75
|
+
});
|
|
76
|
+
var CreateRepoBindingSchema = z.object({
|
|
77
|
+
provider: z.string().min(1),
|
|
78
|
+
owner: z.string().min(1),
|
|
79
|
+
repo: z.string().min(1),
|
|
80
|
+
runnerId: z.string().min(1),
|
|
81
|
+
workspacePath: z.string().min(1).optional(),
|
|
82
|
+
defaultExecutor: z.string().min(1).optional(),
|
|
83
|
+
allowedActors: z.array(z.string().min(1)).optional()
|
|
84
|
+
});
|
|
85
|
+
var CreateSlackChannelBindingSchema = z.object({
|
|
86
|
+
teamId: z.string().min(1),
|
|
87
|
+
channelId: z.string().min(1),
|
|
88
|
+
owner: z.string().min(1),
|
|
89
|
+
repo: z.string().min(1)
|
|
90
|
+
});
|
|
91
|
+
var CreateRunSchema = z.object({
|
|
92
|
+
runId: z.string().min(1),
|
|
93
|
+
event: OpenTagEventSchema
|
|
94
|
+
});
|
|
95
|
+
var CompleteRunSchema = z.object({
|
|
96
|
+
result: OpenTagRunResultSchema
|
|
97
|
+
});
|
|
98
|
+
var ProgressSchema = z.object({
|
|
99
|
+
type: z.string().min(1).optional(),
|
|
100
|
+
message: z.string().min(1),
|
|
101
|
+
at: z.string().datetime().optional()
|
|
102
|
+
});
|
|
103
|
+
function repoKeyFromEvent(event) {
|
|
104
|
+
const owner = event.metadata["owner"];
|
|
105
|
+
const repo = event.metadata["repo"];
|
|
106
|
+
if (typeof owner !== "string" || typeof repo !== "string") return null;
|
|
107
|
+
return {
|
|
108
|
+
provider: typeof event.metadata["repoProvider"] === "string" ? event.metadata["repoProvider"] : "github",
|
|
109
|
+
owner,
|
|
110
|
+
repo
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function isWriteCapable(event) {
|
|
114
|
+
return event.permissions.some((permission) => ["repo:write", "pr:create", "pr:update"].includes(permission.scope));
|
|
115
|
+
}
|
|
116
|
+
function actorIsAllowed(event, allowedActors) {
|
|
117
|
+
if (!allowedActors?.length) return true;
|
|
118
|
+
return allowedActors.includes(event.actor.handle ?? "") || allowedActors.includes(event.actor.providerUserId);
|
|
119
|
+
}
|
|
120
|
+
var noopCallbackSink = {
|
|
121
|
+
async deliver() {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
async function deliverAndAudit(input) {
|
|
126
|
+
await input.sink.deliver(input.message);
|
|
127
|
+
await input.repo.appendRunEvent({
|
|
128
|
+
runId: input.message.runId,
|
|
129
|
+
type: `callback.${input.message.kind}.delivered`,
|
|
130
|
+
payload: input.message
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function isAuthorized(request, pairingToken) {
|
|
134
|
+
if (!pairingToken) return true;
|
|
135
|
+
return request.headers.get("authorization") === `Bearer ${pairingToken}`;
|
|
136
|
+
}
|
|
137
|
+
function createDispatcherApp(input) {
|
|
138
|
+
const sqlite = new Database(input.databasePath);
|
|
139
|
+
migrateSchema(sqlite);
|
|
140
|
+
const repo = createOpenTagRepository(drizzle(sqlite));
|
|
141
|
+
const app = new Hono();
|
|
142
|
+
const callbackSink = input.callbackSink ?? noopCallbackSink;
|
|
143
|
+
app.get("/healthz", (c) => c.json({ ok: true }));
|
|
144
|
+
app.use("/v1/*", async (c, next) => {
|
|
145
|
+
if (!isAuthorized(c.req.raw, input.pairingToken)) {
|
|
146
|
+
return c.json({ error: "unauthorized" }, 401);
|
|
147
|
+
}
|
|
148
|
+
await next();
|
|
149
|
+
});
|
|
150
|
+
app.post("/v1/runners", async (c) => {
|
|
151
|
+
const parsed = CreateRunnerSchema.parse(await c.req.json());
|
|
152
|
+
await repo.registerRunner(parsed);
|
|
153
|
+
return c.json({ ok: true }, 201);
|
|
154
|
+
});
|
|
155
|
+
app.post("/v1/repo-bindings", async (c) => {
|
|
156
|
+
const parsed = CreateRepoBindingSchema.parse(await c.req.json());
|
|
157
|
+
await repo.createRepoBinding({
|
|
158
|
+
provider: parsed.provider,
|
|
159
|
+
owner: parsed.owner,
|
|
160
|
+
repo: parsed.repo,
|
|
161
|
+
runnerId: parsed.runnerId,
|
|
162
|
+
...parsed.workspacePath ? { workspacePath: parsed.workspacePath } : {},
|
|
163
|
+
...parsed.defaultExecutor ? { defaultExecutor: parsed.defaultExecutor } : {},
|
|
164
|
+
...parsed.allowedActors?.length ? { allowedActors: parsed.allowedActors } : {}
|
|
165
|
+
});
|
|
166
|
+
return c.json({ ok: true }, 201);
|
|
167
|
+
});
|
|
168
|
+
app.get("/v1/repo-bindings/:provider/:owner/:repo", async (c) => {
|
|
169
|
+
const binding = await repo.getRepoBinding({
|
|
170
|
+
provider: c.req.param("provider"),
|
|
171
|
+
owner: c.req.param("owner"),
|
|
172
|
+
repo: c.req.param("repo")
|
|
173
|
+
});
|
|
174
|
+
if (!binding) return c.json({ error: "repo_binding_not_found" }, 404);
|
|
175
|
+
return c.json({ binding });
|
|
176
|
+
});
|
|
177
|
+
app.post("/v1/slack-channel-bindings", async (c) => {
|
|
178
|
+
const parsed = CreateSlackChannelBindingSchema.parse(await c.req.json());
|
|
179
|
+
await repo.createSlackChannelBinding(parsed);
|
|
180
|
+
return c.json({ ok: true }, 201);
|
|
181
|
+
});
|
|
182
|
+
app.get("/v1/slack-channel-bindings/:teamId/:channelId", async (c) => {
|
|
183
|
+
const binding = await repo.getSlackChannelBinding({
|
|
184
|
+
teamId: c.req.param("teamId"),
|
|
185
|
+
channelId: c.req.param("channelId")
|
|
186
|
+
});
|
|
187
|
+
if (!binding) return c.json({ error: "slack_channel_binding_not_found" }, 404);
|
|
188
|
+
return c.json({ binding });
|
|
189
|
+
});
|
|
190
|
+
app.post("/v1/runs", async (c) => {
|
|
191
|
+
const parsed = CreateRunSchema.parse(await c.req.json());
|
|
192
|
+
const repoKey = repoKeyFromEvent(parsed.event);
|
|
193
|
+
if (!repoKey) {
|
|
194
|
+
return c.json({ error: "repo_context_missing" }, 422);
|
|
195
|
+
}
|
|
196
|
+
const binding = await repo.getRepoBinding(repoKey);
|
|
197
|
+
if (!binding) {
|
|
198
|
+
return c.json({ error: "repo_not_bound" }, 403);
|
|
199
|
+
}
|
|
200
|
+
if (isWriteCapable(parsed.event) && !actorIsAllowed(parsed.event, binding.allowedActors)) {
|
|
201
|
+
return c.json({ error: "actor_not_allowed_for_write" }, 403);
|
|
202
|
+
}
|
|
203
|
+
const run = await repo.createRun({ id: parsed.runId, event: parsed.event });
|
|
204
|
+
await deliverAndAudit({
|
|
205
|
+
repo,
|
|
206
|
+
sink: callbackSink,
|
|
207
|
+
message: {
|
|
208
|
+
runId: run.id,
|
|
209
|
+
kind: "acknowledgement",
|
|
210
|
+
provider: parsed.event.callback.provider,
|
|
211
|
+
uri: parsed.event.callback.uri,
|
|
212
|
+
body: renderAcknowledgement(run.id),
|
|
213
|
+
...parsed.event.callback.threadKey ? { threadKey: parsed.event.callback.threadKey } : {}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return c.json({ run }, 201);
|
|
217
|
+
});
|
|
218
|
+
app.post("/v1/runners/:runnerId/claim", async (c) => {
|
|
219
|
+
const claimed = await repo.claimNextRun({ runnerId: c.req.param("runnerId"), leaseSeconds: 60 });
|
|
220
|
+
if (!claimed) return c.body(null, 204);
|
|
221
|
+
return c.json(claimed, 200);
|
|
222
|
+
});
|
|
223
|
+
app.post("/v1/runners/:runnerId/runs/:runId/heartbeat", async (c) => {
|
|
224
|
+
const ok = await repo.heartbeat({ runnerId: c.req.param("runnerId"), runId: c.req.param("runId") });
|
|
225
|
+
if (!ok) return c.json({ error: "run_not_claimed_by_runner" }, 404);
|
|
226
|
+
return c.json({ ok: true });
|
|
227
|
+
});
|
|
228
|
+
app.post("/v1/runs/:runId/running", async (c) => {
|
|
229
|
+
const body = z.object({ executor: z.string().min(1) }).parse(await c.req.json());
|
|
230
|
+
await repo.markRunning({ runId: c.req.param("runId"), executor: body.executor });
|
|
231
|
+
return c.json({ ok: true });
|
|
232
|
+
});
|
|
233
|
+
app.post("/v1/runs/:runId/progress", async (c) => {
|
|
234
|
+
const runId = c.req.param("runId");
|
|
235
|
+
const body = ProgressSchema.parse(await c.req.json());
|
|
236
|
+
const stored = await repo.getRun({ runId });
|
|
237
|
+
if (!stored) return c.json({ error: "run_not_found" }, 404);
|
|
238
|
+
await repo.recordProgress({
|
|
239
|
+
runId,
|
|
240
|
+
message: body.message,
|
|
241
|
+
...body.type ? { type: body.type } : {},
|
|
242
|
+
...body.at ? { at: body.at } : {}
|
|
243
|
+
});
|
|
244
|
+
await deliverAndAudit({
|
|
245
|
+
repo,
|
|
246
|
+
sink: callbackSink,
|
|
247
|
+
message: {
|
|
248
|
+
runId,
|
|
249
|
+
kind: "progress",
|
|
250
|
+
provider: stored.event.callback.provider,
|
|
251
|
+
uri: stored.event.callback.uri,
|
|
252
|
+
body: renderProgress({ runId, message: body.message }),
|
|
253
|
+
...stored.event.callback.threadKey ? { threadKey: stored.event.callback.threadKey } : {}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return c.json({ ok: true });
|
|
257
|
+
});
|
|
258
|
+
app.post("/v1/runs/:runId/complete", async (c) => {
|
|
259
|
+
const runId = c.req.param("runId");
|
|
260
|
+
const parsed = CompleteRunSchema.parse(await c.req.json());
|
|
261
|
+
const stored = await repo.getRun({ runId });
|
|
262
|
+
if (!stored) return c.json({ error: "run_not_found" }, 404);
|
|
263
|
+
await repo.completeRun({ runId, result: parsed.result });
|
|
264
|
+
await deliverAndAudit({
|
|
265
|
+
repo,
|
|
266
|
+
sink: callbackSink,
|
|
267
|
+
message: {
|
|
268
|
+
runId,
|
|
269
|
+
kind: "final",
|
|
270
|
+
provider: stored.event.callback.provider,
|
|
271
|
+
uri: stored.event.callback.uri,
|
|
272
|
+
body: renderFinalResult(parsed.result),
|
|
273
|
+
...stored.event.callback.threadKey ? { threadKey: stored.event.callback.threadKey } : {}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return c.json({ ok: true });
|
|
277
|
+
});
|
|
278
|
+
app.get("/v1/runs/:runId", async (c) => {
|
|
279
|
+
const stored = await repo.getRun({ runId: c.req.param("runId") });
|
|
280
|
+
if (!stored) return c.json({ error: "run_not_found" }, 404);
|
|
281
|
+
return c.json(stored);
|
|
282
|
+
});
|
|
283
|
+
app.get("/v1/runs/:runId/events", async (c) => {
|
|
284
|
+
const events = await repo.listRunEvents({ runId: c.req.param("runId") });
|
|
285
|
+
return c.json({ events });
|
|
286
|
+
});
|
|
287
|
+
return app;
|
|
288
|
+
}
|
|
289
|
+
export {
|
|
290
|
+
createCompositeCallbackSink,
|
|
291
|
+
createDispatcherApp,
|
|
292
|
+
createGitHubCallbackSink,
|
|
293
|
+
createSlackCallbackSink
|
|
294
|
+
};
|
|
295
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/callbacks.ts","../src/server.ts"],"sourcesContent":["import { parseSlackThreadKey } from \"@opentag/slack\";\nimport type { CallbackMessage, CallbackSink } from \"./server.js\";\n\nexport type FetchLike = typeof fetch;\n\nexport function createGitHubCallbackSink(input: { token?: string; fetchImpl?: FetchLike }): CallbackSink {\n const fetchImpl = input.fetchImpl ?? fetch;\n\n return {\n async deliver(message: CallbackMessage): Promise<void> {\n if (message.provider !== \"github\") return;\n if (!input.token) return;\n\n const response = await fetchImpl(message.uri, {\n method: \"POST\",\n headers: {\n accept: \"application/vnd.github+json\",\n authorization: `Bearer ${input.token}`,\n \"content-type\": \"application/json\",\n \"x-github-api-version\": \"2022-11-28\"\n },\n body: JSON.stringify({ body: message.body })\n });\n\n if (!response.ok) {\n throw new Error(`deliver GitHub callback failed: ${response.status} ${await response.text()}`);\n }\n }\n };\n}\n\nexport function createSlackCallbackSink(input: { botToken?: string; fetchImpl?: FetchLike }): CallbackSink {\n const fetchImpl = input.fetchImpl ?? fetch;\n\n return {\n async deliver(message: CallbackMessage): Promise<void> {\n if (message.provider !== \"slack\") return;\n if (!input.botToken) return;\n\n const thread = parseSlackThreadKey(message.threadKey ?? \"\");\n const response = await fetchImpl(message.uri, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${input.botToken}`,\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({\n channel: thread.channelId,\n text: message.body,\n thread_ts: thread.threadTs\n })\n });\n\n if (!response.ok) {\n throw new Error(`deliver Slack callback failed: ${response.status} ${await response.text()}`);\n }\n const body = (await response.json()) as { ok?: boolean; error?: string };\n if (body.ok === false) {\n throw new Error(`deliver Slack callback failed: ${body.error ?? \"unknown_error\"}`);\n }\n }\n };\n}\n\nexport function createCompositeCallbackSink(sinks: CallbackSink[]): CallbackSink {\n return {\n async deliver(message: CallbackMessage): Promise<void> {\n for (const sink of sinks) {\n await sink.deliver(message);\n }\n }\n };\n}\n","import { OpenTagEventSchema, OpenTagRunResultSchema } from \"@opentag/core\";\nimport { renderAcknowledgement, renderFinalResult, renderProgress } from \"@opentag/github\";\nimport { createOpenTagRepository, migrateSchema } from \"@opentag/store\";\nimport Database from \"better-sqlite3\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\n\nconst CreateRunnerSchema = z.object({\n runnerId: z.string().min(1),\n name: z.string().min(1)\n});\n\nconst CreateRepoBindingSchema = z.object({\n provider: z.string().min(1),\n owner: z.string().min(1),\n repo: z.string().min(1),\n runnerId: z.string().min(1),\n workspacePath: z.string().min(1).optional(),\n defaultExecutor: z.string().min(1).optional(),\n allowedActors: z.array(z.string().min(1)).optional()\n});\n\nconst CreateSlackChannelBindingSchema = z.object({\n teamId: z.string().min(1),\n channelId: z.string().min(1),\n owner: z.string().min(1),\n repo: z.string().min(1)\n});\n\nconst CreateRunSchema = z.object({\n runId: z.string().min(1),\n event: OpenTagEventSchema\n});\n\nconst CompleteRunSchema = z.object({\n result: OpenTagRunResultSchema\n});\n\nconst ProgressSchema = z.object({\n type: z.string().min(1).optional(),\n message: z.string().min(1),\n at: z.string().datetime().optional()\n});\n\nfunction repoKeyFromEvent(event: z.infer<typeof OpenTagEventSchema>): { provider: string; owner: string; repo: string } | null {\n const owner = event.metadata[\"owner\"];\n const repo = event.metadata[\"repo\"];\n if (typeof owner !== \"string\" || typeof repo !== \"string\") return null;\n return {\n provider: typeof event.metadata[\"repoProvider\"] === \"string\" ? (event.metadata[\"repoProvider\"] as string) : \"github\",\n owner,\n repo\n };\n}\n\nfunction isWriteCapable(event: z.infer<typeof OpenTagEventSchema>): boolean {\n return event.permissions.some((permission) => [\"repo:write\", \"pr:create\", \"pr:update\"].includes(permission.scope));\n}\n\nfunction actorIsAllowed(event: z.infer<typeof OpenTagEventSchema>, allowedActors: string[] | undefined): boolean {\n if (!allowedActors?.length) return true;\n return allowedActors.includes(event.actor.handle ?? \"\") || allowedActors.includes(event.actor.providerUserId);\n}\n\nexport type CallbackMessage = {\n runId: string;\n kind: \"acknowledgement\" | \"progress\" | \"final\";\n provider: \"github\" | \"slack\" | \"lark\" | \"webhook\";\n uri: string;\n body: string;\n threadKey?: string;\n};\n\nexport type CallbackSink = {\n deliver(message: CallbackMessage): Promise<void>;\n};\n\nconst noopCallbackSink: CallbackSink = {\n async deliver() {\n return;\n }\n};\n\nasync function deliverAndAudit(input: {\n repo: ReturnType<typeof createOpenTagRepository>;\n sink: CallbackSink;\n message: CallbackMessage;\n}): Promise<void> {\n await input.sink.deliver(input.message);\n await input.repo.appendRunEvent({\n runId: input.message.runId,\n type: `callback.${input.message.kind}.delivered`,\n payload: input.message\n });\n}\n\nfunction isAuthorized(request: Request, pairingToken: string | undefined): boolean {\n if (!pairingToken) return true;\n return request.headers.get(\"authorization\") === `Bearer ${pairingToken}`;\n}\n\nexport function createDispatcherApp(input: { databasePath: string; callbackSink?: CallbackSink; pairingToken?: string }) {\n const sqlite = new Database(input.databasePath);\n migrateSchema(sqlite);\n const repo = createOpenTagRepository(drizzle(sqlite));\n const app = new Hono();\n const callbackSink = input.callbackSink ?? noopCallbackSink;\n\n app.get(\"/healthz\", (c) => c.json({ ok: true }));\n\n app.use(\"/v1/*\", async (c, next) => {\n if (!isAuthorized(c.req.raw, input.pairingToken)) {\n return c.json({ error: \"unauthorized\" }, 401);\n }\n await next();\n });\n\n app.post(\"/v1/runners\", async (c) => {\n const parsed = CreateRunnerSchema.parse(await c.req.json());\n await repo.registerRunner(parsed);\n return c.json({ ok: true }, 201);\n });\n\n app.post(\"/v1/repo-bindings\", async (c) => {\n const parsed = CreateRepoBindingSchema.parse(await c.req.json());\n await repo.createRepoBinding({\n provider: parsed.provider,\n owner: parsed.owner,\n repo: parsed.repo,\n runnerId: parsed.runnerId,\n ...(parsed.workspacePath ? { workspacePath: parsed.workspacePath } : {}),\n ...(parsed.defaultExecutor ? { defaultExecutor: parsed.defaultExecutor } : {}),\n ...(parsed.allowedActors?.length ? { allowedActors: parsed.allowedActors } : {})\n });\n return c.json({ ok: true }, 201);\n });\n\n app.get(\"/v1/repo-bindings/:provider/:owner/:repo\", async (c) => {\n const binding = await repo.getRepoBinding({\n provider: c.req.param(\"provider\"),\n owner: c.req.param(\"owner\"),\n repo: c.req.param(\"repo\")\n });\n if (!binding) return c.json({ error: \"repo_binding_not_found\" }, 404);\n return c.json({ binding });\n });\n\n app.post(\"/v1/slack-channel-bindings\", async (c) => {\n const parsed = CreateSlackChannelBindingSchema.parse(await c.req.json());\n await repo.createSlackChannelBinding(parsed);\n return c.json({ ok: true }, 201);\n });\n\n app.get(\"/v1/slack-channel-bindings/:teamId/:channelId\", async (c) => {\n const binding = await repo.getSlackChannelBinding({\n teamId: c.req.param(\"teamId\"),\n channelId: c.req.param(\"channelId\")\n });\n if (!binding) return c.json({ error: \"slack_channel_binding_not_found\" }, 404);\n return c.json({ binding });\n });\n\n app.post(\"/v1/runs\", async (c) => {\n const parsed = CreateRunSchema.parse(await c.req.json());\n const repoKey = repoKeyFromEvent(parsed.event);\n if (!repoKey) {\n return c.json({ error: \"repo_context_missing\" }, 422);\n }\n const binding = await repo.getRepoBinding(repoKey);\n if (!binding) {\n return c.json({ error: \"repo_not_bound\" }, 403);\n }\n if (isWriteCapable(parsed.event) && !actorIsAllowed(parsed.event, binding.allowedActors)) {\n return c.json({ error: \"actor_not_allowed_for_write\" }, 403);\n }\n\n const run = await repo.createRun({ id: parsed.runId, event: parsed.event });\n await deliverAndAudit({\n repo,\n sink: callbackSink,\n message: {\n runId: run.id,\n kind: \"acknowledgement\",\n provider: parsed.event.callback.provider,\n uri: parsed.event.callback.uri,\n body: renderAcknowledgement(run.id),\n ...(parsed.event.callback.threadKey ? { threadKey: parsed.event.callback.threadKey } : {})\n }\n });\n return c.json({ run }, 201);\n });\n\n app.post(\"/v1/runners/:runnerId/claim\", async (c) => {\n const claimed = await repo.claimNextRun({ runnerId: c.req.param(\"runnerId\"), leaseSeconds: 60 });\n if (!claimed) return c.body(null, 204);\n return c.json(claimed, 200);\n });\n\n app.post(\"/v1/runners/:runnerId/runs/:runId/heartbeat\", async (c) => {\n const ok = await repo.heartbeat({ runnerId: c.req.param(\"runnerId\"), runId: c.req.param(\"runId\") });\n if (!ok) return c.json({ error: \"run_not_claimed_by_runner\" }, 404);\n return c.json({ ok: true });\n });\n\n app.post(\"/v1/runs/:runId/running\", async (c) => {\n const body = z.object({ executor: z.string().min(1) }).parse(await c.req.json());\n await repo.markRunning({ runId: c.req.param(\"runId\"), executor: body.executor });\n return c.json({ ok: true });\n });\n\n app.post(\"/v1/runs/:runId/progress\", async (c) => {\n const runId = c.req.param(\"runId\");\n const body = ProgressSchema.parse(await c.req.json());\n const stored = await repo.getRun({ runId });\n if (!stored) return c.json({ error: \"run_not_found\" }, 404);\n\n await repo.recordProgress({\n runId,\n message: body.message,\n ...(body.type ? { type: body.type } : {}),\n ...(body.at ? { at: body.at } : {})\n });\n await deliverAndAudit({\n repo,\n sink: callbackSink,\n message: {\n runId,\n kind: \"progress\",\n provider: stored.event.callback.provider,\n uri: stored.event.callback.uri,\n body: renderProgress({ runId, message: body.message }),\n ...(stored.event.callback.threadKey ? { threadKey: stored.event.callback.threadKey } : {})\n }\n });\n return c.json({ ok: true });\n });\n\n app.post(\"/v1/runs/:runId/complete\", async (c) => {\n const runId = c.req.param(\"runId\");\n const parsed = CompleteRunSchema.parse(await c.req.json());\n const stored = await repo.getRun({ runId });\n if (!stored) return c.json({ error: \"run_not_found\" }, 404);\n\n await repo.completeRun({ runId, result: parsed.result });\n await deliverAndAudit({\n repo,\n sink: callbackSink,\n message: {\n runId,\n kind: \"final\",\n provider: stored.event.callback.provider,\n uri: stored.event.callback.uri,\n body: renderFinalResult(parsed.result),\n ...(stored.event.callback.threadKey ? { threadKey: stored.event.callback.threadKey } : {})\n }\n });\n return c.json({ ok: true });\n });\n\n app.get(\"/v1/runs/:runId\", async (c) => {\n const stored = await repo.getRun({ runId: c.req.param(\"runId\") });\n if (!stored) return c.json({ error: \"run_not_found\" }, 404);\n return c.json(stored);\n });\n\n app.get(\"/v1/runs/:runId/events\", async (c) => {\n const events = await repo.listRunEvents({ runId: c.req.param(\"runId\") });\n return c.json({ events });\n });\n\n return app;\n}\n"],"mappings":"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","names":[]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
export type CallbackMessage = {
|
|
3
|
+
runId: string;
|
|
4
|
+
kind: "acknowledgement" | "progress" | "final";
|
|
5
|
+
provider: "github" | "slack" | "lark" | "webhook";
|
|
6
|
+
uri: string;
|
|
7
|
+
body: string;
|
|
8
|
+
threadKey?: string;
|
|
9
|
+
};
|
|
10
|
+
export type CallbackSink = {
|
|
11
|
+
deliver(message: CallbackMessage): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export declare function createDispatcherApp(input: {
|
|
14
|
+
databasePath: string;
|
|
15
|
+
callbackSink?: CallbackSink;
|
|
16
|
+
pairingToken?: string;
|
|
17
|
+
}): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
18
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA4D5B,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,iBAAiB,GAAG,UAAU,GAAG,OAAO,CAAC;IAC/C,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD,CAAC;AA0BF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,8EA0KtH"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opentag/dispatcher",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Embeddable OpenTag dispatcher Hono app and callback sinks.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"development": "./src/index.ts",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"opentag",
|
|
24
|
+
"dispatcher",
|
|
25
|
+
"hono",
|
|
26
|
+
"agents",
|
|
27
|
+
"webhooks"
|
|
28
|
+
],
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"better-sqlite3": "^11.7.0",
|
|
32
|
+
"drizzle-orm": "^0.45.2",
|
|
33
|
+
"hono": "^4.6.15",
|
|
34
|
+
"zod": "^3.24.1",
|
|
35
|
+
"@opentag/core": "0.1.0",
|
|
36
|
+
"@opentag/store": "0.1.0",
|
|
37
|
+
"@opentag/slack": "0.1.0",
|
|
38
|
+
"@opentag/github": "0.1.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
42
|
+
"tsup": "^8.3.5",
|
|
43
|
+
"typescript": "^5.7.2"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup && tsc -b tsconfig.json --emitDeclarationOnly --force",
|
|
47
|
+
"lint": "tsc --noEmit"
|
|
48
|
+
}
|
|
49
|
+
}
|