@contractspec/integration.providers-impls 3.7.5 → 3.7.7
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 +80 -241
- package/dist/impls/composio-fallback-resolver.d.ts +4 -4
- package/dist/impls/composio-fallback-resolver.js +73 -73
- package/dist/impls/composio-mcp.d.ts +1 -1
- package/dist/impls/composio-proxies.d.ts +5 -5
- package/dist/impls/composio-sdk.d.ts +1 -1
- package/dist/impls/elevenlabs-voice.d.ts +1 -1
- package/dist/impls/fal-voice.d.ts +1 -1
- package/dist/impls/gcs-storage.d.ts +1 -1
- package/dist/impls/gmail-inbound.d.ts +1 -1
- package/dist/impls/gradium-voice.d.ts +2 -2
- package/dist/impls/health/base-health-provider.d.ts +1 -1
- package/dist/impls/health/official-health-providers.d.ts +1 -1
- package/dist/impls/health/providers.d.ts +1 -1
- package/dist/impls/health-provider-factory.d.ts +1 -1
- package/dist/impls/index.d.ts +30 -30
- package/dist/impls/index.js +2150 -2150
- package/dist/impls/mistral-conversational.d.ts +1 -1
- package/dist/impls/mistral-conversational.js +159 -159
- package/dist/impls/posthog-reader.d.ts +1 -1
- package/dist/impls/provider-factory.d.ts +11 -11
- package/dist/impls/provider-factory.js +2073 -2073
- package/dist/index.d.ts +12 -12
- package/dist/index.js +2057 -2057
- package/dist/node/impls/composio-fallback-resolver.js +73 -73
- package/dist/node/impls/index.js +2150 -2150
- package/dist/node/impls/mistral-conversational.js +159 -159
- package/dist/node/impls/provider-factory.js +2073 -2073
- package/dist/node/index.js +2057 -2057
- package/dist/node/secrets/provider.js +2 -2
- package/dist/secrets/provider.d.ts +2 -2
- package/dist/secrets/provider.js +2 -2
- package/package.json +13 -13
package/dist/node/index.js
CHANGED
|
@@ -222,79 +222,6 @@ class ComposioMcpProvider {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
// src/impls/composio-sdk.ts
|
|
226
|
-
class ComposioSdkProvider {
|
|
227
|
-
config;
|
|
228
|
-
client;
|
|
229
|
-
constructor(config) {
|
|
230
|
-
this.config = config;
|
|
231
|
-
}
|
|
232
|
-
async executeTool(toolName, args) {
|
|
233
|
-
const client = await this.getClient();
|
|
234
|
-
const userId = args._userId ?? "default";
|
|
235
|
-
try {
|
|
236
|
-
const entity = await client.getEntity(userId);
|
|
237
|
-
const result = await entity.execute(toolName, args);
|
|
238
|
-
return { success: true, data: result };
|
|
239
|
-
} catch (error) {
|
|
240
|
-
return {
|
|
241
|
-
success: false,
|
|
242
|
-
error: error instanceof Error ? error.message : String(error)
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
async searchTools(query) {
|
|
247
|
-
const client = await this.getClient();
|
|
248
|
-
try {
|
|
249
|
-
const tools = await client.actions.list({ query, limit: 20 });
|
|
250
|
-
return tools.map((t) => ({
|
|
251
|
-
name: t.name,
|
|
252
|
-
description: t.description ?? "",
|
|
253
|
-
toolkit: t.appName ?? "",
|
|
254
|
-
parameters: t.parameters ?? {}
|
|
255
|
-
}));
|
|
256
|
-
} catch {
|
|
257
|
-
return [];
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
async getConnectedAccounts(userId) {
|
|
261
|
-
const client = await this.getClient();
|
|
262
|
-
try {
|
|
263
|
-
const entity = await client.getEntity(userId);
|
|
264
|
-
const connections = await entity.getConnections();
|
|
265
|
-
return connections.map((c) => ({
|
|
266
|
-
id: c.id,
|
|
267
|
-
appName: c.appName,
|
|
268
|
-
status: c.status
|
|
269
|
-
}));
|
|
270
|
-
} catch {
|
|
271
|
-
return [];
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
async getMcpConfig(userId) {
|
|
275
|
-
const client = await this.getClient();
|
|
276
|
-
try {
|
|
277
|
-
const entity = await client.getEntity(userId);
|
|
278
|
-
return {
|
|
279
|
-
url: entity.getMcpUrl(),
|
|
280
|
-
headers: entity.getMcpHeaders()
|
|
281
|
-
};
|
|
282
|
-
} catch {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
async getClient() {
|
|
287
|
-
if (this.client)
|
|
288
|
-
return this.client;
|
|
289
|
-
const { Composio } = await import("@composio/core");
|
|
290
|
-
this.client = new Composio({
|
|
291
|
-
apiKey: this.config.apiKey,
|
|
292
|
-
...this.config.baseUrl ? { baseUrl: this.config.baseUrl } : {}
|
|
293
|
-
});
|
|
294
|
-
return this.client;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
225
|
// src/impls/composio-proxies.ts
|
|
299
226
|
function composioToolName(toolkit, action) {
|
|
300
227
|
return `${toolkit.toUpperCase()}_${action.toUpperCase()}`;
|
|
@@ -596,6 +523,79 @@ class ComposioGenericProxy {
|
|
|
596
523
|
}
|
|
597
524
|
}
|
|
598
525
|
|
|
526
|
+
// src/impls/composio-sdk.ts
|
|
527
|
+
class ComposioSdkProvider {
|
|
528
|
+
config;
|
|
529
|
+
client;
|
|
530
|
+
constructor(config) {
|
|
531
|
+
this.config = config;
|
|
532
|
+
}
|
|
533
|
+
async executeTool(toolName, args) {
|
|
534
|
+
const client = await this.getClient();
|
|
535
|
+
const userId = args._userId ?? "default";
|
|
536
|
+
try {
|
|
537
|
+
const entity = await client.getEntity(userId);
|
|
538
|
+
const result = await entity.execute(toolName, args);
|
|
539
|
+
return { success: true, data: result };
|
|
540
|
+
} catch (error) {
|
|
541
|
+
return {
|
|
542
|
+
success: false,
|
|
543
|
+
error: error instanceof Error ? error.message : String(error)
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
async searchTools(query) {
|
|
548
|
+
const client = await this.getClient();
|
|
549
|
+
try {
|
|
550
|
+
const tools = await client.actions.list({ query, limit: 20 });
|
|
551
|
+
return tools.map((t) => ({
|
|
552
|
+
name: t.name,
|
|
553
|
+
description: t.description ?? "",
|
|
554
|
+
toolkit: t.appName ?? "",
|
|
555
|
+
parameters: t.parameters ?? {}
|
|
556
|
+
}));
|
|
557
|
+
} catch {
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async getConnectedAccounts(userId) {
|
|
562
|
+
const client = await this.getClient();
|
|
563
|
+
try {
|
|
564
|
+
const entity = await client.getEntity(userId);
|
|
565
|
+
const connections = await entity.getConnections();
|
|
566
|
+
return connections.map((c) => ({
|
|
567
|
+
id: c.id,
|
|
568
|
+
appName: c.appName,
|
|
569
|
+
status: c.status
|
|
570
|
+
}));
|
|
571
|
+
} catch {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async getMcpConfig(userId) {
|
|
576
|
+
const client = await this.getClient();
|
|
577
|
+
try {
|
|
578
|
+
const entity = await client.getEntity(userId);
|
|
579
|
+
return {
|
|
580
|
+
url: entity.getMcpUrl(),
|
|
581
|
+
headers: entity.getMcpHeaders()
|
|
582
|
+
};
|
|
583
|
+
} catch {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
async getClient() {
|
|
588
|
+
if (this.client)
|
|
589
|
+
return this.client;
|
|
590
|
+
const { Composio } = await import("@composio/core");
|
|
591
|
+
this.client = new Composio({
|
|
592
|
+
apiKey: this.config.apiKey,
|
|
593
|
+
...this.config.baseUrl ? { baseUrl: this.config.baseUrl } : {}
|
|
594
|
+
});
|
|
595
|
+
return this.client;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
599
|
// src/impls/composio-fallback-resolver.ts
|
|
600
600
|
class ComposioFallbackResolver {
|
|
601
601
|
mcpProvider;
|
|
@@ -3960,476 +3960,519 @@ function asString(value) {
|
|
|
3960
3960
|
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
3961
3961
|
}
|
|
3962
3962
|
|
|
3963
|
-
// src/impls/
|
|
3964
|
-
import {
|
|
3963
|
+
// src/impls/jira.ts
|
|
3964
|
+
import { Buffer as Buffer3 } from "node:buffer";
|
|
3965
3965
|
|
|
3966
|
-
class
|
|
3967
|
-
|
|
3968
|
-
|
|
3966
|
+
class JiraProjectManagementProvider {
|
|
3967
|
+
siteUrl;
|
|
3968
|
+
authHeader;
|
|
3969
|
+
defaults;
|
|
3970
|
+
fetchFn;
|
|
3969
3971
|
constructor(options) {
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
+
this.siteUrl = normalizeSiteUrl(options.siteUrl);
|
|
3973
|
+
this.authHeader = buildAuthHeader(options.email, options.apiToken);
|
|
3974
|
+
this.defaults = {
|
|
3975
|
+
projectKey: options.projectKey,
|
|
3976
|
+
issueType: options.issueType,
|
|
3977
|
+
defaultLabels: options.defaultLabels,
|
|
3978
|
+
issueTypeMap: options.issueTypeMap
|
|
3979
|
+
};
|
|
3980
|
+
this.fetchFn = options.fetch ?? fetch;
|
|
3981
|
+
}
|
|
3982
|
+
async createWorkItem(input) {
|
|
3983
|
+
const projectKey = input.projectId ?? this.defaults.projectKey;
|
|
3984
|
+
if (!projectKey) {
|
|
3985
|
+
throw new Error("Jira projectKey is required to create work items.");
|
|
3972
3986
|
}
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3987
|
+
const issueType = resolveIssueType(input.type, this.defaults);
|
|
3988
|
+
const description = buildJiraDescription(input.description);
|
|
3989
|
+
const labels = mergeLabels(this.defaults.defaultLabels, input.tags);
|
|
3990
|
+
const priority = mapPriority(input.priority);
|
|
3991
|
+
const payload = {
|
|
3992
|
+
fields: {
|
|
3993
|
+
project: { key: projectKey },
|
|
3994
|
+
summary: input.title,
|
|
3995
|
+
description,
|
|
3996
|
+
issuetype: { name: issueType },
|
|
3997
|
+
labels,
|
|
3998
|
+
priority: priority ? { name: priority } : undefined,
|
|
3999
|
+
assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
|
|
4000
|
+
duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
|
|
4001
|
+
}
|
|
4002
|
+
};
|
|
4003
|
+
const response = await this.fetchFn(`${this.siteUrl}/rest/api/3/issue`, {
|
|
4004
|
+
method: "POST",
|
|
4005
|
+
headers: {
|
|
4006
|
+
Authorization: this.authHeader,
|
|
4007
|
+
"Content-Type": "application/json",
|
|
4008
|
+
Accept: "application/json"
|
|
4009
|
+
},
|
|
4010
|
+
body: JSON.stringify(payload)
|
|
3977
4011
|
});
|
|
3978
|
-
|
|
4012
|
+
if (!response.ok) {
|
|
4013
|
+
const body = await response.text();
|
|
4014
|
+
throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
|
|
4015
|
+
}
|
|
4016
|
+
const data = await response.json();
|
|
4017
|
+
return {
|
|
4018
|
+
id: data.id ?? data.key ?? "",
|
|
4019
|
+
title: input.title,
|
|
4020
|
+
url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
|
|
4021
|
+
status: input.status,
|
|
4022
|
+
priority: input.priority,
|
|
4023
|
+
tags: input.tags,
|
|
4024
|
+
projectId: projectKey,
|
|
4025
|
+
externalId: input.externalId,
|
|
4026
|
+
metadata: input.metadata
|
|
4027
|
+
};
|
|
3979
4028
|
}
|
|
3980
|
-
async
|
|
3981
|
-
const
|
|
3982
|
-
const
|
|
3983
|
-
|
|
4029
|
+
async createWorkItems(items) {
|
|
4030
|
+
const created = [];
|
|
4031
|
+
for (const item of items) {
|
|
4032
|
+
created.push(await this.createWorkItem(item));
|
|
4033
|
+
}
|
|
4034
|
+
return created;
|
|
3984
4035
|
}
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
if (typeof delta.content === "string") {
|
|
3997
|
-
if (delta.content.length > 0) {
|
|
3998
|
-
aggregatedParts.push({ type: "text", text: delta.content });
|
|
3999
|
-
yield {
|
|
4000
|
-
type: "message_delta",
|
|
4001
|
-
delta: { type: "text", text: delta.content },
|
|
4002
|
-
index: choice.index
|
|
4003
|
-
};
|
|
4004
|
-
}
|
|
4005
|
-
} else if (Array.isArray(delta.content)) {
|
|
4006
|
-
for (const chunk of delta.content) {
|
|
4007
|
-
if (chunk.type === "text" && "text" in chunk) {
|
|
4008
|
-
aggregatedParts.push({ type: "text", text: chunk.text });
|
|
4009
|
-
yield {
|
|
4010
|
-
type: "message_delta",
|
|
4011
|
-
delta: { type: "text", text: chunk.text },
|
|
4012
|
-
index: choice.index
|
|
4013
|
-
};
|
|
4014
|
-
}
|
|
4015
|
-
}
|
|
4016
|
-
}
|
|
4017
|
-
if (delta.toolCalls) {
|
|
4018
|
-
let localIndex = 0;
|
|
4019
|
-
for (const call of delta.toolCalls) {
|
|
4020
|
-
const toolCall = this.fromMistralToolCall(call, localIndex);
|
|
4021
|
-
aggregatedToolCalls.push(toolCall);
|
|
4022
|
-
yield {
|
|
4023
|
-
type: "tool_call",
|
|
4024
|
-
call: toolCall,
|
|
4025
|
-
index: choice.index
|
|
4026
|
-
};
|
|
4027
|
-
localIndex += 1;
|
|
4028
|
-
}
|
|
4029
|
-
}
|
|
4030
|
-
if (choice.finishReason && choice.finishReason !== "null") {
|
|
4031
|
-
finishReason = choice.finishReason;
|
|
4032
|
-
}
|
|
4033
|
-
}
|
|
4034
|
-
if (event.data.usage) {
|
|
4035
|
-
const usageEntry = this.fromUsage(event.data.usage);
|
|
4036
|
-
if (usageEntry) {
|
|
4037
|
-
usage = usageEntry;
|
|
4038
|
-
yield { type: "usage", usage: usageEntry };
|
|
4039
|
-
}
|
|
4040
|
-
}
|
|
4041
|
-
}
|
|
4042
|
-
const message = {
|
|
4043
|
-
role: "assistant",
|
|
4044
|
-
content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
|
|
4045
|
-
};
|
|
4046
|
-
if (aggregatedToolCalls.length > 0) {
|
|
4047
|
-
message.content = [
|
|
4048
|
-
...aggregatedToolCalls,
|
|
4049
|
-
...aggregatedParts.length ? aggregatedParts : []
|
|
4050
|
-
];
|
|
4051
|
-
}
|
|
4052
|
-
yield {
|
|
4053
|
-
type: "end",
|
|
4054
|
-
response: {
|
|
4055
|
-
message,
|
|
4056
|
-
usage,
|
|
4057
|
-
finishReason: mapFinishReason(finishReason)
|
|
4058
|
-
}
|
|
4059
|
-
};
|
|
4060
|
-
}
|
|
4061
|
-
async countTokens(_messages) {
|
|
4062
|
-
throw new Error("Mistral API does not currently support token counting");
|
|
4063
|
-
}
|
|
4064
|
-
buildChatRequest(messages, options) {
|
|
4065
|
-
const model = options.model ?? this.defaultModel;
|
|
4066
|
-
const mappedMessages = messages.map((message) => this.toMistralMessage(message));
|
|
4067
|
-
const request = {
|
|
4068
|
-
model,
|
|
4069
|
-
messages: mappedMessages
|
|
4070
|
-
};
|
|
4071
|
-
if (options.temperature != null) {
|
|
4072
|
-
request.temperature = options.temperature;
|
|
4073
|
-
}
|
|
4074
|
-
if (options.topP != null) {
|
|
4075
|
-
request.topP = options.topP;
|
|
4076
|
-
}
|
|
4077
|
-
if (options.maxOutputTokens != null) {
|
|
4078
|
-
request.maxTokens = options.maxOutputTokens;
|
|
4079
|
-
}
|
|
4080
|
-
if (options.stopSequences?.length) {
|
|
4081
|
-
request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
|
|
4082
|
-
}
|
|
4083
|
-
if (options.tools?.length) {
|
|
4084
|
-
request.tools = options.tools.map((tool) => ({
|
|
4085
|
-
type: "function",
|
|
4086
|
-
function: {
|
|
4087
|
-
name: tool.name,
|
|
4088
|
-
description: tool.description,
|
|
4089
|
-
parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
|
|
4090
|
-
}
|
|
4091
|
-
}));
|
|
4092
|
-
}
|
|
4093
|
-
if (options.responseFormat === "json") {
|
|
4094
|
-
request.responseFormat = { type: "json_object" };
|
|
4095
|
-
}
|
|
4096
|
-
return request;
|
|
4097
|
-
}
|
|
4098
|
-
buildLLMResponse(response) {
|
|
4099
|
-
const firstChoice = response.choices[0];
|
|
4100
|
-
if (!firstChoice) {
|
|
4101
|
-
return {
|
|
4102
|
-
message: {
|
|
4103
|
-
role: "assistant",
|
|
4104
|
-
content: [{ type: "text", text: "" }]
|
|
4105
|
-
},
|
|
4106
|
-
usage: this.fromUsage(response.usage),
|
|
4107
|
-
raw: response
|
|
4108
|
-
};
|
|
4109
|
-
}
|
|
4110
|
-
const message = this.fromAssistantMessage(firstChoice.message);
|
|
4111
|
-
return {
|
|
4112
|
-
message,
|
|
4113
|
-
usage: this.fromUsage(response.usage),
|
|
4114
|
-
finishReason: mapFinishReason(firstChoice.finishReason),
|
|
4115
|
-
raw: response
|
|
4116
|
-
};
|
|
4036
|
+
}
|
|
4037
|
+
function normalizeSiteUrl(siteUrl) {
|
|
4038
|
+
return siteUrl.replace(/\/$/, "");
|
|
4039
|
+
}
|
|
4040
|
+
function buildAuthHeader(email, apiToken) {
|
|
4041
|
+
const token = Buffer3.from(`${email}:${apiToken}`).toString("base64");
|
|
4042
|
+
return `Basic ${token}`;
|
|
4043
|
+
}
|
|
4044
|
+
function resolveIssueType(type, defaults) {
|
|
4045
|
+
if (type && defaults.issueTypeMap?.[type]) {
|
|
4046
|
+
return defaults.issueTypeMap[type] ?? defaults.issueType ?? "Task";
|
|
4117
4047
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4048
|
+
return defaults.issueType ?? "Task";
|
|
4049
|
+
}
|
|
4050
|
+
function mapPriority(priority) {
|
|
4051
|
+
switch (priority) {
|
|
4052
|
+
case "urgent":
|
|
4053
|
+
return "Highest";
|
|
4054
|
+
case "high":
|
|
4055
|
+
return "High";
|
|
4056
|
+
case "medium":
|
|
4057
|
+
return "Medium";
|
|
4058
|
+
case "low":
|
|
4059
|
+
return "Low";
|
|
4060
|
+
case "none":
|
|
4061
|
+
default:
|
|
4120
4062
|
return;
|
|
4121
|
-
return {
|
|
4122
|
-
promptTokens: usage.promptTokens ?? 0,
|
|
4123
|
-
completionTokens: usage.completionTokens ?? 0,
|
|
4124
|
-
totalTokens: usage.totalTokens ?? 0
|
|
4125
|
-
};
|
|
4126
|
-
}
|
|
4127
|
-
fromAssistantMessage(message) {
|
|
4128
|
-
const parts = [];
|
|
4129
|
-
if (typeof message.content === "string") {
|
|
4130
|
-
parts.push({ type: "text", text: message.content });
|
|
4131
|
-
} else if (Array.isArray(message.content)) {
|
|
4132
|
-
message.content.forEach((chunk) => {
|
|
4133
|
-
if (chunk.type === "text" && "text" in chunk) {
|
|
4134
|
-
parts.push({ type: "text", text: chunk.text });
|
|
4135
|
-
}
|
|
4136
|
-
});
|
|
4137
|
-
}
|
|
4138
|
-
const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
|
|
4139
|
-
if (toolCalls.length > 0) {
|
|
4140
|
-
parts.splice(0, 0, ...toolCalls);
|
|
4141
|
-
}
|
|
4142
|
-
if (parts.length === 0) {
|
|
4143
|
-
parts.push({ type: "text", text: "" });
|
|
4144
|
-
}
|
|
4145
|
-
return {
|
|
4146
|
-
role: "assistant",
|
|
4147
|
-
content: parts
|
|
4148
|
-
};
|
|
4149
4063
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4064
|
+
}
|
|
4065
|
+
function mergeLabels(defaults, tags) {
|
|
4066
|
+
const merged = new Set;
|
|
4067
|
+
(defaults ?? []).forEach((label) => merged.add(label));
|
|
4068
|
+
(tags ?? []).forEach((tag) => merged.add(tag));
|
|
4069
|
+
const result = [...merged];
|
|
4070
|
+
return result.length > 0 ? result : undefined;
|
|
4071
|
+
}
|
|
4072
|
+
function buildJiraDescription(description) {
|
|
4073
|
+
if (!description)
|
|
4074
|
+
return;
|
|
4075
|
+
const lines = description.split(/\r?\n/).filter((line) => line.trim());
|
|
4076
|
+
const content = lines.map((line) => ({
|
|
4077
|
+
type: "paragraph",
|
|
4078
|
+
content: [{ type: "text", text: line }]
|
|
4079
|
+
}));
|
|
4080
|
+
if (content.length === 0)
|
|
4081
|
+
return;
|
|
4082
|
+
return { type: "doc", version: 1, content };
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
// src/impls/linear.ts
|
|
4086
|
+
import { LinearClient } from "@linear/sdk";
|
|
4087
|
+
var PRIORITY_MAP = {
|
|
4088
|
+
urgent: 1,
|
|
4089
|
+
high: 2,
|
|
4090
|
+
medium: 3,
|
|
4091
|
+
low: 4,
|
|
4092
|
+
none: 0
|
|
4093
|
+
};
|
|
4094
|
+
|
|
4095
|
+
class LinearProjectManagementProvider {
|
|
4096
|
+
client;
|
|
4097
|
+
defaults;
|
|
4098
|
+
constructor(options) {
|
|
4099
|
+
this.client = options.client ?? new LinearClient({ apiKey: options.apiKey });
|
|
4100
|
+
this.defaults = {
|
|
4101
|
+
teamId: options.teamId,
|
|
4102
|
+
projectId: options.projectId,
|
|
4103
|
+
assigneeId: options.assigneeId,
|
|
4104
|
+
stateId: options.stateId,
|
|
4105
|
+
labelIds: options.labelIds,
|
|
4106
|
+
tagLabelMap: options.tagLabelMap
|
|
4157
4107
|
};
|
|
4158
4108
|
}
|
|
4159
|
-
|
|
4160
|
-
const
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
case "system":
|
|
4164
|
-
return {
|
|
4165
|
-
role: "system",
|
|
4166
|
-
content: textContent ?? ""
|
|
4167
|
-
};
|
|
4168
|
-
case "user":
|
|
4169
|
-
return {
|
|
4170
|
-
role: "user",
|
|
4171
|
-
content: textContent ?? ""
|
|
4172
|
-
};
|
|
4173
|
-
case "assistant":
|
|
4174
|
-
return {
|
|
4175
|
-
role: "assistant",
|
|
4176
|
-
content: toolCalls.length > 0 ? null : textContent ?? "",
|
|
4177
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
4178
|
-
};
|
|
4179
|
-
case "tool":
|
|
4180
|
-
return {
|
|
4181
|
-
role: "tool",
|
|
4182
|
-
content: textContent ?? "",
|
|
4183
|
-
toolCallId: message.toolCallId ?? toolCalls[0]?.id
|
|
4184
|
-
};
|
|
4185
|
-
default:
|
|
4186
|
-
return {
|
|
4187
|
-
role: "user",
|
|
4188
|
-
content: textContent ?? ""
|
|
4189
|
-
};
|
|
4109
|
+
async createWorkItem(input) {
|
|
4110
|
+
const teamId = this.defaults.teamId;
|
|
4111
|
+
if (!teamId) {
|
|
4112
|
+
throw new Error("Linear teamId is required to create work items.");
|
|
4190
4113
|
}
|
|
4114
|
+
const payload = await this.client.createIssue({
|
|
4115
|
+
teamId,
|
|
4116
|
+
title: input.title,
|
|
4117
|
+
description: input.description,
|
|
4118
|
+
priority: mapPriority2(input.priority),
|
|
4119
|
+
estimate: input.estimate,
|
|
4120
|
+
assigneeId: input.assigneeId ?? this.defaults.assigneeId,
|
|
4121
|
+
projectId: input.projectId ?? this.defaults.projectId,
|
|
4122
|
+
stateId: this.defaults.stateId,
|
|
4123
|
+
labelIds: resolveLabelIds(this.defaults, input.tags)
|
|
4124
|
+
});
|
|
4125
|
+
const issue = await payload.issue;
|
|
4126
|
+
const state = issue ? await issue.state : undefined;
|
|
4127
|
+
return {
|
|
4128
|
+
id: issue?.id ?? "",
|
|
4129
|
+
title: issue?.title ?? input.title,
|
|
4130
|
+
url: issue?.url ?? undefined,
|
|
4131
|
+
status: state?.name ?? undefined,
|
|
4132
|
+
priority: input.priority,
|
|
4133
|
+
tags: input.tags,
|
|
4134
|
+
projectId: input.projectId ?? this.defaults.projectId,
|
|
4135
|
+
externalId: input.externalId,
|
|
4136
|
+
metadata: input.metadata
|
|
4137
|
+
};
|
|
4191
4138
|
}
|
|
4192
|
-
|
|
4193
|
-
const
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
extractToolCalls(message) {
|
|
4199
|
-
const toolCallParts = message.content.filter((part) => part.type === "tool-call");
|
|
4200
|
-
return toolCallParts.map((call, index) => ({
|
|
4201
|
-
id: call.id ?? `call_${index}`,
|
|
4202
|
-
type: "function",
|
|
4203
|
-
index,
|
|
4204
|
-
function: {
|
|
4205
|
-
name: call.name,
|
|
4206
|
-
arguments: call.arguments
|
|
4207
|
-
}
|
|
4208
|
-
}));
|
|
4139
|
+
async createWorkItems(items) {
|
|
4140
|
+
const created = [];
|
|
4141
|
+
for (const item of items) {
|
|
4142
|
+
created.push(await this.createWorkItem(item));
|
|
4143
|
+
}
|
|
4144
|
+
return created;
|
|
4209
4145
|
}
|
|
4210
4146
|
}
|
|
4211
|
-
function
|
|
4212
|
-
if (!
|
|
4147
|
+
function mapPriority2(priority) {
|
|
4148
|
+
if (!priority)
|
|
4213
4149
|
return;
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
default:
|
|
4226
|
-
return;
|
|
4150
|
+
return PRIORITY_MAP[priority] ?? undefined;
|
|
4151
|
+
}
|
|
4152
|
+
function resolveLabelIds(defaults, tags) {
|
|
4153
|
+
const labelIds = new Set;
|
|
4154
|
+
(defaults.labelIds ?? []).forEach((id) => labelIds.add(id));
|
|
4155
|
+
if (tags && defaults.tagLabelMap) {
|
|
4156
|
+
tags.forEach((tag) => {
|
|
4157
|
+
const mapped = defaults.tagLabelMap?.[tag];
|
|
4158
|
+
if (mapped)
|
|
4159
|
+
labelIds.add(mapped);
|
|
4160
|
+
});
|
|
4227
4161
|
}
|
|
4162
|
+
const merged = [...labelIds];
|
|
4163
|
+
return merged.length > 0 ? merged : undefined;
|
|
4228
4164
|
}
|
|
4229
4165
|
|
|
4230
|
-
// src/impls/
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4166
|
+
// src/impls/messaging-github.ts
|
|
4167
|
+
class GithubMessagingProvider {
|
|
4168
|
+
token;
|
|
4169
|
+
defaultOwner;
|
|
4170
|
+
defaultRepo;
|
|
4171
|
+
apiBaseUrl;
|
|
4236
4172
|
constructor(options) {
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
this.
|
|
4241
|
-
|
|
4242
|
-
|
|
4173
|
+
this.token = options.token;
|
|
4174
|
+
this.defaultOwner = options.defaultOwner;
|
|
4175
|
+
this.defaultRepo = options.defaultRepo;
|
|
4176
|
+
this.apiBaseUrl = options.apiBaseUrl ?? "https://api.github.com";
|
|
4177
|
+
}
|
|
4178
|
+
async sendMessage(input) {
|
|
4179
|
+
const target = this.resolveTarget(input);
|
|
4180
|
+
const response = await fetch(`${this.apiBaseUrl}/repos/${target.owner}/${target.repo}/issues/${target.issueNumber}/comments`, {
|
|
4181
|
+
method: "POST",
|
|
4182
|
+
headers: {
|
|
4183
|
+
authorization: `Bearer ${this.token}`,
|
|
4184
|
+
accept: "application/vnd.github+json",
|
|
4185
|
+
"content-type": "application/json"
|
|
4186
|
+
},
|
|
4187
|
+
body: JSON.stringify({ body: input.text })
|
|
4243
4188
|
});
|
|
4244
|
-
|
|
4189
|
+
const body = await response.json();
|
|
4190
|
+
if (!response.ok || !body.id) {
|
|
4191
|
+
throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
4192
|
+
}
|
|
4193
|
+
return {
|
|
4194
|
+
id: String(body.id),
|
|
4195
|
+
providerMessageId: body.node_id,
|
|
4196
|
+
status: "sent",
|
|
4197
|
+
sentAt: new Date,
|
|
4198
|
+
metadata: {
|
|
4199
|
+
url: body.html_url ?? "",
|
|
4200
|
+
owner: target.owner,
|
|
4201
|
+
repo: target.repo,
|
|
4202
|
+
issueNumber: String(target.issueNumber)
|
|
4203
|
+
}
|
|
4204
|
+
};
|
|
4245
4205
|
}
|
|
4246
|
-
async
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4206
|
+
async updateMessage(messageId, input) {
|
|
4207
|
+
const owner = input.metadata?.owner ?? this.defaultOwner;
|
|
4208
|
+
const repo = input.metadata?.repo ?? this.defaultRepo;
|
|
4209
|
+
if (!owner || !repo) {
|
|
4210
|
+
throw new Error("GitHub updateMessage requires owner and repo metadata.");
|
|
4211
|
+
}
|
|
4212
|
+
const response = await fetch(`${this.apiBaseUrl}/repos/${owner}/${repo}/issues/comments/${messageId}`, {
|
|
4213
|
+
method: "PATCH",
|
|
4214
|
+
headers: {
|
|
4215
|
+
authorization: `Bearer ${this.token}`,
|
|
4216
|
+
accept: "application/vnd.github+json",
|
|
4217
|
+
"content-type": "application/json"
|
|
4218
|
+
},
|
|
4219
|
+
body: JSON.stringify({ body: input.text })
|
|
4253
4220
|
});
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4221
|
+
const body = await response.json();
|
|
4222
|
+
if (!response.ok || !body.id) {
|
|
4223
|
+
throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
4224
|
+
}
|
|
4225
|
+
return {
|
|
4226
|
+
id: String(body.id),
|
|
4227
|
+
providerMessageId: body.node_id,
|
|
4228
|
+
status: "sent",
|
|
4229
|
+
sentAt: new Date,
|
|
4230
|
+
metadata: {
|
|
4231
|
+
url: body.html_url ?? "",
|
|
4232
|
+
owner,
|
|
4233
|
+
repo
|
|
4234
|
+
}
|
|
4235
|
+
};
|
|
4261
4236
|
}
|
|
4262
|
-
|
|
4263
|
-
const
|
|
4264
|
-
|
|
4265
|
-
|
|
4237
|
+
resolveTarget(input) {
|
|
4238
|
+
const parsedRecipient = parseRecipient(input.recipientId);
|
|
4239
|
+
const owner = parsedRecipient?.owner ?? this.defaultOwner;
|
|
4240
|
+
const repo = parsedRecipient?.repo ?? this.defaultRepo;
|
|
4241
|
+
const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
|
|
4242
|
+
if (!owner || !repo || issueNumber == null) {
|
|
4243
|
+
throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
|
|
4266
4244
|
}
|
|
4267
|
-
return
|
|
4245
|
+
return {
|
|
4246
|
+
owner,
|
|
4247
|
+
repo,
|
|
4248
|
+
issueNumber
|
|
4249
|
+
};
|
|
4268
4250
|
}
|
|
4269
4251
|
}
|
|
4252
|
+
function parseRecipient(value) {
|
|
4253
|
+
if (!value)
|
|
4254
|
+
return null;
|
|
4255
|
+
const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
4256
|
+
if (!match)
|
|
4257
|
+
return null;
|
|
4258
|
+
const owner = match[1];
|
|
4259
|
+
const repo = match[2];
|
|
4260
|
+
const issueNumber = Number(match[3]);
|
|
4261
|
+
if (!owner || !repo || !Number.isInteger(issueNumber)) {
|
|
4262
|
+
return null;
|
|
4263
|
+
}
|
|
4264
|
+
return { owner, repo, issueNumber };
|
|
4265
|
+
}
|
|
4266
|
+
function parseIssueNumber(value) {
|
|
4267
|
+
if (!value)
|
|
4268
|
+
return null;
|
|
4269
|
+
const numeric = Number(value);
|
|
4270
|
+
return Number.isInteger(numeric) ? numeric : null;
|
|
4271
|
+
}
|
|
4270
4272
|
|
|
4271
|
-
// src/impls/
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
wav: "audio/wav",
|
|
4277
|
-
ogg: "audio/ogg",
|
|
4278
|
-
pcm: "audio/pcm",
|
|
4279
|
-
opus: "audio/opus"
|
|
4280
|
-
};
|
|
4281
|
-
|
|
4282
|
-
class MistralSttProvider {
|
|
4283
|
-
apiKey;
|
|
4284
|
-
defaultModel;
|
|
4285
|
-
defaultLanguage;
|
|
4286
|
-
baseUrl;
|
|
4287
|
-
fetchImpl;
|
|
4273
|
+
// src/impls/messaging-slack.ts
|
|
4274
|
+
class SlackMessagingProvider {
|
|
4275
|
+
botToken;
|
|
4276
|
+
defaultChannelId;
|
|
4277
|
+
apiBaseUrl;
|
|
4288
4278
|
constructor(options) {
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
this.apiKey = options.apiKey;
|
|
4293
|
-
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
|
|
4294
|
-
this.defaultLanguage = options.defaultLanguage;
|
|
4295
|
-
this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
|
|
4296
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
4279
|
+
this.botToken = options.botToken;
|
|
4280
|
+
this.defaultChannelId = options.defaultChannelId;
|
|
4281
|
+
this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
|
|
4297
4282
|
}
|
|
4298
|
-
async
|
|
4299
|
-
const
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
const
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4283
|
+
async sendMessage(input) {
|
|
4284
|
+
const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
|
|
4285
|
+
if (!channel) {
|
|
4286
|
+
throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
|
|
4287
|
+
}
|
|
4288
|
+
const payload = {
|
|
4289
|
+
channel,
|
|
4290
|
+
text: input.text,
|
|
4291
|
+
mrkdwn: input.markdown ?? true,
|
|
4292
|
+
thread_ts: input.threadId
|
|
4293
|
+
};
|
|
4294
|
+
const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
|
|
4295
|
+
method: "POST",
|
|
4296
|
+
headers: {
|
|
4297
|
+
authorization: `Bearer ${this.botToken}`,
|
|
4298
|
+
"content-type": "application/json"
|
|
4299
|
+
},
|
|
4300
|
+
body: JSON.stringify(payload)
|
|
4301
|
+
});
|
|
4302
|
+
const body = await response.json();
|
|
4303
|
+
if (!response.ok || !body.ok || !body.ts) {
|
|
4304
|
+
throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
|
|
4305
|
+
}
|
|
4306
|
+
return {
|
|
4307
|
+
id: `slack:${body.channel ?? channel}:${body.ts}`,
|
|
4308
|
+
providerMessageId: body.ts,
|
|
4309
|
+
status: "sent",
|
|
4310
|
+
sentAt: new Date,
|
|
4311
|
+
metadata: {
|
|
4312
|
+
channelId: body.channel ?? channel
|
|
4313
|
+
}
|
|
4314
|
+
};
|
|
4315
|
+
}
|
|
4316
|
+
async updateMessage(messageId, input) {
|
|
4317
|
+
const channel = input.channelId ?? this.defaultChannelId;
|
|
4318
|
+
if (!channel) {
|
|
4319
|
+
throw new Error("Slack updateMessage requires channelId or defaultChannelId.");
|
|
4311
4320
|
}
|
|
4312
|
-
const response = await
|
|
4321
|
+
const response = await fetch(`${this.apiBaseUrl}/chat.update`, {
|
|
4313
4322
|
method: "POST",
|
|
4314
4323
|
headers: {
|
|
4315
|
-
|
|
4324
|
+
authorization: `Bearer ${this.botToken}`,
|
|
4325
|
+
"content-type": "application/json"
|
|
4316
4326
|
},
|
|
4317
|
-
body:
|
|
4327
|
+
body: JSON.stringify({
|
|
4328
|
+
channel,
|
|
4329
|
+
ts: messageId,
|
|
4330
|
+
text: input.text,
|
|
4331
|
+
mrkdwn: input.markdown ?? true
|
|
4332
|
+
})
|
|
4318
4333
|
});
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
throw new Error(`
|
|
4334
|
+
const body = await response.json();
|
|
4335
|
+
if (!response.ok || !body.ok || !body.ts) {
|
|
4336
|
+
throw new Error(`Slack updateMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
|
|
4322
4337
|
}
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
if (segments.length === 0 && text.length > 0) {
|
|
4333
|
-
segments.push({
|
|
4334
|
-
text,
|
|
4335
|
-
startMs: 0,
|
|
4336
|
-
endMs: input.audio.durationMs ?? 0
|
|
4337
|
-
});
|
|
4338
|
+
return {
|
|
4339
|
+
id: `slack:${body.channel ?? channel}:${body.ts}`,
|
|
4340
|
+
providerMessageId: body.ts,
|
|
4341
|
+
status: "sent",
|
|
4342
|
+
sentAt: new Date,
|
|
4343
|
+
metadata: {
|
|
4344
|
+
channelId: body.channel ?? channel
|
|
4345
|
+
}
|
|
4346
|
+
};
|
|
4338
4347
|
}
|
|
4339
|
-
const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
|
|
4340
|
-
const topLevelWords = parseWordTimings(record.words);
|
|
4341
|
-
const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
|
|
4342
|
-
const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
|
|
4343
|
-
const speakers = dedupeSpeakers(segments);
|
|
4344
|
-
return {
|
|
4345
|
-
text,
|
|
4346
|
-
segments,
|
|
4347
|
-
language,
|
|
4348
|
-
durationMs,
|
|
4349
|
-
speakers: speakers.length > 0 ? speakers : undefined,
|
|
4350
|
-
wordTimings
|
|
4351
|
-
};
|
|
4352
4348
|
}
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4349
|
+
|
|
4350
|
+
// src/impls/messaging-whatsapp-meta.ts
|
|
4351
|
+
class MetaWhatsappMessagingProvider {
|
|
4352
|
+
accessToken;
|
|
4353
|
+
phoneNumberId;
|
|
4354
|
+
apiVersion;
|
|
4355
|
+
constructor(options) {
|
|
4356
|
+
this.accessToken = options.accessToken;
|
|
4357
|
+
this.phoneNumberId = options.phoneNumberId;
|
|
4358
|
+
this.apiVersion = options.apiVersion ?? "v22.0";
|
|
4356
4359
|
}
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
if (!text) {
|
|
4362
|
-
continue;
|
|
4360
|
+
async sendMessage(input) {
|
|
4361
|
+
const to = input.recipientId;
|
|
4362
|
+
if (!to) {
|
|
4363
|
+
throw new Error("Meta WhatsApp sendMessage requires recipientId.");
|
|
4363
4364
|
}
|
|
4364
|
-
const
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4365
|
+
const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
|
|
4366
|
+
method: "POST",
|
|
4367
|
+
headers: {
|
|
4368
|
+
authorization: `Bearer ${this.accessToken}`,
|
|
4369
|
+
"content-type": "application/json"
|
|
4370
|
+
},
|
|
4371
|
+
body: JSON.stringify({
|
|
4372
|
+
messaging_product: "whatsapp",
|
|
4373
|
+
to,
|
|
4374
|
+
type: "text",
|
|
4375
|
+
text: {
|
|
4376
|
+
body: input.text,
|
|
4377
|
+
preview_url: false
|
|
4378
|
+
}
|
|
4379
|
+
})
|
|
4373
4380
|
});
|
|
4381
|
+
const body = await response.json();
|
|
4382
|
+
const messageId = body.messages?.[0]?.id;
|
|
4383
|
+
if (!response.ok || !messageId) {
|
|
4384
|
+
const errorCode = body.error?.code != null ? String(body.error.code) : "";
|
|
4385
|
+
throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
|
|
4386
|
+
}
|
|
4387
|
+
return {
|
|
4388
|
+
id: messageId,
|
|
4389
|
+
providerMessageId: messageId,
|
|
4390
|
+
status: "sent",
|
|
4391
|
+
sentAt: new Date,
|
|
4392
|
+
metadata: {
|
|
4393
|
+
phoneNumberId: this.phoneNumberId
|
|
4394
|
+
}
|
|
4395
|
+
};
|
|
4374
4396
|
}
|
|
4375
|
-
return parsed;
|
|
4376
4397
|
}
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4398
|
+
|
|
4399
|
+
// src/impls/messaging-whatsapp-twilio.ts
|
|
4400
|
+
import { Buffer as Buffer4 } from "node:buffer";
|
|
4401
|
+
|
|
4402
|
+
class TwilioWhatsappMessagingProvider {
|
|
4403
|
+
accountSid;
|
|
4404
|
+
authToken;
|
|
4405
|
+
fromNumber;
|
|
4406
|
+
constructor(options) {
|
|
4407
|
+
this.accountSid = options.accountSid;
|
|
4408
|
+
this.authToken = options.authToken;
|
|
4409
|
+
this.fromNumber = options.fromNumber;
|
|
4380
4410
|
}
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
const
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
const endSeconds = readNumber2(wordRecord, "end");
|
|
4387
|
-
if (!word || startSeconds == null || endSeconds == null) {
|
|
4388
|
-
continue;
|
|
4411
|
+
async sendMessage(input) {
|
|
4412
|
+
const to = normalizeWhatsappAddress(input.recipientId);
|
|
4413
|
+
const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
|
|
4414
|
+
if (!to) {
|
|
4415
|
+
throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
|
|
4389
4416
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
startMs: secondsToMs(startSeconds),
|
|
4393
|
-
endMs: secondsToMs(endSeconds),
|
|
4394
|
-
confidence: readNumber2(wordRecord, "confidence")
|
|
4395
|
-
});
|
|
4396
|
-
}
|
|
4397
|
-
return words;
|
|
4398
|
-
}
|
|
4399
|
-
function dedupeSpeakers(segments) {
|
|
4400
|
-
const seen = new Set;
|
|
4401
|
-
const speakers = [];
|
|
4402
|
-
for (const segment of segments) {
|
|
4403
|
-
if (!segment.speakerId || seen.has(segment.speakerId)) {
|
|
4404
|
-
continue;
|
|
4417
|
+
if (!from) {
|
|
4418
|
+
throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
|
|
4405
4419
|
}
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4420
|
+
const params = new URLSearchParams;
|
|
4421
|
+
params.set("To", to);
|
|
4422
|
+
params.set("From", from);
|
|
4423
|
+
params.set("Body", input.text);
|
|
4424
|
+
const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
|
|
4425
|
+
const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
|
|
4426
|
+
method: "POST",
|
|
4427
|
+
headers: {
|
|
4428
|
+
authorization: `Basic ${auth}`,
|
|
4429
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
4430
|
+
},
|
|
4431
|
+
body: params.toString()
|
|
4410
4432
|
});
|
|
4433
|
+
const body = await response.json();
|
|
4434
|
+
if (!response.ok || !body.sid) {
|
|
4435
|
+
throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
|
|
4436
|
+
}
|
|
4437
|
+
return {
|
|
4438
|
+
id: body.sid,
|
|
4439
|
+
providerMessageId: body.sid,
|
|
4440
|
+
status: mapTwilioStatus(body.status),
|
|
4441
|
+
sentAt: new Date,
|
|
4442
|
+
errorCode: body.error_code != null ? String(body.error_code) : undefined,
|
|
4443
|
+
errorMessage: body.error_message ?? undefined,
|
|
4444
|
+
metadata: {
|
|
4445
|
+
from,
|
|
4446
|
+
to
|
|
4447
|
+
}
|
|
4448
|
+
};
|
|
4411
4449
|
}
|
|
4412
|
-
return speakers;
|
|
4413
|
-
}
|
|
4414
|
-
function normalizeBaseUrl(url) {
|
|
4415
|
-
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
4416
4450
|
}
|
|
4417
|
-
function
|
|
4418
|
-
if (value
|
|
4451
|
+
function normalizeWhatsappAddress(value) {
|
|
4452
|
+
if (!value)
|
|
4453
|
+
return null;
|
|
4454
|
+
if (value.startsWith("whatsapp:"))
|
|
4419
4455
|
return value;
|
|
4420
|
-
}
|
|
4421
|
-
return {};
|
|
4422
|
-
}
|
|
4423
|
-
function readString3(record, key) {
|
|
4424
|
-
const value = record[key];
|
|
4425
|
-
return typeof value === "string" ? value : undefined;
|
|
4426
|
-
}
|
|
4427
|
-
function readNumber2(record, key) {
|
|
4428
|
-
const value = record[key];
|
|
4429
|
-
return typeof value === "number" ? value : undefined;
|
|
4456
|
+
return `whatsapp:${value}`;
|
|
4430
4457
|
}
|
|
4431
|
-
function
|
|
4432
|
-
|
|
4458
|
+
function mapTwilioStatus(status) {
|
|
4459
|
+
switch (status) {
|
|
4460
|
+
case "queued":
|
|
4461
|
+
case "accepted":
|
|
4462
|
+
case "scheduled":
|
|
4463
|
+
return "queued";
|
|
4464
|
+
case "sending":
|
|
4465
|
+
return "sending";
|
|
4466
|
+
case "delivered":
|
|
4467
|
+
return "delivered";
|
|
4468
|
+
case "failed":
|
|
4469
|
+
case "undelivered":
|
|
4470
|
+
case "canceled":
|
|
4471
|
+
return "failed";
|
|
4472
|
+
case "sent":
|
|
4473
|
+
default:
|
|
4474
|
+
return "sent";
|
|
4475
|
+
}
|
|
4433
4476
|
}
|
|
4434
4477
|
|
|
4435
4478
|
// src/impls/mistral-conversational.session.ts
|
|
@@ -4591,790 +4634,739 @@ function toError(error) {
|
|
|
4591
4634
|
return new Error(String(error));
|
|
4592
4635
|
}
|
|
4593
4636
|
|
|
4594
|
-
// src/impls/mistral-
|
|
4595
|
-
var
|
|
4596
|
-
var
|
|
4597
|
-
var
|
|
4637
|
+
// src/impls/mistral-stt.ts
|
|
4638
|
+
var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
|
|
4639
|
+
var DEFAULT_MODEL = "voxtral-mini-latest";
|
|
4640
|
+
var AUDIO_MIME_BY_FORMAT = {
|
|
4641
|
+
mp3: "audio/mpeg",
|
|
4642
|
+
wav: "audio/wav",
|
|
4643
|
+
ogg: "audio/ogg",
|
|
4644
|
+
pcm: "audio/pcm",
|
|
4645
|
+
opus: "audio/opus"
|
|
4646
|
+
};
|
|
4598
4647
|
|
|
4599
|
-
class
|
|
4648
|
+
class MistralSttProvider {
|
|
4600
4649
|
apiKey;
|
|
4601
4650
|
defaultModel;
|
|
4602
|
-
|
|
4651
|
+
defaultLanguage;
|
|
4603
4652
|
baseUrl;
|
|
4604
4653
|
fetchImpl;
|
|
4605
|
-
sttProvider;
|
|
4606
4654
|
constructor(options) {
|
|
4607
4655
|
if (!options.apiKey) {
|
|
4608
|
-
throw new Error("
|
|
4656
|
+
throw new Error("MistralSttProvider requires an apiKey");
|
|
4609
4657
|
}
|
|
4610
4658
|
this.apiKey = options.apiKey;
|
|
4611
|
-
this.defaultModel = options.defaultModel ??
|
|
4612
|
-
this.
|
|
4613
|
-
this.baseUrl =
|
|
4659
|
+
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
|
|
4660
|
+
this.defaultLanguage = options.defaultLanguage;
|
|
4661
|
+
this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
|
|
4614
4662
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
4615
|
-
this.sttProvider = options.sttProvider ?? new MistralSttProvider({
|
|
4616
|
-
apiKey: options.apiKey,
|
|
4617
|
-
defaultModel: options.sttOptions?.defaultModel,
|
|
4618
|
-
defaultLanguage: options.sttOptions?.defaultLanguage,
|
|
4619
|
-
serverURL: options.sttOptions?.serverURL ?? options.serverURL,
|
|
4620
|
-
fetchImpl: this.fetchImpl
|
|
4621
|
-
});
|
|
4622
|
-
}
|
|
4623
|
-
async startSession(config) {
|
|
4624
|
-
return new MistralConversationSession({
|
|
4625
|
-
sessionConfig: {
|
|
4626
|
-
...config,
|
|
4627
|
-
voiceId: config.voiceId || this.defaultVoiceId
|
|
4628
|
-
},
|
|
4629
|
-
defaultModel: this.defaultModel,
|
|
4630
|
-
complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
|
|
4631
|
-
sttProvider: this.sttProvider
|
|
4632
|
-
});
|
|
4633
|
-
}
|
|
4634
|
-
async listVoices() {
|
|
4635
|
-
return [
|
|
4636
|
-
{
|
|
4637
|
-
id: this.defaultVoiceId,
|
|
4638
|
-
name: "Mistral Default Voice",
|
|
4639
|
-
description: "Default conversational voice profile.",
|
|
4640
|
-
capabilities: ["conversational"]
|
|
4641
|
-
}
|
|
4642
|
-
];
|
|
4643
4663
|
}
|
|
4644
|
-
async
|
|
4645
|
-
const
|
|
4646
|
-
const
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4664
|
+
async transcribe(input) {
|
|
4665
|
+
const formData = new FormData;
|
|
4666
|
+
const model = input.model ?? this.defaultModel;
|
|
4667
|
+
const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
|
|
4668
|
+
const fileName = `audio.${input.audio.format}`;
|
|
4669
|
+
const audioBytes = new Uint8Array(input.audio.data);
|
|
4670
|
+
const blob = new Blob([audioBytes], { type: mimeType });
|
|
4671
|
+
formData.append("file", blob, fileName);
|
|
4672
|
+
formData.append("model", model);
|
|
4673
|
+
formData.append("response_format", "verbose_json");
|
|
4674
|
+
const language = input.language ?? this.defaultLanguage;
|
|
4675
|
+
if (language) {
|
|
4676
|
+
formData.append("language", language);
|
|
4652
4677
|
}
|
|
4653
|
-
const response = await this.fetchImpl(`${this.baseUrl}/
|
|
4678
|
+
const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
|
|
4654
4679
|
method: "POST",
|
|
4655
4680
|
headers: {
|
|
4656
|
-
Authorization: `Bearer ${this.apiKey}
|
|
4657
|
-
"Content-Type": "application/json"
|
|
4681
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
4658
4682
|
},
|
|
4659
|
-
body:
|
|
4660
|
-
model,
|
|
4661
|
-
messages
|
|
4662
|
-
})
|
|
4683
|
+
body: formData
|
|
4663
4684
|
});
|
|
4664
4685
|
if (!response.ok) {
|
|
4665
4686
|
const body = await response.text();
|
|
4666
|
-
throw new Error(`Mistral
|
|
4687
|
+
throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
|
|
4667
4688
|
}
|
|
4668
4689
|
const payload = await response.json();
|
|
4669
|
-
return
|
|
4670
|
-
}
|
|
4671
|
-
}
|
|
4672
|
-
function normalizeBaseUrl2(url) {
|
|
4673
|
-
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
4674
|
-
}
|
|
4675
|
-
function readAssistantText(payload) {
|
|
4676
|
-
const record = asRecord3(payload);
|
|
4677
|
-
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
4678
|
-
const firstChoice = asRecord3(choices[0]);
|
|
4679
|
-
const message = asRecord3(firstChoice.message);
|
|
4680
|
-
if (typeof message.content === "string") {
|
|
4681
|
-
return message.content;
|
|
4682
|
-
}
|
|
4683
|
-
if (Array.isArray(message.content)) {
|
|
4684
|
-
const textParts = message.content.map((part) => {
|
|
4685
|
-
const entry = asRecord3(part);
|
|
4686
|
-
const text = entry.text;
|
|
4687
|
-
return typeof text === "string" ? text : "";
|
|
4688
|
-
}).filter((text) => text.length > 0);
|
|
4689
|
-
return textParts.join("");
|
|
4690
|
-
}
|
|
4691
|
-
return "";
|
|
4692
|
-
}
|
|
4693
|
-
function asRecord3(value) {
|
|
4694
|
-
if (value && typeof value === "object") {
|
|
4695
|
-
return value;
|
|
4690
|
+
return toTranscriptionResult(payload, input);
|
|
4696
4691
|
}
|
|
4697
|
-
return {};
|
|
4698
4692
|
}
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
url: options.url,
|
|
4710
|
-
apiKey: options.apiKey,
|
|
4711
|
-
...options.clientParams
|
|
4712
|
-
});
|
|
4713
|
-
this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
|
|
4714
|
-
this.distance = options.distance ?? "Cosine";
|
|
4715
|
-
}
|
|
4716
|
-
async upsert(request) {
|
|
4717
|
-
if (request.documents.length === 0)
|
|
4718
|
-
return;
|
|
4719
|
-
const firstDocument = request.documents[0];
|
|
4720
|
-
if (!firstDocument)
|
|
4721
|
-
return;
|
|
4722
|
-
const vectorSize = firstDocument.vector.length;
|
|
4723
|
-
if (this.createCollectionIfMissing) {
|
|
4724
|
-
await this.ensureCollection(request.collection, vectorSize);
|
|
4725
|
-
}
|
|
4726
|
-
const points = request.documents.map((document) => ({
|
|
4727
|
-
id: document.id,
|
|
4728
|
-
vector: document.vector,
|
|
4729
|
-
payload: {
|
|
4730
|
-
...document.payload,
|
|
4731
|
-
...document.namespace ? { namespace: document.namespace } : {},
|
|
4732
|
-
...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
|
|
4733
|
-
}
|
|
4734
|
-
}));
|
|
4735
|
-
await this.client.upsert(request.collection, {
|
|
4736
|
-
wait: true,
|
|
4737
|
-
points
|
|
4738
|
-
});
|
|
4739
|
-
}
|
|
4740
|
-
async search(query) {
|
|
4741
|
-
const results = await this.client.search(query.collection, {
|
|
4742
|
-
vector: query.vector,
|
|
4743
|
-
limit: query.topK,
|
|
4744
|
-
filter: query.filter,
|
|
4745
|
-
score_threshold: query.scoreThreshold,
|
|
4746
|
-
with_payload: true,
|
|
4747
|
-
with_vector: false
|
|
4748
|
-
});
|
|
4749
|
-
return results.map((item) => ({
|
|
4750
|
-
id: String(item.id),
|
|
4751
|
-
score: item.score,
|
|
4752
|
-
payload: item.payload ?? undefined,
|
|
4753
|
-
namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
|
|
4754
|
-
}));
|
|
4755
|
-
}
|
|
4756
|
-
async delete(request) {
|
|
4757
|
-
await this.client.delete(request.collection, {
|
|
4758
|
-
wait: true,
|
|
4759
|
-
points: request.ids
|
|
4693
|
+
function toTranscriptionResult(payload, input) {
|
|
4694
|
+
const record = asRecord2(payload);
|
|
4695
|
+
const text = readString3(record, "text") ?? "";
|
|
4696
|
+
const language = readString3(record, "language") ?? input.language ?? "unknown";
|
|
4697
|
+
const segments = parseSegments(record);
|
|
4698
|
+
if (segments.length === 0 && text.length > 0) {
|
|
4699
|
+
segments.push({
|
|
4700
|
+
text,
|
|
4701
|
+
startMs: 0,
|
|
4702
|
+
endMs: input.audio.durationMs ?? 0
|
|
4760
4703
|
});
|
|
4761
4704
|
}
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4705
|
+
const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
|
|
4706
|
+
const topLevelWords = parseWordTimings(record.words);
|
|
4707
|
+
const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
|
|
4708
|
+
const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
|
|
4709
|
+
const speakers = dedupeSpeakers(segments);
|
|
4710
|
+
return {
|
|
4711
|
+
text,
|
|
4712
|
+
segments,
|
|
4713
|
+
language,
|
|
4714
|
+
durationMs,
|
|
4715
|
+
speakers: speakers.length > 0 ? speakers : undefined,
|
|
4716
|
+
wordTimings
|
|
4717
|
+
};
|
|
4774
4718
|
}
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
import { sql as drizzleSql } from "drizzle-orm";
|
|
4779
|
-
import { drizzle } from "drizzle-orm/postgres-js";
|
|
4780
|
-
import postgres from "postgres";
|
|
4781
|
-
|
|
4782
|
-
class SupabasePostgresProvider {
|
|
4783
|
-
client;
|
|
4784
|
-
db;
|
|
4785
|
-
ownsClient;
|
|
4786
|
-
createDrizzle;
|
|
4787
|
-
constructor(options = {}) {
|
|
4788
|
-
this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
|
|
4789
|
-
if (options.db) {
|
|
4790
|
-
if (!options.client) {
|
|
4791
|
-
throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
|
|
4792
|
-
}
|
|
4793
|
-
this.client = options.client;
|
|
4794
|
-
this.db = options.db;
|
|
4795
|
-
this.ownsClient = false;
|
|
4796
|
-
return;
|
|
4797
|
-
}
|
|
4798
|
-
if (options.client) {
|
|
4799
|
-
this.client = options.client;
|
|
4800
|
-
this.ownsClient = false;
|
|
4801
|
-
} else {
|
|
4802
|
-
if (!options.connectionString) {
|
|
4803
|
-
throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
|
|
4804
|
-
}
|
|
4805
|
-
this.client = postgres(options.connectionString, {
|
|
4806
|
-
max: options.maxConnections,
|
|
4807
|
-
prepare: false,
|
|
4808
|
-
ssl: resolveSslMode(options.sslMode)
|
|
4809
|
-
});
|
|
4810
|
-
this.ownsClient = true;
|
|
4811
|
-
}
|
|
4812
|
-
this.db = this.createDrizzle(this.client);
|
|
4813
|
-
}
|
|
4814
|
-
async query(statement, params = []) {
|
|
4815
|
-
const query = buildParameterizedSql(statement, params);
|
|
4816
|
-
const result = await this.db.execute(query);
|
|
4817
|
-
const rows = asRows(result);
|
|
4818
|
-
return {
|
|
4819
|
-
rows,
|
|
4820
|
-
rowCount: rows.length
|
|
4821
|
-
};
|
|
4822
|
-
}
|
|
4823
|
-
async execute(statement, params = []) {
|
|
4824
|
-
const query = buildParameterizedSql(statement, params);
|
|
4825
|
-
await this.db.execute(query);
|
|
4719
|
+
function parseSegments(record) {
|
|
4720
|
+
if (!Array.isArray(record.segments)) {
|
|
4721
|
+
return [];
|
|
4826
4722
|
}
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4723
|
+
const parsed = [];
|
|
4724
|
+
for (const entry of record.segments) {
|
|
4725
|
+
const segmentRecord = asRecord2(entry);
|
|
4726
|
+
const text = readString3(segmentRecord, "text");
|
|
4727
|
+
if (!text) {
|
|
4728
|
+
continue;
|
|
4729
|
+
}
|
|
4730
|
+
const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
|
|
4731
|
+
const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
|
|
4732
|
+
parsed.push({
|
|
4733
|
+
text,
|
|
4734
|
+
startMs: secondsToMs(startSeconds),
|
|
4735
|
+
endMs: secondsToMs(endSeconds),
|
|
4736
|
+
speakerId: readString3(segmentRecord, "speaker") ?? undefined,
|
|
4737
|
+
confidence: readNumber2(segmentRecord, "confidence"),
|
|
4738
|
+
wordTimings: parseWordTimings(segmentRecord.words)
|
|
4835
4739
|
});
|
|
4836
|
-
return transactionResult;
|
|
4837
4740
|
}
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4741
|
+
return parsed;
|
|
4742
|
+
}
|
|
4743
|
+
function parseWordTimings(value) {
|
|
4744
|
+
if (!Array.isArray(value)) {
|
|
4745
|
+
return [];
|
|
4746
|
+
}
|
|
4747
|
+
const words = [];
|
|
4748
|
+
for (const entry of value) {
|
|
4749
|
+
const wordRecord = asRecord2(entry);
|
|
4750
|
+
const word = readString3(wordRecord, "word");
|
|
4751
|
+
const startSeconds = readNumber2(wordRecord, "start");
|
|
4752
|
+
const endSeconds = readNumber2(wordRecord, "end");
|
|
4753
|
+
if (!word || startSeconds == null || endSeconds == null) {
|
|
4754
|
+
continue;
|
|
4841
4755
|
}
|
|
4756
|
+
words.push({
|
|
4757
|
+
word,
|
|
4758
|
+
startMs: secondsToMs(startSeconds),
|
|
4759
|
+
endMs: secondsToMs(endSeconds),
|
|
4760
|
+
confidence: readNumber2(wordRecord, "confidence")
|
|
4761
|
+
});
|
|
4842
4762
|
}
|
|
4763
|
+
return words;
|
|
4843
4764
|
}
|
|
4844
|
-
function
|
|
4845
|
-
const
|
|
4846
|
-
const
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
const token = match[0];
|
|
4850
|
-
const indexPart = match[1];
|
|
4851
|
-
const start = match.index;
|
|
4852
|
-
if (indexPart == null || start == null)
|
|
4765
|
+
function dedupeSpeakers(segments) {
|
|
4766
|
+
const seen = new Set;
|
|
4767
|
+
const speakers = [];
|
|
4768
|
+
for (const segment of segments) {
|
|
4769
|
+
if (!segment.speakerId || seen.has(segment.speakerId)) {
|
|
4853
4770
|
continue;
|
|
4854
|
-
const parameterIndex = Number(indexPart) - 1;
|
|
4855
|
-
if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
|
|
4856
|
-
throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
|
|
4857
|
-
}
|
|
4858
|
-
const staticSegment = statement.slice(cursor, start);
|
|
4859
|
-
if (staticSegment.length > 0) {
|
|
4860
|
-
segments.push(drizzleSql.raw(staticSegment));
|
|
4861
|
-
}
|
|
4862
|
-
const parameterValue = params[parameterIndex];
|
|
4863
|
-
if (parameterValue === undefined) {
|
|
4864
|
-
throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
|
|
4865
4771
|
}
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
if (tailSegment.length > 0) {
|
|
4872
|
-
segments.push(drizzleSql.raw(tailSegment));
|
|
4873
|
-
}
|
|
4874
|
-
if (segments.length === 0) {
|
|
4875
|
-
return drizzleSql.raw("");
|
|
4772
|
+
seen.add(segment.speakerId);
|
|
4773
|
+
speakers.push({
|
|
4774
|
+
id: segment.speakerId,
|
|
4775
|
+
name: segment.speakerName
|
|
4776
|
+
});
|
|
4876
4777
|
}
|
|
4877
|
-
return
|
|
4778
|
+
return speakers;
|
|
4878
4779
|
}
|
|
4879
|
-
function
|
|
4880
|
-
|
|
4881
|
-
return value.toString();
|
|
4882
|
-
}
|
|
4883
|
-
if (value instanceof Uint8Array) {
|
|
4884
|
-
return Buffer3.from(value);
|
|
4885
|
-
}
|
|
4886
|
-
if (isPlainObject(value)) {
|
|
4887
|
-
return JSON.stringify(value);
|
|
4888
|
-
}
|
|
4889
|
-
return value;
|
|
4780
|
+
function normalizeBaseUrl(url) {
|
|
4781
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
4890
4782
|
}
|
|
4891
|
-
function
|
|
4892
|
-
if (
|
|
4893
|
-
return
|
|
4783
|
+
function asRecord2(value) {
|
|
4784
|
+
if (value && typeof value === "object") {
|
|
4785
|
+
return value;
|
|
4894
4786
|
}
|
|
4895
|
-
return
|
|
4787
|
+
return {};
|
|
4896
4788
|
}
|
|
4897
|
-
function
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
}
|
|
4901
|
-
if (Array.isArray(value)) {
|
|
4902
|
-
return false;
|
|
4903
|
-
}
|
|
4904
|
-
if (value instanceof Date) {
|
|
4905
|
-
return false;
|
|
4906
|
-
}
|
|
4907
|
-
if (value instanceof Uint8Array) {
|
|
4908
|
-
return false;
|
|
4909
|
-
}
|
|
4910
|
-
return true;
|
|
4789
|
+
function readString3(record, key) {
|
|
4790
|
+
const value = record[key];
|
|
4791
|
+
return typeof value === "string" ? value : undefined;
|
|
4911
4792
|
}
|
|
4912
|
-
function
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
case "require":
|
|
4919
|
-
default:
|
|
4920
|
-
return "require";
|
|
4921
|
-
}
|
|
4793
|
+
function readNumber2(record, key) {
|
|
4794
|
+
const value = record[key];
|
|
4795
|
+
return typeof value === "number" ? value : undefined;
|
|
4796
|
+
}
|
|
4797
|
+
function secondsToMs(value) {
|
|
4798
|
+
return Math.round(value * 1000);
|
|
4922
4799
|
}
|
|
4923
4800
|
|
|
4924
|
-
// src/impls/
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4801
|
+
// src/impls/mistral-conversational.ts
|
|
4802
|
+
var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
|
|
4803
|
+
var DEFAULT_MODEL2 = "mistral-small-latest";
|
|
4804
|
+
var DEFAULT_VOICE = "default";
|
|
4805
|
+
|
|
4806
|
+
class MistralConversationalProvider {
|
|
4807
|
+
apiKey;
|
|
4808
|
+
defaultModel;
|
|
4809
|
+
defaultVoiceId;
|
|
4810
|
+
baseUrl;
|
|
4811
|
+
fetchImpl;
|
|
4812
|
+
sttProvider;
|
|
4934
4813
|
constructor(options) {
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
maxConnections: options.maxConnections,
|
|
4938
|
-
sslMode: options.sslMode
|
|
4939
|
-
});
|
|
4940
|
-
this.createTableIfMissing = options.createTableIfMissing ?? true;
|
|
4941
|
-
this.distanceMetric = options.distanceMetric ?? "cosine";
|
|
4942
|
-
const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
|
|
4943
|
-
const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
|
|
4944
|
-
this.quotedSchema = quoteIdentifier(schema);
|
|
4945
|
-
this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
|
|
4946
|
-
this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
|
|
4947
|
-
this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
|
|
4948
|
-
}
|
|
4949
|
-
async upsert(request) {
|
|
4950
|
-
if (request.documents.length === 0) {
|
|
4951
|
-
return;
|
|
4952
|
-
}
|
|
4953
|
-
if (this.createTableIfMissing) {
|
|
4954
|
-
await this.ensureTable();
|
|
4955
|
-
}
|
|
4956
|
-
for (const document of request.documents) {
|
|
4957
|
-
await this.database.execute(`INSERT INTO ${this.qualifiedTable}
|
|
4958
|
-
(collection, id, embedding, payload, namespace, expires_at, updated_at)
|
|
4959
|
-
VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
|
|
4960
|
-
ON CONFLICT (collection, id)
|
|
4961
|
-
DO UPDATE SET
|
|
4962
|
-
embedding = EXCLUDED.embedding,
|
|
4963
|
-
payload = EXCLUDED.payload,
|
|
4964
|
-
namespace = EXCLUDED.namespace,
|
|
4965
|
-
expires_at = EXCLUDED.expires_at,
|
|
4966
|
-
updated_at = now();`, [
|
|
4967
|
-
request.collection,
|
|
4968
|
-
document.id,
|
|
4969
|
-
toVectorLiteral(document.vector),
|
|
4970
|
-
document.payload ? JSON.stringify(document.payload) : null,
|
|
4971
|
-
document.namespace ?? null,
|
|
4972
|
-
document.expiresAt ?? null
|
|
4973
|
-
]);
|
|
4814
|
+
if (!options.apiKey) {
|
|
4815
|
+
throw new Error("MistralConversationalProvider requires an apiKey");
|
|
4974
4816
|
}
|
|
4817
|
+
this.apiKey = options.apiKey;
|
|
4818
|
+
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
|
|
4819
|
+
this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
|
|
4820
|
+
this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
|
|
4821
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
4822
|
+
this.sttProvider = options.sttProvider ?? new MistralSttProvider({
|
|
4823
|
+
apiKey: options.apiKey,
|
|
4824
|
+
defaultModel: options.sttOptions?.defaultModel,
|
|
4825
|
+
defaultLanguage: options.sttOptions?.defaultLanguage,
|
|
4826
|
+
serverURL: options.sttOptions?.serverURL ?? options.serverURL,
|
|
4827
|
+
fetchImpl: this.fetchImpl
|
|
4828
|
+
});
|
|
4975
4829
|
}
|
|
4976
|
-
async
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
AND ($2::text IS NULL OR namespace = $2)
|
|
4986
|
-
AND (expires_at IS NULL OR expires_at > now())
|
|
4987
|
-
AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
|
|
4988
|
-
ORDER BY embedding ${operator} $3::vector
|
|
4989
|
-
LIMIT $5;`, [
|
|
4990
|
-
query.collection,
|
|
4991
|
-
query.namespace ?? null,
|
|
4992
|
-
toVectorLiteral(query.vector),
|
|
4993
|
-
query.filter ? JSON.stringify(query.filter) : null,
|
|
4994
|
-
query.topK
|
|
4995
|
-
]);
|
|
4996
|
-
const mapped = results.rows.map((row) => {
|
|
4997
|
-
const distance = Number(row.distance);
|
|
4998
|
-
return {
|
|
4999
|
-
id: row.id,
|
|
5000
|
-
score: distanceToScore(distance, this.distanceMetric),
|
|
5001
|
-
payload: isRecord(row.payload) ? row.payload : undefined,
|
|
5002
|
-
namespace: row.namespace ?? undefined
|
|
5003
|
-
};
|
|
4830
|
+
async startSession(config) {
|
|
4831
|
+
return new MistralConversationSession({
|
|
4832
|
+
sessionConfig: {
|
|
4833
|
+
...config,
|
|
4834
|
+
voiceId: config.voiceId || this.defaultVoiceId
|
|
4835
|
+
},
|
|
4836
|
+
defaultModel: this.defaultModel,
|
|
4837
|
+
complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
|
|
4838
|
+
sttProvider: this.sttProvider
|
|
5004
4839
|
});
|
|
5005
|
-
const scoreThreshold = query.scoreThreshold;
|
|
5006
|
-
if (scoreThreshold == null) {
|
|
5007
|
-
return mapped;
|
|
5008
|
-
}
|
|
5009
|
-
return mapped.filter((result) => result.score >= scoreThreshold);
|
|
5010
4840
|
}
|
|
5011
|
-
async
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
4841
|
+
async listVoices() {
|
|
4842
|
+
return [
|
|
4843
|
+
{
|
|
4844
|
+
id: this.defaultVoiceId,
|
|
4845
|
+
name: "Mistral Default Voice",
|
|
4846
|
+
description: "Default conversational voice profile.",
|
|
4847
|
+
capabilities: ["conversational"]
|
|
4848
|
+
}
|
|
5019
4849
|
];
|
|
5020
|
-
await this.database.execute(`DELETE FROM ${this.qualifiedTable}
|
|
5021
|
-
WHERE collection = $1
|
|
5022
|
-
AND id = ANY($2::text[])
|
|
5023
|
-
AND ($3::text IS NULL OR namespace = $3);`, params);
|
|
5024
4850
|
}
|
|
5025
|
-
async
|
|
5026
|
-
|
|
5027
|
-
|
|
4851
|
+
async completeConversation(history, sessionConfig) {
|
|
4852
|
+
const model = sessionConfig.llmModel ?? this.defaultModel;
|
|
4853
|
+
const messages = [];
|
|
4854
|
+
if (sessionConfig.systemPrompt) {
|
|
4855
|
+
messages.push({ role: "system", content: sessionConfig.systemPrompt });
|
|
5028
4856
|
}
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
async createTable() {
|
|
5032
|
-
await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
5033
|
-
await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
|
|
5034
|
-
await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
|
|
5035
|
-
collection text NOT NULL,
|
|
5036
|
-
id text NOT NULL,
|
|
5037
|
-
embedding vector NOT NULL,
|
|
5038
|
-
payload jsonb,
|
|
5039
|
-
namespace text,
|
|
5040
|
-
expires_at timestamptz,
|
|
5041
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
5042
|
-
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
5043
|
-
PRIMARY KEY (collection, id)
|
|
5044
|
-
);`);
|
|
5045
|
-
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
|
|
5046
|
-
ON ${this.qualifiedTable} (collection);`);
|
|
5047
|
-
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
|
|
5048
|
-
ON ${this.qualifiedTable} (namespace);`);
|
|
5049
|
-
}
|
|
5050
|
-
get distanceOperator() {
|
|
5051
|
-
switch (this.distanceMetric) {
|
|
5052
|
-
case "l2":
|
|
5053
|
-
return "<->";
|
|
5054
|
-
case "inner_product":
|
|
5055
|
-
return "<#>";
|
|
5056
|
-
case "cosine":
|
|
5057
|
-
default:
|
|
5058
|
-
return "<=>";
|
|
4857
|
+
for (const item of history) {
|
|
4858
|
+
messages.push({ role: item.role, content: item.content });
|
|
5059
4859
|
}
|
|
4860
|
+
const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
4861
|
+
method: "POST",
|
|
4862
|
+
headers: {
|
|
4863
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
4864
|
+
"Content-Type": "application/json"
|
|
4865
|
+
},
|
|
4866
|
+
body: JSON.stringify({
|
|
4867
|
+
model,
|
|
4868
|
+
messages
|
|
4869
|
+
})
|
|
4870
|
+
});
|
|
4871
|
+
if (!response.ok) {
|
|
4872
|
+
const body = await response.text();
|
|
4873
|
+
throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
|
|
4874
|
+
}
|
|
4875
|
+
const payload = await response.json();
|
|
4876
|
+
return readAssistantText(payload);
|
|
5060
4877
|
}
|
|
5061
4878
|
}
|
|
5062
|
-
function
|
|
5063
|
-
|
|
5064
|
-
throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
|
|
5065
|
-
}
|
|
5066
|
-
return value;
|
|
5067
|
-
}
|
|
5068
|
-
function quoteIdentifier(value) {
|
|
5069
|
-
return `"${value.replaceAll('"', '""')}"`;
|
|
4879
|
+
function normalizeBaseUrl2(url) {
|
|
4880
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5070
4881
|
}
|
|
5071
|
-
function
|
|
5072
|
-
|
|
5073
|
-
|
|
4882
|
+
function readAssistantText(payload) {
|
|
4883
|
+
const record = asRecord3(payload);
|
|
4884
|
+
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
4885
|
+
const firstChoice = asRecord3(choices[0]);
|
|
4886
|
+
const message = asRecord3(firstChoice.message);
|
|
4887
|
+
if (typeof message.content === "string") {
|
|
4888
|
+
return message.content;
|
|
5074
4889
|
}
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
4890
|
+
if (Array.isArray(message.content)) {
|
|
4891
|
+
const textParts = message.content.map((part) => {
|
|
4892
|
+
const entry = asRecord3(part);
|
|
4893
|
+
const text = entry.text;
|
|
4894
|
+
return typeof text === "string" ? text : "";
|
|
4895
|
+
}).filter((text) => text.length > 0);
|
|
4896
|
+
return textParts.join("");
|
|
5079
4897
|
}
|
|
5080
|
-
return
|
|
5081
|
-
}
|
|
5082
|
-
function isRecord(value) {
|
|
5083
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4898
|
+
return "";
|
|
5084
4899
|
}
|
|
5085
|
-
function
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
return -distance;
|
|
5089
|
-
case "l2":
|
|
5090
|
-
return 1 / (1 + distance);
|
|
5091
|
-
case "cosine":
|
|
5092
|
-
default:
|
|
5093
|
-
return 1 - distance;
|
|
4900
|
+
function asRecord3(value) {
|
|
4901
|
+
if (value && typeof value === "object") {
|
|
4902
|
+
return value;
|
|
5094
4903
|
}
|
|
4904
|
+
return {};
|
|
5095
4905
|
}
|
|
5096
4906
|
|
|
5097
|
-
// src/impls/
|
|
5098
|
-
import
|
|
5099
|
-
var API_VERSION = "2026-02-25.clover";
|
|
4907
|
+
// src/impls/mistral-embedding.ts
|
|
4908
|
+
import { Mistral } from "@mistralai/mistralai";
|
|
5100
4909
|
|
|
5101
|
-
class
|
|
5102
|
-
|
|
4910
|
+
class MistralEmbeddingProvider {
|
|
4911
|
+
client;
|
|
4912
|
+
defaultModel;
|
|
5103
4913
|
constructor(options) {
|
|
5104
|
-
|
|
5105
|
-
|
|
4914
|
+
if (!options.apiKey) {
|
|
4915
|
+
throw new Error("MistralEmbeddingProvider requires an apiKey");
|
|
4916
|
+
}
|
|
4917
|
+
this.client = options.client ?? new Mistral({
|
|
4918
|
+
apiKey: options.apiKey,
|
|
4919
|
+
serverURL: options.serverURL
|
|
5106
4920
|
});
|
|
4921
|
+
this.defaultModel = options.defaultModel ?? "mistral-embed";
|
|
5107
4922
|
}
|
|
5108
|
-
async
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
4923
|
+
async embedDocuments(documents, options) {
|
|
4924
|
+
if (documents.length === 0)
|
|
4925
|
+
return [];
|
|
4926
|
+
const model = options?.model ?? this.defaultModel;
|
|
4927
|
+
const response = await this.client.embeddings.create({
|
|
4928
|
+
model,
|
|
4929
|
+
inputs: documents.map((doc) => doc.text)
|
|
5114
4930
|
});
|
|
5115
|
-
return
|
|
4931
|
+
return response.data.map((item, index) => ({
|
|
4932
|
+
id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
|
|
4933
|
+
vector: item.embedding ?? [],
|
|
4934
|
+
dimensions: item.embedding?.length ?? 0,
|
|
4935
|
+
model: response.model,
|
|
4936
|
+
metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
|
|
4937
|
+
}));
|
|
5116
4938
|
}
|
|
5117
|
-
async
|
|
5118
|
-
const
|
|
5119
|
-
if (
|
|
5120
|
-
|
|
5121
|
-
|
|
4939
|
+
async embedQuery(query, options) {
|
|
4940
|
+
const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
|
|
4941
|
+
if (!result) {
|
|
4942
|
+
throw new Error("Failed to compute embedding for query");
|
|
4943
|
+
}
|
|
4944
|
+
return result;
|
|
5122
4945
|
}
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
4946
|
+
}
|
|
4947
|
+
|
|
4948
|
+
// src/impls/mistral-llm.ts
|
|
4949
|
+
import { Mistral as Mistral2 } from "@mistralai/mistralai";
|
|
4950
|
+
|
|
4951
|
+
class MistralLLMProvider {
|
|
4952
|
+
client;
|
|
4953
|
+
defaultModel;
|
|
4954
|
+
constructor(options) {
|
|
4955
|
+
if (!options.apiKey) {
|
|
4956
|
+
throw new Error("MistralLLMProvider requires an apiKey");
|
|
4957
|
+
}
|
|
4958
|
+
this.client = options.client ?? new Mistral2({
|
|
4959
|
+
apiKey: options.apiKey,
|
|
4960
|
+
serverURL: options.serverURL,
|
|
4961
|
+
userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
|
|
5135
4962
|
});
|
|
5136
|
-
|
|
5137
|
-
}
|
|
5138
|
-
async capturePayment(paymentIntentId, input) {
|
|
5139
|
-
const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
|
|
5140
|
-
return this.toPaymentIntent(intent);
|
|
4963
|
+
this.defaultModel = options.defaultModel ?? "mistral-large-latest";
|
|
5141
4964
|
}
|
|
5142
|
-
async
|
|
5143
|
-
const
|
|
5144
|
-
|
|
4965
|
+
async chat(messages, options = {}) {
|
|
4966
|
+
const request = this.buildChatRequest(messages, options);
|
|
4967
|
+
const response = await this.client.chat.complete(request);
|
|
4968
|
+
return this.buildLLMResponse(response);
|
|
5145
4969
|
}
|
|
5146
|
-
async
|
|
5147
|
-
const
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
4970
|
+
async* stream(messages, options = {}) {
|
|
4971
|
+
const request = this.buildChatRequest(messages, options);
|
|
4972
|
+
request.stream = true;
|
|
4973
|
+
const stream = await this.client.chat.stream(request);
|
|
4974
|
+
const aggregatedParts = [];
|
|
4975
|
+
const aggregatedToolCalls = [];
|
|
4976
|
+
let usage;
|
|
4977
|
+
let finishReason;
|
|
4978
|
+
for await (const event of stream) {
|
|
4979
|
+
for (const choice of event.data.choices) {
|
|
4980
|
+
const delta = choice.delta;
|
|
4981
|
+
if (typeof delta.content === "string") {
|
|
4982
|
+
if (delta.content.length > 0) {
|
|
4983
|
+
aggregatedParts.push({ type: "text", text: delta.content });
|
|
4984
|
+
yield {
|
|
4985
|
+
type: "message_delta",
|
|
4986
|
+
delta: { type: "text", text: delta.content },
|
|
4987
|
+
index: choice.index
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4990
|
+
} else if (Array.isArray(delta.content)) {
|
|
4991
|
+
for (const chunk of delta.content) {
|
|
4992
|
+
if (chunk.type === "text" && "text" in chunk) {
|
|
4993
|
+
aggregatedParts.push({ type: "text", text: chunk.text });
|
|
4994
|
+
yield {
|
|
4995
|
+
type: "message_delta",
|
|
4996
|
+
delta: { type: "text", text: chunk.text },
|
|
4997
|
+
index: choice.index
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
if (delta.toolCalls) {
|
|
5003
|
+
let localIndex = 0;
|
|
5004
|
+
for (const call of delta.toolCalls) {
|
|
5005
|
+
const toolCall = this.fromMistralToolCall(call, localIndex);
|
|
5006
|
+
aggregatedToolCalls.push(toolCall);
|
|
5007
|
+
yield {
|
|
5008
|
+
type: "tool_call",
|
|
5009
|
+
call: toolCall,
|
|
5010
|
+
index: choice.index
|
|
5011
|
+
};
|
|
5012
|
+
localIndex += 1;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
if (choice.finishReason && choice.finishReason !== "null") {
|
|
5016
|
+
finishReason = choice.finishReason;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
if (event.data.usage) {
|
|
5020
|
+
const usageEntry = this.fromUsage(event.data.usage);
|
|
5021
|
+
if (usageEntry) {
|
|
5022
|
+
usage = usageEntry;
|
|
5023
|
+
yield { type: "usage", usage: usageEntry };
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
const message = {
|
|
5028
|
+
role: "assistant",
|
|
5029
|
+
content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
|
|
5030
|
+
};
|
|
5031
|
+
if (aggregatedToolCalls.length > 0) {
|
|
5032
|
+
message.content = [
|
|
5033
|
+
...aggregatedToolCalls,
|
|
5034
|
+
...aggregatedParts.length ? aggregatedParts : []
|
|
5035
|
+
];
|
|
5036
|
+
}
|
|
5037
|
+
yield {
|
|
5038
|
+
type: "end",
|
|
5039
|
+
response: {
|
|
5040
|
+
message,
|
|
5041
|
+
usage,
|
|
5042
|
+
finishReason: mapFinishReason(finishReason)
|
|
5043
|
+
}
|
|
5165
5044
|
};
|
|
5166
5045
|
}
|
|
5167
|
-
async
|
|
5168
|
-
|
|
5169
|
-
const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
|
|
5170
|
-
const response = await this.stripe.invoices.list({
|
|
5171
|
-
customer: query?.customerId,
|
|
5172
|
-
status: stripeStatus,
|
|
5173
|
-
limit: query?.limit,
|
|
5174
|
-
starting_after: query?.startingAfter
|
|
5175
|
-
});
|
|
5176
|
-
return response.data.map((invoice) => this.toInvoice(invoice));
|
|
5046
|
+
async countTokens(_messages) {
|
|
5047
|
+
throw new Error("Mistral API does not currently support token counting");
|
|
5177
5048
|
}
|
|
5178
|
-
|
|
5179
|
-
const
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
}
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5049
|
+
buildChatRequest(messages, options) {
|
|
5050
|
+
const model = options.model ?? this.defaultModel;
|
|
5051
|
+
const mappedMessages = messages.map((message) => this.toMistralMessage(message));
|
|
5052
|
+
const request = {
|
|
5053
|
+
model,
|
|
5054
|
+
messages: mappedMessages
|
|
5055
|
+
};
|
|
5056
|
+
if (options.temperature != null) {
|
|
5057
|
+
request.temperature = options.temperature;
|
|
5058
|
+
}
|
|
5059
|
+
if (options.topP != null) {
|
|
5060
|
+
request.topP = options.topP;
|
|
5061
|
+
}
|
|
5062
|
+
if (options.maxOutputTokens != null) {
|
|
5063
|
+
request.maxTokens = options.maxOutputTokens;
|
|
5064
|
+
}
|
|
5065
|
+
if (options.stopSequences?.length) {
|
|
5066
|
+
request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
|
|
5067
|
+
}
|
|
5068
|
+
if (options.tools?.length) {
|
|
5069
|
+
request.tools = options.tools.map((tool) => ({
|
|
5070
|
+
type: "function",
|
|
5071
|
+
function: {
|
|
5072
|
+
name: tool.name,
|
|
5073
|
+
description: tool.description,
|
|
5074
|
+
parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
|
|
5075
|
+
}
|
|
5076
|
+
}));
|
|
5077
|
+
}
|
|
5078
|
+
if (options.responseFormat === "json") {
|
|
5079
|
+
request.responseFormat = { type: "json_object" };
|
|
5080
|
+
}
|
|
5081
|
+
return request;
|
|
5200
5082
|
}
|
|
5201
|
-
|
|
5202
|
-
const
|
|
5203
|
-
|
|
5083
|
+
buildLLMResponse(response) {
|
|
5084
|
+
const firstChoice = response.choices[0];
|
|
5085
|
+
if (!firstChoice) {
|
|
5086
|
+
return {
|
|
5087
|
+
message: {
|
|
5088
|
+
role: "assistant",
|
|
5089
|
+
content: [{ type: "text", text: "" }]
|
|
5090
|
+
},
|
|
5091
|
+
usage: this.fromUsage(response.usage),
|
|
5092
|
+
raw: response
|
|
5093
|
+
};
|
|
5094
|
+
}
|
|
5095
|
+
const message = this.fromAssistantMessage(firstChoice.message);
|
|
5204
5096
|
return {
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
|
|
5210
|
-
updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
|
|
5097
|
+
message,
|
|
5098
|
+
usage: this.fromUsage(response.usage),
|
|
5099
|
+
finishReason: mapFinishReason(firstChoice.finishReason),
|
|
5100
|
+
raw: response
|
|
5211
5101
|
};
|
|
5212
5102
|
}
|
|
5213
|
-
|
|
5214
|
-
|
|
5103
|
+
fromUsage(usage) {
|
|
5104
|
+
if (!usage)
|
|
5105
|
+
return;
|
|
5215
5106
|
return {
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
|
|
5220
|
-
description: intent.description ?? undefined,
|
|
5221
|
-
clientSecret: intent.client_secret ?? undefined,
|
|
5222
|
-
metadata,
|
|
5223
|
-
createdAt: new Date(intent.created * 1000),
|
|
5224
|
-
updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
|
|
5107
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
5108
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
5109
|
+
totalTokens: usage.totalTokens ?? 0
|
|
5225
5110
|
};
|
|
5226
5111
|
}
|
|
5227
|
-
|
|
5228
|
-
const
|
|
5112
|
+
fromAssistantMessage(message) {
|
|
5113
|
+
const parts = [];
|
|
5114
|
+
if (typeof message.content === "string") {
|
|
5115
|
+
parts.push({ type: "text", text: message.content });
|
|
5116
|
+
} else if (Array.isArray(message.content)) {
|
|
5117
|
+
message.content.forEach((chunk) => {
|
|
5118
|
+
if (chunk.type === "text" && "text" in chunk) {
|
|
5119
|
+
parts.push({ type: "text", text: chunk.text });
|
|
5120
|
+
}
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
5123
|
+
const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
|
|
5124
|
+
if (toolCalls.length > 0) {
|
|
5125
|
+
parts.splice(0, 0, ...toolCalls);
|
|
5126
|
+
}
|
|
5127
|
+
if (parts.length === 0) {
|
|
5128
|
+
parts.push({ type: "text", text: "" });
|
|
5129
|
+
}
|
|
5229
5130
|
return {
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
status: invoice.status ?? "draft",
|
|
5233
|
-
amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
|
|
5234
|
-
amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
|
|
5235
|
-
customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
|
|
5236
|
-
dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
|
|
5237
|
-
hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
|
|
5238
|
-
metadata,
|
|
5239
|
-
createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
|
|
5240
|
-
updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
|
|
5131
|
+
role: "assistant",
|
|
5132
|
+
content: parts
|
|
5241
5133
|
};
|
|
5242
5134
|
}
|
|
5243
|
-
|
|
5135
|
+
fromMistralToolCall(call, index) {
|
|
5136
|
+
const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
|
|
5244
5137
|
return {
|
|
5245
|
-
|
|
5246
|
-
|
|
5138
|
+
type: "tool-call",
|
|
5139
|
+
id: call.id ?? `tool-call-${index}`,
|
|
5140
|
+
name: call.function.name,
|
|
5141
|
+
arguments: args
|
|
5247
5142
|
};
|
|
5248
5143
|
}
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5144
|
+
toMistralMessage(message) {
|
|
5145
|
+
const textContent = this.extractText(message.content);
|
|
5146
|
+
const toolCalls = this.extractToolCalls(message);
|
|
5147
|
+
switch (message.role) {
|
|
5148
|
+
case "system":
|
|
5149
|
+
return {
|
|
5150
|
+
role: "system",
|
|
5151
|
+
content: textContent ?? ""
|
|
5152
|
+
};
|
|
5153
|
+
case "user":
|
|
5154
|
+
return {
|
|
5155
|
+
role: "user",
|
|
5156
|
+
content: textContent ?? ""
|
|
5157
|
+
};
|
|
5158
|
+
case "assistant":
|
|
5159
|
+
return {
|
|
5160
|
+
role: "assistant",
|
|
5161
|
+
content: toolCalls.length > 0 ? null : textContent ?? "",
|
|
5162
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
5163
|
+
};
|
|
5164
|
+
case "tool":
|
|
5165
|
+
return {
|
|
5166
|
+
role: "tool",
|
|
5167
|
+
content: textContent ?? "",
|
|
5168
|
+
toolCallId: message.toolCallId ?? toolCalls[0]?.id
|
|
5169
|
+
};
|
|
5170
|
+
default:
|
|
5171
|
+
return {
|
|
5172
|
+
role: "user",
|
|
5173
|
+
content: textContent ?? ""
|
|
5174
|
+
};
|
|
5261
5175
|
}
|
|
5262
|
-
return {
|
|
5263
|
-
...base ?? {},
|
|
5264
|
-
...Object.fromEntries(filteredExtras)
|
|
5265
|
-
};
|
|
5266
5176
|
}
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
"duplicate",
|
|
5273
|
-
"fraudulent",
|
|
5274
|
-
"requested_by_customer"
|
|
5275
|
-
];
|
|
5276
|
-
return allowed.includes(reason) ? reason : undefined;
|
|
5277
|
-
}
|
|
5278
|
-
function mapPaymentIntentStatus(status) {
|
|
5279
|
-
switch (status) {
|
|
5280
|
-
case "requires_payment_method":
|
|
5281
|
-
return "requires_payment_method";
|
|
5282
|
-
case "requires_confirmation":
|
|
5283
|
-
return "requires_confirmation";
|
|
5284
|
-
case "requires_action":
|
|
5285
|
-
case "requires_capture":
|
|
5286
|
-
return "requires_action";
|
|
5287
|
-
case "processing":
|
|
5288
|
-
return "processing";
|
|
5289
|
-
case "succeeded":
|
|
5290
|
-
return "succeeded";
|
|
5291
|
-
case "canceled":
|
|
5292
|
-
return "canceled";
|
|
5293
|
-
default:
|
|
5294
|
-
return "requires_payment_method";
|
|
5177
|
+
extractText(parts) {
|
|
5178
|
+
const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
|
|
5179
|
+
if (textParts.length === 0)
|
|
5180
|
+
return null;
|
|
5181
|
+
return textParts.join("");
|
|
5295
5182
|
}
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5183
|
+
extractToolCalls(message) {
|
|
5184
|
+
const toolCallParts = message.content.filter((part) => part.type === "tool-call");
|
|
5185
|
+
return toolCallParts.map((call, index) => ({
|
|
5186
|
+
id: call.id ?? `call_${index}`,
|
|
5187
|
+
type: "function",
|
|
5188
|
+
index,
|
|
5189
|
+
function: {
|
|
5190
|
+
name: call.name,
|
|
5191
|
+
arguments: call.arguments
|
|
5192
|
+
}
|
|
5193
|
+
}));
|
|
5306
5194
|
}
|
|
5307
5195
|
}
|
|
5308
|
-
function
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
case "
|
|
5314
|
-
return "
|
|
5315
|
-
case "
|
|
5316
|
-
|
|
5317
|
-
|
|
5196
|
+
function mapFinishReason(reason) {
|
|
5197
|
+
if (!reason)
|
|
5198
|
+
return;
|
|
5199
|
+
const normalized = reason.toLowerCase();
|
|
5200
|
+
switch (normalized) {
|
|
5201
|
+
case "stop":
|
|
5202
|
+
return "stop";
|
|
5203
|
+
case "length":
|
|
5204
|
+
return "length";
|
|
5205
|
+
case "tool_call":
|
|
5206
|
+
case "tool_calls":
|
|
5207
|
+
return "tool_call";
|
|
5208
|
+
case "content_filter":
|
|
5209
|
+
return "content_filter";
|
|
5318
5210
|
default:
|
|
5319
|
-
return
|
|
5211
|
+
return;
|
|
5320
5212
|
}
|
|
5321
5213
|
}
|
|
5322
5214
|
|
|
5323
|
-
// src/impls/
|
|
5324
|
-
import {
|
|
5215
|
+
// src/impls/notion.ts
|
|
5216
|
+
import { Client } from "@notionhq/client";
|
|
5325
5217
|
|
|
5326
|
-
class
|
|
5218
|
+
class NotionProjectManagementProvider {
|
|
5327
5219
|
client;
|
|
5328
|
-
|
|
5329
|
-
messageStream;
|
|
5220
|
+
defaults;
|
|
5330
5221
|
constructor(options) {
|
|
5331
|
-
this.client = options.client ?? new
|
|
5332
|
-
|
|
5222
|
+
this.client = options.client ?? new Client({ auth: options.apiKey });
|
|
5223
|
+
this.defaults = {
|
|
5224
|
+
databaseId: options.databaseId,
|
|
5225
|
+
summaryParentPageId: options.summaryParentPageId,
|
|
5226
|
+
titleProperty: options.titleProperty,
|
|
5227
|
+
statusProperty: options.statusProperty,
|
|
5228
|
+
priorityProperty: options.priorityProperty,
|
|
5229
|
+
tagsProperty: options.tagsProperty,
|
|
5230
|
+
dueDateProperty: options.dueDateProperty,
|
|
5231
|
+
descriptionProperty: options.descriptionProperty
|
|
5232
|
+
};
|
|
5233
|
+
}
|
|
5234
|
+
async createWorkItem(input) {
|
|
5235
|
+
if (input.type === "summary" && this.defaults.summaryParentPageId) {
|
|
5236
|
+
return this.createSummaryPage(input);
|
|
5237
|
+
}
|
|
5238
|
+
const databaseId = this.defaults.databaseId;
|
|
5239
|
+
if (!databaseId) {
|
|
5240
|
+
throw new Error("Notion databaseId is required to create work items.");
|
|
5241
|
+
}
|
|
5242
|
+
const titleProperty = this.defaults.titleProperty ?? "Name";
|
|
5243
|
+
const properties = {
|
|
5244
|
+
[titleProperty]: buildTitleProperty(input.title)
|
|
5245
|
+
};
|
|
5246
|
+
applySelect(properties, this.defaults.statusProperty, input.status);
|
|
5247
|
+
applySelect(properties, this.defaults.priorityProperty, input.priority);
|
|
5248
|
+
applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
|
|
5249
|
+
applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
|
|
5250
|
+
applyRichText(properties, this.defaults.descriptionProperty, input.description);
|
|
5251
|
+
const page = await this.client.pages.create({
|
|
5252
|
+
parent: { type: "database_id", database_id: databaseId },
|
|
5253
|
+
properties
|
|
5333
5254
|
});
|
|
5334
|
-
|
|
5335
|
-
|
|
5255
|
+
return {
|
|
5256
|
+
id: page.id,
|
|
5257
|
+
title: input.title,
|
|
5258
|
+
url: "url" in page ? page.url : undefined,
|
|
5259
|
+
status: input.status,
|
|
5260
|
+
priority: input.priority,
|
|
5261
|
+
tags: input.tags,
|
|
5262
|
+
projectId: databaseId,
|
|
5263
|
+
externalId: input.externalId,
|
|
5264
|
+
metadata: input.metadata
|
|
5265
|
+
};
|
|
5336
5266
|
}
|
|
5337
|
-
async
|
|
5338
|
-
const
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
MessageStream: this.messageStream,
|
|
5352
|
-
Attachments: buildAttachments(message)
|
|
5267
|
+
async createWorkItems(items) {
|
|
5268
|
+
const created = [];
|
|
5269
|
+
for (const item of items) {
|
|
5270
|
+
created.push(await this.createWorkItem(item));
|
|
5271
|
+
}
|
|
5272
|
+
return created;
|
|
5273
|
+
}
|
|
5274
|
+
async createSummaryPage(input) {
|
|
5275
|
+
const parentId = this.defaults.summaryParentPageId;
|
|
5276
|
+
if (!parentId) {
|
|
5277
|
+
throw new Error("Notion summaryParentPageId is required for summaries.");
|
|
5278
|
+
}
|
|
5279
|
+
const summaryProperties = {
|
|
5280
|
+
title: buildTitleProperty(input.title)
|
|
5353
5281
|
};
|
|
5354
|
-
const
|
|
5282
|
+
const page = await this.client.pages.create({
|
|
5283
|
+
parent: { type: "page_id", page_id: parentId },
|
|
5284
|
+
properties: summaryProperties
|
|
5285
|
+
});
|
|
5286
|
+
if (input.description) {
|
|
5287
|
+
const children = buildParagraphBlocks(input.description);
|
|
5288
|
+
if (children.length > 0) {
|
|
5289
|
+
await this.client.blocks.children.append({
|
|
5290
|
+
block_id: page.id,
|
|
5291
|
+
children
|
|
5292
|
+
});
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5355
5295
|
return {
|
|
5356
|
-
id:
|
|
5357
|
-
|
|
5358
|
-
|
|
5296
|
+
id: page.id,
|
|
5297
|
+
title: input.title,
|
|
5298
|
+
url: "url" in page ? page.url : undefined,
|
|
5299
|
+
status: input.status,
|
|
5300
|
+
priority: input.priority,
|
|
5301
|
+
tags: input.tags,
|
|
5302
|
+
projectId: parentId,
|
|
5303
|
+
externalId: input.externalId,
|
|
5304
|
+
metadata: input.metadata
|
|
5359
5305
|
};
|
|
5360
5306
|
}
|
|
5361
5307
|
}
|
|
5362
|
-
function
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5308
|
+
function buildTitleProperty(title) {
|
|
5309
|
+
return {
|
|
5310
|
+
title: [
|
|
5311
|
+
{
|
|
5312
|
+
type: "text",
|
|
5313
|
+
text: { content: title }
|
|
5314
|
+
}
|
|
5315
|
+
]
|
|
5316
|
+
};
|
|
5367
5317
|
}
|
|
5368
|
-
function
|
|
5369
|
-
|
|
5318
|
+
function buildRichText(text) {
|
|
5319
|
+
return {
|
|
5320
|
+
rich_text: [
|
|
5321
|
+
{
|
|
5322
|
+
type: "text",
|
|
5323
|
+
text: { content: text }
|
|
5324
|
+
}
|
|
5325
|
+
]
|
|
5326
|
+
};
|
|
5327
|
+
}
|
|
5328
|
+
function applySelect(properties, property, value) {
|
|
5329
|
+
if (!property || !value)
|
|
5370
5330
|
return;
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5331
|
+
const next = {
|
|
5332
|
+
select: { name: value }
|
|
5333
|
+
};
|
|
5334
|
+
properties[property] = next;
|
|
5335
|
+
}
|
|
5336
|
+
function applyMultiSelect(properties, property, values) {
|
|
5337
|
+
if (!property || !values || values.length === 0)
|
|
5338
|
+
return;
|
|
5339
|
+
const next = {
|
|
5340
|
+
multi_select: values.map((value) => ({ name: value }))
|
|
5341
|
+
};
|
|
5342
|
+
properties[property] = next;
|
|
5343
|
+
}
|
|
5344
|
+
function applyDate(properties, property, value) {
|
|
5345
|
+
if (!property || !value)
|
|
5346
|
+
return;
|
|
5347
|
+
const next = {
|
|
5348
|
+
date: { start: value.toISOString() }
|
|
5349
|
+
};
|
|
5350
|
+
properties[property] = next;
|
|
5351
|
+
}
|
|
5352
|
+
function applyRichText(properties, property, value) {
|
|
5353
|
+
if (!property || !value)
|
|
5354
|
+
return;
|
|
5355
|
+
properties[property] = buildRichText(value);
|
|
5356
|
+
}
|
|
5357
|
+
function buildParagraphBlocks(text) {
|
|
5358
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim());
|
|
5359
|
+
return lines.map((line) => ({
|
|
5360
|
+
object: "block",
|
|
5361
|
+
type: "paragraph",
|
|
5362
|
+
paragraph: {
|
|
5363
|
+
rich_text: [
|
|
5364
|
+
{
|
|
5365
|
+
type: "text",
|
|
5366
|
+
text: { content: line }
|
|
5367
|
+
}
|
|
5368
|
+
]
|
|
5369
|
+
}
|
|
5378
5370
|
}));
|
|
5379
5371
|
}
|
|
5380
5372
|
|
|
@@ -5465,612 +5457,295 @@ class PosthogAnalyticsReader {
|
|
|
5465
5457
|
offset: input.offset
|
|
5466
5458
|
}
|
|
5467
5459
|
});
|
|
5468
|
-
return response.data;
|
|
5469
|
-
}
|
|
5470
|
-
async getFeatureFlags(input) {
|
|
5471
|
-
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5472
|
-
const response = await this.client.request({
|
|
5473
|
-
method: "GET",
|
|
5474
|
-
path: `/api/projects/${projectId}/feature_flags/`,
|
|
5475
|
-
query: {
|
|
5476
|
-
active: input.active,
|
|
5477
|
-
limit: input.limit,
|
|
5478
|
-
offset: input.offset
|
|
5479
|
-
}
|
|
5480
|
-
});
|
|
5481
|
-
return response.data;
|
|
5482
|
-
}
|
|
5483
|
-
async getAnnotations(input) {
|
|
5484
|
-
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5485
|
-
const response = await this.client.request({
|
|
5486
|
-
method: "GET",
|
|
5487
|
-
path: `/api/projects/${projectId}/annotations/`,
|
|
5488
|
-
query: {
|
|
5489
|
-
limit: input.limit,
|
|
5490
|
-
offset: input.offset,
|
|
5491
|
-
...buildAnnotationDateQuery(input.dateRange)
|
|
5492
|
-
}
|
|
5493
|
-
});
|
|
5494
|
-
return response.data;
|
|
5495
|
-
}
|
|
5496
|
-
}
|
|
5497
|
-
function resolveProjectId(inputProjectId, defaultProjectId) {
|
|
5498
|
-
const projectId = inputProjectId ?? defaultProjectId;
|
|
5499
|
-
if (!projectId) {
|
|
5500
|
-
throw new Error("PostHog projectId is required for API reads.");
|
|
5501
|
-
}
|
|
5502
|
-
return projectId;
|
|
5503
|
-
}
|
|
5504
|
-
function resolveSingleEvent(events) {
|
|
5505
|
-
if (!events || events.length !== 1)
|
|
5506
|
-
return;
|
|
5507
|
-
return events[0];
|
|
5508
|
-
}
|
|
5509
|
-
function resolveEventList(events) {
|
|
5510
|
-
if (!events || events.length <= 1)
|
|
5511
|
-
return;
|
|
5512
|
-
return events.join(",");
|
|
5513
|
-
}
|
|
5514
|
-
function buildEventDateQuery(range) {
|
|
5515
|
-
const after = formatDate(range?.from);
|
|
5516
|
-
const before = formatDate(range?.to);
|
|
5517
|
-
return {
|
|
5518
|
-
after,
|
|
5519
|
-
before,
|
|
5520
|
-
timezone: range?.timezone
|
|
5521
|
-
};
|
|
5522
|
-
}
|
|
5523
|
-
function buildAnnotationDateQuery(range) {
|
|
5524
|
-
const dateFrom = formatDate(range?.from);
|
|
5525
|
-
const dateTo = formatDate(range?.to);
|
|
5526
|
-
return {
|
|
5527
|
-
date_from: dateFrom,
|
|
5528
|
-
date_to: dateTo,
|
|
5529
|
-
timezone: range?.timezone
|
|
5530
|
-
};
|
|
5531
|
-
}
|
|
5532
|
-
function formatDate(value) {
|
|
5533
|
-
if (!value)
|
|
5534
|
-
return;
|
|
5535
|
-
return typeof value === "string" ? value : value.toISOString();
|
|
5536
|
-
}
|
|
5537
|
-
|
|
5538
|
-
// src/impls/posthog-utils.ts
|
|
5539
|
-
function normalizeHost(host) {
|
|
5540
|
-
return host.replace(/\/$/, "");
|
|
5541
|
-
}
|
|
5542
|
-
function buildUrl(host, path, query) {
|
|
5543
|
-
if (/^https?:\/\//.test(path)) {
|
|
5544
|
-
return appendQuery(path, query);
|
|
5545
|
-
}
|
|
5546
|
-
const normalizedPath = path.replace(/^\/+/, "");
|
|
5547
|
-
return appendQuery(`${host}/${normalizedPath}`, query);
|
|
5548
|
-
}
|
|
5549
|
-
function appendQuery(url, query) {
|
|
5550
|
-
if (!query)
|
|
5551
|
-
return url;
|
|
5552
|
-
const params = new URLSearchParams;
|
|
5553
|
-
Object.entries(query).forEach(([key, value]) => {
|
|
5554
|
-
if (value === undefined)
|
|
5555
|
-
return;
|
|
5556
|
-
params.set(key, String(value));
|
|
5557
|
-
});
|
|
5558
|
-
const suffix = params.toString();
|
|
5559
|
-
return suffix ? `${url}?${suffix}` : url;
|
|
5560
|
-
}
|
|
5561
|
-
function parseJson(value) {
|
|
5562
|
-
if (!value)
|
|
5563
|
-
return {};
|
|
5564
|
-
try {
|
|
5565
|
-
return JSON.parse(value);
|
|
5566
|
-
} catch {
|
|
5567
|
-
return value;
|
|
5568
|
-
}
|
|
5569
|
-
}
|
|
5570
|
-
|
|
5571
|
-
// src/impls/posthog.ts
|
|
5572
|
-
import { PostHog } from "posthog-node";
|
|
5573
|
-
var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
|
|
5574
|
-
|
|
5575
|
-
class PosthogAnalyticsProvider {
|
|
5576
|
-
host;
|
|
5577
|
-
projectId;
|
|
5578
|
-
projectApiKey;
|
|
5579
|
-
personalApiKey;
|
|
5580
|
-
mcpUrl;
|
|
5581
|
-
fetchFn;
|
|
5582
|
-
client;
|
|
5583
|
-
reader;
|
|
5584
|
-
constructor(options) {
|
|
5585
|
-
this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
|
|
5586
|
-
this.projectId = options.projectId;
|
|
5587
|
-
this.projectApiKey = options.projectApiKey;
|
|
5588
|
-
this.personalApiKey = options.personalApiKey;
|
|
5589
|
-
this.mcpUrl = options.mcpUrl;
|
|
5590
|
-
this.fetchFn = options.fetch ?? fetch;
|
|
5591
|
-
this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
|
|
5592
|
-
host: this.host,
|
|
5593
|
-
requestTimeout: options.requestTimeoutMs ?? 1e4
|
|
5594
|
-
}) : undefined);
|
|
5595
|
-
this.reader = new PosthogAnalyticsReader({
|
|
5596
|
-
projectId: this.projectId,
|
|
5597
|
-
client: { request: this.request.bind(this) }
|
|
5598
|
-
});
|
|
5599
|
-
}
|
|
5600
|
-
async capture(event) {
|
|
5601
|
-
if (!this.client) {
|
|
5602
|
-
throw new Error("PostHog projectApiKey is required for capture.");
|
|
5603
|
-
}
|
|
5604
|
-
await this.client.capture({
|
|
5605
|
-
distinctId: event.distinctId,
|
|
5606
|
-
event: event.event,
|
|
5607
|
-
properties: event.properties,
|
|
5608
|
-
timestamp: event.timestamp,
|
|
5609
|
-
groups: event.groups
|
|
5610
|
-
});
|
|
5611
|
-
}
|
|
5612
|
-
async identify(input) {
|
|
5613
|
-
if (!this.client) {
|
|
5614
|
-
throw new Error("PostHog projectApiKey is required for identify.");
|
|
5615
|
-
}
|
|
5616
|
-
await this.client.identify({
|
|
5617
|
-
distinctId: input.distinctId,
|
|
5618
|
-
properties: {
|
|
5619
|
-
...input.properties ? { $set: input.properties } : {},
|
|
5620
|
-
...input.setOnce ? { $set_once: input.setOnce } : {}
|
|
5621
|
-
}
|
|
5622
|
-
});
|
|
5623
|
-
}
|
|
5624
|
-
async queryHogQL(input) {
|
|
5625
|
-
return this.reader.queryHogQL(input);
|
|
5626
|
-
}
|
|
5627
|
-
async getEvents(input) {
|
|
5628
|
-
return this.reader.getEvents(input);
|
|
5629
|
-
}
|
|
5630
|
-
async getPersons(input) {
|
|
5631
|
-
return this.reader.getPersons(input);
|
|
5632
|
-
}
|
|
5633
|
-
async getInsights(input) {
|
|
5634
|
-
return this.reader.getInsights(input);
|
|
5635
|
-
}
|
|
5636
|
-
async getInsightResult(input) {
|
|
5637
|
-
return this.reader.getInsightResult(input);
|
|
5638
|
-
}
|
|
5639
|
-
async getCohorts(input) {
|
|
5640
|
-
return this.reader.getCohorts(input);
|
|
5641
|
-
}
|
|
5642
|
-
async getFeatureFlags(input) {
|
|
5643
|
-
return this.reader.getFeatureFlags(input);
|
|
5644
|
-
}
|
|
5645
|
-
async getAnnotations(input) {
|
|
5646
|
-
return this.reader.getAnnotations(input);
|
|
5647
|
-
}
|
|
5648
|
-
async request(request) {
|
|
5649
|
-
if (!this.personalApiKey) {
|
|
5650
|
-
throw new Error("PostHog personalApiKey is required for API requests.");
|
|
5651
|
-
}
|
|
5652
|
-
const url = buildUrl(this.host, request.path, request.query);
|
|
5653
|
-
const response = await this.fetchFn(url, {
|
|
5654
|
-
method: request.method,
|
|
5655
|
-
headers: {
|
|
5656
|
-
Authorization: `Bearer ${this.personalApiKey}`,
|
|
5657
|
-
"Content-Type": "application/json",
|
|
5658
|
-
...request.headers ?? {}
|
|
5659
|
-
},
|
|
5660
|
-
body: request.body ? JSON.stringify(request.body) : undefined
|
|
5661
|
-
});
|
|
5662
|
-
const text = await response.text();
|
|
5663
|
-
const data = parseJson(text);
|
|
5664
|
-
return {
|
|
5665
|
-
status: response.status,
|
|
5666
|
-
data,
|
|
5667
|
-
headers: Object.fromEntries(response.headers.entries())
|
|
5668
|
-
};
|
|
5669
|
-
}
|
|
5670
|
-
async callMcpTool(call) {
|
|
5671
|
-
if (!this.mcpUrl) {
|
|
5672
|
-
throw new Error("PostHog MCP URL is not configured.");
|
|
5673
|
-
}
|
|
5674
|
-
const response = await this.fetchFn(this.mcpUrl, {
|
|
5675
|
-
method: "POST",
|
|
5676
|
-
headers: {
|
|
5677
|
-
"Content-Type": "application/json"
|
|
5678
|
-
},
|
|
5679
|
-
body: JSON.stringify({
|
|
5680
|
-
jsonrpc: "2.0",
|
|
5681
|
-
id: 1,
|
|
5682
|
-
method: "tools/call",
|
|
5683
|
-
params: {
|
|
5684
|
-
name: call.name,
|
|
5685
|
-
arguments: call.arguments ?? {}
|
|
5686
|
-
}
|
|
5687
|
-
})
|
|
5688
|
-
});
|
|
5689
|
-
if (!response.ok) {
|
|
5690
|
-
const body = await response.text();
|
|
5691
|
-
throw new Error(`PostHog MCP error (${response.status}): ${body}`);
|
|
5692
|
-
}
|
|
5693
|
-
const result = await response.json();
|
|
5694
|
-
if (result.error) {
|
|
5695
|
-
throw new Error(result.error.message ?? "PostHog MCP error");
|
|
5696
|
-
}
|
|
5697
|
-
return result.result ?? null;
|
|
5698
|
-
}
|
|
5699
|
-
}
|
|
5700
|
-
|
|
5701
|
-
// src/impls/twilio-sms.ts
|
|
5702
|
-
import Twilio from "twilio";
|
|
5703
|
-
|
|
5704
|
-
class TwilioSmsProvider {
|
|
5705
|
-
client;
|
|
5706
|
-
fromNumber;
|
|
5707
|
-
constructor(options) {
|
|
5708
|
-
this.client = options.client ?? Twilio(options.accountSid, options.authToken);
|
|
5709
|
-
this.fromNumber = options.fromNumber;
|
|
5460
|
+
return response.data;
|
|
5710
5461
|
}
|
|
5711
|
-
async
|
|
5712
|
-
const
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5462
|
+
async getFeatureFlags(input) {
|
|
5463
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5464
|
+
const response = await this.client.request({
|
|
5465
|
+
method: "GET",
|
|
5466
|
+
path: `/api/projects/${projectId}/feature_flags/`,
|
|
5467
|
+
query: {
|
|
5468
|
+
active: input.active,
|
|
5469
|
+
limit: input.limit,
|
|
5470
|
+
offset: input.offset
|
|
5471
|
+
}
|
|
5716
5472
|
});
|
|
5717
|
-
return
|
|
5718
|
-
id: message.sid,
|
|
5719
|
-
to: message.to ?? input.to,
|
|
5720
|
-
from: message.from ?? input.from ?? this.fromNumber ?? "",
|
|
5721
|
-
body: message.body ?? input.body,
|
|
5722
|
-
status: mapStatus(message.status),
|
|
5723
|
-
sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
|
|
5724
|
-
deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
|
|
5725
|
-
price: message.price ? Number(message.price) : undefined,
|
|
5726
|
-
priceCurrency: message.priceUnit ?? undefined,
|
|
5727
|
-
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
5728
|
-
errorMessage: message.errorMessage ?? undefined
|
|
5729
|
-
};
|
|
5473
|
+
return response.data;
|
|
5730
5474
|
}
|
|
5731
|
-
async
|
|
5732
|
-
const
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5475
|
+
async getAnnotations(input) {
|
|
5476
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5477
|
+
const response = await this.client.request({
|
|
5478
|
+
method: "GET",
|
|
5479
|
+
path: `/api/projects/${projectId}/annotations/`,
|
|
5480
|
+
query: {
|
|
5481
|
+
limit: input.limit,
|
|
5482
|
+
offset: input.offset,
|
|
5483
|
+
...buildAnnotationDateQuery(input.dateRange)
|
|
5484
|
+
}
|
|
5485
|
+
});
|
|
5486
|
+
return response.data;
|
|
5739
5487
|
}
|
|
5740
5488
|
}
|
|
5741
|
-
function
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
case "scheduled":
|
|
5746
|
-
return "queued";
|
|
5747
|
-
case "sending":
|
|
5748
|
-
case "processing":
|
|
5749
|
-
return "sending";
|
|
5750
|
-
case "sent":
|
|
5751
|
-
return "sent";
|
|
5752
|
-
case "delivered":
|
|
5753
|
-
return "delivered";
|
|
5754
|
-
case "undelivered":
|
|
5755
|
-
return "undelivered";
|
|
5756
|
-
case "failed":
|
|
5757
|
-
case "canceled":
|
|
5758
|
-
return "failed";
|
|
5759
|
-
default:
|
|
5760
|
-
return "queued";
|
|
5489
|
+
function resolveProjectId(inputProjectId, defaultProjectId) {
|
|
5490
|
+
const projectId = inputProjectId ?? defaultProjectId;
|
|
5491
|
+
if (!projectId) {
|
|
5492
|
+
throw new Error("PostHog projectId is required for API reads.");
|
|
5761
5493
|
}
|
|
5494
|
+
return projectId;
|
|
5495
|
+
}
|
|
5496
|
+
function resolveSingleEvent(events) {
|
|
5497
|
+
if (!events || events.length !== 1)
|
|
5498
|
+
return;
|
|
5499
|
+
return events[0];
|
|
5500
|
+
}
|
|
5501
|
+
function resolveEventList(events) {
|
|
5502
|
+
if (!events || events.length <= 1)
|
|
5503
|
+
return;
|
|
5504
|
+
return events.join(",");
|
|
5505
|
+
}
|
|
5506
|
+
function buildEventDateQuery(range) {
|
|
5507
|
+
const after = formatDate(range?.from);
|
|
5508
|
+
const before = formatDate(range?.to);
|
|
5509
|
+
return {
|
|
5510
|
+
after,
|
|
5511
|
+
before,
|
|
5512
|
+
timezone: range?.timezone
|
|
5513
|
+
};
|
|
5514
|
+
}
|
|
5515
|
+
function buildAnnotationDateQuery(range) {
|
|
5516
|
+
const dateFrom = formatDate(range?.from);
|
|
5517
|
+
const dateTo = formatDate(range?.to);
|
|
5518
|
+
return {
|
|
5519
|
+
date_from: dateFrom,
|
|
5520
|
+
date_to: dateTo,
|
|
5521
|
+
timezone: range?.timezone
|
|
5522
|
+
};
|
|
5523
|
+
}
|
|
5524
|
+
function formatDate(value) {
|
|
5525
|
+
if (!value)
|
|
5526
|
+
return;
|
|
5527
|
+
return typeof value === "string" ? value : value.toISOString();
|
|
5762
5528
|
}
|
|
5763
5529
|
|
|
5764
|
-
// src/impls/
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
this.defaultChannelId = options.defaultChannelId;
|
|
5772
|
-
this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
|
|
5773
|
-
}
|
|
5774
|
-
async sendMessage(input) {
|
|
5775
|
-
const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
|
|
5776
|
-
if (!channel) {
|
|
5777
|
-
throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
|
|
5778
|
-
}
|
|
5779
|
-
const payload = {
|
|
5780
|
-
channel,
|
|
5781
|
-
text: input.text,
|
|
5782
|
-
mrkdwn: input.markdown ?? true,
|
|
5783
|
-
thread_ts: input.threadId
|
|
5784
|
-
};
|
|
5785
|
-
const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
|
|
5786
|
-
method: "POST",
|
|
5787
|
-
headers: {
|
|
5788
|
-
authorization: `Bearer ${this.botToken}`,
|
|
5789
|
-
"content-type": "application/json"
|
|
5790
|
-
},
|
|
5791
|
-
body: JSON.stringify(payload)
|
|
5792
|
-
});
|
|
5793
|
-
const body = await response.json();
|
|
5794
|
-
if (!response.ok || !body.ok || !body.ts) {
|
|
5795
|
-
throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
|
|
5796
|
-
}
|
|
5797
|
-
return {
|
|
5798
|
-
id: `slack:${body.channel ?? channel}:${body.ts}`,
|
|
5799
|
-
providerMessageId: body.ts,
|
|
5800
|
-
status: "sent",
|
|
5801
|
-
sentAt: new Date,
|
|
5802
|
-
metadata: {
|
|
5803
|
-
channelId: body.channel ?? channel
|
|
5804
|
-
}
|
|
5805
|
-
};
|
|
5530
|
+
// src/impls/posthog-utils.ts
|
|
5531
|
+
function normalizeHost(host) {
|
|
5532
|
+
return host.replace(/\/$/, "");
|
|
5533
|
+
}
|
|
5534
|
+
function buildUrl(host, path, query) {
|
|
5535
|
+
if (/^https?:\/\//.test(path)) {
|
|
5536
|
+
return appendQuery(path, query);
|
|
5806
5537
|
}
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
}
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
return {
|
|
5830
|
-
id: `slack:${body.channel ?? channel}:${body.ts}`,
|
|
5831
|
-
providerMessageId: body.ts,
|
|
5832
|
-
status: "sent",
|
|
5833
|
-
sentAt: new Date,
|
|
5834
|
-
metadata: {
|
|
5835
|
-
channelId: body.channel ?? channel
|
|
5836
|
-
}
|
|
5837
|
-
};
|
|
5538
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
5539
|
+
return appendQuery(`${host}/${normalizedPath}`, query);
|
|
5540
|
+
}
|
|
5541
|
+
function appendQuery(url, query) {
|
|
5542
|
+
if (!query)
|
|
5543
|
+
return url;
|
|
5544
|
+
const params = new URLSearchParams;
|
|
5545
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
5546
|
+
if (value === undefined)
|
|
5547
|
+
return;
|
|
5548
|
+
params.set(key, String(value));
|
|
5549
|
+
});
|
|
5550
|
+
const suffix = params.toString();
|
|
5551
|
+
return suffix ? `${url}?${suffix}` : url;
|
|
5552
|
+
}
|
|
5553
|
+
function parseJson(value) {
|
|
5554
|
+
if (!value)
|
|
5555
|
+
return {};
|
|
5556
|
+
try {
|
|
5557
|
+
return JSON.parse(value);
|
|
5558
|
+
} catch {
|
|
5559
|
+
return value;
|
|
5838
5560
|
}
|
|
5839
5561
|
}
|
|
5840
5562
|
|
|
5841
|
-
// src/impls/
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5563
|
+
// src/impls/posthog.ts
|
|
5564
|
+
import { PostHog } from "posthog-node";
|
|
5565
|
+
var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
|
|
5566
|
+
|
|
5567
|
+
class PosthogAnalyticsProvider {
|
|
5568
|
+
host;
|
|
5569
|
+
projectId;
|
|
5570
|
+
projectApiKey;
|
|
5571
|
+
personalApiKey;
|
|
5572
|
+
mcpUrl;
|
|
5573
|
+
fetchFn;
|
|
5574
|
+
client;
|
|
5575
|
+
reader;
|
|
5847
5576
|
constructor(options) {
|
|
5848
|
-
this.
|
|
5849
|
-
this.
|
|
5850
|
-
this.
|
|
5851
|
-
this.
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
},
|
|
5862
|
-
body: JSON.stringify({ body: input.text })
|
|
5577
|
+
this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
|
|
5578
|
+
this.projectId = options.projectId;
|
|
5579
|
+
this.projectApiKey = options.projectApiKey;
|
|
5580
|
+
this.personalApiKey = options.personalApiKey;
|
|
5581
|
+
this.mcpUrl = options.mcpUrl;
|
|
5582
|
+
this.fetchFn = options.fetch ?? fetch;
|
|
5583
|
+
this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
|
|
5584
|
+
host: this.host,
|
|
5585
|
+
requestTimeout: options.requestTimeoutMs ?? 1e4
|
|
5586
|
+
}) : undefined);
|
|
5587
|
+
this.reader = new PosthogAnalyticsReader({
|
|
5588
|
+
projectId: this.projectId,
|
|
5589
|
+
client: { request: this.request.bind(this) }
|
|
5863
5590
|
});
|
|
5864
|
-
const body = await response.json();
|
|
5865
|
-
if (!response.ok || !body.id) {
|
|
5866
|
-
throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
5867
|
-
}
|
|
5868
|
-
return {
|
|
5869
|
-
id: String(body.id),
|
|
5870
|
-
providerMessageId: body.node_id,
|
|
5871
|
-
status: "sent",
|
|
5872
|
-
sentAt: new Date,
|
|
5873
|
-
metadata: {
|
|
5874
|
-
url: body.html_url ?? "",
|
|
5875
|
-
owner: target.owner,
|
|
5876
|
-
repo: target.repo,
|
|
5877
|
-
issueNumber: String(target.issueNumber)
|
|
5878
|
-
}
|
|
5879
|
-
};
|
|
5880
5591
|
}
|
|
5881
|
-
async
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
if (!owner || !repo) {
|
|
5885
|
-
throw new Error("GitHub updateMessage requires owner and repo metadata.");
|
|
5592
|
+
async capture(event) {
|
|
5593
|
+
if (!this.client) {
|
|
5594
|
+
throw new Error("PostHog projectApiKey is required for capture.");
|
|
5886
5595
|
}
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
},
|
|
5894
|
-
body: JSON.stringify({ body: input.text })
|
|
5596
|
+
await this.client.capture({
|
|
5597
|
+
distinctId: event.distinctId,
|
|
5598
|
+
event: event.event,
|
|
5599
|
+
properties: event.properties,
|
|
5600
|
+
timestamp: event.timestamp,
|
|
5601
|
+
groups: event.groups
|
|
5895
5602
|
});
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5603
|
+
}
|
|
5604
|
+
async identify(input) {
|
|
5605
|
+
if (!this.client) {
|
|
5606
|
+
throw new Error("PostHog projectApiKey is required for identify.");
|
|
5899
5607
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
metadata: {
|
|
5906
|
-
url: body.html_url ?? "",
|
|
5907
|
-
owner,
|
|
5908
|
-
repo
|
|
5608
|
+
await this.client.identify({
|
|
5609
|
+
distinctId: input.distinctId,
|
|
5610
|
+
properties: {
|
|
5611
|
+
...input.properties ? { $set: input.properties } : {},
|
|
5612
|
+
...input.setOnce ? { $set_once: input.setOnce } : {}
|
|
5909
5613
|
}
|
|
5910
|
-
};
|
|
5614
|
+
});
|
|
5911
5615
|
}
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5616
|
+
async queryHogQL(input) {
|
|
5617
|
+
return this.reader.queryHogQL(input);
|
|
5618
|
+
}
|
|
5619
|
+
async getEvents(input) {
|
|
5620
|
+
return this.reader.getEvents(input);
|
|
5621
|
+
}
|
|
5622
|
+
async getPersons(input) {
|
|
5623
|
+
return this.reader.getPersons(input);
|
|
5624
|
+
}
|
|
5625
|
+
async getInsights(input) {
|
|
5626
|
+
return this.reader.getInsights(input);
|
|
5627
|
+
}
|
|
5628
|
+
async getInsightResult(input) {
|
|
5629
|
+
return this.reader.getInsightResult(input);
|
|
5630
|
+
}
|
|
5631
|
+
async getCohorts(input) {
|
|
5632
|
+
return this.reader.getCohorts(input);
|
|
5633
|
+
}
|
|
5634
|
+
async getFeatureFlags(input) {
|
|
5635
|
+
return this.reader.getFeatureFlags(input);
|
|
5636
|
+
}
|
|
5637
|
+
async getAnnotations(input) {
|
|
5638
|
+
return this.reader.getAnnotations(input);
|
|
5639
|
+
}
|
|
5640
|
+
async request(request) {
|
|
5641
|
+
if (!this.personalApiKey) {
|
|
5642
|
+
throw new Error("PostHog personalApiKey is required for API requests.");
|
|
5919
5643
|
}
|
|
5644
|
+
const url = buildUrl(this.host, request.path, request.query);
|
|
5645
|
+
const response = await this.fetchFn(url, {
|
|
5646
|
+
method: request.method,
|
|
5647
|
+
headers: {
|
|
5648
|
+
Authorization: `Bearer ${this.personalApiKey}`,
|
|
5649
|
+
"Content-Type": "application/json",
|
|
5650
|
+
...request.headers ?? {}
|
|
5651
|
+
},
|
|
5652
|
+
body: request.body ? JSON.stringify(request.body) : undefined
|
|
5653
|
+
});
|
|
5654
|
+
const text = await response.text();
|
|
5655
|
+
const data = parseJson(text);
|
|
5920
5656
|
return {
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5657
|
+
status: response.status,
|
|
5658
|
+
data,
|
|
5659
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
5924
5660
|
};
|
|
5925
5661
|
}
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
return null;
|
|
5930
|
-
const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
5931
|
-
if (!match)
|
|
5932
|
-
return null;
|
|
5933
|
-
const owner = match[1];
|
|
5934
|
-
const repo = match[2];
|
|
5935
|
-
const issueNumber = Number(match[3]);
|
|
5936
|
-
if (!owner || !repo || !Number.isInteger(issueNumber)) {
|
|
5937
|
-
return null;
|
|
5938
|
-
}
|
|
5939
|
-
return { owner, repo, issueNumber };
|
|
5940
|
-
}
|
|
5941
|
-
function parseIssueNumber(value) {
|
|
5942
|
-
if (!value)
|
|
5943
|
-
return null;
|
|
5944
|
-
const numeric = Number(value);
|
|
5945
|
-
return Number.isInteger(numeric) ? numeric : null;
|
|
5946
|
-
}
|
|
5947
|
-
|
|
5948
|
-
// src/impls/messaging-whatsapp-meta.ts
|
|
5949
|
-
class MetaWhatsappMessagingProvider {
|
|
5950
|
-
accessToken;
|
|
5951
|
-
phoneNumberId;
|
|
5952
|
-
apiVersion;
|
|
5953
|
-
constructor(options) {
|
|
5954
|
-
this.accessToken = options.accessToken;
|
|
5955
|
-
this.phoneNumberId = options.phoneNumberId;
|
|
5956
|
-
this.apiVersion = options.apiVersion ?? "v22.0";
|
|
5957
|
-
}
|
|
5958
|
-
async sendMessage(input) {
|
|
5959
|
-
const to = input.recipientId;
|
|
5960
|
-
if (!to) {
|
|
5961
|
-
throw new Error("Meta WhatsApp sendMessage requires recipientId.");
|
|
5662
|
+
async callMcpTool(call) {
|
|
5663
|
+
if (!this.mcpUrl) {
|
|
5664
|
+
throw new Error("PostHog MCP URL is not configured.");
|
|
5962
5665
|
}
|
|
5963
|
-
const response = await
|
|
5666
|
+
const response = await this.fetchFn(this.mcpUrl, {
|
|
5964
5667
|
method: "POST",
|
|
5965
5668
|
headers: {
|
|
5966
|
-
|
|
5967
|
-
"content-type": "application/json"
|
|
5669
|
+
"Content-Type": "application/json"
|
|
5968
5670
|
},
|
|
5969
5671
|
body: JSON.stringify({
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5672
|
+
jsonrpc: "2.0",
|
|
5673
|
+
id: 1,
|
|
5674
|
+
method: "tools/call",
|
|
5675
|
+
params: {
|
|
5676
|
+
name: call.name,
|
|
5677
|
+
arguments: call.arguments ?? {}
|
|
5976
5678
|
}
|
|
5977
5679
|
})
|
|
5978
5680
|
});
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
const errorCode = body.error?.code != null ? String(body.error.code) : "";
|
|
5983
|
-
throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
|
|
5681
|
+
if (!response.ok) {
|
|
5682
|
+
const body = await response.text();
|
|
5683
|
+
throw new Error(`PostHog MCP error (${response.status}): ${body}`);
|
|
5984
5684
|
}
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
metadata: {
|
|
5991
|
-
phoneNumberId: this.phoneNumberId
|
|
5992
|
-
}
|
|
5993
|
-
};
|
|
5685
|
+
const result = await response.json();
|
|
5686
|
+
if (result.error) {
|
|
5687
|
+
throw new Error(result.error.message ?? "PostHog MCP error");
|
|
5688
|
+
}
|
|
5689
|
+
return result.result ?? null;
|
|
5994
5690
|
}
|
|
5995
5691
|
}
|
|
5996
5692
|
|
|
5997
|
-
// src/impls/
|
|
5998
|
-
import {
|
|
5693
|
+
// src/impls/postmark-email.ts
|
|
5694
|
+
import { ServerClient } from "postmark";
|
|
5999
5695
|
|
|
6000
|
-
class
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
5696
|
+
class PostmarkEmailProvider {
|
|
5697
|
+
client;
|
|
5698
|
+
defaultFromEmail;
|
|
5699
|
+
messageStream;
|
|
6004
5700
|
constructor(options) {
|
|
6005
|
-
this.
|
|
6006
|
-
|
|
6007
|
-
this.fromNumber = options.fromNumber;
|
|
6008
|
-
}
|
|
6009
|
-
async sendMessage(input) {
|
|
6010
|
-
const to = normalizeWhatsappAddress(input.recipientId);
|
|
6011
|
-
const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
|
|
6012
|
-
if (!to) {
|
|
6013
|
-
throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
|
|
6014
|
-
}
|
|
6015
|
-
if (!from) {
|
|
6016
|
-
throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
|
|
6017
|
-
}
|
|
6018
|
-
const params = new URLSearchParams;
|
|
6019
|
-
params.set("To", to);
|
|
6020
|
-
params.set("From", from);
|
|
6021
|
-
params.set("Body", input.text);
|
|
6022
|
-
const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
|
|
6023
|
-
const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
|
|
6024
|
-
method: "POST",
|
|
6025
|
-
headers: {
|
|
6026
|
-
authorization: `Basic ${auth}`,
|
|
6027
|
-
"content-type": "application/x-www-form-urlencoded"
|
|
6028
|
-
},
|
|
6029
|
-
body: params.toString()
|
|
5701
|
+
this.client = options.client ?? new ServerClient(options.serverToken, {
|
|
5702
|
+
useHttps: true
|
|
6030
5703
|
});
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
5704
|
+
this.defaultFromEmail = options.defaultFromEmail;
|
|
5705
|
+
this.messageStream = options.messageStream;
|
|
5706
|
+
}
|
|
5707
|
+
async sendEmail(message) {
|
|
5708
|
+
const request = {
|
|
5709
|
+
From: formatAddress2(message.from) ?? this.defaultFromEmail,
|
|
5710
|
+
To: message.to.map((addr) => formatAddress2(addr)).join(", "),
|
|
5711
|
+
Cc: message.cc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
|
|
5712
|
+
Bcc: message.bcc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
|
|
5713
|
+
ReplyTo: message.replyTo ? formatAddress2(message.replyTo) : undefined,
|
|
5714
|
+
Subject: message.subject,
|
|
5715
|
+
TextBody: message.textBody,
|
|
5716
|
+
HtmlBody: message.htmlBody,
|
|
5717
|
+
Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
|
|
5718
|
+
Name: name,
|
|
5719
|
+
Value: value
|
|
5720
|
+
})) : undefined,
|
|
5721
|
+
MessageStream: this.messageStream,
|
|
5722
|
+
Attachments: buildAttachments(message)
|
|
5723
|
+
};
|
|
5724
|
+
const response = await this.client.sendEmail(request);
|
|
6035
5725
|
return {
|
|
6036
|
-
id:
|
|
6037
|
-
providerMessageId:
|
|
6038
|
-
|
|
6039
|
-
sentAt: new Date,
|
|
6040
|
-
errorCode: body.error_code != null ? String(body.error_code) : undefined,
|
|
6041
|
-
errorMessage: body.error_message ?? undefined,
|
|
6042
|
-
metadata: {
|
|
6043
|
-
from,
|
|
6044
|
-
to
|
|
6045
|
-
}
|
|
5726
|
+
id: response.MessageID,
|
|
5727
|
+
providerMessageId: response.MessageID,
|
|
5728
|
+
queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
|
|
6046
5729
|
};
|
|
6047
5730
|
}
|
|
6048
5731
|
}
|
|
6049
|
-
function
|
|
6050
|
-
if (
|
|
6051
|
-
return
|
|
6052
|
-
if (value.startsWith("whatsapp:"))
|
|
6053
|
-
return value;
|
|
6054
|
-
return `whatsapp:${value}`;
|
|
6055
|
-
}
|
|
6056
|
-
function mapTwilioStatus(status) {
|
|
6057
|
-
switch (status) {
|
|
6058
|
-
case "queued":
|
|
6059
|
-
case "accepted":
|
|
6060
|
-
case "scheduled":
|
|
6061
|
-
return "queued";
|
|
6062
|
-
case "sending":
|
|
6063
|
-
return "sending";
|
|
6064
|
-
case "delivered":
|
|
6065
|
-
return "delivered";
|
|
6066
|
-
case "failed":
|
|
6067
|
-
case "undelivered":
|
|
6068
|
-
case "canceled":
|
|
6069
|
-
return "failed";
|
|
6070
|
-
case "sent":
|
|
6071
|
-
default:
|
|
6072
|
-
return "sent";
|
|
5732
|
+
function formatAddress2(address) {
|
|
5733
|
+
if (address.name) {
|
|
5734
|
+
return `"${address.name}" <${address.email}>`;
|
|
6073
5735
|
}
|
|
5736
|
+
return address.email;
|
|
5737
|
+
}
|
|
5738
|
+
function buildAttachments(message) {
|
|
5739
|
+
if (!message.attachments?.length)
|
|
5740
|
+
return;
|
|
5741
|
+
return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
|
|
5742
|
+
Name: attachment.filename,
|
|
5743
|
+
Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
|
|
5744
|
+
ContentType: attachment.contentType,
|
|
5745
|
+
ContentID: null,
|
|
5746
|
+
ContentLength: attachment.sizeBytes,
|
|
5747
|
+
Disposition: "attachment"
|
|
5748
|
+
}));
|
|
6074
5749
|
}
|
|
6075
5750
|
|
|
6076
5751
|
// src/impls/powens-client.ts
|
|
@@ -6495,367 +6170,629 @@ class PowensOpenBankingProvider {
|
|
|
6495
6170
|
this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
|
|
6496
6171
|
throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
|
|
6497
6172
|
}
|
|
6498
|
-
}
|
|
6499
|
-
|
|
6500
|
-
// src/impls/
|
|
6501
|
-
import {
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
this.
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6173
|
+
}
|
|
6174
|
+
|
|
6175
|
+
// src/impls/qdrant-vector.ts
|
|
6176
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
6177
|
+
|
|
6178
|
+
class QdrantVectorProvider {
|
|
6179
|
+
client;
|
|
6180
|
+
createCollectionIfMissing;
|
|
6181
|
+
distance;
|
|
6182
|
+
constructor(options) {
|
|
6183
|
+
this.client = options.client ?? new QdrantClient({
|
|
6184
|
+
url: options.url,
|
|
6185
|
+
apiKey: options.apiKey,
|
|
6186
|
+
...options.clientParams
|
|
6187
|
+
});
|
|
6188
|
+
this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
|
|
6189
|
+
this.distance = options.distance ?? "Cosine";
|
|
6190
|
+
}
|
|
6191
|
+
async upsert(request) {
|
|
6192
|
+
if (request.documents.length === 0)
|
|
6193
|
+
return;
|
|
6194
|
+
const firstDocument = request.documents[0];
|
|
6195
|
+
if (!firstDocument)
|
|
6196
|
+
return;
|
|
6197
|
+
const vectorSize = firstDocument.vector.length;
|
|
6198
|
+
if (this.createCollectionIfMissing) {
|
|
6199
|
+
await this.ensureCollection(request.collection, vectorSize);
|
|
6200
|
+
}
|
|
6201
|
+
const points = request.documents.map((document) => ({
|
|
6202
|
+
id: document.id,
|
|
6203
|
+
vector: document.vector,
|
|
6204
|
+
payload: {
|
|
6205
|
+
...document.payload,
|
|
6206
|
+
...document.namespace ? { namespace: document.namespace } : {},
|
|
6207
|
+
...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
|
|
6208
|
+
}
|
|
6209
|
+
}));
|
|
6210
|
+
await this.client.upsert(request.collection, {
|
|
6211
|
+
wait: true,
|
|
6212
|
+
points
|
|
6213
|
+
});
|
|
6214
|
+
}
|
|
6215
|
+
async search(query) {
|
|
6216
|
+
const results = await this.client.search(query.collection, {
|
|
6217
|
+
vector: query.vector,
|
|
6218
|
+
limit: query.topK,
|
|
6219
|
+
filter: query.filter,
|
|
6220
|
+
score_threshold: query.scoreThreshold,
|
|
6221
|
+
with_payload: true,
|
|
6222
|
+
with_vector: false
|
|
6223
|
+
});
|
|
6224
|
+
return results.map((item) => ({
|
|
6225
|
+
id: String(item.id),
|
|
6226
|
+
score: item.score,
|
|
6227
|
+
payload: item.payload ?? undefined,
|
|
6228
|
+
namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
|
|
6229
|
+
}));
|
|
6230
|
+
}
|
|
6231
|
+
async delete(request) {
|
|
6232
|
+
await this.client.delete(request.collection, {
|
|
6233
|
+
wait: true,
|
|
6234
|
+
points: request.ids
|
|
6235
|
+
});
|
|
6236
|
+
}
|
|
6237
|
+
async ensureCollection(collectionName, vectorSize) {
|
|
6238
|
+
try {
|
|
6239
|
+
await this.client.getCollection(collectionName);
|
|
6240
|
+
} catch (_error) {
|
|
6241
|
+
await this.client.createCollection(collectionName, {
|
|
6242
|
+
vectors: {
|
|
6243
|
+
size: vectorSize,
|
|
6244
|
+
distance: this.distance
|
|
6245
|
+
}
|
|
6246
|
+
});
|
|
6247
|
+
}
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
|
|
6251
|
+
// src/impls/stripe-payments.ts
|
|
6252
|
+
import Stripe from "stripe";
|
|
6253
|
+
var API_VERSION = "2026-02-25.clover";
|
|
6254
|
+
|
|
6255
|
+
class StripePaymentsProvider {
|
|
6256
|
+
stripe;
|
|
6257
|
+
constructor(options) {
|
|
6258
|
+
this.stripe = options.stripe ?? new Stripe(options.apiKey, {
|
|
6259
|
+
apiVersion: API_VERSION
|
|
6260
|
+
});
|
|
6261
|
+
}
|
|
6262
|
+
async createCustomer(input) {
|
|
6263
|
+
const customer = await this.stripe.customers.create({
|
|
6264
|
+
email: input.email,
|
|
6265
|
+
name: input.name,
|
|
6266
|
+
description: input.description,
|
|
6267
|
+
metadata: input.metadata
|
|
6268
|
+
});
|
|
6269
|
+
return this.toCustomer(customer);
|
|
6270
|
+
}
|
|
6271
|
+
async getCustomer(customerId) {
|
|
6272
|
+
const customer = await this.stripe.customers.retrieve(customerId);
|
|
6273
|
+
if (customer.deleted)
|
|
6274
|
+
return null;
|
|
6275
|
+
return this.toCustomer(customer);
|
|
6276
|
+
}
|
|
6277
|
+
async createPaymentIntent(input) {
|
|
6278
|
+
const intent = await this.stripe.paymentIntents.create({
|
|
6279
|
+
amount: input.amount.amount,
|
|
6280
|
+
currency: input.amount.currency,
|
|
6281
|
+
customer: input.customerId,
|
|
6282
|
+
description: input.description,
|
|
6283
|
+
capture_method: input.captureMethod ?? "automatic",
|
|
6284
|
+
confirmation_method: input.confirmationMethod ?? "automatic",
|
|
6285
|
+
automatic_payment_methods: { enabled: true },
|
|
6286
|
+
metadata: input.metadata,
|
|
6287
|
+
return_url: input.returnUrl,
|
|
6288
|
+
statement_descriptor: input.statementDescriptor
|
|
6289
|
+
});
|
|
6290
|
+
return this.toPaymentIntent(intent);
|
|
6291
|
+
}
|
|
6292
|
+
async capturePayment(paymentIntentId, input) {
|
|
6293
|
+
const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
|
|
6294
|
+
return this.toPaymentIntent(intent);
|
|
6295
|
+
}
|
|
6296
|
+
async cancelPaymentIntent(paymentIntentId) {
|
|
6297
|
+
const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
|
|
6298
|
+
return this.toPaymentIntent(intent);
|
|
6299
|
+
}
|
|
6300
|
+
async refundPayment(input) {
|
|
6301
|
+
const refund = await this.stripe.refunds.create({
|
|
6302
|
+
payment_intent: input.paymentIntentId,
|
|
6303
|
+
amount: input.amount?.amount,
|
|
6304
|
+
reason: mapRefundReason(input.reason),
|
|
6305
|
+
metadata: input.metadata
|
|
6306
|
+
});
|
|
6307
|
+
const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
|
|
6308
|
+
return {
|
|
6309
|
+
id: refund.id,
|
|
6310
|
+
paymentIntentId,
|
|
6311
|
+
amount: {
|
|
6312
|
+
amount: refund.amount ?? 0,
|
|
6313
|
+
currency: refund.currency?.toUpperCase() ?? "USD"
|
|
6314
|
+
},
|
|
6315
|
+
status: mapRefundStatus(refund.status),
|
|
6316
|
+
reason: refund.reason ?? undefined,
|
|
6317
|
+
metadata: this.toMetadata(refund.metadata),
|
|
6318
|
+
createdAt: refund.created ? new Date(refund.created * 1000) : undefined
|
|
6319
|
+
};
|
|
6320
|
+
}
|
|
6321
|
+
async listInvoices(query) {
|
|
6322
|
+
const requestedStatus = query?.status?.[0];
|
|
6323
|
+
const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
|
|
6324
|
+
const response = await this.stripe.invoices.list({
|
|
6325
|
+
customer: query?.customerId,
|
|
6326
|
+
status: stripeStatus,
|
|
6327
|
+
limit: query?.limit,
|
|
6328
|
+
starting_after: query?.startingAfter
|
|
6329
|
+
});
|
|
6330
|
+
return response.data.map((invoice) => this.toInvoice(invoice));
|
|
6331
|
+
}
|
|
6332
|
+
async listTransactions(query) {
|
|
6333
|
+
const response = await this.stripe.charges.list({
|
|
6334
|
+
customer: query?.customerId,
|
|
6335
|
+
payment_intent: query?.paymentIntentId,
|
|
6336
|
+
limit: query?.limit,
|
|
6337
|
+
starting_after: query?.startingAfter
|
|
6338
|
+
});
|
|
6339
|
+
return response.data.map((charge) => ({
|
|
6340
|
+
id: charge.id,
|
|
6341
|
+
paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
|
|
6342
|
+
amount: {
|
|
6343
|
+
amount: charge.amount,
|
|
6344
|
+
currency: charge.currency?.toUpperCase() ?? "USD"
|
|
6345
|
+
},
|
|
6346
|
+
type: "capture",
|
|
6347
|
+
status: mapChargeStatus(charge.status),
|
|
6348
|
+
description: charge.description ?? undefined,
|
|
6349
|
+
createdAt: new Date(charge.created * 1000),
|
|
6350
|
+
metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
|
|
6351
|
+
balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
|
|
6352
|
+
})
|
|
6353
|
+
}));
|
|
6354
|
+
}
|
|
6355
|
+
toCustomer(customer) {
|
|
6356
|
+
const metadata = this.toMetadata(customer.metadata);
|
|
6357
|
+
const updatedAtValue = metadata?.updatedAt;
|
|
6358
|
+
return {
|
|
6359
|
+
id: customer.id,
|
|
6360
|
+
email: customer.email ?? undefined,
|
|
6361
|
+
name: customer.name ?? undefined,
|
|
6362
|
+
metadata,
|
|
6363
|
+
createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
|
|
6364
|
+
updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
|
|
6365
|
+
};
|
|
6366
|
+
}
|
|
6367
|
+
toPaymentIntent(intent) {
|
|
6368
|
+
const metadata = this.toMetadata(intent.metadata);
|
|
6369
|
+
return {
|
|
6370
|
+
id: intent.id,
|
|
6371
|
+
amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
|
|
6372
|
+
status: mapPaymentIntentStatus(intent.status),
|
|
6373
|
+
customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
|
|
6374
|
+
description: intent.description ?? undefined,
|
|
6375
|
+
clientSecret: intent.client_secret ?? undefined,
|
|
6376
|
+
metadata,
|
|
6377
|
+
createdAt: new Date(intent.created * 1000),
|
|
6378
|
+
updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
|
|
6379
|
+
};
|
|
6380
|
+
}
|
|
6381
|
+
toInvoice(invoice) {
|
|
6382
|
+
const metadata = this.toMetadata(invoice.metadata);
|
|
6383
|
+
return {
|
|
6384
|
+
id: invoice.id,
|
|
6385
|
+
number: invoice.number ?? undefined,
|
|
6386
|
+
status: invoice.status ?? "draft",
|
|
6387
|
+
amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
|
|
6388
|
+
amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
|
|
6389
|
+
customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
|
|
6390
|
+
dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
|
|
6391
|
+
hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
|
|
6392
|
+
metadata,
|
|
6393
|
+
createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
|
|
6394
|
+
updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
|
|
6522
6395
|
};
|
|
6523
6396
|
}
|
|
6524
|
-
|
|
6525
|
-
const teamId = this.defaults.teamId;
|
|
6526
|
-
if (!teamId) {
|
|
6527
|
-
throw new Error("Linear teamId is required to create work items.");
|
|
6528
|
-
}
|
|
6529
|
-
const payload = await this.client.createIssue({
|
|
6530
|
-
teamId,
|
|
6531
|
-
title: input.title,
|
|
6532
|
-
description: input.description,
|
|
6533
|
-
priority: mapPriority(input.priority),
|
|
6534
|
-
estimate: input.estimate,
|
|
6535
|
-
assigneeId: input.assigneeId ?? this.defaults.assigneeId,
|
|
6536
|
-
projectId: input.projectId ?? this.defaults.projectId,
|
|
6537
|
-
stateId: this.defaults.stateId,
|
|
6538
|
-
labelIds: resolveLabelIds(this.defaults, input.tags)
|
|
6539
|
-
});
|
|
6540
|
-
const issue = await payload.issue;
|
|
6541
|
-
const state = issue ? await issue.state : undefined;
|
|
6397
|
+
toMoney(amount, currency) {
|
|
6542
6398
|
return {
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
url: issue?.url ?? undefined,
|
|
6546
|
-
status: state?.name ?? undefined,
|
|
6547
|
-
priority: input.priority,
|
|
6548
|
-
tags: input.tags,
|
|
6549
|
-
projectId: input.projectId ?? this.defaults.projectId,
|
|
6550
|
-
externalId: input.externalId,
|
|
6551
|
-
metadata: input.metadata
|
|
6399
|
+
amount,
|
|
6400
|
+
currency: currency?.toUpperCase() ?? "USD"
|
|
6552
6401
|
};
|
|
6553
6402
|
}
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6403
|
+
toMetadata(metadata) {
|
|
6404
|
+
if (!metadata)
|
|
6405
|
+
return;
|
|
6406
|
+
const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
|
|
6407
|
+
if (entries.length === 0)
|
|
6408
|
+
return;
|
|
6409
|
+
return Object.fromEntries(entries);
|
|
6410
|
+
}
|
|
6411
|
+
mergeMetadata(base, extras) {
|
|
6412
|
+
const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
|
|
6413
|
+
if (!base && filteredExtras.length === 0) {
|
|
6414
|
+
return;
|
|
6558
6415
|
}
|
|
6559
|
-
return
|
|
6416
|
+
return {
|
|
6417
|
+
...base ?? {},
|
|
6418
|
+
...Object.fromEntries(filteredExtras)
|
|
6419
|
+
};
|
|
6560
6420
|
}
|
|
6561
6421
|
}
|
|
6562
|
-
function
|
|
6563
|
-
if (!
|
|
6422
|
+
function mapRefundReason(reason) {
|
|
6423
|
+
if (!reason)
|
|
6564
6424
|
return;
|
|
6565
|
-
|
|
6425
|
+
const allowed = [
|
|
6426
|
+
"duplicate",
|
|
6427
|
+
"fraudulent",
|
|
6428
|
+
"requested_by_customer"
|
|
6429
|
+
];
|
|
6430
|
+
return allowed.includes(reason) ? reason : undefined;
|
|
6566
6431
|
}
|
|
6567
|
-
function
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6432
|
+
function mapPaymentIntentStatus(status) {
|
|
6433
|
+
switch (status) {
|
|
6434
|
+
case "requires_payment_method":
|
|
6435
|
+
return "requires_payment_method";
|
|
6436
|
+
case "requires_confirmation":
|
|
6437
|
+
return "requires_confirmation";
|
|
6438
|
+
case "requires_action":
|
|
6439
|
+
case "requires_capture":
|
|
6440
|
+
return "requires_action";
|
|
6441
|
+
case "processing":
|
|
6442
|
+
return "processing";
|
|
6443
|
+
case "succeeded":
|
|
6444
|
+
return "succeeded";
|
|
6445
|
+
case "canceled":
|
|
6446
|
+
return "canceled";
|
|
6447
|
+
default:
|
|
6448
|
+
return "requires_payment_method";
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6451
|
+
function mapRefundStatus(status) {
|
|
6452
|
+
switch (status) {
|
|
6453
|
+
case "pending":
|
|
6454
|
+
case "succeeded":
|
|
6455
|
+
case "failed":
|
|
6456
|
+
case "canceled":
|
|
6457
|
+
return status;
|
|
6458
|
+
default:
|
|
6459
|
+
return "pending";
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
function mapChargeStatus(status) {
|
|
6463
|
+
switch (status) {
|
|
6464
|
+
case "pending":
|
|
6465
|
+
case "processing":
|
|
6466
|
+
return "pending";
|
|
6467
|
+
case "succeeded":
|
|
6468
|
+
return "succeeded";
|
|
6469
|
+
case "failed":
|
|
6470
|
+
case "canceled":
|
|
6471
|
+
return "failed";
|
|
6472
|
+
default:
|
|
6473
|
+
return "pending";
|
|
6576
6474
|
}
|
|
6577
|
-
const merged = [...labelIds];
|
|
6578
|
-
return merged.length > 0 ? merged : undefined;
|
|
6579
6475
|
}
|
|
6580
6476
|
|
|
6581
|
-
// src/impls/
|
|
6477
|
+
// src/impls/supabase-psql.ts
|
|
6582
6478
|
import { Buffer as Buffer5 } from "node:buffer";
|
|
6479
|
+
import { sql as drizzleSql } from "drizzle-orm";
|
|
6480
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
6481
|
+
import postgres from "postgres";
|
|
6583
6482
|
|
|
6584
|
-
class
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
constructor(options) {
|
|
6590
|
-
this.
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
}
|
|
6600
|
-
async createWorkItem(input) {
|
|
6601
|
-
const projectKey = input.projectId ?? this.defaults.projectKey;
|
|
6602
|
-
if (!projectKey) {
|
|
6603
|
-
throw new Error("Jira projectKey is required to create work items.");
|
|
6483
|
+
class SupabasePostgresProvider {
|
|
6484
|
+
client;
|
|
6485
|
+
db;
|
|
6486
|
+
ownsClient;
|
|
6487
|
+
createDrizzle;
|
|
6488
|
+
constructor(options = {}) {
|
|
6489
|
+
this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
|
|
6490
|
+
if (options.db) {
|
|
6491
|
+
if (!options.client) {
|
|
6492
|
+
throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
|
|
6493
|
+
}
|
|
6494
|
+
this.client = options.client;
|
|
6495
|
+
this.db = options.db;
|
|
6496
|
+
this.ownsClient = false;
|
|
6497
|
+
return;
|
|
6604
6498
|
}
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
project: { key: projectKey },
|
|
6612
|
-
summary: input.title,
|
|
6613
|
-
description,
|
|
6614
|
-
issuetype: { name: issueType },
|
|
6615
|
-
labels,
|
|
6616
|
-
priority: priority ? { name: priority } : undefined,
|
|
6617
|
-
assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
|
|
6618
|
-
duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
|
|
6499
|
+
if (options.client) {
|
|
6500
|
+
this.client = options.client;
|
|
6501
|
+
this.ownsClient = false;
|
|
6502
|
+
} else {
|
|
6503
|
+
if (!options.connectionString) {
|
|
6504
|
+
throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
|
|
6619
6505
|
}
|
|
6506
|
+
this.client = postgres(options.connectionString, {
|
|
6507
|
+
max: options.maxConnections,
|
|
6508
|
+
prepare: false,
|
|
6509
|
+
ssl: resolveSslMode(options.sslMode)
|
|
6510
|
+
});
|
|
6511
|
+
this.ownsClient = true;
|
|
6512
|
+
}
|
|
6513
|
+
this.db = this.createDrizzle(this.client);
|
|
6514
|
+
}
|
|
6515
|
+
async query(statement, params = []) {
|
|
6516
|
+
const query = buildParameterizedSql(statement, params);
|
|
6517
|
+
const result = await this.db.execute(query);
|
|
6518
|
+
const rows = asRows(result);
|
|
6519
|
+
return {
|
|
6520
|
+
rows,
|
|
6521
|
+
rowCount: rows.length
|
|
6620
6522
|
};
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6523
|
+
}
|
|
6524
|
+
async execute(statement, params = []) {
|
|
6525
|
+
const query = buildParameterizedSql(statement, params);
|
|
6526
|
+
await this.db.execute(query);
|
|
6527
|
+
}
|
|
6528
|
+
async transaction(run) {
|
|
6529
|
+
const transactionResult = this.client.begin(async (transactionClient) => {
|
|
6530
|
+
const transactionalProvider = new SupabasePostgresProvider({
|
|
6531
|
+
client: transactionClient,
|
|
6532
|
+
db: this.createDrizzle(transactionClient),
|
|
6533
|
+
createDrizzle: this.createDrizzle
|
|
6534
|
+
});
|
|
6535
|
+
return run(transactionalProvider);
|
|
6629
6536
|
});
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6537
|
+
return transactionResult;
|
|
6538
|
+
}
|
|
6539
|
+
async close() {
|
|
6540
|
+
if (this.ownsClient) {
|
|
6541
|
+
await this.client.end({ timeout: 5 });
|
|
6633
6542
|
}
|
|
6634
|
-
const data = await response.json();
|
|
6635
|
-
return {
|
|
6636
|
-
id: data.id ?? data.key ?? "",
|
|
6637
|
-
title: input.title,
|
|
6638
|
-
url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
|
|
6639
|
-
status: input.status,
|
|
6640
|
-
priority: input.priority,
|
|
6641
|
-
tags: input.tags,
|
|
6642
|
-
projectId: projectKey,
|
|
6643
|
-
externalId: input.externalId,
|
|
6644
|
-
metadata: input.metadata
|
|
6645
|
-
};
|
|
6646
6543
|
}
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6544
|
+
}
|
|
6545
|
+
function buildParameterizedSql(statement, params) {
|
|
6546
|
+
const segments = [];
|
|
6547
|
+
const pattern = /\$(\d+)/g;
|
|
6548
|
+
let cursor = 0;
|
|
6549
|
+
for (const match of statement.matchAll(pattern)) {
|
|
6550
|
+
const token = match[0];
|
|
6551
|
+
const indexPart = match[1];
|
|
6552
|
+
const start = match.index;
|
|
6553
|
+
if (indexPart == null || start == null)
|
|
6554
|
+
continue;
|
|
6555
|
+
const parameterIndex = Number(indexPart) - 1;
|
|
6556
|
+
if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
|
|
6557
|
+
throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
|
|
6651
6558
|
}
|
|
6652
|
-
|
|
6559
|
+
const staticSegment = statement.slice(cursor, start);
|
|
6560
|
+
if (staticSegment.length > 0) {
|
|
6561
|
+
segments.push(drizzleSql.raw(staticSegment));
|
|
6562
|
+
}
|
|
6563
|
+
const parameterValue = params[parameterIndex];
|
|
6564
|
+
if (parameterValue === undefined) {
|
|
6565
|
+
throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
|
|
6566
|
+
}
|
|
6567
|
+
const normalizedValue = normalizeParam(parameterValue);
|
|
6568
|
+
segments.push(drizzleSql`${normalizedValue}`);
|
|
6569
|
+
cursor = start + token.length;
|
|
6570
|
+
}
|
|
6571
|
+
const tailSegment = statement.slice(cursor);
|
|
6572
|
+
if (tailSegment.length > 0) {
|
|
6573
|
+
segments.push(drizzleSql.raw(tailSegment));
|
|
6574
|
+
}
|
|
6575
|
+
if (segments.length === 0) {
|
|
6576
|
+
return drizzleSql.raw("");
|
|
6653
6577
|
}
|
|
6578
|
+
return drizzleSql.join(segments);
|
|
6654
6579
|
}
|
|
6655
|
-
function
|
|
6656
|
-
|
|
6580
|
+
function normalizeParam(value) {
|
|
6581
|
+
if (typeof value === "bigint") {
|
|
6582
|
+
return value.toString();
|
|
6583
|
+
}
|
|
6584
|
+
if (value instanceof Uint8Array) {
|
|
6585
|
+
return Buffer5.from(value);
|
|
6586
|
+
}
|
|
6587
|
+
if (isPlainObject(value)) {
|
|
6588
|
+
return JSON.stringify(value);
|
|
6589
|
+
}
|
|
6590
|
+
return value;
|
|
6657
6591
|
}
|
|
6658
|
-
function
|
|
6659
|
-
|
|
6660
|
-
|
|
6592
|
+
function asRows(result) {
|
|
6593
|
+
if (!Array.isArray(result)) {
|
|
6594
|
+
return [];
|
|
6595
|
+
}
|
|
6596
|
+
return result;
|
|
6661
6597
|
}
|
|
6662
|
-
function
|
|
6663
|
-
if (
|
|
6664
|
-
return
|
|
6598
|
+
function isPlainObject(value) {
|
|
6599
|
+
if (value == null || typeof value !== "object") {
|
|
6600
|
+
return false;
|
|
6665
6601
|
}
|
|
6666
|
-
|
|
6602
|
+
if (Array.isArray(value)) {
|
|
6603
|
+
return false;
|
|
6604
|
+
}
|
|
6605
|
+
if (value instanceof Date) {
|
|
6606
|
+
return false;
|
|
6607
|
+
}
|
|
6608
|
+
if (value instanceof Uint8Array) {
|
|
6609
|
+
return false;
|
|
6610
|
+
}
|
|
6611
|
+
return true;
|
|
6667
6612
|
}
|
|
6668
|
-
function
|
|
6669
|
-
switch (
|
|
6670
|
-
case "
|
|
6671
|
-
return
|
|
6672
|
-
case "
|
|
6673
|
-
return "
|
|
6674
|
-
case "
|
|
6675
|
-
return "Medium";
|
|
6676
|
-
case "low":
|
|
6677
|
-
return "Low";
|
|
6678
|
-
case "none":
|
|
6613
|
+
function resolveSslMode(mode) {
|
|
6614
|
+
switch (mode) {
|
|
6615
|
+
case "allow":
|
|
6616
|
+
return false;
|
|
6617
|
+
case "prefer":
|
|
6618
|
+
return "prefer";
|
|
6619
|
+
case "require":
|
|
6679
6620
|
default:
|
|
6680
|
-
return;
|
|
6621
|
+
return "require";
|
|
6681
6622
|
}
|
|
6682
6623
|
}
|
|
6683
|
-
function mergeLabels(defaults, tags) {
|
|
6684
|
-
const merged = new Set;
|
|
6685
|
-
(defaults ?? []).forEach((label) => merged.add(label));
|
|
6686
|
-
(tags ?? []).forEach((tag) => merged.add(tag));
|
|
6687
|
-
const result = [...merged];
|
|
6688
|
-
return result.length > 0 ? result : undefined;
|
|
6689
|
-
}
|
|
6690
|
-
function buildJiraDescription(description) {
|
|
6691
|
-
if (!description)
|
|
6692
|
-
return;
|
|
6693
|
-
const lines = description.split(/\r?\n/).filter((line) => line.trim());
|
|
6694
|
-
const content = lines.map((line) => ({
|
|
6695
|
-
type: "paragraph",
|
|
6696
|
-
content: [{ type: "text", text: line }]
|
|
6697
|
-
}));
|
|
6698
|
-
if (content.length === 0)
|
|
6699
|
-
return;
|
|
6700
|
-
return { type: "doc", version: 1, content };
|
|
6701
|
-
}
|
|
6702
|
-
|
|
6703
|
-
// src/impls/notion.ts
|
|
6704
|
-
import { Client } from "@notionhq/client";
|
|
6705
6624
|
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6625
|
+
// src/impls/supabase-vector.ts
|
|
6626
|
+
class SupabaseVectorProvider {
|
|
6627
|
+
database;
|
|
6628
|
+
createTableIfMissing;
|
|
6629
|
+
distanceMetric;
|
|
6630
|
+
quotedSchema;
|
|
6631
|
+
qualifiedTable;
|
|
6632
|
+
collectionIndex;
|
|
6633
|
+
namespaceIndex;
|
|
6634
|
+
ensureTablePromise;
|
|
6709
6635
|
constructor(options) {
|
|
6710
|
-
this.
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
}
|
|
6636
|
+
this.database = options.database ?? new SupabasePostgresProvider({
|
|
6637
|
+
connectionString: options.connectionString,
|
|
6638
|
+
maxConnections: options.maxConnections,
|
|
6639
|
+
sslMode: options.sslMode
|
|
6640
|
+
});
|
|
6641
|
+
this.createTableIfMissing = options.createTableIfMissing ?? true;
|
|
6642
|
+
this.distanceMetric = options.distanceMetric ?? "cosine";
|
|
6643
|
+
const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
|
|
6644
|
+
const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
|
|
6645
|
+
this.quotedSchema = quoteIdentifier(schema);
|
|
6646
|
+
this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
|
|
6647
|
+
this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
|
|
6648
|
+
this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
|
|
6721
6649
|
}
|
|
6722
|
-
async
|
|
6723
|
-
if (
|
|
6724
|
-
return
|
|
6650
|
+
async upsert(request) {
|
|
6651
|
+
if (request.documents.length === 0) {
|
|
6652
|
+
return;
|
|
6725
6653
|
}
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
throw new Error("Notion databaseId is required to create work items.");
|
|
6654
|
+
if (this.createTableIfMissing) {
|
|
6655
|
+
await this.ensureTable();
|
|
6729
6656
|
}
|
|
6730
|
-
const
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6657
|
+
for (const document of request.documents) {
|
|
6658
|
+
await this.database.execute(`INSERT INTO ${this.qualifiedTable}
|
|
6659
|
+
(collection, id, embedding, payload, namespace, expires_at, updated_at)
|
|
6660
|
+
VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
|
|
6661
|
+
ON CONFLICT (collection, id)
|
|
6662
|
+
DO UPDATE SET
|
|
6663
|
+
embedding = EXCLUDED.embedding,
|
|
6664
|
+
payload = EXCLUDED.payload,
|
|
6665
|
+
namespace = EXCLUDED.namespace,
|
|
6666
|
+
expires_at = EXCLUDED.expires_at,
|
|
6667
|
+
updated_at = now();`, [
|
|
6668
|
+
request.collection,
|
|
6669
|
+
document.id,
|
|
6670
|
+
toVectorLiteral(document.vector),
|
|
6671
|
+
document.payload ? JSON.stringify(document.payload) : null,
|
|
6672
|
+
document.namespace ?? null,
|
|
6673
|
+
document.expiresAt ?? null
|
|
6674
|
+
]);
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
async search(query) {
|
|
6678
|
+
const operator = this.distanceOperator;
|
|
6679
|
+
const results = await this.database.query(`SELECT
|
|
6680
|
+
id,
|
|
6681
|
+
payload,
|
|
6682
|
+
namespace,
|
|
6683
|
+
(embedding ${operator} $3::vector) AS distance
|
|
6684
|
+
FROM ${this.qualifiedTable}
|
|
6685
|
+
WHERE collection = $1
|
|
6686
|
+
AND ($2::text IS NULL OR namespace = $2)
|
|
6687
|
+
AND (expires_at IS NULL OR expires_at > now())
|
|
6688
|
+
AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
|
|
6689
|
+
ORDER BY embedding ${operator} $3::vector
|
|
6690
|
+
LIMIT $5;`, [
|
|
6691
|
+
query.collection,
|
|
6692
|
+
query.namespace ?? null,
|
|
6693
|
+
toVectorLiteral(query.vector),
|
|
6694
|
+
query.filter ? JSON.stringify(query.filter) : null,
|
|
6695
|
+
query.topK
|
|
6696
|
+
]);
|
|
6697
|
+
const mapped = results.rows.map((row) => {
|
|
6698
|
+
const distance = Number(row.distance);
|
|
6699
|
+
return {
|
|
6700
|
+
id: row.id,
|
|
6701
|
+
score: distanceToScore(distance, this.distanceMetric),
|
|
6702
|
+
payload: isRecord(row.payload) ? row.payload : undefined,
|
|
6703
|
+
namespace: row.namespace ?? undefined
|
|
6704
|
+
};
|
|
6742
6705
|
});
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
priority: input.priority,
|
|
6749
|
-
tags: input.tags,
|
|
6750
|
-
projectId: databaseId,
|
|
6751
|
-
externalId: input.externalId,
|
|
6752
|
-
metadata: input.metadata
|
|
6753
|
-
};
|
|
6706
|
+
const scoreThreshold = query.scoreThreshold;
|
|
6707
|
+
if (scoreThreshold == null) {
|
|
6708
|
+
return mapped;
|
|
6709
|
+
}
|
|
6710
|
+
return mapped.filter((result) => result.score >= scoreThreshold);
|
|
6754
6711
|
}
|
|
6755
|
-
async
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
created.push(await this.createWorkItem(item));
|
|
6712
|
+
async delete(request) {
|
|
6713
|
+
if (request.ids.length === 0) {
|
|
6714
|
+
return;
|
|
6759
6715
|
}
|
|
6760
|
-
|
|
6716
|
+
const params = [
|
|
6717
|
+
request.collection,
|
|
6718
|
+
request.ids,
|
|
6719
|
+
request.namespace ?? null
|
|
6720
|
+
];
|
|
6721
|
+
await this.database.execute(`DELETE FROM ${this.qualifiedTable}
|
|
6722
|
+
WHERE collection = $1
|
|
6723
|
+
AND id = ANY($2::text[])
|
|
6724
|
+
AND ($3::text IS NULL OR namespace = $3);`, params);
|
|
6761
6725
|
}
|
|
6762
|
-
async
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
throw new Error("Notion summaryParentPageId is required for summaries.");
|
|
6726
|
+
async ensureTable() {
|
|
6727
|
+
if (!this.ensureTablePromise) {
|
|
6728
|
+
this.ensureTablePromise = this.createTable();
|
|
6766
6729
|
}
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6730
|
+
await this.ensureTablePromise;
|
|
6731
|
+
}
|
|
6732
|
+
async createTable() {
|
|
6733
|
+
await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
6734
|
+
await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
|
|
6735
|
+
await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
|
|
6736
|
+
collection text NOT NULL,
|
|
6737
|
+
id text NOT NULL,
|
|
6738
|
+
embedding vector NOT NULL,
|
|
6739
|
+
payload jsonb,
|
|
6740
|
+
namespace text,
|
|
6741
|
+
expires_at timestamptz,
|
|
6742
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
6743
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
6744
|
+
PRIMARY KEY (collection, id)
|
|
6745
|
+
);`);
|
|
6746
|
+
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
|
|
6747
|
+
ON ${this.qualifiedTable} (collection);`);
|
|
6748
|
+
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
|
|
6749
|
+
ON ${this.qualifiedTable} (namespace);`);
|
|
6750
|
+
}
|
|
6751
|
+
get distanceOperator() {
|
|
6752
|
+
switch (this.distanceMetric) {
|
|
6753
|
+
case "l2":
|
|
6754
|
+
return "<->";
|
|
6755
|
+
case "inner_product":
|
|
6756
|
+
return "<#>";
|
|
6757
|
+
case "cosine":
|
|
6758
|
+
default:
|
|
6759
|
+
return "<=>";
|
|
6782
6760
|
}
|
|
6783
|
-
return {
|
|
6784
|
-
id: page.id,
|
|
6785
|
-
title: input.title,
|
|
6786
|
-
url: "url" in page ? page.url : undefined,
|
|
6787
|
-
status: input.status,
|
|
6788
|
-
priority: input.priority,
|
|
6789
|
-
tags: input.tags,
|
|
6790
|
-
projectId: parentId,
|
|
6791
|
-
externalId: input.externalId,
|
|
6792
|
-
metadata: input.metadata
|
|
6793
|
-
};
|
|
6794
6761
|
}
|
|
6795
6762
|
}
|
|
6796
|
-
function
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
text: { content: title }
|
|
6802
|
-
}
|
|
6803
|
-
]
|
|
6804
|
-
};
|
|
6805
|
-
}
|
|
6806
|
-
function buildRichText(text) {
|
|
6807
|
-
return {
|
|
6808
|
-
rich_text: [
|
|
6809
|
-
{
|
|
6810
|
-
type: "text",
|
|
6811
|
-
text: { content: text }
|
|
6812
|
-
}
|
|
6813
|
-
]
|
|
6814
|
-
};
|
|
6815
|
-
}
|
|
6816
|
-
function applySelect(properties, property, value) {
|
|
6817
|
-
if (!property || !value)
|
|
6818
|
-
return;
|
|
6819
|
-
const next = {
|
|
6820
|
-
select: { name: value }
|
|
6821
|
-
};
|
|
6822
|
-
properties[property] = next;
|
|
6763
|
+
function sanitizeIdentifier(value, label) {
|
|
6764
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
6765
|
+
throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
|
|
6766
|
+
}
|
|
6767
|
+
return value;
|
|
6823
6768
|
}
|
|
6824
|
-
function
|
|
6825
|
-
|
|
6826
|
-
return;
|
|
6827
|
-
const next = {
|
|
6828
|
-
multi_select: values.map((value) => ({ name: value }))
|
|
6829
|
-
};
|
|
6830
|
-
properties[property] = next;
|
|
6769
|
+
function quoteIdentifier(value) {
|
|
6770
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
6831
6771
|
}
|
|
6832
|
-
function
|
|
6833
|
-
if (
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6772
|
+
function toVectorLiteral(vector) {
|
|
6773
|
+
if (vector.length === 0) {
|
|
6774
|
+
throw new Error("Supabase vectors must contain at least one dimension.");
|
|
6775
|
+
}
|
|
6776
|
+
for (const value of vector) {
|
|
6777
|
+
if (!Number.isFinite(value)) {
|
|
6778
|
+
throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
return `[${vector.join(",")}]`;
|
|
6839
6782
|
}
|
|
6840
|
-
function
|
|
6841
|
-
|
|
6842
|
-
return;
|
|
6843
|
-
properties[property] = buildRichText(value);
|
|
6783
|
+
function isRecord(value) {
|
|
6784
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6844
6785
|
}
|
|
6845
|
-
function
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
}
|
|
6856
|
-
]
|
|
6857
|
-
}
|
|
6858
|
-
}));
|
|
6786
|
+
function distanceToScore(distance, metric) {
|
|
6787
|
+
switch (metric) {
|
|
6788
|
+
case "inner_product":
|
|
6789
|
+
return -distance;
|
|
6790
|
+
case "l2":
|
|
6791
|
+
return 1 / (1 + distance);
|
|
6792
|
+
case "cosine":
|
|
6793
|
+
default:
|
|
6794
|
+
return 1 - distance;
|
|
6795
|
+
}
|
|
6859
6796
|
}
|
|
6860
6797
|
|
|
6861
6798
|
// src/impls/tldv-meeting-recorder.ts
|
|
@@ -7001,11 +6938,74 @@ async function safeReadError4(response) {
|
|
|
7001
6938
|
}
|
|
7002
6939
|
}
|
|
7003
6940
|
|
|
6941
|
+
// src/impls/twilio-sms.ts
|
|
6942
|
+
import Twilio from "twilio";
|
|
6943
|
+
|
|
6944
|
+
class TwilioSmsProvider {
|
|
6945
|
+
client;
|
|
6946
|
+
fromNumber;
|
|
6947
|
+
constructor(options) {
|
|
6948
|
+
this.client = options.client ?? Twilio(options.accountSid, options.authToken);
|
|
6949
|
+
this.fromNumber = options.fromNumber;
|
|
6950
|
+
}
|
|
6951
|
+
async sendSms(input) {
|
|
6952
|
+
const message = await this.client.messages.create({
|
|
6953
|
+
to: input.to,
|
|
6954
|
+
from: input.from ?? this.fromNumber,
|
|
6955
|
+
body: input.body
|
|
6956
|
+
});
|
|
6957
|
+
return {
|
|
6958
|
+
id: message.sid,
|
|
6959
|
+
to: message.to ?? input.to,
|
|
6960
|
+
from: message.from ?? input.from ?? this.fromNumber ?? "",
|
|
6961
|
+
body: message.body ?? input.body,
|
|
6962
|
+
status: mapStatus(message.status),
|
|
6963
|
+
sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
|
|
6964
|
+
deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
|
|
6965
|
+
price: message.price ? Number(message.price) : undefined,
|
|
6966
|
+
priceCurrency: message.priceUnit ?? undefined,
|
|
6967
|
+
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
6968
|
+
errorMessage: message.errorMessage ?? undefined
|
|
6969
|
+
};
|
|
6970
|
+
}
|
|
6971
|
+
async getDeliveryStatus(messageId) {
|
|
6972
|
+
const message = await this.client.messages(messageId).fetch();
|
|
6973
|
+
return {
|
|
6974
|
+
status: mapStatus(message.status),
|
|
6975
|
+
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
6976
|
+
errorMessage: message.errorMessage ?? undefined,
|
|
6977
|
+
updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
|
|
6978
|
+
};
|
|
6979
|
+
}
|
|
6980
|
+
}
|
|
6981
|
+
function mapStatus(status) {
|
|
6982
|
+
switch (status) {
|
|
6983
|
+
case "queued":
|
|
6984
|
+
case "accepted":
|
|
6985
|
+
case "scheduled":
|
|
6986
|
+
return "queued";
|
|
6987
|
+
case "sending":
|
|
6988
|
+
case "processing":
|
|
6989
|
+
return "sending";
|
|
6990
|
+
case "sent":
|
|
6991
|
+
return "sent";
|
|
6992
|
+
case "delivered":
|
|
6993
|
+
return "delivered";
|
|
6994
|
+
case "undelivered":
|
|
6995
|
+
return "undelivered";
|
|
6996
|
+
case "failed":
|
|
6997
|
+
case "canceled":
|
|
6998
|
+
return "failed";
|
|
6999
|
+
default:
|
|
7000
|
+
return "queued";
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
|
|
7004
7004
|
// src/impls/provider-factory.ts
|
|
7005
7005
|
import { Buffer as Buffer6 } from "node:buffer";
|
|
7006
|
-
import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
|
|
7007
|
-
import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
|
|
7008
7006
|
import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
|
|
7007
|
+
import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
|
|
7008
|
+
import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
|
|
7009
7009
|
var SECRET_CACHE = new Map;
|
|
7010
7010
|
|
|
7011
7011
|
class IntegrationProviderFactory {
|
|
@@ -7458,34 +7458,34 @@ function requireConfig(context, key, message) {
|
|
|
7458
7458
|
}
|
|
7459
7459
|
return value;
|
|
7460
7460
|
}
|
|
7461
|
-
// src/
|
|
7461
|
+
// src/llm.ts
|
|
7462
7462
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7463
7463
|
|
|
7464
|
-
// src/
|
|
7464
|
+
// src/meeting-recorder.ts
|
|
7465
7465
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7466
7466
|
|
|
7467
|
-
// src/
|
|
7467
|
+
// src/messaging.ts
|
|
7468
7468
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7469
7469
|
|
|
7470
|
-
// src/
|
|
7470
|
+
// src/openbanking.ts
|
|
7471
7471
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7472
7472
|
|
|
7473
|
-
// src/
|
|
7473
|
+
// src/payments.ts
|
|
7474
7474
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7475
7475
|
|
|
7476
|
-
// src/
|
|
7476
|
+
// src/project-management.ts
|
|
7477
7477
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7478
7478
|
|
|
7479
|
-
// src/
|
|
7479
|
+
// src/sms.ts
|
|
7480
7480
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7481
7481
|
|
|
7482
|
-
// src/
|
|
7482
|
+
// src/storage.ts
|
|
7483
7483
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7484
7484
|
|
|
7485
|
-
// src/
|
|
7485
|
+
// src/vector-store.ts
|
|
7486
7486
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7487
7487
|
|
|
7488
|
-
// src/
|
|
7488
|
+
// src/voice.ts
|
|
7489
7489
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7490
7490
|
export {
|
|
7491
7491
|
resolveToolkit,
|