@openclaw/crabline 0.1.11 → 0.1.13
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 +137 -23
- package/dist/src/bin/crabline.js +3 -1
- package/dist/src/bin/crabline.js.map +1 -1
- package/dist/src/cli/program.d.ts +5 -1
- package/dist/src/cli/program.js +281 -52
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/load.js +39 -3
- package/dist/src/config/load.js.map +1 -1
- package/dist/src/config/schema.d.ts +20 -0
- package/dist/src/config/schema.js +163 -35
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/errors.d.ts +1 -0
- package/dist/src/core/errors.js +6 -3
- package/dist/src/core/errors.js.map +1 -1
- package/dist/src/core/http-path.d.ts +1 -0
- package/dist/src/core/http-path.js +14 -0
- package/dist/src/core/http-path.js.map +1 -0
- package/dist/src/core/matcher.d.ts +3 -1
- package/dist/src/core/matcher.js +58 -2
- package/dist/src/core/matcher.js.map +1 -1
- package/dist/src/core/nonces.d.ts +2 -0
- package/dist/src/core/nonces.js +11 -1
- package/dist/src/core/nonces.js.map +1 -1
- package/dist/src/core/reporters.d.ts +5 -0
- package/dist/src/core/reporters.js +26 -2
- package/dist/src/core/reporters.js.map +1 -1
- package/dist/src/core/run.d.ts +3 -1
- package/dist/src/core/run.js +178 -66
- package/dist/src/core/run.js.map +1 -1
- package/dist/src/index.d.ts +12 -10
- package/dist/src/index.js +6 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/matrix-ids.d.ts +5 -0
- package/dist/src/matrix-ids.js +83 -0
- package/dist/src/matrix-ids.js.map +1 -0
- package/dist/src/openclaw/artifact-generation.d.ts +7 -6
- package/dist/src/openclaw/artifact-generation.js +385 -41
- package/dist/src/openclaw/artifact-generation.js.map +1 -1
- package/dist/src/openclaw/bridges/discord.d.ts +1 -0
- package/dist/src/openclaw/bridges/discord.js +149 -0
- package/dist/src/openclaw/bridges/discord.js.map +1 -0
- package/dist/src/openclaw/bridges/matrix.js +22 -5
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +14 -9
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
- package/dist/src/openclaw/bridges/probe-response.js +5 -0
- package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
- package/dist/src/openclaw/bridges/signal.js +16 -7
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +117 -24
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +46 -20
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +33 -17
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +9 -4
- package/dist/src/openclaw/bridges/zalo.js.map +1 -1
- package/dist/src/openclaw/outbound-contract.js +3 -0
- package/dist/src/openclaw/outbound-contract.js.map +1 -1
- package/dist/src/openclaw/private-file.d.ts +38 -3
- package/dist/src/openclaw/private-file.js +2008 -87
- package/dist/src/openclaw/private-file.js.map +1 -1
- package/dist/src/openclaw/{smoke-lock.d.ts → provider-readiness-lock.d.ts} +14 -5
- package/dist/src/openclaw/provider-readiness-lock.js +1320 -0
- package/dist/src/openclaw/provider-readiness-lock.js.map +1 -0
- package/dist/src/openclaw/shared.d.ts +10 -17
- package/dist/src/openclaw/shared.js +53 -19
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw.d.ts +3 -5
- package/dist/src/openclaw.js +245 -60
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/platform/process-owned-lock.d.ts +11 -0
- package/dist/src/platform/process-owned-lock.js +1698 -0
- package/dist/src/platform/process-owned-lock.js.map +1 -0
- package/dist/src/platform/recorder-directory.d.ts +1 -0
- package/dist/src/platform/recorder-directory.js +14 -0
- package/dist/src/platform/recorder-directory.js.map +1 -0
- package/dist/src/platform/windows-acl.d.ts +18 -0
- package/dist/src/platform/windows-acl.js +1367 -0
- package/dist/src/platform/windows-acl.js.map +1 -0
- package/dist/src/platform/windows-lock-root.d.ts +13 -0
- package/dist/src/platform/windows-lock-root.js +98 -0
- package/dist/src/platform/windows-lock-root.js.map +1 -0
- package/dist/src/providers/builtin/discord.d.ts +4 -0
- package/dist/src/providers/builtin/discord.js +52 -9
- package/dist/src/providers/builtin/discord.js.map +1 -1
- package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
- package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
- package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
- package/dist/src/providers/builtin/feishu.d.ts +1 -0
- package/dist/src/providers/builtin/feishu.js +96 -26
- package/dist/src/providers/builtin/feishu.js.map +1 -1
- package/dist/src/providers/builtin/googlechat.d.ts +1 -0
- package/dist/src/providers/builtin/googlechat.js +95 -26
- package/dist/src/providers/builtin/googlechat.js.map +1 -1
- package/dist/src/providers/builtin/imessage.js +60 -17
- package/dist/src/providers/builtin/imessage.js.map +1 -1
- package/dist/src/providers/builtin/loopback.js +88 -35
- package/dist/src/providers/builtin/loopback.js.map +1 -1
- package/dist/src/providers/builtin/matrix.d.ts +2 -2
- package/dist/src/providers/builtin/matrix.js +78 -21
- package/dist/src/providers/builtin/matrix.js.map +1 -1
- package/dist/src/providers/builtin/mattermost.d.ts +11 -7
- package/dist/src/providers/builtin/mattermost.js +138 -24
- package/dist/src/providers/builtin/mattermost.js.map +1 -1
- package/dist/src/providers/builtin/msteams.d.ts +1 -0
- package/dist/src/providers/builtin/msteams.js +118 -38
- package/dist/src/providers/builtin/msteams.js.map +1 -1
- package/dist/src/providers/builtin/native-local-mock.js +4 -4
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
- package/dist/src/providers/builtin/script.js +1057 -146
- package/dist/src/providers/builtin/script.js.map +1 -1
- package/dist/src/providers/builtin/slack.d.ts +8 -1
- package/dist/src/providers/builtin/slack.js +327 -24
- package/dist/src/providers/builtin/slack.js.map +1 -1
- package/dist/src/providers/builtin/telegram.d.ts +1 -1
- package/dist/src/providers/builtin/telegram.js +55 -14
- package/dist/src/providers/builtin/telegram.js.map +1 -1
- package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
- package/dist/src/providers/builtin/whatsapp.js +157 -29
- package/dist/src/providers/builtin/whatsapp.js.map +1 -1
- package/dist/src/providers/builtin/zalo.d.ts +5 -2
- package/dist/src/providers/builtin/zalo.js +31 -7
- package/dist/src/providers/builtin/zalo.js.map +1 -1
- package/dist/src/providers/catalog.d.ts +1 -1
- package/dist/src/providers/catalog.js +1 -1
- package/dist/src/providers/catalog.js.map +1 -1
- package/dist/src/providers/local-mock.d.ts +4 -0
- package/dist/src/providers/local-mock.js +162 -35
- package/dist/src/providers/local-mock.js.map +1 -1
- package/dist/src/providers/native-ids.d.ts +2 -0
- package/dist/src/providers/native-ids.js +3 -0
- package/dist/src/providers/native-ids.js.map +1 -1
- package/dist/src/providers/recorder.d.ts +14 -3
- package/dist/src/providers/recorder.js +521 -41
- package/dist/src/providers/recorder.js.map +1 -1
- package/dist/src/providers/registry.d.ts +1 -0
- package/dist/src/providers/registry.js +65 -20
- package/dist/src/providers/registry.js.map +1 -1
- package/dist/src/providers/signed-jwt.d.ts +6 -1
- package/dist/src/providers/signed-jwt.js +241 -50
- package/dist/src/providers/signed-jwt.js.map +1 -1
- package/dist/src/providers/slack-ids.d.ts +1 -0
- package/dist/src/providers/slack-ids.js +5 -0
- package/dist/src/providers/slack-ids.js.map +1 -1
- package/dist/src/providers/target-normalizers.d.ts +9 -1
- package/dist/src/providers/target-normalizers.js +177 -58
- package/dist/src/providers/target-normalizers.js.map +1 -1
- package/dist/src/providers/webhook-server.d.ts +3 -0
- package/dist/src/providers/webhook-server.js +268 -47
- package/dist/src/providers/webhook-server.js.map +1 -1
- package/dist/src/servers/discord.d.ts +40 -0
- package/dist/src/servers/discord.js +842 -0
- package/dist/src/servers/discord.js.map +1 -0
- package/dist/src/servers/http.d.ts +14 -2
- package/dist/src/servers/http.js +229 -58
- package/dist/src/servers/http.js.map +1 -1
- package/dist/src/servers/index.d.ts +10 -4
- package/dist/src/servers/index.js +4 -0
- package/dist/src/servers/index.js.map +1 -1
- package/dist/src/servers/matrix.d.ts +3 -0
- package/dist/src/servers/matrix.js +268 -110
- package/dist/src/servers/matrix.js.map +1 -1
- package/dist/src/servers/mattermost.d.ts +5 -0
- package/dist/src/servers/mattermost.js +389 -91
- package/dist/src/servers/mattermost.js.map +1 -1
- package/dist/src/servers/recorder.d.ts +11 -0
- package/dist/src/servers/recorder.js +1072 -69
- package/dist/src/servers/recorder.js.map +1 -1
- package/dist/src/servers/signal.d.ts +1 -0
- package/dist/src/servers/signal.js +279 -44
- package/dist/src/servers/signal.js.map +1 -1
- package/dist/src/servers/slack.d.ts +15 -0
- package/dist/src/servers/slack.js +270 -54
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram-identity.d.ts +6 -0
- package/dist/src/servers/telegram-identity.js +27 -0
- package/dist/src/servers/telegram-identity.js.map +1 -0
- package/dist/src/servers/telegram.d.ts +17 -6
- package/dist/src/servers/telegram.js +1218 -107
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/webhook-target.d.ts +12 -4
- package/dist/src/servers/webhook-target.js +226 -28
- package/dist/src/servers/webhook-target.js.map +1 -1
- package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
- package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
- package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
- package/dist/src/servers/whatsapp-jid.d.ts +1 -0
- package/dist/src/servers/whatsapp-jid.js +9 -5
- package/dist/src/servers/whatsapp-jid.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
- package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
- package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
- package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
- package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
- package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
- package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/signal.js +3 -3
- package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
- package/dist/src/servers/whatsapp.d.ts +7 -1
- package/dist/src/servers/whatsapp.js +149 -25
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/dist/src/servers/zalo.d.ts +1 -0
- package/dist/src/servers/zalo.js +193 -82
- package/dist/src/servers/zalo.js.map +1 -1
- package/docs/channel-setup.md +251 -64
- package/fixtures/examples/crabline.example.yaml +3 -2
- package/package.json +18 -10
- package/dist/src/openclaw/smoke-lock.js +0 -1004
- package/dist/src/openclaw/smoke-lock.js.map +0 -1
- package/fixtures/examples/openclaw-bridge.yaml +0 -125
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { CrablineError, ensureErrorMessage } from "../core/errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isJsonMediaType } from "../servers/http.js";
|
|
4
|
+
import { appendRecordedInbound, appendRecordedInboundBatch, cloneRecordedInboundCursor, createRecordedInboundCursor, waitForRecordedInbound, watchRecordedInbound, } from "./recorder.js";
|
|
4
5
|
import { startWebhookServer } from "./webhook-server.js";
|
|
5
6
|
export { createGenericLocalMockTargetCodec } from "./target-normalizers.js";
|
|
6
7
|
const MAX_WAIT_CURSORS = 64;
|
|
8
|
+
const DEFAULT_WEBHOOK_CLEANUP_GRACE_MS = 250;
|
|
7
9
|
function cursorHasAdvanced(candidate, current) {
|
|
8
10
|
if (!current) {
|
|
9
11
|
return true;
|
|
@@ -57,10 +59,33 @@ function isAddressInChannel(threadId, channelId) {
|
|
|
57
59
|
function createMessageId(platform) {
|
|
58
60
|
return `${platform}-mock-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
59
61
|
}
|
|
62
|
+
function unsafeGeneratedRecorderProviderId(providerId) {
|
|
63
|
+
return (path.posix.parse(providerId).root !== "" ||
|
|
64
|
+
path.win32.parse(providerId).root !== "" ||
|
|
65
|
+
providerId.split(/[\\/]/u).includes(".."));
|
|
66
|
+
}
|
|
67
|
+
export function resolveGeneratedLocalMockRecorderPath(providerId, suffix = "") {
|
|
68
|
+
const recorderDirectory = path.resolve(".crabline", "recorders");
|
|
69
|
+
if (unsafeGeneratedRecorderProviderId(providerId)) {
|
|
70
|
+
throw new CrablineError("Provider ID cannot contain absolute or parent-directory paths.", {
|
|
71
|
+
kind: "config",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const recorderPath = path.resolve(recorderDirectory, `${providerId}${suffix}.jsonl`);
|
|
75
|
+
const relativePath = path.relative(recorderDirectory, recorderPath);
|
|
76
|
+
if (relativePath === ".." ||
|
|
77
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
78
|
+
path.isAbsolute(relativePath)) {
|
|
79
|
+
throw new CrablineError("Generated provider recorder path escapes its directory.", {
|
|
80
|
+
kind: "config",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return recorderPath;
|
|
84
|
+
}
|
|
60
85
|
function toRecorderPath(providerId, configuredPath) {
|
|
61
86
|
return configuredPath
|
|
62
87
|
? path.resolve(configuredPath)
|
|
63
|
-
:
|
|
88
|
+
: resolveGeneratedLocalMockRecorderPath(providerId);
|
|
64
89
|
}
|
|
65
90
|
function authorFromPayload(payload) {
|
|
66
91
|
const explicit = payload.message?.author ?? payload.author;
|
|
@@ -99,7 +124,7 @@ function normalizeWebhookPayload(payload) {
|
|
|
99
124
|
if (envelope.authorIsBot !== undefined && typeof envelope.authorIsBot !== "boolean") {
|
|
100
125
|
throw new CrablineError(`${label}.authorIsBot must be a boolean`, { kind: "inbound" });
|
|
101
126
|
}
|
|
102
|
-
for (const field of ["id", "
|
|
127
|
+
for (const field of ["id", "threadId"]) {
|
|
103
128
|
const value = envelope[field];
|
|
104
129
|
if (value !== undefined && (typeof value !== "string" || value.length === 0)) {
|
|
105
130
|
throw new CrablineError(`${label}.${field} must be a non-empty string`, {
|
|
@@ -107,12 +132,45 @@ function normalizeWebhookPayload(payload) {
|
|
|
107
132
|
});
|
|
108
133
|
}
|
|
109
134
|
}
|
|
135
|
+
if (envelope.text !== undefined && typeof envelope.text !== "string") {
|
|
136
|
+
throw new CrablineError(`${label}.text must be a string`, {
|
|
137
|
+
kind: "inbound",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
110
140
|
}
|
|
111
141
|
return payload;
|
|
112
142
|
}
|
|
113
143
|
function mockReplyText(params) {
|
|
114
144
|
return `[${params.platform} mock] ${params.text}`;
|
|
115
145
|
}
|
|
146
|
+
function reportPostCommitWebhookSettlementFailure(providerId, error) {
|
|
147
|
+
try {
|
|
148
|
+
process.emitWarning(`Webhook acceptance committed for provider ${JSON.stringify(providerId)}, but settlement failed: ${ensureErrorMessage(error)}`, {
|
|
149
|
+
code: "CRABLINE_WEBHOOK_SETTLEMENT",
|
|
150
|
+
type: "ProviderWebhookWarning",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Error reporting must not change the result of an accepted webhook.
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function settleWebhookHandlers(providerId, handlers, graceMs) {
|
|
158
|
+
if (handlers.length === 0) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
let timer;
|
|
162
|
+
const deadline = new Promise((_, reject) => {
|
|
163
|
+
timer = setTimeout(() => {
|
|
164
|
+
reject(new CrablineError(`Provider "${providerId}" webhook handlers did not settle within ${graceMs}ms after cleanup.`, { kind: "timeout" }));
|
|
165
|
+
}, graceMs);
|
|
166
|
+
});
|
|
167
|
+
try {
|
|
168
|
+
await Promise.race([Promise.allSettled(handlers).then(() => undefined), deadline]);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
116
174
|
export class LocalMockProviderAdapter {
|
|
117
175
|
id;
|
|
118
176
|
platform;
|
|
@@ -123,12 +181,15 @@ export class LocalMockProviderAdapter {
|
|
|
123
181
|
#options;
|
|
124
182
|
#publicUrl;
|
|
125
183
|
#recorderPath;
|
|
184
|
+
#webhookCleanupGraceMs;
|
|
126
185
|
#activeSends = new Set();
|
|
186
|
+
#activeWebhookHandlers = new Set();
|
|
127
187
|
#cleanupController = new AbortController();
|
|
128
188
|
#waitCursors = new Map();
|
|
129
189
|
#cleanupBegun = false;
|
|
130
190
|
#cleanupPromise = null;
|
|
131
191
|
#server = null;
|
|
192
|
+
#serverClosing = null;
|
|
132
193
|
#serverStarting = null;
|
|
133
194
|
constructor(params) {
|
|
134
195
|
this.id = params.id;
|
|
@@ -139,6 +200,14 @@ export class LocalMockProviderAdapter {
|
|
|
139
200
|
this.#options = params.options;
|
|
140
201
|
this.#publicUrl = params.options.publicUrl ?? params.options.webhook?.publicUrl;
|
|
141
202
|
this.#recorderPath = toRecorderPath(params.id, params.options.recorderPath);
|
|
203
|
+
const webhookCleanupGraceMs = params.options.webhookCleanupGraceMs ?? DEFAULT_WEBHOOK_CLEANUP_GRACE_MS;
|
|
204
|
+
if (!Number.isSafeInteger(webhookCleanupGraceMs) || webhookCleanupGraceMs < 1) {
|
|
205
|
+
throw new CrablineError("Webhook cleanup grace must be a positive safe integer.", {
|
|
206
|
+
kind: "config",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
this.#webhookCleanupGraceMs = webhookCleanupGraceMs;
|
|
210
|
+
this.#installCleanupFence();
|
|
142
211
|
}
|
|
143
212
|
normalizeTarget(target) {
|
|
144
213
|
this.#assertActive();
|
|
@@ -181,28 +250,30 @@ export class LocalMockProviderAdapter {
|
|
|
181
250
|
return sending;
|
|
182
251
|
}
|
|
183
252
|
async #send(context) {
|
|
184
|
-
const
|
|
253
|
+
const signal = this.#signalFor(context.signal);
|
|
185
254
|
const threadId = this.#codec.resolveThreadId(context.fixture.target);
|
|
186
255
|
const messageId = createMessageId(this.platform);
|
|
187
|
-
signal
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
256
|
+
signal.throwIfAborted();
|
|
257
|
+
const events = [
|
|
258
|
+
{
|
|
259
|
+
author: "user",
|
|
260
|
+
id: messageId,
|
|
261
|
+
provider: this.id,
|
|
262
|
+
raw: {
|
|
263
|
+
direction: "outbound",
|
|
264
|
+
mode: context.mode,
|
|
265
|
+
platform: this.platform,
|
|
266
|
+
},
|
|
267
|
+
recordedDirection: "outbound",
|
|
268
|
+
sentAt: new Date().toISOString(),
|
|
269
|
+
text: context.text,
|
|
270
|
+
threadId,
|
|
196
271
|
},
|
|
197
|
-
|
|
198
|
-
sentAt: new Date().toISOString(),
|
|
199
|
-
text: context.text,
|
|
200
|
-
threadId,
|
|
201
|
-
});
|
|
272
|
+
];
|
|
202
273
|
if (context.mode !== "send" && context.fixture.target.behavior !== "sink") {
|
|
203
274
|
await sleep(this.#config.loopback?.delayMs ?? 25, signal);
|
|
204
|
-
signal
|
|
205
|
-
|
|
275
|
+
signal.throwIfAborted();
|
|
276
|
+
events.push({
|
|
206
277
|
author: "assistant",
|
|
207
278
|
id: `${messageId}-reply`,
|
|
208
279
|
provider: this.id,
|
|
@@ -217,6 +288,7 @@ export class LocalMockProviderAdapter {
|
|
|
217
288
|
threadId,
|
|
218
289
|
});
|
|
219
290
|
}
|
|
291
|
+
await appendRecordedInboundBatch(this.#recorderPath, events);
|
|
220
292
|
return {
|
|
221
293
|
accepted: true,
|
|
222
294
|
messageId,
|
|
@@ -266,6 +338,7 @@ export class LocalMockProviderAdapter {
|
|
|
266
338
|
this.#waitCursors.get(cursorKey) === cursorState) {
|
|
267
339
|
this.#waitCursors.delete(cursorKey);
|
|
268
340
|
}
|
|
341
|
+
pruneInactiveWaitCursors(this.#waitCursors);
|
|
269
342
|
}
|
|
270
343
|
}
|
|
271
344
|
async *watch(context) {
|
|
@@ -291,30 +364,60 @@ export class LocalMockProviderAdapter {
|
|
|
291
364
|
this.#cleanupBegun = true;
|
|
292
365
|
this.#cleanupController.abort(this.#cleanedUpError());
|
|
293
366
|
this.#waitCursors.clear();
|
|
367
|
+
this.#serverClosing = this.#closeWebhookServer();
|
|
368
|
+
void this.#serverClosing.catch(() => undefined);
|
|
369
|
+
}
|
|
370
|
+
#installCleanupFence() {
|
|
371
|
+
const adapter = this;
|
|
372
|
+
if (adapter.beginCleanup) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
Object.defineProperty(adapter, "beginCleanup", {
|
|
376
|
+
value: () => this.#beginCleanup(),
|
|
377
|
+
});
|
|
294
378
|
}
|
|
295
379
|
async cleanup() {
|
|
296
380
|
this.#beginCleanup();
|
|
297
381
|
this.#cleanupPromise ??= (async () => {
|
|
298
382
|
const sends = [...this.#activeSends];
|
|
299
|
-
const
|
|
383
|
+
const webhookHandlers = [...this.#activeWebhookHandlers];
|
|
384
|
+
const results = await Promise.allSettled([
|
|
385
|
+
this.#serverClosing,
|
|
386
|
+
...sends,
|
|
387
|
+
settleWebhookHandlers(this.id, webhookHandlers, this.#webhookCleanupGraceMs),
|
|
388
|
+
]);
|
|
389
|
+
const serverResult = results[0];
|
|
300
390
|
if (serverResult?.status === "rejected") {
|
|
301
391
|
throw serverResult.reason;
|
|
302
392
|
}
|
|
393
|
+
const webhookResult = results.at(-1);
|
|
394
|
+
if (webhookResult?.status === "rejected") {
|
|
395
|
+
throw webhookResult.reason;
|
|
396
|
+
}
|
|
303
397
|
})();
|
|
304
398
|
await this.#cleanupPromise;
|
|
305
399
|
}
|
|
306
|
-
|
|
400
|
+
#handleWebhook(request) {
|
|
307
401
|
if (this.#cleanupBegun) {
|
|
308
|
-
return new Response("provider is shutting down", { status: 503 });
|
|
402
|
+
return Promise.resolve(new Response("provider is shutting down", { status: 503 }));
|
|
309
403
|
}
|
|
404
|
+
const admittedRequest = new Request(request, {
|
|
405
|
+
signal: this.#signalFor(request.signal),
|
|
406
|
+
});
|
|
407
|
+
const handling = this.#handleAdmittedWebhook(admittedRequest);
|
|
408
|
+
this.#activeWebhookHandlers.add(handling);
|
|
409
|
+
void handling.then(() => this.#activeWebhookHandlers.delete(handling), () => this.#activeWebhookHandlers.delete(handling));
|
|
410
|
+
return handling;
|
|
411
|
+
}
|
|
412
|
+
async #handleAdmittedWebhook(request) {
|
|
310
413
|
const rawBody = await request.text();
|
|
311
414
|
const authenticationFailure = await this.#options.authenticateWebhookRequest?.(request, rawBody);
|
|
312
415
|
if (authenticationFailure) {
|
|
313
416
|
return authenticationFailure;
|
|
314
417
|
}
|
|
315
|
-
const
|
|
418
|
+
const isJson = isJsonMediaType(request.headers.get("content-type") ?? "");
|
|
316
419
|
let rawPayload;
|
|
317
|
-
if (
|
|
420
|
+
if (isJson) {
|
|
318
421
|
try {
|
|
319
422
|
rawPayload = JSON.parse(rawBody);
|
|
320
423
|
}
|
|
@@ -342,12 +445,29 @@ export class LocalMockProviderAdapter {
|
|
|
342
445
|
settle(response.ok);
|
|
343
446
|
return response;
|
|
344
447
|
};
|
|
448
|
+
let uncommittedResponse;
|
|
449
|
+
const settleCommittedAcceptance = () => {
|
|
450
|
+
if (settled) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
settled = true;
|
|
454
|
+
try {
|
|
455
|
+
this.#options.settleWebhookRequest?.({
|
|
456
|
+
accepted: true,
|
|
457
|
+
payload: rawPayload,
|
|
458
|
+
rawBody,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
reportPostCommitWebhookSettlementFailure(this.id, error);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
345
465
|
try {
|
|
346
466
|
const directResponse = await this.#options.handleWebhookPayload?.(rawPayload, request, rawBody);
|
|
347
467
|
if (directResponse) {
|
|
348
468
|
return respond(directResponse);
|
|
349
469
|
}
|
|
350
|
-
if (
|
|
470
|
+
if (!isJson) {
|
|
351
471
|
return respond(new Response("expected application/json", { status: 415 }));
|
|
352
472
|
}
|
|
353
473
|
let payload;
|
|
@@ -365,12 +485,15 @@ export class LocalMockProviderAdapter {
|
|
|
365
485
|
const id = payload.message?.id ?? payload.id ?? createMessageId(this.platform);
|
|
366
486
|
const threadId = payload.message?.threadId ?? payload.threadId;
|
|
367
487
|
const text = payload.message?.text ?? payload.text;
|
|
368
|
-
if (!threadId ||
|
|
488
|
+
if (!threadId || text === undefined) {
|
|
369
489
|
return respond(new Response("payload requires message.threadId and message.text", { status: 400 }));
|
|
370
490
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
491
|
+
const successResponse = (await this.#options.createWebhookSuccessResponse?.(rawPayload, id)) ??
|
|
492
|
+
new Response(JSON.stringify({ ok: true, id }), {
|
|
493
|
+
headers: { "content-type": "application/json" },
|
|
494
|
+
status: 200,
|
|
495
|
+
});
|
|
496
|
+
uncommittedResponse = successResponse;
|
|
374
497
|
await appendRecordedInbound(this.#recorderPath, {
|
|
375
498
|
author: authorFromPayload(payload),
|
|
376
499
|
id,
|
|
@@ -381,13 +504,12 @@ export class LocalMockProviderAdapter {
|
|
|
381
504
|
text,
|
|
382
505
|
threadId,
|
|
383
506
|
});
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
status: 200,
|
|
388
|
-
}));
|
|
507
|
+
settleCommittedAcceptance();
|
|
508
|
+
uncommittedResponse = undefined;
|
|
509
|
+
return successResponse;
|
|
389
510
|
}
|
|
390
511
|
catch (error) {
|
|
512
|
+
void uncommittedResponse?.body?.cancel(error).catch(() => undefined);
|
|
391
513
|
settle(false);
|
|
392
514
|
throw error;
|
|
393
515
|
}
|
|
@@ -411,8 +533,13 @@ export class LocalMockProviderAdapter {
|
|
|
411
533
|
return await startWebhookServer({
|
|
412
534
|
handle: (request) => this.#handleWebhook(request),
|
|
413
535
|
host,
|
|
536
|
+
methods: this.#options.webhookMethods ??
|
|
537
|
+
(this.#options.handleWebhookPayload ? ["GET", "POST"] : ["POST"]),
|
|
414
538
|
path: webhookPath,
|
|
415
539
|
port,
|
|
540
|
+
...(this.#options.preflightWebhookRequest
|
|
541
|
+
? { preflight: this.#options.preflightWebhookRequest }
|
|
542
|
+
: {}),
|
|
416
543
|
});
|
|
417
544
|
}
|
|
418
545
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-mock.js","sourceRoot":"","sources":["../../../src/providers/local-mock.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { CrablineError, ensureErrorMessage } from \"../core/errors.js\";\nimport type { ProviderConfig, ProviderPlatform } from \"../config/schema.js\";\nimport {\n appendRecordedInbound,\n cloneRecordedInboundCursor,\n createRecordedInboundCursor,\n waitForRecordedInbound,\n watchRecordedInbound,\n type RecordedInboundCursor,\n} from \"./recorder.js\";\nimport type {\n InboundEnvelope,\n NormalizedTarget,\n ProbeResult,\n ProviderAdapter,\n ProviderContext,\n SendContext,\n SendResult,\n WaitContext,\n WatchContext,\n} from \"./types.js\";\nimport { startWebhookServer, type StartedWebhookServer } from \"./webhook-server.js\";\n\nexport { createGenericLocalMockTargetCodec } from \"./target-normalizers.js\";\n\nexport type LocalMockWebhookConfig = {\n host?: string;\n path?: string;\n port?: number;\n publicUrl?: string | undefined;\n};\n\nexport type LocalMockAdapterOptions = {\n authenticateWebhookRequest?: (\n request: Request,\n rawBody: string,\n ) => Promise<Response | undefined> | Response | undefined;\n createWebhookSuccessResponse?: (payload: unknown, id: string) => Promise<Response> | Response;\n defaultWebhook: Required<Pick<LocalMockWebhookConfig, \"host\" | \"path\" | \"port\">>;\n endpointLabel: string;\n matchesThread?: (\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: NormalizedTarget,\n raw?: unknown,\n ) => boolean;\n handleWebhookPayload?: (\n payload: unknown,\n request: Request,\n rawBody: string,\n ) => Promise<Response | undefined> | Response | undefined;\n normalizeWebhookPayload?: (payload: unknown) => unknown;\n platform: ProviderPlatform;\n publicUrl?: string | undefined;\n recorderPath?: string | undefined;\n settleWebhookRequest?: (params: { accepted: boolean; payload: unknown; rawBody: string }) => void;\n webhook?: LocalMockWebhookConfig | undefined;\n};\n\nconst MAX_WAIT_CURSORS = 64;\n\ntype WaitCursorState = {\n active: number;\n cursor?: RecordedInboundCursor | undefined;\n};\n\nfunction cursorHasAdvanced(\n candidate: RecordedInboundCursor,\n current: RecordedInboundCursor | undefined,\n): boolean {\n if (!current) {\n return true;\n }\n if (candidate.readState.generation !== current.readState.generation) {\n return candidate.readState.generation > current.readState.generation;\n }\n if (candidate.readState.offset !== current.readState.offset) {\n return candidate.readState.offset > current.readState.offset;\n }\n return candidate.buffered.length < current.buffered.length;\n}\n\nfunction pruneInactiveWaitCursors(cursors: Map<string, WaitCursorState>): void {\n for (const [key, state] of cursors) {\n if (cursors.size <= MAX_WAIT_CURSORS) {\n return;\n }\n if (state.active === 0) {\n cursors.delete(key);\n }\n }\n}\n\nexport type LocalMockTargetCodec = {\n normalize(target: ProviderContext[\"fixture\"][\"target\"]): NormalizedTarget;\n resolveThreadId(target: ProviderContext[\"fixture\"][\"target\"]): string;\n};\n\ntype MockWebhookPayload = {\n author?: \"assistant\" | \"system\" | \"user\";\n authorIsBot?: boolean;\n id?: string;\n message?: {\n author?: \"assistant\" | \"system\" | \"user\";\n authorIsBot?: boolean;\n id?: string;\n raw?: unknown;\n text?: string;\n threadId?: string;\n };\n raw?: unknown;\n text?: string;\n threadId?: string;\n};\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const cleanup = () => signal?.removeEventListener(\"abort\", abort);\n const abort = () => {\n if (timer) {\n clearTimeout(timer);\n }\n cleanup();\n reject(signal?.reason ?? new Error(\"Provider operation aborted.\"));\n };\n timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n\nfunction isAddressInChannel(threadId: string, channelId?: string): boolean {\n if (!channelId) {\n return true;\n }\n return threadId === channelId || threadId.startsWith(`${channelId}:`);\n}\n\nfunction createMessageId(platform: ProviderPlatform) {\n return `${platform}-mock-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction toRecorderPath(providerId: string, configuredPath?: string): string {\n return configuredPath\n ? path.resolve(configuredPath)\n : path.resolve(\".crabline\", \"recorders\", `${providerId}.jsonl`);\n}\n\nfunction authorFromPayload(payload: MockWebhookPayload): InboundEnvelope[\"author\"] {\n const explicit = payload.message?.author ?? payload.author;\n if (explicit) {\n return explicit;\n }\n return (payload.message?.authorIsBot ?? payload.authorIsBot ?? true) ? \"assistant\" : \"user\";\n}\n\nfunction normalizeWebhookPayload(payload: unknown): MockWebhookPayload {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new CrablineError(\"mock webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n const record = payload as Record<string, unknown>;\n const messageValue = record.message;\n if (\n messageValue !== undefined &&\n (!messageValue || typeof messageValue !== \"object\" || Array.isArray(messageValue))\n ) {\n throw new CrablineError(\"mock webhook payload message must be an object\", {\n kind: \"inbound\",\n });\n }\n\n for (const [label, envelope] of [\n [\"payload\", record],\n [\"payload.message\", messageValue as Record<string, unknown> | undefined],\n ] as const) {\n if (!envelope) {\n continue;\n }\n if (\n envelope.author !== undefined &&\n envelope.author !== \"assistant\" &&\n envelope.author !== \"system\" &&\n envelope.author !== \"user\"\n ) {\n throw new CrablineError(`${label}.author must be assistant, system, or user`, {\n kind: \"inbound\",\n });\n }\n if (envelope.authorIsBot !== undefined && typeof envelope.authorIsBot !== \"boolean\") {\n throw new CrablineError(`${label}.authorIsBot must be a boolean`, { kind: \"inbound\" });\n }\n for (const field of [\"id\", \"text\", \"threadId\"] as const) {\n const value = envelope[field];\n if (value !== undefined && (typeof value !== \"string\" || value.length === 0)) {\n throw new CrablineError(`${label}.${field} must be a non-empty string`, {\n kind: \"inbound\",\n });\n }\n }\n }\n\n return payload as MockWebhookPayload;\n}\n\nfunction mockReplyText(params: { platform: ProviderPlatform; text: string }) {\n return `[${params.platform} mock] ${params.text}`;\n}\n\nexport class LocalMockProviderAdapter implements ProviderAdapter {\n readonly id;\n readonly platform;\n readonly status = \"ready\" as const;\n readonly supports: ProviderAdapter[\"supports\"];\n\n readonly #codec: LocalMockTargetCodec;\n readonly #config: ProviderConfig;\n readonly #options: LocalMockAdapterOptions;\n readonly #publicUrl: string | undefined;\n readonly #recorderPath: string;\n readonly #activeSends = new Set<Promise<SendResult>>();\n readonly #cleanupController = new AbortController();\n readonly #waitCursors = new Map<string, WaitCursorState>();\n #cleanupBegun = false;\n #cleanupPromise: Promise<void> | null = null;\n #server: StartedWebhookServer | null = null;\n #serverStarting: Promise<StartedWebhookServer> | null = null;\n\n constructor(params: {\n codec: LocalMockTargetCodec;\n config: ProviderConfig;\n id: string;\n options: LocalMockAdapterOptions;\n }) {\n this.id = params.id;\n this.platform = params.options.platform;\n this.supports = [...params.config.capabilities];\n this.#codec = params.codec;\n this.#config = params.config;\n this.#options = params.options;\n this.#publicUrl = params.options.publicUrl ?? params.options.webhook?.publicUrl;\n this.#recorderPath = toRecorderPath(params.id, params.options.recorderPath);\n }\n\n normalizeTarget(target: ProviderContext[\"fixture\"][\"target\"]): NormalizedTarget {\n this.#assertActive();\n return this.#codec.normalize(target);\n }\n\n async probe(context: ProviderContext): Promise<ProbeResult> {\n context.signal?.throwIfAborted();\n const server = await this.#ensureWebhookServer();\n context.signal?.throwIfAborted();\n const target = this.normalizeTarget(context.fixture.target);\n const details = [\n `${this.platform} local mock ready`,\n `recorder path ${this.#recorderPath}`,\n `${this.#options.endpointLabel} ${server.endpointUrl}`,\n ];\n if (this.#publicUrl) {\n details.push(`public webhook ${this.#publicUrl}`);\n }\n if (target.threadId) {\n details.push(`thread reachable ${target.threadId}`);\n } else if (target.channelId) {\n details.push(`channel reachable ${target.channelId}`);\n } else {\n details.push(`dm reachable ${this.#codec.resolveThreadId(context.fixture.target)}`);\n }\n return { details, healthy: true };\n }\n\n send(context: SendContext): Promise<SendResult> {\n try {\n this.#assertActive();\n } catch (error) {\n return Promise.reject(error);\n }\n const sending = this.#send(context);\n this.#activeSends.add(sending);\n void sending.then(\n () => this.#activeSends.delete(sending),\n () => this.#activeSends.delete(sending),\n );\n return sending;\n }\n\n async #send(context: SendContext): Promise<SendResult> {\n const { signal } = context;\n const threadId = this.#codec.resolveThreadId(context.fixture.target);\n const messageId = createMessageId(this.platform);\n signal?.throwIfAborted();\n await appendRecordedInbound(this.#recorderPath, {\n author: \"user\",\n id: messageId,\n provider: this.id,\n raw: {\n direction: \"outbound\",\n mode: context.mode,\n platform: this.platform,\n },\n recordedDirection: \"outbound\",\n sentAt: new Date().toISOString(),\n text: context.text,\n threadId,\n });\n\n if (context.mode !== \"send\" && context.fixture.target.behavior !== \"sink\") {\n await sleep(this.#config.loopback?.delayMs ?? 25, signal);\n signal?.throwIfAborted();\n await appendRecordedInbound(this.#recorderPath, {\n author: \"assistant\",\n id: `${messageId}-reply`,\n provider: this.id,\n raw: {\n direction: \"mock-reply\",\n mode: context.mode,\n platform: this.platform,\n },\n recordedDirection: \"inbound\",\n sentAt: new Date().toISOString(),\n text: mockReplyText({ platform: this.platform, text: context.text }),\n threadId,\n });\n }\n\n return {\n accepted: true,\n messageId,\n threadId,\n };\n }\n\n async waitForInbound(context: WaitContext): Promise<InboundEnvelope | null> {\n this.#assertActive();\n const signal = this.#signalFor(context.signal);\n await this.#ensureWebhookServer();\n const target = this.normalizeTarget(context.fixture.target);\n const expectedAuthor = context.fixture.inboundMatch.author;\n const channelId = context.threadId ?? target.threadId ?? target.channelId;\n const excludedIds = new Set(context.excludeIds ?? []);\n const cursorKey = JSON.stringify([context.nonce, context.since, channelId, expectedAuthor]);\n const cursorState = this.#waitCursors.get(cursorKey) ?? { active: 0 };\n const cursor = cursorState.cursor\n ? cloneRecordedInboundCursor(cursorState.cursor)\n : createRecordedInboundCursor();\n cursorState.active++;\n this.#waitCursors.delete(cursorKey);\n this.#waitCursors.set(cursorKey, cursorState);\n pruneInactiveWaitCursors(this.#waitCursors);\n\n try {\n const event = await waitForRecordedInbound({\n cursor,\n filePath: this.#recorderPath,\n matches: (candidate) =>\n candidate.provider === this.id &&\n !excludedIds.has(candidate.id) &&\n (expectedAuthor === \"any\" || candidate.author === expectedAuthor) &&\n (this.#options.matchesThread ?? isAddressInChannel)(\n candidate.threadId,\n channelId,\n target,\n candidate.raw,\n ),\n recordedDirection: \"inbound\",\n since: context.since,\n signal,\n timeoutMs: context.timeoutMs,\n });\n if (\n this.#waitCursors.get(cursorKey) === cursorState &&\n cursorHasAdvanced(cursor, cursorState.cursor)\n ) {\n cursorState.cursor = cursor;\n }\n return event;\n } finally {\n cursorState.active--;\n if (\n cursorState.active === 0 &&\n !cursorState.cursor &&\n this.#waitCursors.get(cursorKey) === cursorState\n ) {\n this.#waitCursors.delete(cursorKey);\n }\n }\n }\n\n async *watch(context: WatchContext): AsyncIterable<InboundEnvelope> {\n this.#assertActive();\n const signal = this.#signalFor(context.signal);\n await this.#ensureWebhookServer();\n const target = this.normalizeTarget(context.fixture.target);\n for await (const event of watchRecordedInbound({\n filePath: this.#recorderPath,\n matches: (entry) =>\n entry.provider === this.id &&\n (this.#options.matchesThread ?? isAddressInChannel)(\n entry.threadId,\n target.threadId ?? target.channelId,\n target,\n entry.raw,\n ),\n recordedDirection: \"inbound\",\n signal,\n since: context.since,\n })) {\n yield event;\n }\n }\n\n #beginCleanup(): void {\n if (this.#cleanupBegun) {\n return;\n }\n this.#cleanupBegun = true;\n this.#cleanupController.abort(this.#cleanedUpError());\n this.#waitCursors.clear();\n }\n\n async cleanup(): Promise<void> {\n this.#beginCleanup();\n this.#cleanupPromise ??= (async () => {\n const sends = [...this.#activeSends];\n const [serverResult] = await Promise.allSettled([this.#closeWebhookServer(), ...sends]);\n if (serverResult?.status === \"rejected\") {\n throw serverResult.reason;\n }\n })();\n await this.#cleanupPromise;\n }\n\n async #handleWebhook(request: Request): Promise<Response> {\n if (this.#cleanupBegun) {\n return new Response(\"provider is shutting down\", { status: 503 });\n }\n const rawBody = await request.text();\n const authenticationFailure = await this.#options.authenticateWebhookRequest?.(\n request,\n rawBody,\n );\n if (authenticationFailure) {\n return authenticationFailure;\n }\n const mediaType = request.headers.get(\"content-type\")?.split(\";\", 1)[0]?.trim().toLowerCase();\n let rawPayload: unknown;\n if (mediaType === \"application/json\") {\n try {\n rawPayload = JSON.parse(rawBody) as unknown;\n } catch {\n this.#options.settleWebhookRequest?.({\n accepted: false,\n payload: undefined,\n rawBody,\n });\n return new Response(\"invalid JSON\", { status: 400 });\n }\n } else {\n rawPayload = rawBody;\n }\n let settled = false;\n const settle = (accepted: boolean) => {\n if (settled) {\n return;\n }\n settled = true;\n this.#options.settleWebhookRequest?.({ accepted, payload: rawPayload, rawBody });\n };\n const respond = (response: Response) => {\n settle(response.ok);\n return response;\n };\n try {\n const directResponse = await this.#options.handleWebhookPayload?.(\n rawPayload,\n request,\n rawBody,\n );\n if (directResponse) {\n return respond(directResponse);\n }\n if (mediaType !== \"application/json\") {\n return respond(new Response(\"expected application/json\", { status: 415 }));\n }\n let payload: MockWebhookPayload;\n try {\n payload = normalizeWebhookPayload(\n this.#options.normalizeWebhookPayload\n ? this.#options.normalizeWebhookPayload(rawPayload)\n : rawPayload,\n );\n } catch (error) {\n if (error instanceof CrablineError && error.kind === \"inbound\") {\n return respond(new Response(ensureErrorMessage(error), { status: 400 }));\n }\n throw error;\n }\n\n const id = payload.message?.id ?? payload.id ?? createMessageId(this.platform);\n const threadId = payload.message?.threadId ?? payload.threadId;\n const text = payload.message?.text ?? payload.text;\n if (!threadId || !text) {\n return respond(\n new Response(\"payload requires message.threadId and message.text\", { status: 400 }),\n );\n }\n if (this.#cleanupBegun) {\n return respond(new Response(\"provider is shutting down\", { status: 503 }));\n }\n\n await appendRecordedInbound(this.#recorderPath, {\n author: authorFromPayload(payload),\n id,\n provider: this.id,\n raw: payload.message?.raw ?? payload.raw ?? payload,\n recordedDirection: \"inbound\",\n sentAt: new Date().toISOString(),\n text,\n threadId,\n });\n return respond(\n (await this.#options.createWebhookSuccessResponse?.(rawPayload, id)) ??\n new Response(JSON.stringify({ ok: true, id }), {\n headers: { \"content-type\": \"application/json\" },\n status: 200,\n }),\n );\n } catch (error) {\n settle(false);\n throw error;\n }\n }\n\n async #ensureWebhookServer(): Promise<StartedWebhookServer> {\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n if (this.#server) {\n return this.#server;\n }\n if (this.#serverStarting) {\n return await this.#serverStarting;\n }\n\n const webhook = this.#options.webhook;\n const host = webhook?.host ?? this.#options.defaultWebhook.host;\n const port = webhook?.port ?? this.#options.defaultWebhook.port;\n const webhookPath = webhook?.path ?? this.#options.defaultWebhook.path;\n const starting = (async () => {\n try {\n return await startWebhookServer({\n handle: (request) => this.#handleWebhook(request),\n host,\n path: webhookPath,\n port,\n });\n } catch (error) {\n throw new CrablineError(\n `${this.platform} local mock webhook server failed: ${ensureErrorMessage(error)}`,\n { cause: error, kind: \"connectivity\" },\n );\n }\n })();\n this.#serverStarting = starting;\n\n try {\n const server = await starting;\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n this.#server = server;\n return server;\n } finally {\n if (this.#serverStarting === starting) {\n this.#serverStarting = null;\n }\n }\n }\n\n async #closeWebhookServer(): Promise<void> {\n let server = this.#server;\n if (!server && this.#serverStarting) {\n try {\n server = await this.#serverStarting;\n } catch {\n return;\n }\n }\n if (!server) {\n return;\n }\n\n if (this.#server === server) {\n this.#server = null;\n }\n await server.close();\n }\n\n #cleanedUpError(): CrablineError {\n return new CrablineError(`Provider \"${this.id}\" has been cleaned up.`, { kind: \"config\" });\n }\n\n #assertActive(): void {\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n }\n\n #signalFor(signal?: AbortSignal): AbortSignal {\n return signal\n ? AbortSignal.any([signal, this.#cleanupController.signal])\n : this.#cleanupController.signal;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"local-mock.js","sourceRoot":"","sources":["../../../src/providers/local-mock.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { CrablineError, ensureErrorMessage } from \"../core/errors.js\";\nimport type { ProviderConfig, ProviderPlatform } from \"../config/schema.js\";\nimport { isJsonMediaType } from \"../servers/http.js\";\nimport {\n appendRecordedInbound,\n appendRecordedInboundBatch,\n cloneRecordedInboundCursor,\n createRecordedInboundCursor,\n waitForRecordedInbound,\n watchRecordedInbound,\n type RecordedInboundCursor,\n} from \"./recorder.js\";\nimport type {\n InboundEnvelope,\n NormalizedTarget,\n ProbeResult,\n ProviderAdapter,\n ProviderContext,\n SendContext,\n SendResult,\n WaitContext,\n WatchContext,\n} from \"./types.js\";\nimport { startWebhookServer, type StartedWebhookServer } from \"./webhook-server.js\";\n\nexport { createGenericLocalMockTargetCodec } from \"./target-normalizers.js\";\n\nexport type LocalMockWebhookConfig = {\n host?: string;\n path?: string;\n port?: number;\n publicUrl?: string | undefined;\n};\n\nexport type LocalMockAdapterOptions = {\n authenticateWebhookRequest?: (\n request: Request,\n rawBody: string,\n ) => Promise<Response | undefined> | Response | undefined;\n createWebhookSuccessResponse?: (payload: unknown, id: string) => Promise<Response> | Response;\n defaultWebhook: Required<Pick<LocalMockWebhookConfig, \"host\" | \"path\" | \"port\">>;\n endpointLabel: string;\n matchesThread?: (\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: NormalizedTarget,\n raw?: unknown,\n ) => boolean;\n handleWebhookPayload?: (\n payload: unknown,\n request: Request,\n rawBody: string,\n ) => Promise<Response | undefined> | Response | undefined;\n normalizeWebhookPayload?: (payload: unknown) => unknown;\n platform: ProviderPlatform;\n preflightWebhookRequest?: (\n request: Request,\n ) => Promise<Response | undefined> | Response | undefined;\n publicUrl?: string | undefined;\n recorderPath?: string | undefined;\n settleWebhookRequest?: (params: { accepted: boolean; payload: unknown; rawBody: string }) => void;\n webhook?: LocalMockWebhookConfig | undefined;\n webhookCleanupGraceMs?: number | undefined;\n webhookMethods?: readonly string[] | undefined;\n};\n\nconst MAX_WAIT_CURSORS = 64;\nconst DEFAULT_WEBHOOK_CLEANUP_GRACE_MS = 250;\n\ntype WaitCursorState = {\n active: number;\n cursor?: RecordedInboundCursor | undefined;\n};\n\nfunction cursorHasAdvanced(\n candidate: RecordedInboundCursor,\n current: RecordedInboundCursor | undefined,\n): boolean {\n if (!current) {\n return true;\n }\n if (candidate.readState.generation !== current.readState.generation) {\n return candidate.readState.generation > current.readState.generation;\n }\n if (candidate.readState.offset !== current.readState.offset) {\n return candidate.readState.offset > current.readState.offset;\n }\n return candidate.buffered.length < current.buffered.length;\n}\n\nfunction pruneInactiveWaitCursors(cursors: Map<string, WaitCursorState>): void {\n for (const [key, state] of cursors) {\n if (cursors.size <= MAX_WAIT_CURSORS) {\n return;\n }\n if (state.active === 0) {\n cursors.delete(key);\n }\n }\n}\n\nexport type LocalMockTargetCodec = {\n normalize(target: ProviderContext[\"fixture\"][\"target\"]): NormalizedTarget;\n resolveThreadId(target: ProviderContext[\"fixture\"][\"target\"]): string;\n};\n\ntype MockWebhookPayload = {\n author?: \"assistant\" | \"system\" | \"user\";\n authorIsBot?: boolean;\n id?: string;\n message?: {\n author?: \"assistant\" | \"system\" | \"user\";\n authorIsBot?: boolean;\n id?: string;\n raw?: unknown;\n text?: string;\n threadId?: string;\n };\n raw?: unknown;\n text?: string;\n threadId?: string;\n};\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const cleanup = () => signal?.removeEventListener(\"abort\", abort);\n const abort = () => {\n if (timer) {\n clearTimeout(timer);\n }\n cleanup();\n reject(signal?.reason ?? new Error(\"Provider operation aborted.\"));\n };\n timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n\nfunction isAddressInChannel(threadId: string, channelId?: string): boolean {\n if (!channelId) {\n return true;\n }\n return threadId === channelId || threadId.startsWith(`${channelId}:`);\n}\n\nfunction createMessageId(platform: ProviderPlatform) {\n return `${platform}-mock-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction unsafeGeneratedRecorderProviderId(providerId: string): boolean {\n return (\n path.posix.parse(providerId).root !== \"\" ||\n path.win32.parse(providerId).root !== \"\" ||\n providerId.split(/[\\\\/]/u).includes(\"..\")\n );\n}\n\nexport function resolveGeneratedLocalMockRecorderPath(providerId: string, suffix = \"\"): string {\n const recorderDirectory = path.resolve(\".crabline\", \"recorders\");\n if (unsafeGeneratedRecorderProviderId(providerId)) {\n throw new CrablineError(\"Provider ID cannot contain absolute or parent-directory paths.\", {\n kind: \"config\",\n });\n }\n\n const recorderPath = path.resolve(recorderDirectory, `${providerId}${suffix}.jsonl`);\n const relativePath = path.relative(recorderDirectory, recorderPath);\n if (\n relativePath === \"..\" ||\n relativePath.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relativePath)\n ) {\n throw new CrablineError(\"Generated provider recorder path escapes its directory.\", {\n kind: \"config\",\n });\n }\n return recorderPath;\n}\n\nfunction toRecorderPath(providerId: string, configuredPath?: string): string {\n return configuredPath\n ? path.resolve(configuredPath)\n : resolveGeneratedLocalMockRecorderPath(providerId);\n}\n\nfunction authorFromPayload(payload: MockWebhookPayload): InboundEnvelope[\"author\"] {\n const explicit = payload.message?.author ?? payload.author;\n if (explicit) {\n return explicit;\n }\n return (payload.message?.authorIsBot ?? payload.authorIsBot ?? true) ? \"assistant\" : \"user\";\n}\n\nfunction normalizeWebhookPayload(payload: unknown): MockWebhookPayload {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new CrablineError(\"mock webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n const record = payload as Record<string, unknown>;\n const messageValue = record.message;\n if (\n messageValue !== undefined &&\n (!messageValue || typeof messageValue !== \"object\" || Array.isArray(messageValue))\n ) {\n throw new CrablineError(\"mock webhook payload message must be an object\", {\n kind: \"inbound\",\n });\n }\n\n for (const [label, envelope] of [\n [\"payload\", record],\n [\"payload.message\", messageValue as Record<string, unknown> | undefined],\n ] as const) {\n if (!envelope) {\n continue;\n }\n if (\n envelope.author !== undefined &&\n envelope.author !== \"assistant\" &&\n envelope.author !== \"system\" &&\n envelope.author !== \"user\"\n ) {\n throw new CrablineError(`${label}.author must be assistant, system, or user`, {\n kind: \"inbound\",\n });\n }\n if (envelope.authorIsBot !== undefined && typeof envelope.authorIsBot !== \"boolean\") {\n throw new CrablineError(`${label}.authorIsBot must be a boolean`, { kind: \"inbound\" });\n }\n for (const field of [\"id\", \"threadId\"] as const) {\n const value = envelope[field];\n if (value !== undefined && (typeof value !== \"string\" || value.length === 0)) {\n throw new CrablineError(`${label}.${field} must be a non-empty string`, {\n kind: \"inbound\",\n });\n }\n }\n if (envelope.text !== undefined && typeof envelope.text !== \"string\") {\n throw new CrablineError(`${label}.text must be a string`, {\n kind: \"inbound\",\n });\n }\n }\n\n return payload as MockWebhookPayload;\n}\n\nfunction mockReplyText(params: { platform: ProviderPlatform; text: string }) {\n return `[${params.platform} mock] ${params.text}`;\n}\n\nfunction reportPostCommitWebhookSettlementFailure(providerId: string, error: unknown): void {\n try {\n process.emitWarning(\n `Webhook acceptance committed for provider ${JSON.stringify(providerId)}, but settlement failed: ${ensureErrorMessage(error)}`,\n {\n code: \"CRABLINE_WEBHOOK_SETTLEMENT\",\n type: \"ProviderWebhookWarning\",\n },\n );\n } catch {\n // Error reporting must not change the result of an accepted webhook.\n }\n}\n\nasync function settleWebhookHandlers(\n providerId: string,\n handlers: readonly Promise<Response>[],\n graceMs: number,\n): Promise<void> {\n if (handlers.length === 0) {\n return;\n }\n let timer: NodeJS.Timeout | undefined;\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(\n new CrablineError(\n `Provider \"${providerId}\" webhook handlers did not settle within ${graceMs}ms after cleanup.`,\n { kind: \"timeout\" },\n ),\n );\n }, graceMs);\n });\n try {\n await Promise.race([Promise.allSettled(handlers).then(() => undefined), deadline]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport class LocalMockProviderAdapter implements ProviderAdapter {\n readonly id;\n readonly platform;\n readonly status = \"ready\" as const;\n readonly supports: ProviderAdapter[\"supports\"];\n\n readonly #codec: LocalMockTargetCodec;\n readonly #config: ProviderConfig;\n readonly #options: LocalMockAdapterOptions;\n readonly #publicUrl: string | undefined;\n readonly #recorderPath: string;\n readonly #webhookCleanupGraceMs: number;\n readonly #activeSends = new Set<Promise<SendResult>>();\n readonly #activeWebhookHandlers = new Set<Promise<Response>>();\n readonly #cleanupController = new AbortController();\n readonly #waitCursors = new Map<string, WaitCursorState>();\n #cleanupBegun = false;\n #cleanupPromise: Promise<void> | null = null;\n #server: StartedWebhookServer | null = null;\n #serverClosing: Promise<void> | null = null;\n #serverStarting: Promise<StartedWebhookServer> | null = null;\n\n constructor(params: {\n codec: LocalMockTargetCodec;\n config: ProviderConfig;\n id: string;\n options: LocalMockAdapterOptions;\n }) {\n this.id = params.id;\n this.platform = params.options.platform;\n this.supports = [...params.config.capabilities];\n this.#codec = params.codec;\n this.#config = params.config;\n this.#options = params.options;\n this.#publicUrl = params.options.publicUrl ?? params.options.webhook?.publicUrl;\n this.#recorderPath = toRecorderPath(params.id, params.options.recorderPath);\n const webhookCleanupGraceMs =\n params.options.webhookCleanupGraceMs ?? DEFAULT_WEBHOOK_CLEANUP_GRACE_MS;\n if (!Number.isSafeInteger(webhookCleanupGraceMs) || webhookCleanupGraceMs < 1) {\n throw new CrablineError(\"Webhook cleanup grace must be a positive safe integer.\", {\n kind: \"config\",\n });\n }\n this.#webhookCleanupGraceMs = webhookCleanupGraceMs;\n this.#installCleanupFence();\n }\n\n normalizeTarget(target: ProviderContext[\"fixture\"][\"target\"]): NormalizedTarget {\n this.#assertActive();\n return this.#codec.normalize(target);\n }\n\n async probe(context: ProviderContext): Promise<ProbeResult> {\n context.signal?.throwIfAborted();\n const server = await this.#ensureWebhookServer();\n context.signal?.throwIfAborted();\n const target = this.normalizeTarget(context.fixture.target);\n const details = [\n `${this.platform} local mock ready`,\n `recorder path ${this.#recorderPath}`,\n `${this.#options.endpointLabel} ${server.endpointUrl}`,\n ];\n if (this.#publicUrl) {\n details.push(`public webhook ${this.#publicUrl}`);\n }\n if (target.threadId) {\n details.push(`thread reachable ${target.threadId}`);\n } else if (target.channelId) {\n details.push(`channel reachable ${target.channelId}`);\n } else {\n details.push(`dm reachable ${this.#codec.resolveThreadId(context.fixture.target)}`);\n }\n return { details, healthy: true };\n }\n\n send(context: SendContext): Promise<SendResult> {\n try {\n this.#assertActive();\n } catch (error) {\n return Promise.reject(error);\n }\n const sending = this.#send(context);\n this.#activeSends.add(sending);\n void sending.then(\n () => this.#activeSends.delete(sending),\n () => this.#activeSends.delete(sending),\n );\n return sending;\n }\n\n async #send(context: SendContext): Promise<SendResult> {\n const signal = this.#signalFor(context.signal);\n const threadId = this.#codec.resolveThreadId(context.fixture.target);\n const messageId = createMessageId(this.platform);\n signal.throwIfAborted();\n const events: Parameters<typeof appendRecordedInboundBatch>[1] = [\n {\n author: \"user\",\n id: messageId,\n provider: this.id,\n raw: {\n direction: \"outbound\",\n mode: context.mode,\n platform: this.platform,\n },\n recordedDirection: \"outbound\",\n sentAt: new Date().toISOString(),\n text: context.text,\n threadId,\n },\n ];\n\n if (context.mode !== \"send\" && context.fixture.target.behavior !== \"sink\") {\n await sleep(this.#config.loopback?.delayMs ?? 25, signal);\n signal.throwIfAborted();\n events.push({\n author: \"assistant\",\n id: `${messageId}-reply`,\n provider: this.id,\n raw: {\n direction: \"mock-reply\",\n mode: context.mode,\n platform: this.platform,\n },\n recordedDirection: \"inbound\",\n sentAt: new Date().toISOString(),\n text: mockReplyText({ platform: this.platform, text: context.text }),\n threadId,\n });\n }\n await appendRecordedInboundBatch(this.#recorderPath, events);\n\n return {\n accepted: true,\n messageId,\n threadId,\n };\n }\n\n async waitForInbound(context: WaitContext): Promise<InboundEnvelope | null> {\n this.#assertActive();\n const signal = this.#signalFor(context.signal);\n await this.#ensureWebhookServer();\n const target = this.normalizeTarget(context.fixture.target);\n const expectedAuthor = context.fixture.inboundMatch.author;\n const channelId = context.threadId ?? target.threadId ?? target.channelId;\n const excludedIds = new Set(context.excludeIds ?? []);\n const cursorKey = JSON.stringify([context.nonce, context.since, channelId, expectedAuthor]);\n const cursorState = this.#waitCursors.get(cursorKey) ?? { active: 0 };\n const cursor = cursorState.cursor\n ? cloneRecordedInboundCursor(cursorState.cursor)\n : createRecordedInboundCursor();\n cursorState.active++;\n this.#waitCursors.delete(cursorKey);\n this.#waitCursors.set(cursorKey, cursorState);\n pruneInactiveWaitCursors(this.#waitCursors);\n\n try {\n const event = await waitForRecordedInbound({\n cursor,\n filePath: this.#recorderPath,\n matches: (candidate) =>\n candidate.provider === this.id &&\n !excludedIds.has(candidate.id) &&\n (expectedAuthor === \"any\" || candidate.author === expectedAuthor) &&\n (this.#options.matchesThread ?? isAddressInChannel)(\n candidate.threadId,\n channelId,\n target,\n candidate.raw,\n ),\n recordedDirection: \"inbound\",\n since: context.since,\n signal,\n timeoutMs: context.timeoutMs,\n });\n if (\n this.#waitCursors.get(cursorKey) === cursorState &&\n cursorHasAdvanced(cursor, cursorState.cursor)\n ) {\n cursorState.cursor = cursor;\n }\n return event;\n } finally {\n cursorState.active--;\n if (\n cursorState.active === 0 &&\n !cursorState.cursor &&\n this.#waitCursors.get(cursorKey) === cursorState\n ) {\n this.#waitCursors.delete(cursorKey);\n }\n pruneInactiveWaitCursors(this.#waitCursors);\n }\n }\n\n async *watch(context: WatchContext): AsyncIterable<InboundEnvelope> {\n this.#assertActive();\n const signal = this.#signalFor(context.signal);\n await this.#ensureWebhookServer();\n const target = this.normalizeTarget(context.fixture.target);\n for await (const event of watchRecordedInbound({\n filePath: this.#recorderPath,\n matches: (entry) =>\n entry.provider === this.id &&\n (this.#options.matchesThread ?? isAddressInChannel)(\n entry.threadId,\n target.threadId ?? target.channelId,\n target,\n entry.raw,\n ),\n recordedDirection: \"inbound\",\n signal,\n since: context.since,\n })) {\n yield event;\n }\n }\n\n #beginCleanup(): void {\n if (this.#cleanupBegun) {\n return;\n }\n this.#cleanupBegun = true;\n this.#cleanupController.abort(this.#cleanedUpError());\n this.#waitCursors.clear();\n this.#serverClosing = this.#closeWebhookServer();\n void this.#serverClosing.catch(() => undefined);\n }\n\n #installCleanupFence(): void {\n const adapter = this as ProviderAdapter;\n if (adapter.beginCleanup) {\n return;\n }\n Object.defineProperty(adapter, \"beginCleanup\", {\n value: () => this.#beginCleanup(),\n });\n }\n\n async cleanup(): Promise<void> {\n this.#beginCleanup();\n this.#cleanupPromise ??= (async () => {\n const sends = [...this.#activeSends];\n const webhookHandlers = [...this.#activeWebhookHandlers];\n const results = await Promise.allSettled([\n this.#serverClosing,\n ...sends,\n settleWebhookHandlers(this.id, webhookHandlers, this.#webhookCleanupGraceMs),\n ]);\n const serverResult = results[0];\n if (serverResult?.status === \"rejected\") {\n throw serverResult.reason;\n }\n const webhookResult = results.at(-1);\n if (webhookResult?.status === \"rejected\") {\n throw webhookResult.reason;\n }\n })();\n await this.#cleanupPromise;\n }\n\n #handleWebhook(request: Request): Promise<Response> {\n if (this.#cleanupBegun) {\n return Promise.resolve(new Response(\"provider is shutting down\", { status: 503 }));\n }\n const admittedRequest = new Request(request, {\n signal: this.#signalFor(request.signal),\n });\n const handling = this.#handleAdmittedWebhook(admittedRequest);\n this.#activeWebhookHandlers.add(handling);\n void handling.then(\n () => this.#activeWebhookHandlers.delete(handling),\n () => this.#activeWebhookHandlers.delete(handling),\n );\n return handling;\n }\n\n async #handleAdmittedWebhook(request: Request): Promise<Response> {\n const rawBody = await request.text();\n const authenticationFailure = await this.#options.authenticateWebhookRequest?.(\n request,\n rawBody,\n );\n if (authenticationFailure) {\n return authenticationFailure;\n }\n const isJson = isJsonMediaType(request.headers.get(\"content-type\") ?? \"\");\n let rawPayload: unknown;\n if (isJson) {\n try {\n rawPayload = JSON.parse(rawBody) as unknown;\n } catch {\n this.#options.settleWebhookRequest?.({\n accepted: false,\n payload: undefined,\n rawBody,\n });\n return new Response(\"invalid JSON\", { status: 400 });\n }\n } else {\n rawPayload = rawBody;\n }\n let settled = false;\n const settle = (accepted: boolean) => {\n if (settled) {\n return;\n }\n settled = true;\n this.#options.settleWebhookRequest?.({ accepted, payload: rawPayload, rawBody });\n };\n const respond = (response: Response) => {\n settle(response.ok);\n return response;\n };\n let uncommittedResponse: Response | undefined;\n const settleCommittedAcceptance = () => {\n if (settled) {\n return;\n }\n settled = true;\n try {\n this.#options.settleWebhookRequest?.({\n accepted: true,\n payload: rawPayload,\n rawBody,\n });\n } catch (error) {\n reportPostCommitWebhookSettlementFailure(this.id, error);\n }\n };\n try {\n const directResponse = await this.#options.handleWebhookPayload?.(\n rawPayload,\n request,\n rawBody,\n );\n if (directResponse) {\n return respond(directResponse);\n }\n if (!isJson) {\n return respond(new Response(\"expected application/json\", { status: 415 }));\n }\n let payload: MockWebhookPayload;\n try {\n payload = normalizeWebhookPayload(\n this.#options.normalizeWebhookPayload\n ? this.#options.normalizeWebhookPayload(rawPayload)\n : rawPayload,\n );\n } catch (error) {\n if (error instanceof CrablineError && error.kind === \"inbound\") {\n return respond(new Response(ensureErrorMessage(error), { status: 400 }));\n }\n throw error;\n }\n\n const id = payload.message?.id ?? payload.id ?? createMessageId(this.platform);\n const threadId = payload.message?.threadId ?? payload.threadId;\n const text = payload.message?.text ?? payload.text;\n if (!threadId || text === undefined) {\n return respond(\n new Response(\"payload requires message.threadId and message.text\", { status: 400 }),\n );\n }\n const successResponse =\n (await this.#options.createWebhookSuccessResponse?.(rawPayload, id)) ??\n new Response(JSON.stringify({ ok: true, id }), {\n headers: { \"content-type\": \"application/json\" },\n status: 200,\n });\n uncommittedResponse = successResponse;\n await appendRecordedInbound(this.#recorderPath, {\n author: authorFromPayload(payload),\n id,\n provider: this.id,\n raw: payload.message?.raw ?? payload.raw ?? payload,\n recordedDirection: \"inbound\",\n sentAt: new Date().toISOString(),\n text,\n threadId,\n });\n settleCommittedAcceptance();\n uncommittedResponse = undefined;\n return successResponse;\n } catch (error) {\n void uncommittedResponse?.body?.cancel(error).catch(() => undefined);\n settle(false);\n throw error;\n }\n }\n\n async #ensureWebhookServer(): Promise<StartedWebhookServer> {\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n if (this.#server) {\n return this.#server;\n }\n if (this.#serverStarting) {\n return await this.#serverStarting;\n }\n\n const webhook = this.#options.webhook;\n const host = webhook?.host ?? this.#options.defaultWebhook.host;\n const port = webhook?.port ?? this.#options.defaultWebhook.port;\n const webhookPath = webhook?.path ?? this.#options.defaultWebhook.path;\n const starting = (async () => {\n try {\n return await startWebhookServer({\n handle: (request) => this.#handleWebhook(request),\n host,\n methods:\n this.#options.webhookMethods ??\n (this.#options.handleWebhookPayload ? [\"GET\", \"POST\"] : [\"POST\"]),\n path: webhookPath,\n port,\n ...(this.#options.preflightWebhookRequest\n ? { preflight: this.#options.preflightWebhookRequest }\n : {}),\n });\n } catch (error) {\n throw new CrablineError(\n `${this.platform} local mock webhook server failed: ${ensureErrorMessage(error)}`,\n { cause: error, kind: \"connectivity\" },\n );\n }\n })();\n this.#serverStarting = starting;\n\n try {\n const server = await starting;\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n this.#server = server;\n return server;\n } finally {\n if (this.#serverStarting === starting) {\n this.#serverStarting = null;\n }\n }\n }\n\n async #closeWebhookServer(): Promise<void> {\n let server = this.#server;\n if (!server && this.#serverStarting) {\n try {\n server = await this.#serverStarting;\n } catch {\n return;\n }\n }\n if (!server) {\n return;\n }\n\n if (this.#server === server) {\n this.#server = null;\n }\n await server.close();\n }\n\n #cleanedUpError(): CrablineError {\n return new CrablineError(`Provider \"${this.id}\" has been cleaned up.`, { kind: \"config\" });\n }\n\n #assertActive(): void {\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n }\n\n #signalFor(signal?: AbortSignal): AbortSignal {\n return signal\n ? AbortSignal.any([signal, this.#cleanupController.signal])\n : this.#cleanupController.signal;\n }\n}\n"]}
|
|
@@ -2,5 +2,7 @@ export type NativeIdRule = {
|
|
|
2
2
|
example: string;
|
|
3
3
|
name: string;
|
|
4
4
|
pattern: RegExp;
|
|
5
|
+
validate?: ((value: string) => boolean) | undefined;
|
|
5
6
|
};
|
|
7
|
+
export declare function matchesNativeId(value: string, rule: NativeIdRule): boolean;
|
|
6
8
|
export declare function numericNativeId(value: number | bigint): string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-ids.js","sourceRoot":"","sources":["../../../src/providers/native-ids.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"native-ids.js","sourceRoot":"","sources":["../../../src/providers/native-ids.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,IAAkB;IAC/D,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAsB;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["export type NativeIdRule = {\n example: string;\n name: string;\n pattern: RegExp;\n validate?: ((value: string) => boolean) | undefined;\n};\n\nexport function matchesNativeId(value: string, rule: NativeIdRule): boolean {\n return rule.pattern.test(value) && (rule.validate?.(value) ?? true);\n}\n\nexport function numericNativeId(value: number | bigint): string | undefined {\n if (typeof value === \"number\" && !Number.isSafeInteger(value)) {\n return undefined;\n }\n return value.toString();\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WindowsDirectorySecuritySnapshot } from "../platform/windows-acl.js";
|
|
1
2
|
import type { InboundEnvelope } from "./types.js";
|
|
2
3
|
export type RecordableInboundEnvelope = InboundEnvelope & {
|
|
3
4
|
recordedDirection?: "inbound" | "outbound";
|
|
@@ -5,13 +6,18 @@ export type RecordableInboundEnvelope = InboundEnvelope & {
|
|
|
5
6
|
export type RecordedInboundEnvelope = RecordableInboundEnvelope & {
|
|
6
7
|
recordedAt: string;
|
|
7
8
|
};
|
|
9
|
+
export declare class ProviderRecorderCommittedError extends AggregateError {
|
|
10
|
+
readonly committed = true;
|
|
11
|
+
readonly indeterminate = true;
|
|
12
|
+
constructor(filePath: string, cause: unknown, relatedErrors?: unknown[]);
|
|
13
|
+
}
|
|
8
14
|
type IncrementalReadState = {
|
|
9
15
|
caughtUp: boolean;
|
|
10
16
|
continuity: Buffer;
|
|
11
17
|
generation: number;
|
|
12
18
|
identity: {
|
|
13
|
-
dev:
|
|
14
|
-
ino:
|
|
19
|
+
dev: bigint;
|
|
20
|
+
ino: bigint;
|
|
15
21
|
} | undefined;
|
|
16
22
|
offset: number;
|
|
17
23
|
pending: Buffer;
|
|
@@ -23,8 +29,13 @@ export type RecordedInboundCursor = {
|
|
|
23
29
|
};
|
|
24
30
|
export declare function createRecordedInboundCursor(): RecordedInboundCursor;
|
|
25
31
|
export declare function cloneRecordedInboundCursor(cursor: RecordedInboundCursor): RecordedInboundCursor;
|
|
32
|
+
export declare function secureProviderRecorderLockRoot(root: string, currentUserId: number | undefined, options?: {
|
|
33
|
+
platform?: NodeJS.Platform;
|
|
34
|
+
createWindowsDirectory?: (directoryPath: string) => Promise<void>;
|
|
35
|
+
readWindowsDirectorySecuritySnapshot?: (directoryPath: string) => Promise<WindowsDirectorySecuritySnapshot>;
|
|
36
|
+
}): Promise<string>;
|
|
26
37
|
export declare function appendRecordedInbound(filePath: string, event: RecordableInboundEnvelope): Promise<RecordedInboundEnvelope>;
|
|
27
|
-
export declare function appendRecordedInboundBatch(filePath: string, events:
|
|
38
|
+
export declare function appendRecordedInboundBatch(filePath: string, events: RecordableInboundEnvelope[]): Promise<RecordedInboundEnvelope[]>;
|
|
28
39
|
export declare function readRecordedInbound(filePath: string): Promise<RecordedInboundEnvelope[]>;
|
|
29
40
|
export declare function waitForRecordedInbound(params: {
|
|
30
41
|
cursor?: RecordedInboundCursor | undefined;
|