@contractspec/integration.providers-impls 3.7.6 → 3.8.2
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 +31 -30
- package/dist/impls/index.js +2189 -2138
- package/dist/impls/messaging-telegram.d.ts +13 -0
- package/dist/impls/messaging-telegram.js +49 -0
- 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 +2116 -2066
- package/dist/index.d.ts +12 -12
- package/dist/index.js +2197 -2146
- package/dist/node/impls/composio-fallback-resolver.js +73 -73
- package/dist/node/impls/index.js +2189 -2138
- package/dist/node/impls/messaging-telegram.js +49 -0
- package/dist/node/impls/mistral-conversational.js +159 -159
- package/dist/node/impls/provider-factory.js +2116 -2066
- package/dist/node/index.js +2197 -2146
- 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 +25 -13
package/dist/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,563 @@ 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 "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
|
-
};
|
|
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";
|
|
4060
4047
|
}
|
|
4061
|
-
|
|
4062
|
-
|
|
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:
|
|
4062
|
+
return;
|
|
4063
4063
|
}
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
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
|
|
4070
4107
|
};
|
|
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
4108
|
}
|
|
4098
|
-
|
|
4099
|
-
const
|
|
4100
|
-
if (!
|
|
4101
|
-
|
|
4102
|
-
message: {
|
|
4103
|
-
role: "assistant",
|
|
4104
|
-
content: [{ type: "text", text: "" }]
|
|
4105
|
-
},
|
|
4106
|
-
usage: this.fromUsage(response.usage),
|
|
4107
|
-
raw: response
|
|
4108
|
-
};
|
|
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.");
|
|
4109
4113
|
}
|
|
4110
|
-
const
|
|
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;
|
|
4111
4127
|
return {
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
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
|
|
4116
4137
|
};
|
|
4117
4138
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4139
|
+
async createWorkItems(items) {
|
|
4140
|
+
const created = [];
|
|
4141
|
+
for (const item of items) {
|
|
4142
|
+
created.push(await this.createWorkItem(item));
|
|
4143
|
+
}
|
|
4144
|
+
return created;
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
function mapPriority2(priority) {
|
|
4148
|
+
if (!priority)
|
|
4149
|
+
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
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
const merged = [...labelIds];
|
|
4163
|
+
return merged.length > 0 ? merged : undefined;
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
// src/impls/messaging-github.ts
|
|
4167
|
+
class GithubMessagingProvider {
|
|
4168
|
+
token;
|
|
4169
|
+
defaultOwner;
|
|
4170
|
+
defaultRepo;
|
|
4171
|
+
apiBaseUrl;
|
|
4172
|
+
constructor(options) {
|
|
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 })
|
|
4188
|
+
});
|
|
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
|
+
}
|
|
4121
4193
|
return {
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
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
|
+
}
|
|
4125
4204
|
};
|
|
4126
4205
|
}
|
|
4127
|
-
|
|
4128
|
-
const
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
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);
|
|
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.");
|
|
4141
4211
|
}
|
|
4142
|
-
|
|
4143
|
-
|
|
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 })
|
|
4220
|
+
});
|
|
4221
|
+
const body = await response.json();
|
|
4222
|
+
if (!response.ok || !body.id) {
|
|
4223
|
+
throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
4144
4224
|
}
|
|
4145
4225
|
return {
|
|
4146
|
-
|
|
4147
|
-
|
|
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
|
+
}
|
|
4148
4235
|
};
|
|
4149
4236
|
}
|
|
4150
|
-
|
|
4151
|
-
const
|
|
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).");
|
|
4244
|
+
}
|
|
4152
4245
|
return {
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
arguments: args
|
|
4246
|
+
owner,
|
|
4247
|
+
repo,
|
|
4248
|
+
issueNumber
|
|
4157
4249
|
};
|
|
4158
4250
|
}
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
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
|
-
};
|
|
4190
|
-
}
|
|
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;
|
|
4191
4263
|
}
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
return
|
|
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
|
+
}
|
|
4272
|
+
|
|
4273
|
+
// src/impls/messaging-slack.ts
|
|
4274
|
+
class SlackMessagingProvider {
|
|
4275
|
+
botToken;
|
|
4276
|
+
defaultChannelId;
|
|
4277
|
+
apiBaseUrl;
|
|
4278
|
+
constructor(options) {
|
|
4279
|
+
this.botToken = options.botToken;
|
|
4280
|
+
this.defaultChannelId = options.defaultChannelId;
|
|
4281
|
+
this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
|
|
4197
4282
|
}
|
|
4198
|
-
|
|
4199
|
-
const
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
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
|
|
4207
4313
|
}
|
|
4208
|
-
}
|
|
4314
|
+
};
|
|
4209
4315
|
}
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
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.");
|
|
4320
|
+
}
|
|
4321
|
+
const response = await fetch(`${this.apiBaseUrl}/chat.update`, {
|
|
4322
|
+
method: "POST",
|
|
4323
|
+
headers: {
|
|
4324
|
+
authorization: `Bearer ${this.botToken}`,
|
|
4325
|
+
"content-type": "application/json"
|
|
4326
|
+
},
|
|
4327
|
+
body: JSON.stringify({
|
|
4328
|
+
channel,
|
|
4329
|
+
ts: messageId,
|
|
4330
|
+
text: input.text,
|
|
4331
|
+
mrkdwn: input.markdown ?? true
|
|
4332
|
+
})
|
|
4333
|
+
});
|
|
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}`}`);
|
|
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
|
+
};
|
|
4227
4347
|
}
|
|
4228
4348
|
}
|
|
4229
4349
|
|
|
4230
|
-
// src/impls/
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
defaultModel;
|
|
4350
|
+
// src/impls/messaging-telegram.ts
|
|
4351
|
+
class TelegramMessagingProvider {
|
|
4352
|
+
botToken;
|
|
4353
|
+
defaultChatId;
|
|
4354
|
+
apiBaseUrl;
|
|
4236
4355
|
constructor(options) {
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
this.client = options.client ?? new Mistral2({
|
|
4241
|
-
apiKey: options.apiKey,
|
|
4242
|
-
serverURL: options.serverURL
|
|
4243
|
-
});
|
|
4244
|
-
this.defaultModel = options.defaultModel ?? "mistral-embed";
|
|
4356
|
+
this.botToken = options.botToken;
|
|
4357
|
+
this.defaultChatId = options.defaultChatId;
|
|
4358
|
+
this.apiBaseUrl = options.apiBaseUrl ?? "https://api.telegram.org";
|
|
4245
4359
|
}
|
|
4246
|
-
async
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4360
|
+
async sendMessage(input) {
|
|
4361
|
+
const chatId = input.channelId ?? input.recipientId ?? this.defaultChatId ?? undefined;
|
|
4362
|
+
if (!chatId) {
|
|
4363
|
+
throw new Error("Telegram sendMessage requires channelId, recipientId, or defaultChatId.");
|
|
4364
|
+
}
|
|
4365
|
+
const messageThreadId = input.threadId && input.threadId !== chatId ? Number.parseInt(input.threadId, 10) : undefined;
|
|
4366
|
+
const response = await fetch(`${this.apiBaseUrl}/bot${this.botToken}/sendMessage`, {
|
|
4367
|
+
method: "POST",
|
|
4368
|
+
headers: {
|
|
4369
|
+
"content-type": "application/json"
|
|
4370
|
+
},
|
|
4371
|
+
body: JSON.stringify({
|
|
4372
|
+
chat_id: chatId,
|
|
4373
|
+
text: input.text,
|
|
4374
|
+
message_thread_id: Number.isFinite(messageThreadId) ? messageThreadId : undefined
|
|
4375
|
+
})
|
|
4253
4376
|
});
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
model: response.model,
|
|
4259
|
-
metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
|
|
4260
|
-
}));
|
|
4261
|
-
}
|
|
4262
|
-
async embedQuery(query, options) {
|
|
4263
|
-
const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
|
|
4264
|
-
if (!result) {
|
|
4265
|
-
throw new Error("Failed to compute embedding for query");
|
|
4377
|
+
const body = await response.json();
|
|
4378
|
+
const providerMessageId = body.result?.message_id;
|
|
4379
|
+
if (!response.ok || !body.ok || providerMessageId == null) {
|
|
4380
|
+
throw new Error(`Telegram sendMessage failed: ${body.description ?? `HTTP_${response.status}`}`);
|
|
4266
4381
|
}
|
|
4267
|
-
return
|
|
4382
|
+
return {
|
|
4383
|
+
id: `telegram:${chatId}:${providerMessageId}`,
|
|
4384
|
+
providerMessageId: String(providerMessageId),
|
|
4385
|
+
status: "sent",
|
|
4386
|
+
sentAt: new Date,
|
|
4387
|
+
metadata: {
|
|
4388
|
+
chatId: String(body.result?.chat?.id ?? chatId)
|
|
4389
|
+
}
|
|
4390
|
+
};
|
|
4268
4391
|
}
|
|
4269
4392
|
}
|
|
4270
4393
|
|
|
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;
|
|
4394
|
+
// src/impls/messaging-whatsapp-meta.ts
|
|
4395
|
+
class MetaWhatsappMessagingProvider {
|
|
4396
|
+
accessToken;
|
|
4397
|
+
phoneNumberId;
|
|
4398
|
+
apiVersion;
|
|
4288
4399
|
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;
|
|
4400
|
+
this.accessToken = options.accessToken;
|
|
4401
|
+
this.phoneNumberId = options.phoneNumberId;
|
|
4402
|
+
this.apiVersion = options.apiVersion ?? "v22.0";
|
|
4297
4403
|
}
|
|
4298
|
-
async
|
|
4299
|
-
const
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
const fileName = `audio.${input.audio.format}`;
|
|
4303
|
-
const audioBytes = new Uint8Array(input.audio.data);
|
|
4304
|
-
const blob = new Blob([audioBytes], { type: mimeType });
|
|
4305
|
-
formData.append("file", blob, fileName);
|
|
4306
|
-
formData.append("model", model);
|
|
4307
|
-
formData.append("response_format", "verbose_json");
|
|
4308
|
-
const language = input.language ?? this.defaultLanguage;
|
|
4309
|
-
if (language) {
|
|
4310
|
-
formData.append("language", language);
|
|
4404
|
+
async sendMessage(input) {
|
|
4405
|
+
const to = input.recipientId;
|
|
4406
|
+
if (!to) {
|
|
4407
|
+
throw new Error("Meta WhatsApp sendMessage requires recipientId.");
|
|
4311
4408
|
}
|
|
4312
|
-
const response = await this.
|
|
4409
|
+
const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
|
|
4313
4410
|
method: "POST",
|
|
4314
4411
|
headers: {
|
|
4315
|
-
|
|
4412
|
+
authorization: `Bearer ${this.accessToken}`,
|
|
4413
|
+
"content-type": "application/json"
|
|
4316
4414
|
},
|
|
4317
|
-
body:
|
|
4415
|
+
body: JSON.stringify({
|
|
4416
|
+
messaging_product: "whatsapp",
|
|
4417
|
+
to,
|
|
4418
|
+
type: "text",
|
|
4419
|
+
text: {
|
|
4420
|
+
body: input.text,
|
|
4421
|
+
preview_url: false
|
|
4422
|
+
}
|
|
4423
|
+
})
|
|
4318
4424
|
});
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4425
|
+
const body = await response.json();
|
|
4426
|
+
const messageId = body.messages?.[0]?.id;
|
|
4427
|
+
if (!response.ok || !messageId) {
|
|
4428
|
+
const errorCode = body.error?.code != null ? String(body.error.code) : "";
|
|
4429
|
+
throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
|
|
4322
4430
|
}
|
|
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
|
-
});
|
|
4431
|
+
return {
|
|
4432
|
+
id: messageId,
|
|
4433
|
+
providerMessageId: messageId,
|
|
4434
|
+
status: "sent",
|
|
4435
|
+
sentAt: new Date,
|
|
4436
|
+
metadata: {
|
|
4437
|
+
phoneNumberId: this.phoneNumberId
|
|
4438
|
+
}
|
|
4439
|
+
};
|
|
4338
4440
|
}
|
|
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
4441
|
}
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4442
|
+
|
|
4443
|
+
// src/impls/messaging-whatsapp-twilio.ts
|
|
4444
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
4445
|
+
|
|
4446
|
+
class TwilioWhatsappMessagingProvider {
|
|
4447
|
+
accountSid;
|
|
4448
|
+
authToken;
|
|
4449
|
+
fromNumber;
|
|
4450
|
+
constructor(options) {
|
|
4451
|
+
this.accountSid = options.accountSid;
|
|
4452
|
+
this.authToken = options.authToken;
|
|
4453
|
+
this.fromNumber = options.fromNumber;
|
|
4356
4454
|
}
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
const
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
continue;
|
|
4455
|
+
async sendMessage(input) {
|
|
4456
|
+
const to = normalizeWhatsappAddress(input.recipientId);
|
|
4457
|
+
const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
|
|
4458
|
+
if (!to) {
|
|
4459
|
+
throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
|
|
4363
4460
|
}
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
parsed.push({
|
|
4367
|
-
text,
|
|
4368
|
-
startMs: secondsToMs(startSeconds),
|
|
4369
|
-
endMs: secondsToMs(endSeconds),
|
|
4370
|
-
speakerId: readString3(segmentRecord, "speaker") ?? undefined,
|
|
4371
|
-
confidence: readNumber2(segmentRecord, "confidence"),
|
|
4372
|
-
wordTimings: parseWordTimings(segmentRecord.words)
|
|
4373
|
-
});
|
|
4374
|
-
}
|
|
4375
|
-
return parsed;
|
|
4376
|
-
}
|
|
4377
|
-
function parseWordTimings(value) {
|
|
4378
|
-
if (!Array.isArray(value)) {
|
|
4379
|
-
return [];
|
|
4380
|
-
}
|
|
4381
|
-
const words = [];
|
|
4382
|
-
for (const entry of value) {
|
|
4383
|
-
const wordRecord = asRecord2(entry);
|
|
4384
|
-
const word = readString3(wordRecord, "word");
|
|
4385
|
-
const startSeconds = readNumber2(wordRecord, "start");
|
|
4386
|
-
const endSeconds = readNumber2(wordRecord, "end");
|
|
4387
|
-
if (!word || startSeconds == null || endSeconds == null) {
|
|
4388
|
-
continue;
|
|
4461
|
+
if (!from) {
|
|
4462
|
+
throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
|
|
4389
4463
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4464
|
+
const params = new URLSearchParams;
|
|
4465
|
+
params.set("To", to);
|
|
4466
|
+
params.set("From", from);
|
|
4467
|
+
params.set("Body", input.text);
|
|
4468
|
+
const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
|
|
4469
|
+
const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
|
|
4470
|
+
method: "POST",
|
|
4471
|
+
headers: {
|
|
4472
|
+
authorization: `Basic ${auth}`,
|
|
4473
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
4474
|
+
},
|
|
4475
|
+
body: params.toString()
|
|
4395
4476
|
});
|
|
4396
|
-
|
|
4397
|
-
|
|
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;
|
|
4477
|
+
const body = await response.json();
|
|
4478
|
+
if (!response.ok || !body.sid) {
|
|
4479
|
+
throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
|
|
4405
4480
|
}
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4481
|
+
return {
|
|
4482
|
+
id: body.sid,
|
|
4483
|
+
providerMessageId: body.sid,
|
|
4484
|
+
status: mapTwilioStatus(body.status),
|
|
4485
|
+
sentAt: new Date,
|
|
4486
|
+
errorCode: body.error_code != null ? String(body.error_code) : undefined,
|
|
4487
|
+
errorMessage: body.error_message ?? undefined,
|
|
4488
|
+
metadata: {
|
|
4489
|
+
from,
|
|
4490
|
+
to
|
|
4491
|
+
}
|
|
4492
|
+
};
|
|
4411
4493
|
}
|
|
4412
|
-
return speakers;
|
|
4413
4494
|
}
|
|
4414
|
-
function
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
if (value && typeof value === "object") {
|
|
4495
|
+
function normalizeWhatsappAddress(value) {
|
|
4496
|
+
if (!value)
|
|
4497
|
+
return null;
|
|
4498
|
+
if (value.startsWith("whatsapp:"))
|
|
4419
4499
|
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;
|
|
4500
|
+
return `whatsapp:${value}`;
|
|
4430
4501
|
}
|
|
4431
|
-
function
|
|
4432
|
-
|
|
4502
|
+
function mapTwilioStatus(status) {
|
|
4503
|
+
switch (status) {
|
|
4504
|
+
case "queued":
|
|
4505
|
+
case "accepted":
|
|
4506
|
+
case "scheduled":
|
|
4507
|
+
return "queued";
|
|
4508
|
+
case "sending":
|
|
4509
|
+
return "sending";
|
|
4510
|
+
case "delivered":
|
|
4511
|
+
return "delivered";
|
|
4512
|
+
case "failed":
|
|
4513
|
+
case "undelivered":
|
|
4514
|
+
case "canceled":
|
|
4515
|
+
return "failed";
|
|
4516
|
+
case "sent":
|
|
4517
|
+
default:
|
|
4518
|
+
return "sent";
|
|
4519
|
+
}
|
|
4433
4520
|
}
|
|
4434
4521
|
|
|
4435
4522
|
// src/impls/mistral-conversational.session.ts
|
|
@@ -4505,876 +4592,825 @@ class MistralConversationSession {
|
|
|
4505
4592
|
return this.closedSummary;
|
|
4506
4593
|
}
|
|
4507
4594
|
this.closed = true;
|
|
4508
|
-
await this.pending;
|
|
4509
|
-
const durationMs = Date.now() - this.startedAt;
|
|
4510
|
-
const summary = {
|
|
4511
|
-
sessionId: this.sessionId,
|
|
4512
|
-
durationMs,
|
|
4513
|
-
turns: this.turns.map((turn) => ({
|
|
4514
|
-
role: turn.role === "assistant" ? "agent" : turn.role,
|
|
4515
|
-
text: turn.text,
|
|
4516
|
-
startMs: turn.startMs,
|
|
4517
|
-
endMs: turn.endMs
|
|
4518
|
-
})),
|
|
4519
|
-
transcript: this.turns.map((turn) => `${turn.role}: ${turn.text}`).join(`
|
|
4520
|
-
`)
|
|
4521
|
-
};
|
|
4522
|
-
this.closedSummary = summary;
|
|
4523
|
-
this.queue.push({
|
|
4524
|
-
type: "session_ended",
|
|
4525
|
-
reason: "closed_by_client",
|
|
4526
|
-
durationMs
|
|
4527
|
-
});
|
|
4528
|
-
this.queue.close();
|
|
4529
|
-
return summary;
|
|
4530
|
-
}
|
|
4531
|
-
async handleUserText(text) {
|
|
4532
|
-
if (this.closed) {
|
|
4533
|
-
return;
|
|
4534
|
-
}
|
|
4535
|
-
const userStart = Date.now();
|
|
4536
|
-
this.queue.push({ type: "user_speech_started" });
|
|
4537
|
-
this.queue.push({ type: "user_speech_ended", transcript: text });
|
|
4538
|
-
this.queue.push({
|
|
4539
|
-
type: "transcript",
|
|
4540
|
-
role: "user",
|
|
4541
|
-
text,
|
|
4542
|
-
timestamp: userStart
|
|
4543
|
-
});
|
|
4544
|
-
this.turns.push({
|
|
4545
|
-
role: "user",
|
|
4546
|
-
text,
|
|
4547
|
-
startMs: userStart,
|
|
4548
|
-
endMs: Date.now()
|
|
4549
|
-
});
|
|
4550
|
-
this.history.push({ role: "user", content: text });
|
|
4551
|
-
const assistantStart = Date.now();
|
|
4552
|
-
const assistantText = await this.complete(this.history, {
|
|
4553
|
-
...this.sessionConfig,
|
|
4554
|
-
llmModel: this.sessionConfig.llmModel ?? this.defaultModel
|
|
4555
|
-
});
|
|
4556
|
-
if (this.closed) {
|
|
4557
|
-
return;
|
|
4558
|
-
}
|
|
4559
|
-
const normalizedAssistantText = assistantText.trim();
|
|
4560
|
-
const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
|
|
4561
|
-
this.queue.push({
|
|
4562
|
-
type: "agent_speech_started",
|
|
4563
|
-
text: finalAssistantText
|
|
4564
|
-
});
|
|
4565
|
-
this.queue.push({
|
|
4566
|
-
type: "transcript",
|
|
4567
|
-
role: "agent",
|
|
4568
|
-
text: finalAssistantText,
|
|
4569
|
-
timestamp: assistantStart
|
|
4570
|
-
});
|
|
4571
|
-
this.queue.push({ type: "agent_speech_ended" });
|
|
4572
|
-
this.turns.push({
|
|
4573
|
-
role: "assistant",
|
|
4574
|
-
text: finalAssistantText,
|
|
4575
|
-
startMs: assistantStart,
|
|
4576
|
-
endMs: Date.now()
|
|
4577
|
-
});
|
|
4578
|
-
this.history.push({ role: "assistant", content: finalAssistantText });
|
|
4579
|
-
}
|
|
4580
|
-
emitError(error) {
|
|
4581
|
-
if (this.closed) {
|
|
4582
|
-
return;
|
|
4583
|
-
}
|
|
4584
|
-
this.queue.push({ type: "error", error: toError(error) });
|
|
4585
|
-
}
|
|
4586
|
-
}
|
|
4587
|
-
function toError(error) {
|
|
4588
|
-
if (error instanceof Error) {
|
|
4589
|
-
return error;
|
|
4590
|
-
}
|
|
4591
|
-
return new Error(String(error));
|
|
4592
|
-
}
|
|
4593
|
-
|
|
4594
|
-
// src/impls/mistral-conversational.ts
|
|
4595
|
-
var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
|
|
4596
|
-
var DEFAULT_MODEL2 = "mistral-small-latest";
|
|
4597
|
-
var DEFAULT_VOICE = "default";
|
|
4598
|
-
|
|
4599
|
-
class MistralConversationalProvider {
|
|
4600
|
-
apiKey;
|
|
4601
|
-
defaultModel;
|
|
4602
|
-
defaultVoiceId;
|
|
4603
|
-
baseUrl;
|
|
4604
|
-
fetchImpl;
|
|
4605
|
-
sttProvider;
|
|
4606
|
-
constructor(options) {
|
|
4607
|
-
if (!options.apiKey) {
|
|
4608
|
-
throw new Error("MistralConversationalProvider requires an apiKey");
|
|
4609
|
-
}
|
|
4610
|
-
this.apiKey = options.apiKey;
|
|
4611
|
-
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
|
|
4612
|
-
this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
|
|
4613
|
-
this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
|
|
4614
|
-
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
|
-
}
|
|
4644
|
-
async completeConversation(history, sessionConfig) {
|
|
4645
|
-
const model = sessionConfig.llmModel ?? this.defaultModel;
|
|
4646
|
-
const messages = [];
|
|
4647
|
-
if (sessionConfig.systemPrompt) {
|
|
4648
|
-
messages.push({ role: "system", content: sessionConfig.systemPrompt });
|
|
4649
|
-
}
|
|
4650
|
-
for (const item of history) {
|
|
4651
|
-
messages.push({ role: item.role, content: item.content });
|
|
4652
|
-
}
|
|
4653
|
-
const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
4654
|
-
method: "POST",
|
|
4655
|
-
headers: {
|
|
4656
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
4657
|
-
"Content-Type": "application/json"
|
|
4658
|
-
},
|
|
4659
|
-
body: JSON.stringify({
|
|
4660
|
-
model,
|
|
4661
|
-
messages
|
|
4662
|
-
})
|
|
4663
|
-
});
|
|
4664
|
-
if (!response.ok) {
|
|
4665
|
-
const body = await response.text();
|
|
4666
|
-
throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
|
|
4667
|
-
}
|
|
4668
|
-
const payload = await response.json();
|
|
4669
|
-
return readAssistantText(payload);
|
|
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;
|
|
4696
|
-
}
|
|
4697
|
-
return {};
|
|
4698
|
-
}
|
|
4699
|
-
|
|
4700
|
-
// src/impls/qdrant-vector.ts
|
|
4701
|
-
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
4702
|
-
|
|
4703
|
-
class QdrantVectorProvider {
|
|
4704
|
-
client;
|
|
4705
|
-
createCollectionIfMissing;
|
|
4706
|
-
distance;
|
|
4707
|
-
constructor(options) {
|
|
4708
|
-
this.client = options.client ?? new QdrantClient({
|
|
4709
|
-
url: options.url,
|
|
4710
|
-
apiKey: options.apiKey,
|
|
4711
|
-
...options.clientParams
|
|
4595
|
+
await this.pending;
|
|
4596
|
+
const durationMs = Date.now() - this.startedAt;
|
|
4597
|
+
const summary = {
|
|
4598
|
+
sessionId: this.sessionId,
|
|
4599
|
+
durationMs,
|
|
4600
|
+
turns: this.turns.map((turn) => ({
|
|
4601
|
+
role: turn.role === "assistant" ? "agent" : turn.role,
|
|
4602
|
+
text: turn.text,
|
|
4603
|
+
startMs: turn.startMs,
|
|
4604
|
+
endMs: turn.endMs
|
|
4605
|
+
})),
|
|
4606
|
+
transcript: this.turns.map((turn) => `${turn.role}: ${turn.text}`).join(`
|
|
4607
|
+
`)
|
|
4608
|
+
};
|
|
4609
|
+
this.closedSummary = summary;
|
|
4610
|
+
this.queue.push({
|
|
4611
|
+
type: "session_ended",
|
|
4612
|
+
reason: "closed_by_client",
|
|
4613
|
+
durationMs
|
|
4712
4614
|
});
|
|
4713
|
-
this.
|
|
4714
|
-
|
|
4615
|
+
this.queue.close();
|
|
4616
|
+
return summary;
|
|
4715
4617
|
}
|
|
4716
|
-
async
|
|
4717
|
-
if (
|
|
4618
|
+
async handleUserText(text) {
|
|
4619
|
+
if (this.closed) {
|
|
4718
4620
|
return;
|
|
4719
|
-
|
|
4720
|
-
|
|
4621
|
+
}
|
|
4622
|
+
const userStart = Date.now();
|
|
4623
|
+
this.queue.push({ type: "user_speech_started" });
|
|
4624
|
+
this.queue.push({ type: "user_speech_ended", transcript: text });
|
|
4625
|
+
this.queue.push({
|
|
4626
|
+
type: "transcript",
|
|
4627
|
+
role: "user",
|
|
4628
|
+
text,
|
|
4629
|
+
timestamp: userStart
|
|
4630
|
+
});
|
|
4631
|
+
this.turns.push({
|
|
4632
|
+
role: "user",
|
|
4633
|
+
text,
|
|
4634
|
+
startMs: userStart,
|
|
4635
|
+
endMs: Date.now()
|
|
4636
|
+
});
|
|
4637
|
+
this.history.push({ role: "user", content: text });
|
|
4638
|
+
const assistantStart = Date.now();
|
|
4639
|
+
const assistantText = await this.complete(this.history, {
|
|
4640
|
+
...this.sessionConfig,
|
|
4641
|
+
llmModel: this.sessionConfig.llmModel ?? this.defaultModel
|
|
4642
|
+
});
|
|
4643
|
+
if (this.closed) {
|
|
4721
4644
|
return;
|
|
4722
|
-
const vectorSize = firstDocument.vector.length;
|
|
4723
|
-
if (this.createCollectionIfMissing) {
|
|
4724
|
-
await this.ensureCollection(request.collection, vectorSize);
|
|
4725
4645
|
}
|
|
4726
|
-
const
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
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
|
|
4646
|
+
const normalizedAssistantText = assistantText.trim();
|
|
4647
|
+
const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
|
|
4648
|
+
this.queue.push({
|
|
4649
|
+
type: "agent_speech_started",
|
|
4650
|
+
text: finalAssistantText
|
|
4738
4651
|
});
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
filter: query.filter,
|
|
4745
|
-
score_threshold: query.scoreThreshold,
|
|
4746
|
-
with_payload: true,
|
|
4747
|
-
with_vector: false
|
|
4652
|
+
this.queue.push({
|
|
4653
|
+
type: "transcript",
|
|
4654
|
+
role: "agent",
|
|
4655
|
+
text: finalAssistantText,
|
|
4656
|
+
timestamp: assistantStart
|
|
4748
4657
|
});
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
}
|
|
4756
|
-
async delete(request) {
|
|
4757
|
-
await this.client.delete(request.collection, {
|
|
4758
|
-
wait: true,
|
|
4759
|
-
points: request.ids
|
|
4658
|
+
this.queue.push({ type: "agent_speech_ended" });
|
|
4659
|
+
this.turns.push({
|
|
4660
|
+
role: "assistant",
|
|
4661
|
+
text: finalAssistantText,
|
|
4662
|
+
startMs: assistantStart,
|
|
4663
|
+
endMs: Date.now()
|
|
4760
4664
|
});
|
|
4665
|
+
this.history.push({ role: "assistant", content: finalAssistantText });
|
|
4761
4666
|
}
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
} catch (_error) {
|
|
4766
|
-
await this.client.createCollection(collectionName, {
|
|
4767
|
-
vectors: {
|
|
4768
|
-
size: vectorSize,
|
|
4769
|
-
distance: this.distance
|
|
4770
|
-
}
|
|
4771
|
-
});
|
|
4667
|
+
emitError(error) {
|
|
4668
|
+
if (this.closed) {
|
|
4669
|
+
return;
|
|
4772
4670
|
}
|
|
4671
|
+
this.queue.push({ type: "error", error: toError(error) });
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
function toError(error) {
|
|
4675
|
+
if (error instanceof Error) {
|
|
4676
|
+
return error;
|
|
4773
4677
|
}
|
|
4678
|
+
return new Error(String(error));
|
|
4774
4679
|
}
|
|
4775
4680
|
|
|
4776
|
-
// src/impls/
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4681
|
+
// src/impls/mistral-stt.ts
|
|
4682
|
+
var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
|
|
4683
|
+
var DEFAULT_MODEL = "voxtral-mini-latest";
|
|
4684
|
+
var AUDIO_MIME_BY_FORMAT = {
|
|
4685
|
+
mp3: "audio/mpeg",
|
|
4686
|
+
wav: "audio/wav",
|
|
4687
|
+
ogg: "audio/ogg",
|
|
4688
|
+
pcm: "audio/pcm",
|
|
4689
|
+
opus: "audio/opus"
|
|
4690
|
+
};
|
|
4781
4691
|
|
|
4782
|
-
class
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
if (options.
|
|
4790
|
-
|
|
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;
|
|
4692
|
+
class MistralSttProvider {
|
|
4693
|
+
apiKey;
|
|
4694
|
+
defaultModel;
|
|
4695
|
+
defaultLanguage;
|
|
4696
|
+
baseUrl;
|
|
4697
|
+
fetchImpl;
|
|
4698
|
+
constructor(options) {
|
|
4699
|
+
if (!options.apiKey) {
|
|
4700
|
+
throw new Error("MistralSttProvider requires an apiKey");
|
|
4811
4701
|
}
|
|
4812
|
-
this.
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
const rows = asRows(result);
|
|
4818
|
-
return {
|
|
4819
|
-
rows,
|
|
4820
|
-
rowCount: rows.length
|
|
4821
|
-
};
|
|
4702
|
+
this.apiKey = options.apiKey;
|
|
4703
|
+
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
|
|
4704
|
+
this.defaultLanguage = options.defaultLanguage;
|
|
4705
|
+
this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
|
|
4706
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
4822
4707
|
}
|
|
4823
|
-
async
|
|
4824
|
-
const
|
|
4825
|
-
|
|
4708
|
+
async transcribe(input) {
|
|
4709
|
+
const formData = new FormData;
|
|
4710
|
+
const model = input.model ?? this.defaultModel;
|
|
4711
|
+
const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
|
|
4712
|
+
const fileName = `audio.${input.audio.format}`;
|
|
4713
|
+
const audioBytes = new Uint8Array(input.audio.data);
|
|
4714
|
+
const blob = new Blob([audioBytes], { type: mimeType });
|
|
4715
|
+
formData.append("file", blob, fileName);
|
|
4716
|
+
formData.append("model", model);
|
|
4717
|
+
formData.append("response_format", "verbose_json");
|
|
4718
|
+
const language = input.language ?? this.defaultLanguage;
|
|
4719
|
+
if (language) {
|
|
4720
|
+
formData.append("language", language);
|
|
4721
|
+
}
|
|
4722
|
+
const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
|
|
4723
|
+
method: "POST",
|
|
4724
|
+
headers: {
|
|
4725
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
4726
|
+
},
|
|
4727
|
+
body: formData
|
|
4728
|
+
});
|
|
4729
|
+
if (!response.ok) {
|
|
4730
|
+
const body = await response.text();
|
|
4731
|
+
throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
|
|
4732
|
+
}
|
|
4733
|
+
const payload = await response.json();
|
|
4734
|
+
return toTranscriptionResult(payload, input);
|
|
4826
4735
|
}
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4736
|
+
}
|
|
4737
|
+
function toTranscriptionResult(payload, input) {
|
|
4738
|
+
const record = asRecord2(payload);
|
|
4739
|
+
const text = readString3(record, "text") ?? "";
|
|
4740
|
+
const language = readString3(record, "language") ?? input.language ?? "unknown";
|
|
4741
|
+
const segments = parseSegments(record);
|
|
4742
|
+
if (segments.length === 0 && text.length > 0) {
|
|
4743
|
+
segments.push({
|
|
4744
|
+
text,
|
|
4745
|
+
startMs: 0,
|
|
4746
|
+
endMs: input.audio.durationMs ?? 0
|
|
4835
4747
|
});
|
|
4836
|
-
return transactionResult;
|
|
4837
4748
|
}
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4749
|
+
const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
|
|
4750
|
+
const topLevelWords = parseWordTimings(record.words);
|
|
4751
|
+
const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
|
|
4752
|
+
const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
|
|
4753
|
+
const speakers = dedupeSpeakers(segments);
|
|
4754
|
+
return {
|
|
4755
|
+
text,
|
|
4756
|
+
segments,
|
|
4757
|
+
language,
|
|
4758
|
+
durationMs,
|
|
4759
|
+
speakers: speakers.length > 0 ? speakers : undefined,
|
|
4760
|
+
wordTimings
|
|
4761
|
+
};
|
|
4762
|
+
}
|
|
4763
|
+
function parseSegments(record) {
|
|
4764
|
+
if (!Array.isArray(record.segments)) {
|
|
4765
|
+
return [];
|
|
4766
|
+
}
|
|
4767
|
+
const parsed = [];
|
|
4768
|
+
for (const entry of record.segments) {
|
|
4769
|
+
const segmentRecord = asRecord2(entry);
|
|
4770
|
+
const text = readString3(segmentRecord, "text");
|
|
4771
|
+
if (!text) {
|
|
4772
|
+
continue;
|
|
4841
4773
|
}
|
|
4774
|
+
const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
|
|
4775
|
+
const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
|
|
4776
|
+
parsed.push({
|
|
4777
|
+
text,
|
|
4778
|
+
startMs: secondsToMs(startSeconds),
|
|
4779
|
+
endMs: secondsToMs(endSeconds),
|
|
4780
|
+
speakerId: readString3(segmentRecord, "speaker") ?? undefined,
|
|
4781
|
+
confidence: readNumber2(segmentRecord, "confidence"),
|
|
4782
|
+
wordTimings: parseWordTimings(segmentRecord.words)
|
|
4783
|
+
});
|
|
4842
4784
|
}
|
|
4785
|
+
return parsed;
|
|
4843
4786
|
}
|
|
4844
|
-
function
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
const
|
|
4851
|
-
const
|
|
4852
|
-
|
|
4787
|
+
function parseWordTimings(value) {
|
|
4788
|
+
if (!Array.isArray(value)) {
|
|
4789
|
+
return [];
|
|
4790
|
+
}
|
|
4791
|
+
const words = [];
|
|
4792
|
+
for (const entry of value) {
|
|
4793
|
+
const wordRecord = asRecord2(entry);
|
|
4794
|
+
const word = readString3(wordRecord, "word");
|
|
4795
|
+
const startSeconds = readNumber2(wordRecord, "start");
|
|
4796
|
+
const endSeconds = readNumber2(wordRecord, "end");
|
|
4797
|
+
if (!word || startSeconds == null || endSeconds == null) {
|
|
4853
4798
|
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
4799
|
}
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
segments.push(drizzleSql.raw(tailSegment));
|
|
4873
|
-
}
|
|
4874
|
-
if (segments.length === 0) {
|
|
4875
|
-
return drizzleSql.raw("");
|
|
4800
|
+
words.push({
|
|
4801
|
+
word,
|
|
4802
|
+
startMs: secondsToMs(startSeconds),
|
|
4803
|
+
endMs: secondsToMs(endSeconds),
|
|
4804
|
+
confidence: readNumber2(wordRecord, "confidence")
|
|
4805
|
+
});
|
|
4876
4806
|
}
|
|
4877
|
-
return
|
|
4807
|
+
return words;
|
|
4878
4808
|
}
|
|
4879
|
-
function
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4809
|
+
function dedupeSpeakers(segments) {
|
|
4810
|
+
const seen = new Set;
|
|
4811
|
+
const speakers = [];
|
|
4812
|
+
for (const segment of segments) {
|
|
4813
|
+
if (!segment.speakerId || seen.has(segment.speakerId)) {
|
|
4814
|
+
continue;
|
|
4815
|
+
}
|
|
4816
|
+
seen.add(segment.speakerId);
|
|
4817
|
+
speakers.push({
|
|
4818
|
+
id: segment.speakerId,
|
|
4819
|
+
name: segment.speakerName
|
|
4820
|
+
});
|
|
4888
4821
|
}
|
|
4889
|
-
return
|
|
4822
|
+
return speakers;
|
|
4890
4823
|
}
|
|
4891
|
-
function
|
|
4892
|
-
|
|
4893
|
-
return [];
|
|
4894
|
-
}
|
|
4895
|
-
return result;
|
|
4824
|
+
function normalizeBaseUrl(url) {
|
|
4825
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
4896
4826
|
}
|
|
4897
|
-
function
|
|
4898
|
-
if (value
|
|
4899
|
-
return
|
|
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;
|
|
4827
|
+
function asRecord2(value) {
|
|
4828
|
+
if (value && typeof value === "object") {
|
|
4829
|
+
return value;
|
|
4909
4830
|
}
|
|
4910
|
-
return
|
|
4831
|
+
return {};
|
|
4911
4832
|
}
|
|
4912
|
-
function
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4833
|
+
function readString3(record, key) {
|
|
4834
|
+
const value = record[key];
|
|
4835
|
+
return typeof value === "string" ? value : undefined;
|
|
4836
|
+
}
|
|
4837
|
+
function readNumber2(record, key) {
|
|
4838
|
+
const value = record[key];
|
|
4839
|
+
return typeof value === "number" ? value : undefined;
|
|
4840
|
+
}
|
|
4841
|
+
function secondsToMs(value) {
|
|
4842
|
+
return Math.round(value * 1000);
|
|
4922
4843
|
}
|
|
4923
4844
|
|
|
4924
|
-
// src/impls/
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4845
|
+
// src/impls/mistral-conversational.ts
|
|
4846
|
+
var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
|
|
4847
|
+
var DEFAULT_MODEL2 = "mistral-small-latest";
|
|
4848
|
+
var DEFAULT_VOICE = "default";
|
|
4849
|
+
|
|
4850
|
+
class MistralConversationalProvider {
|
|
4851
|
+
apiKey;
|
|
4852
|
+
defaultModel;
|
|
4853
|
+
defaultVoiceId;
|
|
4854
|
+
baseUrl;
|
|
4855
|
+
fetchImpl;
|
|
4856
|
+
sttProvider;
|
|
4934
4857
|
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
|
-
]);
|
|
4858
|
+
if (!options.apiKey) {
|
|
4859
|
+
throw new Error("MistralConversationalProvider requires an apiKey");
|
|
4974
4860
|
}
|
|
4861
|
+
this.apiKey = options.apiKey;
|
|
4862
|
+
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
|
|
4863
|
+
this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
|
|
4864
|
+
this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
|
|
4865
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
4866
|
+
this.sttProvider = options.sttProvider ?? new MistralSttProvider({
|
|
4867
|
+
apiKey: options.apiKey,
|
|
4868
|
+
defaultModel: options.sttOptions?.defaultModel,
|
|
4869
|
+
defaultLanguage: options.sttOptions?.defaultLanguage,
|
|
4870
|
+
serverURL: options.sttOptions?.serverURL ?? options.serverURL,
|
|
4871
|
+
fetchImpl: this.fetchImpl
|
|
4872
|
+
});
|
|
4975
4873
|
}
|
|
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
|
-
};
|
|
4874
|
+
async startSession(config) {
|
|
4875
|
+
return new MistralConversationSession({
|
|
4876
|
+
sessionConfig: {
|
|
4877
|
+
...config,
|
|
4878
|
+
voiceId: config.voiceId || this.defaultVoiceId
|
|
4879
|
+
},
|
|
4880
|
+
defaultModel: this.defaultModel,
|
|
4881
|
+
complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
|
|
4882
|
+
sttProvider: this.sttProvider
|
|
5004
4883
|
});
|
|
5005
|
-
const scoreThreshold = query.scoreThreshold;
|
|
5006
|
-
if (scoreThreshold == null) {
|
|
5007
|
-
return mapped;
|
|
5008
|
-
}
|
|
5009
|
-
return mapped.filter((result) => result.score >= scoreThreshold);
|
|
5010
4884
|
}
|
|
5011
|
-
async
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
4885
|
+
async listVoices() {
|
|
4886
|
+
return [
|
|
4887
|
+
{
|
|
4888
|
+
id: this.defaultVoiceId,
|
|
4889
|
+
name: "Mistral Default Voice",
|
|
4890
|
+
description: "Default conversational voice profile.",
|
|
4891
|
+
capabilities: ["conversational"]
|
|
4892
|
+
}
|
|
5019
4893
|
];
|
|
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
4894
|
}
|
|
5025
|
-
async
|
|
5026
|
-
|
|
5027
|
-
|
|
4895
|
+
async completeConversation(history, sessionConfig) {
|
|
4896
|
+
const model = sessionConfig.llmModel ?? this.defaultModel;
|
|
4897
|
+
const messages = [];
|
|
4898
|
+
if (sessionConfig.systemPrompt) {
|
|
4899
|
+
messages.push({ role: "system", content: sessionConfig.systemPrompt });
|
|
5028
4900
|
}
|
|
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 "<=>";
|
|
4901
|
+
for (const item of history) {
|
|
4902
|
+
messages.push({ role: item.role, content: item.content });
|
|
5059
4903
|
}
|
|
4904
|
+
const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
4905
|
+
method: "POST",
|
|
4906
|
+
headers: {
|
|
4907
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
4908
|
+
"Content-Type": "application/json"
|
|
4909
|
+
},
|
|
4910
|
+
body: JSON.stringify({
|
|
4911
|
+
model,
|
|
4912
|
+
messages
|
|
4913
|
+
})
|
|
4914
|
+
});
|
|
4915
|
+
if (!response.ok) {
|
|
4916
|
+
const body = await response.text();
|
|
4917
|
+
throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
|
|
4918
|
+
}
|
|
4919
|
+
const payload = await response.json();
|
|
4920
|
+
return readAssistantText(payload);
|
|
5060
4921
|
}
|
|
5061
4922
|
}
|
|
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('"', '""')}"`;
|
|
4923
|
+
function normalizeBaseUrl2(url) {
|
|
4924
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5070
4925
|
}
|
|
5071
|
-
function
|
|
5072
|
-
|
|
5073
|
-
|
|
4926
|
+
function readAssistantText(payload) {
|
|
4927
|
+
const record = asRecord3(payload);
|
|
4928
|
+
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
4929
|
+
const firstChoice = asRecord3(choices[0]);
|
|
4930
|
+
const message = asRecord3(firstChoice.message);
|
|
4931
|
+
if (typeof message.content === "string") {
|
|
4932
|
+
return message.content;
|
|
5074
4933
|
}
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
4934
|
+
if (Array.isArray(message.content)) {
|
|
4935
|
+
const textParts = message.content.map((part) => {
|
|
4936
|
+
const entry = asRecord3(part);
|
|
4937
|
+
const text = entry.text;
|
|
4938
|
+
return typeof text === "string" ? text : "";
|
|
4939
|
+
}).filter((text) => text.length > 0);
|
|
4940
|
+
return textParts.join("");
|
|
5079
4941
|
}
|
|
5080
|
-
return
|
|
5081
|
-
}
|
|
5082
|
-
function isRecord(value) {
|
|
5083
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4942
|
+
return "";
|
|
5084
4943
|
}
|
|
5085
|
-
function
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
return -distance;
|
|
5089
|
-
case "l2":
|
|
5090
|
-
return 1 / (1 + distance);
|
|
5091
|
-
case "cosine":
|
|
5092
|
-
default:
|
|
5093
|
-
return 1 - distance;
|
|
4944
|
+
function asRecord3(value) {
|
|
4945
|
+
if (value && typeof value === "object") {
|
|
4946
|
+
return value;
|
|
5094
4947
|
}
|
|
4948
|
+
return {};
|
|
5095
4949
|
}
|
|
5096
4950
|
|
|
5097
|
-
// src/impls/
|
|
5098
|
-
import
|
|
5099
|
-
var API_VERSION = "2026-02-25.clover";
|
|
4951
|
+
// src/impls/mistral-embedding.ts
|
|
4952
|
+
import { Mistral } from "@mistralai/mistralai";
|
|
5100
4953
|
|
|
5101
|
-
class
|
|
5102
|
-
|
|
4954
|
+
class MistralEmbeddingProvider {
|
|
4955
|
+
client;
|
|
4956
|
+
defaultModel;
|
|
5103
4957
|
constructor(options) {
|
|
5104
|
-
|
|
5105
|
-
|
|
4958
|
+
if (!options.apiKey) {
|
|
4959
|
+
throw new Error("MistralEmbeddingProvider requires an apiKey");
|
|
4960
|
+
}
|
|
4961
|
+
this.client = options.client ?? new Mistral({
|
|
4962
|
+
apiKey: options.apiKey,
|
|
4963
|
+
serverURL: options.serverURL
|
|
5106
4964
|
});
|
|
4965
|
+
this.defaultModel = options.defaultModel ?? "mistral-embed";
|
|
5107
4966
|
}
|
|
5108
|
-
async
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
4967
|
+
async embedDocuments(documents, options) {
|
|
4968
|
+
if (documents.length === 0)
|
|
4969
|
+
return [];
|
|
4970
|
+
const model = options?.model ?? this.defaultModel;
|
|
4971
|
+
const response = await this.client.embeddings.create({
|
|
4972
|
+
model,
|
|
4973
|
+
inputs: documents.map((doc) => doc.text)
|
|
5114
4974
|
});
|
|
5115
|
-
return
|
|
4975
|
+
return response.data.map((item, index) => ({
|
|
4976
|
+
id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
|
|
4977
|
+
vector: item.embedding ?? [],
|
|
4978
|
+
dimensions: item.embedding?.length ?? 0,
|
|
4979
|
+
model: response.model,
|
|
4980
|
+
metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
|
|
4981
|
+
}));
|
|
5116
4982
|
}
|
|
5117
|
-
async
|
|
5118
|
-
const
|
|
5119
|
-
if (
|
|
5120
|
-
|
|
5121
|
-
|
|
4983
|
+
async embedQuery(query, options) {
|
|
4984
|
+
const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
|
|
4985
|
+
if (!result) {
|
|
4986
|
+
throw new Error("Failed to compute embedding for query");
|
|
4987
|
+
}
|
|
4988
|
+
return result;
|
|
5122
4989
|
}
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
4990
|
+
}
|
|
4991
|
+
|
|
4992
|
+
// src/impls/mistral-llm.ts
|
|
4993
|
+
import { Mistral as Mistral2 } from "@mistralai/mistralai";
|
|
4994
|
+
|
|
4995
|
+
class MistralLLMProvider {
|
|
4996
|
+
client;
|
|
4997
|
+
defaultModel;
|
|
4998
|
+
constructor(options) {
|
|
4999
|
+
if (!options.apiKey) {
|
|
5000
|
+
throw new Error("MistralLLMProvider requires an apiKey");
|
|
5001
|
+
}
|
|
5002
|
+
this.client = options.client ?? new Mistral2({
|
|
5003
|
+
apiKey: options.apiKey,
|
|
5004
|
+
serverURL: options.serverURL,
|
|
5005
|
+
userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
|
|
5135
5006
|
});
|
|
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);
|
|
5007
|
+
this.defaultModel = options.defaultModel ?? "mistral-large-latest";
|
|
5141
5008
|
}
|
|
5142
|
-
async
|
|
5143
|
-
const
|
|
5144
|
-
|
|
5009
|
+
async chat(messages, options = {}) {
|
|
5010
|
+
const request = this.buildChatRequest(messages, options);
|
|
5011
|
+
const response = await this.client.chat.complete(request);
|
|
5012
|
+
return this.buildLLMResponse(response);
|
|
5145
5013
|
}
|
|
5146
|
-
async
|
|
5147
|
-
const
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5014
|
+
async* stream(messages, options = {}) {
|
|
5015
|
+
const request = this.buildChatRequest(messages, options);
|
|
5016
|
+
request.stream = true;
|
|
5017
|
+
const stream = await this.client.chat.stream(request);
|
|
5018
|
+
const aggregatedParts = [];
|
|
5019
|
+
const aggregatedToolCalls = [];
|
|
5020
|
+
let usage;
|
|
5021
|
+
let finishReason;
|
|
5022
|
+
for await (const event of stream) {
|
|
5023
|
+
for (const choice of event.data.choices) {
|
|
5024
|
+
const delta = choice.delta;
|
|
5025
|
+
if (typeof delta.content === "string") {
|
|
5026
|
+
if (delta.content.length > 0) {
|
|
5027
|
+
aggregatedParts.push({ type: "text", text: delta.content });
|
|
5028
|
+
yield {
|
|
5029
|
+
type: "message_delta",
|
|
5030
|
+
delta: { type: "text", text: delta.content },
|
|
5031
|
+
index: choice.index
|
|
5032
|
+
};
|
|
5033
|
+
}
|
|
5034
|
+
} else if (Array.isArray(delta.content)) {
|
|
5035
|
+
for (const chunk of delta.content) {
|
|
5036
|
+
if (chunk.type === "text" && "text" in chunk) {
|
|
5037
|
+
aggregatedParts.push({ type: "text", text: chunk.text });
|
|
5038
|
+
yield {
|
|
5039
|
+
type: "message_delta",
|
|
5040
|
+
delta: { type: "text", text: chunk.text },
|
|
5041
|
+
index: choice.index
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
if (delta.toolCalls) {
|
|
5047
|
+
let localIndex = 0;
|
|
5048
|
+
for (const call of delta.toolCalls) {
|
|
5049
|
+
const toolCall = this.fromMistralToolCall(call, localIndex);
|
|
5050
|
+
aggregatedToolCalls.push(toolCall);
|
|
5051
|
+
yield {
|
|
5052
|
+
type: "tool_call",
|
|
5053
|
+
call: toolCall,
|
|
5054
|
+
index: choice.index
|
|
5055
|
+
};
|
|
5056
|
+
localIndex += 1;
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
if (choice.finishReason && choice.finishReason !== "null") {
|
|
5060
|
+
finishReason = choice.finishReason;
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
if (event.data.usage) {
|
|
5064
|
+
const usageEntry = this.fromUsage(event.data.usage);
|
|
5065
|
+
if (usageEntry) {
|
|
5066
|
+
usage = usageEntry;
|
|
5067
|
+
yield { type: "usage", usage: usageEntry };
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
const message = {
|
|
5072
|
+
role: "assistant",
|
|
5073
|
+
content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
|
|
5074
|
+
};
|
|
5075
|
+
if (aggregatedToolCalls.length > 0) {
|
|
5076
|
+
message.content = [
|
|
5077
|
+
...aggregatedToolCalls,
|
|
5078
|
+
...aggregatedParts.length ? aggregatedParts : []
|
|
5079
|
+
];
|
|
5080
|
+
}
|
|
5081
|
+
yield {
|
|
5082
|
+
type: "end",
|
|
5083
|
+
response: {
|
|
5084
|
+
message,
|
|
5085
|
+
usage,
|
|
5086
|
+
finishReason: mapFinishReason(finishReason)
|
|
5087
|
+
}
|
|
5165
5088
|
};
|
|
5166
5089
|
}
|
|
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));
|
|
5090
|
+
async countTokens(_messages) {
|
|
5091
|
+
throw new Error("Mistral API does not currently support token counting");
|
|
5177
5092
|
}
|
|
5178
|
-
|
|
5179
|
-
const
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
}
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5093
|
+
buildChatRequest(messages, options) {
|
|
5094
|
+
const model = options.model ?? this.defaultModel;
|
|
5095
|
+
const mappedMessages = messages.map((message) => this.toMistralMessage(message));
|
|
5096
|
+
const request = {
|
|
5097
|
+
model,
|
|
5098
|
+
messages: mappedMessages
|
|
5099
|
+
};
|
|
5100
|
+
if (options.temperature != null) {
|
|
5101
|
+
request.temperature = options.temperature;
|
|
5102
|
+
}
|
|
5103
|
+
if (options.topP != null) {
|
|
5104
|
+
request.topP = options.topP;
|
|
5105
|
+
}
|
|
5106
|
+
if (options.maxOutputTokens != null) {
|
|
5107
|
+
request.maxTokens = options.maxOutputTokens;
|
|
5108
|
+
}
|
|
5109
|
+
if (options.stopSequences?.length) {
|
|
5110
|
+
request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
|
|
5111
|
+
}
|
|
5112
|
+
if (options.tools?.length) {
|
|
5113
|
+
request.tools = options.tools.map((tool) => ({
|
|
5114
|
+
type: "function",
|
|
5115
|
+
function: {
|
|
5116
|
+
name: tool.name,
|
|
5117
|
+
description: tool.description,
|
|
5118
|
+
parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
|
|
5119
|
+
}
|
|
5120
|
+
}));
|
|
5121
|
+
}
|
|
5122
|
+
if (options.responseFormat === "json") {
|
|
5123
|
+
request.responseFormat = { type: "json_object" };
|
|
5124
|
+
}
|
|
5125
|
+
return request;
|
|
5200
5126
|
}
|
|
5201
|
-
|
|
5202
|
-
const
|
|
5203
|
-
|
|
5127
|
+
buildLLMResponse(response) {
|
|
5128
|
+
const firstChoice = response.choices[0];
|
|
5129
|
+
if (!firstChoice) {
|
|
5130
|
+
return {
|
|
5131
|
+
message: {
|
|
5132
|
+
role: "assistant",
|
|
5133
|
+
content: [{ type: "text", text: "" }]
|
|
5134
|
+
},
|
|
5135
|
+
usage: this.fromUsage(response.usage),
|
|
5136
|
+
raw: response
|
|
5137
|
+
};
|
|
5138
|
+
}
|
|
5139
|
+
const message = this.fromAssistantMessage(firstChoice.message);
|
|
5204
5140
|
return {
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
|
|
5210
|
-
updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
|
|
5141
|
+
message,
|
|
5142
|
+
usage: this.fromUsage(response.usage),
|
|
5143
|
+
finishReason: mapFinishReason(firstChoice.finishReason),
|
|
5144
|
+
raw: response
|
|
5211
5145
|
};
|
|
5212
5146
|
}
|
|
5213
|
-
|
|
5214
|
-
|
|
5147
|
+
fromUsage(usage) {
|
|
5148
|
+
if (!usage)
|
|
5149
|
+
return;
|
|
5215
5150
|
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)
|
|
5151
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
5152
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
5153
|
+
totalTokens: usage.totalTokens ?? 0
|
|
5225
5154
|
};
|
|
5226
5155
|
}
|
|
5227
|
-
|
|
5228
|
-
const
|
|
5156
|
+
fromAssistantMessage(message) {
|
|
5157
|
+
const parts = [];
|
|
5158
|
+
if (typeof message.content === "string") {
|
|
5159
|
+
parts.push({ type: "text", text: message.content });
|
|
5160
|
+
} else if (Array.isArray(message.content)) {
|
|
5161
|
+
message.content.forEach((chunk) => {
|
|
5162
|
+
if (chunk.type === "text" && "text" in chunk) {
|
|
5163
|
+
parts.push({ type: "text", text: chunk.text });
|
|
5164
|
+
}
|
|
5165
|
+
});
|
|
5166
|
+
}
|
|
5167
|
+
const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
|
|
5168
|
+
if (toolCalls.length > 0) {
|
|
5169
|
+
parts.splice(0, 0, ...toolCalls);
|
|
5170
|
+
}
|
|
5171
|
+
if (parts.length === 0) {
|
|
5172
|
+
parts.push({ type: "text", text: "" });
|
|
5173
|
+
}
|
|
5229
5174
|
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
|
|
5175
|
+
role: "assistant",
|
|
5176
|
+
content: parts
|
|
5241
5177
|
};
|
|
5242
5178
|
}
|
|
5243
|
-
|
|
5179
|
+
fromMistralToolCall(call, index) {
|
|
5180
|
+
const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
|
|
5244
5181
|
return {
|
|
5245
|
-
|
|
5246
|
-
|
|
5182
|
+
type: "tool-call",
|
|
5183
|
+
id: call.id ?? `tool-call-${index}`,
|
|
5184
|
+
name: call.function.name,
|
|
5185
|
+
arguments: args
|
|
5247
5186
|
};
|
|
5248
5187
|
}
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5188
|
+
toMistralMessage(message) {
|
|
5189
|
+
const textContent = this.extractText(message.content);
|
|
5190
|
+
const toolCalls = this.extractToolCalls(message);
|
|
5191
|
+
switch (message.role) {
|
|
5192
|
+
case "system":
|
|
5193
|
+
return {
|
|
5194
|
+
role: "system",
|
|
5195
|
+
content: textContent ?? ""
|
|
5196
|
+
};
|
|
5197
|
+
case "user":
|
|
5198
|
+
return {
|
|
5199
|
+
role: "user",
|
|
5200
|
+
content: textContent ?? ""
|
|
5201
|
+
};
|
|
5202
|
+
case "assistant":
|
|
5203
|
+
return {
|
|
5204
|
+
role: "assistant",
|
|
5205
|
+
content: toolCalls.length > 0 ? null : textContent ?? "",
|
|
5206
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
5207
|
+
};
|
|
5208
|
+
case "tool":
|
|
5209
|
+
return {
|
|
5210
|
+
role: "tool",
|
|
5211
|
+
content: textContent ?? "",
|
|
5212
|
+
toolCallId: message.toolCallId ?? toolCalls[0]?.id
|
|
5213
|
+
};
|
|
5214
|
+
default:
|
|
5215
|
+
return {
|
|
5216
|
+
role: "user",
|
|
5217
|
+
content: textContent ?? ""
|
|
5218
|
+
};
|
|
5261
5219
|
}
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5220
|
+
}
|
|
5221
|
+
extractText(parts) {
|
|
5222
|
+
const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
|
|
5223
|
+
if (textParts.length === 0)
|
|
5224
|
+
return null;
|
|
5225
|
+
return textParts.join("");
|
|
5226
|
+
}
|
|
5227
|
+
extractToolCalls(message) {
|
|
5228
|
+
const toolCallParts = message.content.filter((part) => part.type === "tool-call");
|
|
5229
|
+
return toolCallParts.map((call, index) => ({
|
|
5230
|
+
id: call.id ?? `call_${index}`,
|
|
5231
|
+
type: "function",
|
|
5232
|
+
index,
|
|
5233
|
+
function: {
|
|
5234
|
+
name: call.name,
|
|
5235
|
+
arguments: call.arguments
|
|
5236
|
+
}
|
|
5237
|
+
}));
|
|
5266
5238
|
}
|
|
5267
5239
|
}
|
|
5268
|
-
function
|
|
5240
|
+
function mapFinishReason(reason) {
|
|
5269
5241
|
if (!reason)
|
|
5270
5242
|
return;
|
|
5271
|
-
const
|
|
5272
|
-
|
|
5273
|
-
"
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
case "
|
|
5281
|
-
return "
|
|
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";
|
|
5295
|
-
}
|
|
5296
|
-
}
|
|
5297
|
-
function mapRefundStatus(status) {
|
|
5298
|
-
switch (status) {
|
|
5299
|
-
case "pending":
|
|
5300
|
-
case "succeeded":
|
|
5301
|
-
case "failed":
|
|
5302
|
-
case "canceled":
|
|
5303
|
-
return status;
|
|
5304
|
-
default:
|
|
5305
|
-
return "pending";
|
|
5306
|
-
}
|
|
5307
|
-
}
|
|
5308
|
-
function mapChargeStatus(status) {
|
|
5309
|
-
switch (status) {
|
|
5310
|
-
case "pending":
|
|
5311
|
-
case "processing":
|
|
5312
|
-
return "pending";
|
|
5313
|
-
case "succeeded":
|
|
5314
|
-
return "succeeded";
|
|
5315
|
-
case "failed":
|
|
5316
|
-
case "canceled":
|
|
5317
|
-
return "failed";
|
|
5243
|
+
const normalized = reason.toLowerCase();
|
|
5244
|
+
switch (normalized) {
|
|
5245
|
+
case "stop":
|
|
5246
|
+
return "stop";
|
|
5247
|
+
case "length":
|
|
5248
|
+
return "length";
|
|
5249
|
+
case "tool_call":
|
|
5250
|
+
case "tool_calls":
|
|
5251
|
+
return "tool_call";
|
|
5252
|
+
case "content_filter":
|
|
5253
|
+
return "content_filter";
|
|
5318
5254
|
default:
|
|
5319
|
-
return
|
|
5255
|
+
return;
|
|
5320
5256
|
}
|
|
5321
5257
|
}
|
|
5322
5258
|
|
|
5323
|
-
// src/impls/
|
|
5324
|
-
import {
|
|
5259
|
+
// src/impls/notion.ts
|
|
5260
|
+
import { Client } from "@notionhq/client";
|
|
5325
5261
|
|
|
5326
|
-
class
|
|
5262
|
+
class NotionProjectManagementProvider {
|
|
5327
5263
|
client;
|
|
5328
|
-
|
|
5329
|
-
messageStream;
|
|
5264
|
+
defaults;
|
|
5330
5265
|
constructor(options) {
|
|
5331
|
-
this.client = options.client ?? new
|
|
5332
|
-
|
|
5266
|
+
this.client = options.client ?? new Client({ auth: options.apiKey });
|
|
5267
|
+
this.defaults = {
|
|
5268
|
+
databaseId: options.databaseId,
|
|
5269
|
+
summaryParentPageId: options.summaryParentPageId,
|
|
5270
|
+
titleProperty: options.titleProperty,
|
|
5271
|
+
statusProperty: options.statusProperty,
|
|
5272
|
+
priorityProperty: options.priorityProperty,
|
|
5273
|
+
tagsProperty: options.tagsProperty,
|
|
5274
|
+
dueDateProperty: options.dueDateProperty,
|
|
5275
|
+
descriptionProperty: options.descriptionProperty
|
|
5276
|
+
};
|
|
5277
|
+
}
|
|
5278
|
+
async createWorkItem(input) {
|
|
5279
|
+
if (input.type === "summary" && this.defaults.summaryParentPageId) {
|
|
5280
|
+
return this.createSummaryPage(input);
|
|
5281
|
+
}
|
|
5282
|
+
const databaseId = this.defaults.databaseId;
|
|
5283
|
+
if (!databaseId) {
|
|
5284
|
+
throw new Error("Notion databaseId is required to create work items.");
|
|
5285
|
+
}
|
|
5286
|
+
const titleProperty = this.defaults.titleProperty ?? "Name";
|
|
5287
|
+
const properties = {
|
|
5288
|
+
[titleProperty]: buildTitleProperty(input.title)
|
|
5289
|
+
};
|
|
5290
|
+
applySelect(properties, this.defaults.statusProperty, input.status);
|
|
5291
|
+
applySelect(properties, this.defaults.priorityProperty, input.priority);
|
|
5292
|
+
applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
|
|
5293
|
+
applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
|
|
5294
|
+
applyRichText(properties, this.defaults.descriptionProperty, input.description);
|
|
5295
|
+
const page = await this.client.pages.create({
|
|
5296
|
+
parent: { type: "database_id", database_id: databaseId },
|
|
5297
|
+
properties
|
|
5333
5298
|
});
|
|
5334
|
-
|
|
5335
|
-
|
|
5299
|
+
return {
|
|
5300
|
+
id: page.id,
|
|
5301
|
+
title: input.title,
|
|
5302
|
+
url: "url" in page ? page.url : undefined,
|
|
5303
|
+
status: input.status,
|
|
5304
|
+
priority: input.priority,
|
|
5305
|
+
tags: input.tags,
|
|
5306
|
+
projectId: databaseId,
|
|
5307
|
+
externalId: input.externalId,
|
|
5308
|
+
metadata: input.metadata
|
|
5309
|
+
};
|
|
5336
5310
|
}
|
|
5337
|
-
async
|
|
5338
|
-
const
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
MessageStream: this.messageStream,
|
|
5352
|
-
Attachments: buildAttachments(message)
|
|
5311
|
+
async createWorkItems(items) {
|
|
5312
|
+
const created = [];
|
|
5313
|
+
for (const item of items) {
|
|
5314
|
+
created.push(await this.createWorkItem(item));
|
|
5315
|
+
}
|
|
5316
|
+
return created;
|
|
5317
|
+
}
|
|
5318
|
+
async createSummaryPage(input) {
|
|
5319
|
+
const parentId = this.defaults.summaryParentPageId;
|
|
5320
|
+
if (!parentId) {
|
|
5321
|
+
throw new Error("Notion summaryParentPageId is required for summaries.");
|
|
5322
|
+
}
|
|
5323
|
+
const summaryProperties = {
|
|
5324
|
+
title: buildTitleProperty(input.title)
|
|
5353
5325
|
};
|
|
5354
|
-
const
|
|
5326
|
+
const page = await this.client.pages.create({
|
|
5327
|
+
parent: { type: "page_id", page_id: parentId },
|
|
5328
|
+
properties: summaryProperties
|
|
5329
|
+
});
|
|
5330
|
+
if (input.description) {
|
|
5331
|
+
const children = buildParagraphBlocks(input.description);
|
|
5332
|
+
if (children.length > 0) {
|
|
5333
|
+
await this.client.blocks.children.append({
|
|
5334
|
+
block_id: page.id,
|
|
5335
|
+
children
|
|
5336
|
+
});
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5355
5339
|
return {
|
|
5356
|
-
id:
|
|
5357
|
-
|
|
5358
|
-
|
|
5340
|
+
id: page.id,
|
|
5341
|
+
title: input.title,
|
|
5342
|
+
url: "url" in page ? page.url : undefined,
|
|
5343
|
+
status: input.status,
|
|
5344
|
+
priority: input.priority,
|
|
5345
|
+
tags: input.tags,
|
|
5346
|
+
projectId: parentId,
|
|
5347
|
+
externalId: input.externalId,
|
|
5348
|
+
metadata: input.metadata
|
|
5359
5349
|
};
|
|
5360
5350
|
}
|
|
5361
5351
|
}
|
|
5362
|
-
function
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5352
|
+
function buildTitleProperty(title) {
|
|
5353
|
+
return {
|
|
5354
|
+
title: [
|
|
5355
|
+
{
|
|
5356
|
+
type: "text",
|
|
5357
|
+
text: { content: title }
|
|
5358
|
+
}
|
|
5359
|
+
]
|
|
5360
|
+
};
|
|
5367
5361
|
}
|
|
5368
|
-
function
|
|
5369
|
-
|
|
5362
|
+
function buildRichText(text) {
|
|
5363
|
+
return {
|
|
5364
|
+
rich_text: [
|
|
5365
|
+
{
|
|
5366
|
+
type: "text",
|
|
5367
|
+
text: { content: text }
|
|
5368
|
+
}
|
|
5369
|
+
]
|
|
5370
|
+
};
|
|
5371
|
+
}
|
|
5372
|
+
function applySelect(properties, property, value) {
|
|
5373
|
+
if (!property || !value)
|
|
5370
5374
|
return;
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5375
|
+
const next = {
|
|
5376
|
+
select: { name: value }
|
|
5377
|
+
};
|
|
5378
|
+
properties[property] = next;
|
|
5379
|
+
}
|
|
5380
|
+
function applyMultiSelect(properties, property, values) {
|
|
5381
|
+
if (!property || !values || values.length === 0)
|
|
5382
|
+
return;
|
|
5383
|
+
const next = {
|
|
5384
|
+
multi_select: values.map((value) => ({ name: value }))
|
|
5385
|
+
};
|
|
5386
|
+
properties[property] = next;
|
|
5387
|
+
}
|
|
5388
|
+
function applyDate(properties, property, value) {
|
|
5389
|
+
if (!property || !value)
|
|
5390
|
+
return;
|
|
5391
|
+
const next = {
|
|
5392
|
+
date: { start: value.toISOString() }
|
|
5393
|
+
};
|
|
5394
|
+
properties[property] = next;
|
|
5395
|
+
}
|
|
5396
|
+
function applyRichText(properties, property, value) {
|
|
5397
|
+
if (!property || !value)
|
|
5398
|
+
return;
|
|
5399
|
+
properties[property] = buildRichText(value);
|
|
5400
|
+
}
|
|
5401
|
+
function buildParagraphBlocks(text) {
|
|
5402
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim());
|
|
5403
|
+
return lines.map((line) => ({
|
|
5404
|
+
object: "block",
|
|
5405
|
+
type: "paragraph",
|
|
5406
|
+
paragraph: {
|
|
5407
|
+
rich_text: [
|
|
5408
|
+
{
|
|
5409
|
+
type: "text",
|
|
5410
|
+
text: { content: line }
|
|
5411
|
+
}
|
|
5412
|
+
]
|
|
5413
|
+
}
|
|
5378
5414
|
}));
|
|
5379
5415
|
}
|
|
5380
5416
|
|
|
@@ -5465,612 +5501,295 @@ class PosthogAnalyticsReader {
|
|
|
5465
5501
|
offset: input.offset
|
|
5466
5502
|
}
|
|
5467
5503
|
});
|
|
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;
|
|
5710
|
-
}
|
|
5711
|
-
async sendSms(input) {
|
|
5712
|
-
const message = await this.client.messages.create({
|
|
5713
|
-
to: input.to,
|
|
5714
|
-
from: input.from ?? this.fromNumber,
|
|
5715
|
-
body: input.body
|
|
5504
|
+
return response.data;
|
|
5505
|
+
}
|
|
5506
|
+
async getFeatureFlags(input) {
|
|
5507
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5508
|
+
const response = await this.client.request({
|
|
5509
|
+
method: "GET",
|
|
5510
|
+
path: `/api/projects/${projectId}/feature_flags/`,
|
|
5511
|
+
query: {
|
|
5512
|
+
active: input.active,
|
|
5513
|
+
limit: input.limit,
|
|
5514
|
+
offset: input.offset
|
|
5515
|
+
}
|
|
5716
5516
|
});
|
|
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
|
-
};
|
|
5517
|
+
return response.data;
|
|
5730
5518
|
}
|
|
5731
|
-
async
|
|
5732
|
-
const
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5519
|
+
async getAnnotations(input) {
|
|
5520
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5521
|
+
const response = await this.client.request({
|
|
5522
|
+
method: "GET",
|
|
5523
|
+
path: `/api/projects/${projectId}/annotations/`,
|
|
5524
|
+
query: {
|
|
5525
|
+
limit: input.limit,
|
|
5526
|
+
offset: input.offset,
|
|
5527
|
+
...buildAnnotationDateQuery(input.dateRange)
|
|
5528
|
+
}
|
|
5529
|
+
});
|
|
5530
|
+
return response.data;
|
|
5739
5531
|
}
|
|
5740
5532
|
}
|
|
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";
|
|
5533
|
+
function resolveProjectId(inputProjectId, defaultProjectId) {
|
|
5534
|
+
const projectId = inputProjectId ?? defaultProjectId;
|
|
5535
|
+
if (!projectId) {
|
|
5536
|
+
throw new Error("PostHog projectId is required for API reads.");
|
|
5761
5537
|
}
|
|
5538
|
+
return projectId;
|
|
5539
|
+
}
|
|
5540
|
+
function resolveSingleEvent(events) {
|
|
5541
|
+
if (!events || events.length !== 1)
|
|
5542
|
+
return;
|
|
5543
|
+
return events[0];
|
|
5544
|
+
}
|
|
5545
|
+
function resolveEventList(events) {
|
|
5546
|
+
if (!events || events.length <= 1)
|
|
5547
|
+
return;
|
|
5548
|
+
return events.join(",");
|
|
5549
|
+
}
|
|
5550
|
+
function buildEventDateQuery(range) {
|
|
5551
|
+
const after = formatDate(range?.from);
|
|
5552
|
+
const before = formatDate(range?.to);
|
|
5553
|
+
return {
|
|
5554
|
+
after,
|
|
5555
|
+
before,
|
|
5556
|
+
timezone: range?.timezone
|
|
5557
|
+
};
|
|
5558
|
+
}
|
|
5559
|
+
function buildAnnotationDateQuery(range) {
|
|
5560
|
+
const dateFrom = formatDate(range?.from);
|
|
5561
|
+
const dateTo = formatDate(range?.to);
|
|
5562
|
+
return {
|
|
5563
|
+
date_from: dateFrom,
|
|
5564
|
+
date_to: dateTo,
|
|
5565
|
+
timezone: range?.timezone
|
|
5566
|
+
};
|
|
5567
|
+
}
|
|
5568
|
+
function formatDate(value) {
|
|
5569
|
+
if (!value)
|
|
5570
|
+
return;
|
|
5571
|
+
return typeof value === "string" ? value : value.toISOString();
|
|
5762
5572
|
}
|
|
5763
5573
|
|
|
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
|
-
};
|
|
5574
|
+
// src/impls/posthog-utils.ts
|
|
5575
|
+
function normalizeHost(host) {
|
|
5576
|
+
return host.replace(/\/$/, "");
|
|
5577
|
+
}
|
|
5578
|
+
function buildUrl(host, path, query) {
|
|
5579
|
+
if (/^https?:\/\//.test(path)) {
|
|
5580
|
+
return appendQuery(path, query);
|
|
5806
5581
|
}
|
|
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
|
-
};
|
|
5582
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
5583
|
+
return appendQuery(`${host}/${normalizedPath}`, query);
|
|
5584
|
+
}
|
|
5585
|
+
function appendQuery(url, query) {
|
|
5586
|
+
if (!query)
|
|
5587
|
+
return url;
|
|
5588
|
+
const params = new URLSearchParams;
|
|
5589
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
5590
|
+
if (value === undefined)
|
|
5591
|
+
return;
|
|
5592
|
+
params.set(key, String(value));
|
|
5593
|
+
});
|
|
5594
|
+
const suffix = params.toString();
|
|
5595
|
+
return suffix ? `${url}?${suffix}` : url;
|
|
5596
|
+
}
|
|
5597
|
+
function parseJson(value) {
|
|
5598
|
+
if (!value)
|
|
5599
|
+
return {};
|
|
5600
|
+
try {
|
|
5601
|
+
return JSON.parse(value);
|
|
5602
|
+
} catch {
|
|
5603
|
+
return value;
|
|
5838
5604
|
}
|
|
5839
5605
|
}
|
|
5840
5606
|
|
|
5841
|
-
// src/impls/
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5607
|
+
// src/impls/posthog.ts
|
|
5608
|
+
import { PostHog } from "posthog-node";
|
|
5609
|
+
var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
|
|
5610
|
+
|
|
5611
|
+
class PosthogAnalyticsProvider {
|
|
5612
|
+
host;
|
|
5613
|
+
projectId;
|
|
5614
|
+
projectApiKey;
|
|
5615
|
+
personalApiKey;
|
|
5616
|
+
mcpUrl;
|
|
5617
|
+
fetchFn;
|
|
5618
|
+
client;
|
|
5619
|
+
reader;
|
|
5847
5620
|
constructor(options) {
|
|
5848
|
-
this.
|
|
5849
|
-
this.
|
|
5850
|
-
this.
|
|
5851
|
-
this.
|
|
5621
|
+
this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
|
|
5622
|
+
this.projectId = options.projectId;
|
|
5623
|
+
this.projectApiKey = options.projectApiKey;
|
|
5624
|
+
this.personalApiKey = options.personalApiKey;
|
|
5625
|
+
this.mcpUrl = options.mcpUrl;
|
|
5626
|
+
this.fetchFn = options.fetch ?? fetch;
|
|
5627
|
+
this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
|
|
5628
|
+
host: this.host,
|
|
5629
|
+
requestTimeout: options.requestTimeoutMs ?? 1e4
|
|
5630
|
+
}) : undefined);
|
|
5631
|
+
this.reader = new PosthogAnalyticsReader({
|
|
5632
|
+
projectId: this.projectId,
|
|
5633
|
+
client: { request: this.request.bind(this) }
|
|
5634
|
+
});
|
|
5852
5635
|
}
|
|
5853
|
-
async
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5636
|
+
async capture(event) {
|
|
5637
|
+
if (!this.client) {
|
|
5638
|
+
throw new Error("PostHog projectApiKey is required for capture.");
|
|
5639
|
+
}
|
|
5640
|
+
await this.client.capture({
|
|
5641
|
+
distinctId: event.distinctId,
|
|
5642
|
+
event: event.event,
|
|
5643
|
+
properties: event.properties,
|
|
5644
|
+
timestamp: event.timestamp,
|
|
5645
|
+
groups: event.groups
|
|
5863
5646
|
});
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5647
|
+
}
|
|
5648
|
+
async identify(input) {
|
|
5649
|
+
if (!this.client) {
|
|
5650
|
+
throw new Error("PostHog projectApiKey is required for identify.");
|
|
5867
5651
|
}
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
metadata: {
|
|
5874
|
-
url: body.html_url ?? "",
|
|
5875
|
-
owner: target.owner,
|
|
5876
|
-
repo: target.repo,
|
|
5877
|
-
issueNumber: String(target.issueNumber)
|
|
5652
|
+
await this.client.identify({
|
|
5653
|
+
distinctId: input.distinctId,
|
|
5654
|
+
properties: {
|
|
5655
|
+
...input.properties ? { $set: input.properties } : {},
|
|
5656
|
+
...input.setOnce ? { $set_once: input.setOnce } : {}
|
|
5878
5657
|
}
|
|
5879
|
-
};
|
|
5658
|
+
});
|
|
5880
5659
|
}
|
|
5881
|
-
async
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5660
|
+
async queryHogQL(input) {
|
|
5661
|
+
return this.reader.queryHogQL(input);
|
|
5662
|
+
}
|
|
5663
|
+
async getEvents(input) {
|
|
5664
|
+
return this.reader.getEvents(input);
|
|
5665
|
+
}
|
|
5666
|
+
async getPersons(input) {
|
|
5667
|
+
return this.reader.getPersons(input);
|
|
5668
|
+
}
|
|
5669
|
+
async getInsights(input) {
|
|
5670
|
+
return this.reader.getInsights(input);
|
|
5671
|
+
}
|
|
5672
|
+
async getInsightResult(input) {
|
|
5673
|
+
return this.reader.getInsightResult(input);
|
|
5674
|
+
}
|
|
5675
|
+
async getCohorts(input) {
|
|
5676
|
+
return this.reader.getCohorts(input);
|
|
5677
|
+
}
|
|
5678
|
+
async getFeatureFlags(input) {
|
|
5679
|
+
return this.reader.getFeatureFlags(input);
|
|
5680
|
+
}
|
|
5681
|
+
async getAnnotations(input) {
|
|
5682
|
+
return this.reader.getAnnotations(input);
|
|
5683
|
+
}
|
|
5684
|
+
async request(request) {
|
|
5685
|
+
if (!this.personalApiKey) {
|
|
5686
|
+
throw new Error("PostHog personalApiKey is required for API requests.");
|
|
5886
5687
|
}
|
|
5887
|
-
const
|
|
5888
|
-
|
|
5688
|
+
const url = buildUrl(this.host, request.path, request.query);
|
|
5689
|
+
const response = await this.fetchFn(url, {
|
|
5690
|
+
method: request.method,
|
|
5889
5691
|
headers: {
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5692
|
+
Authorization: `Bearer ${this.personalApiKey}`,
|
|
5693
|
+
"Content-Type": "application/json",
|
|
5694
|
+
...request.headers ?? {}
|
|
5893
5695
|
},
|
|
5894
|
-
body: JSON.stringify(
|
|
5696
|
+
body: request.body ? JSON.stringify(request.body) : undefined
|
|
5895
5697
|
});
|
|
5896
|
-
const
|
|
5897
|
-
|
|
5898
|
-
throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
5899
|
-
}
|
|
5900
|
-
return {
|
|
5901
|
-
id: String(body.id),
|
|
5902
|
-
providerMessageId: body.node_id,
|
|
5903
|
-
status: "sent",
|
|
5904
|
-
sentAt: new Date,
|
|
5905
|
-
metadata: {
|
|
5906
|
-
url: body.html_url ?? "",
|
|
5907
|
-
owner,
|
|
5908
|
-
repo
|
|
5909
|
-
}
|
|
5910
|
-
};
|
|
5911
|
-
}
|
|
5912
|
-
resolveTarget(input) {
|
|
5913
|
-
const parsedRecipient = parseRecipient(input.recipientId);
|
|
5914
|
-
const owner = parsedRecipient?.owner ?? this.defaultOwner;
|
|
5915
|
-
const repo = parsedRecipient?.repo ?? this.defaultRepo;
|
|
5916
|
-
const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
|
|
5917
|
-
if (!owner || !repo || issueNumber == null) {
|
|
5918
|
-
throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
|
|
5919
|
-
}
|
|
5698
|
+
const text = await response.text();
|
|
5699
|
+
const data = parseJson(text);
|
|
5920
5700
|
return {
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5701
|
+
status: response.status,
|
|
5702
|
+
data,
|
|
5703
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
5924
5704
|
};
|
|
5925
5705
|
}
|
|
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.");
|
|
5706
|
+
async callMcpTool(call) {
|
|
5707
|
+
if (!this.mcpUrl) {
|
|
5708
|
+
throw new Error("PostHog MCP URL is not configured.");
|
|
5962
5709
|
}
|
|
5963
|
-
const response = await
|
|
5710
|
+
const response = await this.fetchFn(this.mcpUrl, {
|
|
5964
5711
|
method: "POST",
|
|
5965
5712
|
headers: {
|
|
5966
|
-
|
|
5967
|
-
"content-type": "application/json"
|
|
5713
|
+
"Content-Type": "application/json"
|
|
5968
5714
|
},
|
|
5969
5715
|
body: JSON.stringify({
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5716
|
+
jsonrpc: "2.0",
|
|
5717
|
+
id: 1,
|
|
5718
|
+
method: "tools/call",
|
|
5719
|
+
params: {
|
|
5720
|
+
name: call.name,
|
|
5721
|
+
arguments: call.arguments ?? {}
|
|
5976
5722
|
}
|
|
5977
5723
|
})
|
|
5978
5724
|
});
|
|
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})` : ""}`);
|
|
5725
|
+
if (!response.ok) {
|
|
5726
|
+
const body = await response.text();
|
|
5727
|
+
throw new Error(`PostHog MCP error (${response.status}): ${body}`);
|
|
5984
5728
|
}
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
metadata: {
|
|
5991
|
-
phoneNumberId: this.phoneNumberId
|
|
5992
|
-
}
|
|
5993
|
-
};
|
|
5729
|
+
const result = await response.json();
|
|
5730
|
+
if (result.error) {
|
|
5731
|
+
throw new Error(result.error.message ?? "PostHog MCP error");
|
|
5732
|
+
}
|
|
5733
|
+
return result.result ?? null;
|
|
5994
5734
|
}
|
|
5995
5735
|
}
|
|
5996
5736
|
|
|
5997
|
-
// src/impls/
|
|
5998
|
-
import {
|
|
5737
|
+
// src/impls/postmark-email.ts
|
|
5738
|
+
import { ServerClient } from "postmark";
|
|
5999
5739
|
|
|
6000
|
-
class
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
5740
|
+
class PostmarkEmailProvider {
|
|
5741
|
+
client;
|
|
5742
|
+
defaultFromEmail;
|
|
5743
|
+
messageStream;
|
|
6004
5744
|
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()
|
|
5745
|
+
this.client = options.client ?? new ServerClient(options.serverToken, {
|
|
5746
|
+
useHttps: true
|
|
6030
5747
|
});
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
5748
|
+
this.defaultFromEmail = options.defaultFromEmail;
|
|
5749
|
+
this.messageStream = options.messageStream;
|
|
5750
|
+
}
|
|
5751
|
+
async sendEmail(message) {
|
|
5752
|
+
const request = {
|
|
5753
|
+
From: formatAddress2(message.from) ?? this.defaultFromEmail,
|
|
5754
|
+
To: message.to.map((addr) => formatAddress2(addr)).join(", "),
|
|
5755
|
+
Cc: message.cc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
|
|
5756
|
+
Bcc: message.bcc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
|
|
5757
|
+
ReplyTo: message.replyTo ? formatAddress2(message.replyTo) : undefined,
|
|
5758
|
+
Subject: message.subject,
|
|
5759
|
+
TextBody: message.textBody,
|
|
5760
|
+
HtmlBody: message.htmlBody,
|
|
5761
|
+
Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
|
|
5762
|
+
Name: name,
|
|
5763
|
+
Value: value
|
|
5764
|
+
})) : undefined,
|
|
5765
|
+
MessageStream: this.messageStream,
|
|
5766
|
+
Attachments: buildAttachments(message)
|
|
5767
|
+
};
|
|
5768
|
+
const response = await this.client.sendEmail(request);
|
|
5769
|
+
return {
|
|
5770
|
+
id: response.MessageID,
|
|
5771
|
+
providerMessageId: response.MessageID,
|
|
5772
|
+
queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
|
|
6046
5773
|
};
|
|
6047
5774
|
}
|
|
6048
5775
|
}
|
|
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";
|
|
5776
|
+
function formatAddress2(address) {
|
|
5777
|
+
if (address.name) {
|
|
5778
|
+
return `"${address.name}" <${address.email}>`;
|
|
6073
5779
|
}
|
|
5780
|
+
return address.email;
|
|
5781
|
+
}
|
|
5782
|
+
function buildAttachments(message) {
|
|
5783
|
+
if (!message.attachments?.length)
|
|
5784
|
+
return;
|
|
5785
|
+
return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
|
|
5786
|
+
Name: attachment.filename,
|
|
5787
|
+
Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
|
|
5788
|
+
ContentType: attachment.contentType,
|
|
5789
|
+
ContentID: null,
|
|
5790
|
+
ContentLength: attachment.sizeBytes,
|
|
5791
|
+
Disposition: "attachment"
|
|
5792
|
+
}));
|
|
6074
5793
|
}
|
|
6075
5794
|
|
|
6076
5795
|
// src/impls/powens-client.ts
|
|
@@ -6495,367 +6214,629 @@ class PowensOpenBankingProvider {
|
|
|
6495
6214
|
this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
|
|
6496
6215
|
throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
|
|
6497
6216
|
}
|
|
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
|
-
|
|
6217
|
+
}
|
|
6218
|
+
|
|
6219
|
+
// src/impls/qdrant-vector.ts
|
|
6220
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
6221
|
+
|
|
6222
|
+
class QdrantVectorProvider {
|
|
6223
|
+
client;
|
|
6224
|
+
createCollectionIfMissing;
|
|
6225
|
+
distance;
|
|
6226
|
+
constructor(options) {
|
|
6227
|
+
this.client = options.client ?? new QdrantClient({
|
|
6228
|
+
url: options.url,
|
|
6229
|
+
apiKey: options.apiKey,
|
|
6230
|
+
...options.clientParams
|
|
6231
|
+
});
|
|
6232
|
+
this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
|
|
6233
|
+
this.distance = options.distance ?? "Cosine";
|
|
6234
|
+
}
|
|
6235
|
+
async upsert(request) {
|
|
6236
|
+
if (request.documents.length === 0)
|
|
6237
|
+
return;
|
|
6238
|
+
const firstDocument = request.documents[0];
|
|
6239
|
+
if (!firstDocument)
|
|
6240
|
+
return;
|
|
6241
|
+
const vectorSize = firstDocument.vector.length;
|
|
6242
|
+
if (this.createCollectionIfMissing) {
|
|
6243
|
+
await this.ensureCollection(request.collection, vectorSize);
|
|
6244
|
+
}
|
|
6245
|
+
const points = request.documents.map((document) => ({
|
|
6246
|
+
id: document.id,
|
|
6247
|
+
vector: document.vector,
|
|
6248
|
+
payload: {
|
|
6249
|
+
...document.payload,
|
|
6250
|
+
...document.namespace ? { namespace: document.namespace } : {},
|
|
6251
|
+
...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
|
|
6252
|
+
}
|
|
6253
|
+
}));
|
|
6254
|
+
await this.client.upsert(request.collection, {
|
|
6255
|
+
wait: true,
|
|
6256
|
+
points
|
|
6257
|
+
});
|
|
6258
|
+
}
|
|
6259
|
+
async search(query) {
|
|
6260
|
+
const results = await this.client.search(query.collection, {
|
|
6261
|
+
vector: query.vector,
|
|
6262
|
+
limit: query.topK,
|
|
6263
|
+
filter: query.filter,
|
|
6264
|
+
score_threshold: query.scoreThreshold,
|
|
6265
|
+
with_payload: true,
|
|
6266
|
+
with_vector: false
|
|
6267
|
+
});
|
|
6268
|
+
return results.map((item) => ({
|
|
6269
|
+
id: String(item.id),
|
|
6270
|
+
score: item.score,
|
|
6271
|
+
payload: item.payload ?? undefined,
|
|
6272
|
+
namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
|
|
6273
|
+
}));
|
|
6274
|
+
}
|
|
6275
|
+
async delete(request) {
|
|
6276
|
+
await this.client.delete(request.collection, {
|
|
6277
|
+
wait: true,
|
|
6278
|
+
points: request.ids
|
|
6279
|
+
});
|
|
6280
|
+
}
|
|
6281
|
+
async ensureCollection(collectionName, vectorSize) {
|
|
6282
|
+
try {
|
|
6283
|
+
await this.client.getCollection(collectionName);
|
|
6284
|
+
} catch (_error) {
|
|
6285
|
+
await this.client.createCollection(collectionName, {
|
|
6286
|
+
vectors: {
|
|
6287
|
+
size: vectorSize,
|
|
6288
|
+
distance: this.distance
|
|
6289
|
+
}
|
|
6290
|
+
});
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
6293
|
+
}
|
|
6294
|
+
|
|
6295
|
+
// src/impls/stripe-payments.ts
|
|
6296
|
+
import Stripe from "stripe";
|
|
6297
|
+
var API_VERSION = "2026-02-25.clover";
|
|
6298
|
+
|
|
6299
|
+
class StripePaymentsProvider {
|
|
6300
|
+
stripe;
|
|
6301
|
+
constructor(options) {
|
|
6302
|
+
this.stripe = options.stripe ?? new Stripe(options.apiKey, {
|
|
6303
|
+
apiVersion: API_VERSION
|
|
6304
|
+
});
|
|
6305
|
+
}
|
|
6306
|
+
async createCustomer(input) {
|
|
6307
|
+
const customer = await this.stripe.customers.create({
|
|
6308
|
+
email: input.email,
|
|
6309
|
+
name: input.name,
|
|
6310
|
+
description: input.description,
|
|
6311
|
+
metadata: input.metadata
|
|
6312
|
+
});
|
|
6313
|
+
return this.toCustomer(customer);
|
|
6314
|
+
}
|
|
6315
|
+
async getCustomer(customerId) {
|
|
6316
|
+
const customer = await this.stripe.customers.retrieve(customerId);
|
|
6317
|
+
if (customer.deleted)
|
|
6318
|
+
return null;
|
|
6319
|
+
return this.toCustomer(customer);
|
|
6320
|
+
}
|
|
6321
|
+
async createPaymentIntent(input) {
|
|
6322
|
+
const intent = await this.stripe.paymentIntents.create({
|
|
6323
|
+
amount: input.amount.amount,
|
|
6324
|
+
currency: input.amount.currency,
|
|
6325
|
+
customer: input.customerId,
|
|
6326
|
+
description: input.description,
|
|
6327
|
+
capture_method: input.captureMethod ?? "automatic",
|
|
6328
|
+
confirmation_method: input.confirmationMethod ?? "automatic",
|
|
6329
|
+
automatic_payment_methods: { enabled: true },
|
|
6330
|
+
metadata: input.metadata,
|
|
6331
|
+
return_url: input.returnUrl,
|
|
6332
|
+
statement_descriptor: input.statementDescriptor
|
|
6333
|
+
});
|
|
6334
|
+
return this.toPaymentIntent(intent);
|
|
6335
|
+
}
|
|
6336
|
+
async capturePayment(paymentIntentId, input) {
|
|
6337
|
+
const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
|
|
6338
|
+
return this.toPaymentIntent(intent);
|
|
6339
|
+
}
|
|
6340
|
+
async cancelPaymentIntent(paymentIntentId) {
|
|
6341
|
+
const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
|
|
6342
|
+
return this.toPaymentIntent(intent);
|
|
6343
|
+
}
|
|
6344
|
+
async refundPayment(input) {
|
|
6345
|
+
const refund = await this.stripe.refunds.create({
|
|
6346
|
+
payment_intent: input.paymentIntentId,
|
|
6347
|
+
amount: input.amount?.amount,
|
|
6348
|
+
reason: mapRefundReason(input.reason),
|
|
6349
|
+
metadata: input.metadata
|
|
6350
|
+
});
|
|
6351
|
+
const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
|
|
6352
|
+
return {
|
|
6353
|
+
id: refund.id,
|
|
6354
|
+
paymentIntentId,
|
|
6355
|
+
amount: {
|
|
6356
|
+
amount: refund.amount ?? 0,
|
|
6357
|
+
currency: refund.currency?.toUpperCase() ?? "USD"
|
|
6358
|
+
},
|
|
6359
|
+
status: mapRefundStatus(refund.status),
|
|
6360
|
+
reason: refund.reason ?? undefined,
|
|
6361
|
+
metadata: this.toMetadata(refund.metadata),
|
|
6362
|
+
createdAt: refund.created ? new Date(refund.created * 1000) : undefined
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6365
|
+
async listInvoices(query) {
|
|
6366
|
+
const requestedStatus = query?.status?.[0];
|
|
6367
|
+
const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
|
|
6368
|
+
const response = await this.stripe.invoices.list({
|
|
6369
|
+
customer: query?.customerId,
|
|
6370
|
+
status: stripeStatus,
|
|
6371
|
+
limit: query?.limit,
|
|
6372
|
+
starting_after: query?.startingAfter
|
|
6373
|
+
});
|
|
6374
|
+
return response.data.map((invoice) => this.toInvoice(invoice));
|
|
6375
|
+
}
|
|
6376
|
+
async listTransactions(query) {
|
|
6377
|
+
const response = await this.stripe.charges.list({
|
|
6378
|
+
customer: query?.customerId,
|
|
6379
|
+
payment_intent: query?.paymentIntentId,
|
|
6380
|
+
limit: query?.limit,
|
|
6381
|
+
starting_after: query?.startingAfter
|
|
6382
|
+
});
|
|
6383
|
+
return response.data.map((charge) => ({
|
|
6384
|
+
id: charge.id,
|
|
6385
|
+
paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
|
|
6386
|
+
amount: {
|
|
6387
|
+
amount: charge.amount,
|
|
6388
|
+
currency: charge.currency?.toUpperCase() ?? "USD"
|
|
6389
|
+
},
|
|
6390
|
+
type: "capture",
|
|
6391
|
+
status: mapChargeStatus(charge.status),
|
|
6392
|
+
description: charge.description ?? undefined,
|
|
6393
|
+
createdAt: new Date(charge.created * 1000),
|
|
6394
|
+
metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
|
|
6395
|
+
balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
|
|
6396
|
+
})
|
|
6397
|
+
}));
|
|
6398
|
+
}
|
|
6399
|
+
toCustomer(customer) {
|
|
6400
|
+
const metadata = this.toMetadata(customer.metadata);
|
|
6401
|
+
const updatedAtValue = metadata?.updatedAt;
|
|
6402
|
+
return {
|
|
6403
|
+
id: customer.id,
|
|
6404
|
+
email: customer.email ?? undefined,
|
|
6405
|
+
name: customer.name ?? undefined,
|
|
6406
|
+
metadata,
|
|
6407
|
+
createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
|
|
6408
|
+
updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
|
|
6409
|
+
};
|
|
6410
|
+
}
|
|
6411
|
+
toPaymentIntent(intent) {
|
|
6412
|
+
const metadata = this.toMetadata(intent.metadata);
|
|
6413
|
+
return {
|
|
6414
|
+
id: intent.id,
|
|
6415
|
+
amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
|
|
6416
|
+
status: mapPaymentIntentStatus(intent.status),
|
|
6417
|
+
customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
|
|
6418
|
+
description: intent.description ?? undefined,
|
|
6419
|
+
clientSecret: intent.client_secret ?? undefined,
|
|
6420
|
+
metadata,
|
|
6421
|
+
createdAt: new Date(intent.created * 1000),
|
|
6422
|
+
updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
|
|
6423
|
+
};
|
|
6424
|
+
}
|
|
6425
|
+
toInvoice(invoice) {
|
|
6426
|
+
const metadata = this.toMetadata(invoice.metadata);
|
|
6427
|
+
return {
|
|
6428
|
+
id: invoice.id,
|
|
6429
|
+
number: invoice.number ?? undefined,
|
|
6430
|
+
status: invoice.status ?? "draft",
|
|
6431
|
+
amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
|
|
6432
|
+
amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
|
|
6433
|
+
customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
|
|
6434
|
+
dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
|
|
6435
|
+
hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
|
|
6436
|
+
metadata,
|
|
6437
|
+
createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
|
|
6438
|
+
updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
|
|
6522
6439
|
};
|
|
6523
6440
|
}
|
|
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;
|
|
6441
|
+
toMoney(amount, currency) {
|
|
6542
6442
|
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
|
|
6443
|
+
amount,
|
|
6444
|
+
currency: currency?.toUpperCase() ?? "USD"
|
|
6552
6445
|
};
|
|
6553
6446
|
}
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6447
|
+
toMetadata(metadata) {
|
|
6448
|
+
if (!metadata)
|
|
6449
|
+
return;
|
|
6450
|
+
const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
|
|
6451
|
+
if (entries.length === 0)
|
|
6452
|
+
return;
|
|
6453
|
+
return Object.fromEntries(entries);
|
|
6454
|
+
}
|
|
6455
|
+
mergeMetadata(base, extras) {
|
|
6456
|
+
const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
|
|
6457
|
+
if (!base && filteredExtras.length === 0) {
|
|
6458
|
+
return;
|
|
6558
6459
|
}
|
|
6559
|
-
return
|
|
6460
|
+
return {
|
|
6461
|
+
...base ?? {},
|
|
6462
|
+
...Object.fromEntries(filteredExtras)
|
|
6463
|
+
};
|
|
6560
6464
|
}
|
|
6561
6465
|
}
|
|
6562
|
-
function
|
|
6563
|
-
if (!
|
|
6466
|
+
function mapRefundReason(reason) {
|
|
6467
|
+
if (!reason)
|
|
6564
6468
|
return;
|
|
6565
|
-
|
|
6469
|
+
const allowed = [
|
|
6470
|
+
"duplicate",
|
|
6471
|
+
"fraudulent",
|
|
6472
|
+
"requested_by_customer"
|
|
6473
|
+
];
|
|
6474
|
+
return allowed.includes(reason) ? reason : undefined;
|
|
6566
6475
|
}
|
|
6567
|
-
function
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6476
|
+
function mapPaymentIntentStatus(status) {
|
|
6477
|
+
switch (status) {
|
|
6478
|
+
case "requires_payment_method":
|
|
6479
|
+
return "requires_payment_method";
|
|
6480
|
+
case "requires_confirmation":
|
|
6481
|
+
return "requires_confirmation";
|
|
6482
|
+
case "requires_action":
|
|
6483
|
+
case "requires_capture":
|
|
6484
|
+
return "requires_action";
|
|
6485
|
+
case "processing":
|
|
6486
|
+
return "processing";
|
|
6487
|
+
case "succeeded":
|
|
6488
|
+
return "succeeded";
|
|
6489
|
+
case "canceled":
|
|
6490
|
+
return "canceled";
|
|
6491
|
+
default:
|
|
6492
|
+
return "requires_payment_method";
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
function mapRefundStatus(status) {
|
|
6496
|
+
switch (status) {
|
|
6497
|
+
case "pending":
|
|
6498
|
+
case "succeeded":
|
|
6499
|
+
case "failed":
|
|
6500
|
+
case "canceled":
|
|
6501
|
+
return status;
|
|
6502
|
+
default:
|
|
6503
|
+
return "pending";
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
function mapChargeStatus(status) {
|
|
6507
|
+
switch (status) {
|
|
6508
|
+
case "pending":
|
|
6509
|
+
case "processing":
|
|
6510
|
+
return "pending";
|
|
6511
|
+
case "succeeded":
|
|
6512
|
+
return "succeeded";
|
|
6513
|
+
case "failed":
|
|
6514
|
+
case "canceled":
|
|
6515
|
+
return "failed";
|
|
6516
|
+
default:
|
|
6517
|
+
return "pending";
|
|
6576
6518
|
}
|
|
6577
|
-
const merged = [...labelIds];
|
|
6578
|
-
return merged.length > 0 ? merged : undefined;
|
|
6579
6519
|
}
|
|
6580
6520
|
|
|
6581
|
-
// src/impls/
|
|
6521
|
+
// src/impls/supabase-psql.ts
|
|
6582
6522
|
import { Buffer as Buffer5 } from "buffer";
|
|
6523
|
+
import { sql as drizzleSql } from "drizzle-orm";
|
|
6524
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
6525
|
+
import postgres from "postgres";
|
|
6583
6526
|
|
|
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.");
|
|
6527
|
+
class SupabasePostgresProvider {
|
|
6528
|
+
client;
|
|
6529
|
+
db;
|
|
6530
|
+
ownsClient;
|
|
6531
|
+
createDrizzle;
|
|
6532
|
+
constructor(options = {}) {
|
|
6533
|
+
this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
|
|
6534
|
+
if (options.db) {
|
|
6535
|
+
if (!options.client) {
|
|
6536
|
+
throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
|
|
6537
|
+
}
|
|
6538
|
+
this.client = options.client;
|
|
6539
|
+
this.db = options.db;
|
|
6540
|
+
this.ownsClient = false;
|
|
6541
|
+
return;
|
|
6604
6542
|
}
|
|
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
|
|
6543
|
+
if (options.client) {
|
|
6544
|
+
this.client = options.client;
|
|
6545
|
+
this.ownsClient = false;
|
|
6546
|
+
} else {
|
|
6547
|
+
if (!options.connectionString) {
|
|
6548
|
+
throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
|
|
6619
6549
|
}
|
|
6550
|
+
this.client = postgres(options.connectionString, {
|
|
6551
|
+
max: options.maxConnections,
|
|
6552
|
+
prepare: false,
|
|
6553
|
+
ssl: resolveSslMode(options.sslMode)
|
|
6554
|
+
});
|
|
6555
|
+
this.ownsClient = true;
|
|
6556
|
+
}
|
|
6557
|
+
this.db = this.createDrizzle(this.client);
|
|
6558
|
+
}
|
|
6559
|
+
async query(statement, params = []) {
|
|
6560
|
+
const query = buildParameterizedSql(statement, params);
|
|
6561
|
+
const result = await this.db.execute(query);
|
|
6562
|
+
const rows = asRows(result);
|
|
6563
|
+
return {
|
|
6564
|
+
rows,
|
|
6565
|
+
rowCount: rows.length
|
|
6620
6566
|
};
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6567
|
+
}
|
|
6568
|
+
async execute(statement, params = []) {
|
|
6569
|
+
const query = buildParameterizedSql(statement, params);
|
|
6570
|
+
await this.db.execute(query);
|
|
6571
|
+
}
|
|
6572
|
+
async transaction(run) {
|
|
6573
|
+
const transactionResult = this.client.begin(async (transactionClient) => {
|
|
6574
|
+
const transactionalProvider = new SupabasePostgresProvider({
|
|
6575
|
+
client: transactionClient,
|
|
6576
|
+
db: this.createDrizzle(transactionClient),
|
|
6577
|
+
createDrizzle: this.createDrizzle
|
|
6578
|
+
});
|
|
6579
|
+
return run(transactionalProvider);
|
|
6629
6580
|
});
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6581
|
+
return transactionResult;
|
|
6582
|
+
}
|
|
6583
|
+
async close() {
|
|
6584
|
+
if (this.ownsClient) {
|
|
6585
|
+
await this.client.end({ timeout: 5 });
|
|
6633
6586
|
}
|
|
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
6587
|
}
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6588
|
+
}
|
|
6589
|
+
function buildParameterizedSql(statement, params) {
|
|
6590
|
+
const segments = [];
|
|
6591
|
+
const pattern = /\$(\d+)/g;
|
|
6592
|
+
let cursor = 0;
|
|
6593
|
+
for (const match of statement.matchAll(pattern)) {
|
|
6594
|
+
const token = match[0];
|
|
6595
|
+
const indexPart = match[1];
|
|
6596
|
+
const start = match.index;
|
|
6597
|
+
if (indexPart == null || start == null)
|
|
6598
|
+
continue;
|
|
6599
|
+
const parameterIndex = Number(indexPart) - 1;
|
|
6600
|
+
if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
|
|
6601
|
+
throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
|
|
6651
6602
|
}
|
|
6652
|
-
|
|
6603
|
+
const staticSegment = statement.slice(cursor, start);
|
|
6604
|
+
if (staticSegment.length > 0) {
|
|
6605
|
+
segments.push(drizzleSql.raw(staticSegment));
|
|
6606
|
+
}
|
|
6607
|
+
const parameterValue = params[parameterIndex];
|
|
6608
|
+
if (parameterValue === undefined) {
|
|
6609
|
+
throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
|
|
6610
|
+
}
|
|
6611
|
+
const normalizedValue = normalizeParam(parameterValue);
|
|
6612
|
+
segments.push(drizzleSql`${normalizedValue}`);
|
|
6613
|
+
cursor = start + token.length;
|
|
6614
|
+
}
|
|
6615
|
+
const tailSegment = statement.slice(cursor);
|
|
6616
|
+
if (tailSegment.length > 0) {
|
|
6617
|
+
segments.push(drizzleSql.raw(tailSegment));
|
|
6618
|
+
}
|
|
6619
|
+
if (segments.length === 0) {
|
|
6620
|
+
return drizzleSql.raw("");
|
|
6653
6621
|
}
|
|
6622
|
+
return drizzleSql.join(segments);
|
|
6654
6623
|
}
|
|
6655
|
-
function
|
|
6656
|
-
|
|
6624
|
+
function normalizeParam(value) {
|
|
6625
|
+
if (typeof value === "bigint") {
|
|
6626
|
+
return value.toString();
|
|
6627
|
+
}
|
|
6628
|
+
if (value instanceof Uint8Array) {
|
|
6629
|
+
return Buffer5.from(value);
|
|
6630
|
+
}
|
|
6631
|
+
if (isPlainObject(value)) {
|
|
6632
|
+
return JSON.stringify(value);
|
|
6633
|
+
}
|
|
6634
|
+
return value;
|
|
6657
6635
|
}
|
|
6658
|
-
function
|
|
6659
|
-
|
|
6660
|
-
|
|
6636
|
+
function asRows(result) {
|
|
6637
|
+
if (!Array.isArray(result)) {
|
|
6638
|
+
return [];
|
|
6639
|
+
}
|
|
6640
|
+
return result;
|
|
6661
6641
|
}
|
|
6662
|
-
function
|
|
6663
|
-
if (
|
|
6664
|
-
return
|
|
6642
|
+
function isPlainObject(value) {
|
|
6643
|
+
if (value == null || typeof value !== "object") {
|
|
6644
|
+
return false;
|
|
6665
6645
|
}
|
|
6666
|
-
|
|
6646
|
+
if (Array.isArray(value)) {
|
|
6647
|
+
return false;
|
|
6648
|
+
}
|
|
6649
|
+
if (value instanceof Date) {
|
|
6650
|
+
return false;
|
|
6651
|
+
}
|
|
6652
|
+
if (value instanceof Uint8Array) {
|
|
6653
|
+
return false;
|
|
6654
|
+
}
|
|
6655
|
+
return true;
|
|
6667
6656
|
}
|
|
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":
|
|
6657
|
+
function resolveSslMode(mode) {
|
|
6658
|
+
switch (mode) {
|
|
6659
|
+
case "allow":
|
|
6660
|
+
return false;
|
|
6661
|
+
case "prefer":
|
|
6662
|
+
return "prefer";
|
|
6663
|
+
case "require":
|
|
6679
6664
|
default:
|
|
6680
|
-
return;
|
|
6665
|
+
return "require";
|
|
6681
6666
|
}
|
|
6682
6667
|
}
|
|
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
6668
|
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6669
|
+
// src/impls/supabase-vector.ts
|
|
6670
|
+
class SupabaseVectorProvider {
|
|
6671
|
+
database;
|
|
6672
|
+
createTableIfMissing;
|
|
6673
|
+
distanceMetric;
|
|
6674
|
+
quotedSchema;
|
|
6675
|
+
qualifiedTable;
|
|
6676
|
+
collectionIndex;
|
|
6677
|
+
namespaceIndex;
|
|
6678
|
+
ensureTablePromise;
|
|
6709
6679
|
constructor(options) {
|
|
6710
|
-
this.
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
}
|
|
6680
|
+
this.database = options.database ?? new SupabasePostgresProvider({
|
|
6681
|
+
connectionString: options.connectionString,
|
|
6682
|
+
maxConnections: options.maxConnections,
|
|
6683
|
+
sslMode: options.sslMode
|
|
6684
|
+
});
|
|
6685
|
+
this.createTableIfMissing = options.createTableIfMissing ?? true;
|
|
6686
|
+
this.distanceMetric = options.distanceMetric ?? "cosine";
|
|
6687
|
+
const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
|
|
6688
|
+
const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
|
|
6689
|
+
this.quotedSchema = quoteIdentifier(schema);
|
|
6690
|
+
this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
|
|
6691
|
+
this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
|
|
6692
|
+
this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
|
|
6721
6693
|
}
|
|
6722
|
-
async
|
|
6723
|
-
if (
|
|
6724
|
-
return
|
|
6694
|
+
async upsert(request) {
|
|
6695
|
+
if (request.documents.length === 0) {
|
|
6696
|
+
return;
|
|
6725
6697
|
}
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6698
|
+
if (this.createTableIfMissing) {
|
|
6699
|
+
await this.ensureTable();
|
|
6700
|
+
}
|
|
6701
|
+
for (const document of request.documents) {
|
|
6702
|
+
await this.database.execute(`INSERT INTO ${this.qualifiedTable}
|
|
6703
|
+
(collection, id, embedding, payload, namespace, expires_at, updated_at)
|
|
6704
|
+
VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
|
|
6705
|
+
ON CONFLICT (collection, id)
|
|
6706
|
+
DO UPDATE SET
|
|
6707
|
+
embedding = EXCLUDED.embedding,
|
|
6708
|
+
payload = EXCLUDED.payload,
|
|
6709
|
+
namespace = EXCLUDED.namespace,
|
|
6710
|
+
expires_at = EXCLUDED.expires_at,
|
|
6711
|
+
updated_at = now();`, [
|
|
6712
|
+
request.collection,
|
|
6713
|
+
document.id,
|
|
6714
|
+
toVectorLiteral(document.vector),
|
|
6715
|
+
document.payload ? JSON.stringify(document.payload) : null,
|
|
6716
|
+
document.namespace ?? null,
|
|
6717
|
+
document.expiresAt ?? null
|
|
6718
|
+
]);
|
|
6729
6719
|
}
|
|
6730
|
-
const titleProperty = this.defaults.titleProperty ?? "Name";
|
|
6731
|
-
const properties = {
|
|
6732
|
-
[titleProperty]: buildTitleProperty(input.title)
|
|
6733
|
-
};
|
|
6734
|
-
applySelect(properties, this.defaults.statusProperty, input.status);
|
|
6735
|
-
applySelect(properties, this.defaults.priorityProperty, input.priority);
|
|
6736
|
-
applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
|
|
6737
|
-
applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
|
|
6738
|
-
applyRichText(properties, this.defaults.descriptionProperty, input.description);
|
|
6739
|
-
const page = await this.client.pages.create({
|
|
6740
|
-
parent: { type: "database_id", database_id: databaseId },
|
|
6741
|
-
properties
|
|
6742
|
-
});
|
|
6743
|
-
return {
|
|
6744
|
-
id: page.id,
|
|
6745
|
-
title: input.title,
|
|
6746
|
-
url: "url" in page ? page.url : undefined,
|
|
6747
|
-
status: input.status,
|
|
6748
|
-
priority: input.priority,
|
|
6749
|
-
tags: input.tags,
|
|
6750
|
-
projectId: databaseId,
|
|
6751
|
-
externalId: input.externalId,
|
|
6752
|
-
metadata: input.metadata
|
|
6753
|
-
};
|
|
6754
6720
|
}
|
|
6755
|
-
async
|
|
6756
|
-
const
|
|
6757
|
-
|
|
6758
|
-
|
|
6721
|
+
async search(query) {
|
|
6722
|
+
const operator = this.distanceOperator;
|
|
6723
|
+
const results = await this.database.query(`SELECT
|
|
6724
|
+
id,
|
|
6725
|
+
payload,
|
|
6726
|
+
namespace,
|
|
6727
|
+
(embedding ${operator} $3::vector) AS distance
|
|
6728
|
+
FROM ${this.qualifiedTable}
|
|
6729
|
+
WHERE collection = $1
|
|
6730
|
+
AND ($2::text IS NULL OR namespace = $2)
|
|
6731
|
+
AND (expires_at IS NULL OR expires_at > now())
|
|
6732
|
+
AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
|
|
6733
|
+
ORDER BY embedding ${operator} $3::vector
|
|
6734
|
+
LIMIT $5;`, [
|
|
6735
|
+
query.collection,
|
|
6736
|
+
query.namespace ?? null,
|
|
6737
|
+
toVectorLiteral(query.vector),
|
|
6738
|
+
query.filter ? JSON.stringify(query.filter) : null,
|
|
6739
|
+
query.topK
|
|
6740
|
+
]);
|
|
6741
|
+
const mapped = results.rows.map((row) => {
|
|
6742
|
+
const distance = Number(row.distance);
|
|
6743
|
+
return {
|
|
6744
|
+
id: row.id,
|
|
6745
|
+
score: distanceToScore(distance, this.distanceMetric),
|
|
6746
|
+
payload: isRecord(row.payload) ? row.payload : undefined,
|
|
6747
|
+
namespace: row.namespace ?? undefined
|
|
6748
|
+
};
|
|
6749
|
+
});
|
|
6750
|
+
const scoreThreshold = query.scoreThreshold;
|
|
6751
|
+
if (scoreThreshold == null) {
|
|
6752
|
+
return mapped;
|
|
6759
6753
|
}
|
|
6760
|
-
return
|
|
6754
|
+
return mapped.filter((result) => result.score >= scoreThreshold);
|
|
6761
6755
|
}
|
|
6762
|
-
async
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
throw new Error("Notion summaryParentPageId is required for summaries.");
|
|
6756
|
+
async delete(request) {
|
|
6757
|
+
if (request.ids.length === 0) {
|
|
6758
|
+
return;
|
|
6766
6759
|
}
|
|
6767
|
-
const
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6760
|
+
const params = [
|
|
6761
|
+
request.collection,
|
|
6762
|
+
request.ids,
|
|
6763
|
+
request.namespace ?? null
|
|
6764
|
+
];
|
|
6765
|
+
await this.database.execute(`DELETE FROM ${this.qualifiedTable}
|
|
6766
|
+
WHERE collection = $1
|
|
6767
|
+
AND id = ANY($2::text[])
|
|
6768
|
+
AND ($3::text IS NULL OR namespace = $3);`, params);
|
|
6769
|
+
}
|
|
6770
|
+
async ensureTable() {
|
|
6771
|
+
if (!this.ensureTablePromise) {
|
|
6772
|
+
this.ensureTablePromise = this.createTable();
|
|
6773
|
+
}
|
|
6774
|
+
await this.ensureTablePromise;
|
|
6775
|
+
}
|
|
6776
|
+
async createTable() {
|
|
6777
|
+
await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
6778
|
+
await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
|
|
6779
|
+
await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
|
|
6780
|
+
collection text NOT NULL,
|
|
6781
|
+
id text NOT NULL,
|
|
6782
|
+
embedding vector NOT NULL,
|
|
6783
|
+
payload jsonb,
|
|
6784
|
+
namespace text,
|
|
6785
|
+
expires_at timestamptz,
|
|
6786
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
6787
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
6788
|
+
PRIMARY KEY (collection, id)
|
|
6789
|
+
);`);
|
|
6790
|
+
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
|
|
6791
|
+
ON ${this.qualifiedTable} (collection);`);
|
|
6792
|
+
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
|
|
6793
|
+
ON ${this.qualifiedTable} (namespace);`);
|
|
6794
|
+
}
|
|
6795
|
+
get distanceOperator() {
|
|
6796
|
+
switch (this.distanceMetric) {
|
|
6797
|
+
case "l2":
|
|
6798
|
+
return "<->";
|
|
6799
|
+
case "inner_product":
|
|
6800
|
+
return "<#>";
|
|
6801
|
+
case "cosine":
|
|
6802
|
+
default:
|
|
6803
|
+
return "<=>";
|
|
6782
6804
|
}
|
|
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
6805
|
}
|
|
6795
6806
|
}
|
|
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;
|
|
6807
|
+
function sanitizeIdentifier(value, label) {
|
|
6808
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
6809
|
+
throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
|
|
6810
|
+
}
|
|
6811
|
+
return value;
|
|
6823
6812
|
}
|
|
6824
|
-
function
|
|
6825
|
-
|
|
6826
|
-
return;
|
|
6827
|
-
const next = {
|
|
6828
|
-
multi_select: values.map((value) => ({ name: value }))
|
|
6829
|
-
};
|
|
6830
|
-
properties[property] = next;
|
|
6813
|
+
function quoteIdentifier(value) {
|
|
6814
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
6831
6815
|
}
|
|
6832
|
-
function
|
|
6833
|
-
if (
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6816
|
+
function toVectorLiteral(vector) {
|
|
6817
|
+
if (vector.length === 0) {
|
|
6818
|
+
throw new Error("Supabase vectors must contain at least one dimension.");
|
|
6819
|
+
}
|
|
6820
|
+
for (const value of vector) {
|
|
6821
|
+
if (!Number.isFinite(value)) {
|
|
6822
|
+
throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
|
|
6823
|
+
}
|
|
6824
|
+
}
|
|
6825
|
+
return `[${vector.join(",")}]`;
|
|
6839
6826
|
}
|
|
6840
|
-
function
|
|
6841
|
-
|
|
6842
|
-
return;
|
|
6843
|
-
properties[property] = buildRichText(value);
|
|
6827
|
+
function isRecord(value) {
|
|
6828
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6844
6829
|
}
|
|
6845
|
-
function
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
}
|
|
6856
|
-
]
|
|
6857
|
-
}
|
|
6858
|
-
}));
|
|
6830
|
+
function distanceToScore(distance, metric) {
|
|
6831
|
+
switch (metric) {
|
|
6832
|
+
case "inner_product":
|
|
6833
|
+
return -distance;
|
|
6834
|
+
case "l2":
|
|
6835
|
+
return 1 / (1 + distance);
|
|
6836
|
+
case "cosine":
|
|
6837
|
+
default:
|
|
6838
|
+
return 1 - distance;
|
|
6839
|
+
}
|
|
6859
6840
|
}
|
|
6860
6841
|
|
|
6861
6842
|
// src/impls/tldv-meeting-recorder.ts
|
|
@@ -7001,11 +6982,74 @@ async function safeReadError4(response) {
|
|
|
7001
6982
|
}
|
|
7002
6983
|
}
|
|
7003
6984
|
|
|
6985
|
+
// src/impls/twilio-sms.ts
|
|
6986
|
+
import Twilio from "twilio";
|
|
6987
|
+
|
|
6988
|
+
class TwilioSmsProvider {
|
|
6989
|
+
client;
|
|
6990
|
+
fromNumber;
|
|
6991
|
+
constructor(options) {
|
|
6992
|
+
this.client = options.client ?? Twilio(options.accountSid, options.authToken);
|
|
6993
|
+
this.fromNumber = options.fromNumber;
|
|
6994
|
+
}
|
|
6995
|
+
async sendSms(input) {
|
|
6996
|
+
const message = await this.client.messages.create({
|
|
6997
|
+
to: input.to,
|
|
6998
|
+
from: input.from ?? this.fromNumber,
|
|
6999
|
+
body: input.body
|
|
7000
|
+
});
|
|
7001
|
+
return {
|
|
7002
|
+
id: message.sid,
|
|
7003
|
+
to: message.to ?? input.to,
|
|
7004
|
+
from: message.from ?? input.from ?? this.fromNumber ?? "",
|
|
7005
|
+
body: message.body ?? input.body,
|
|
7006
|
+
status: mapStatus(message.status),
|
|
7007
|
+
sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
|
|
7008
|
+
deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
|
|
7009
|
+
price: message.price ? Number(message.price) : undefined,
|
|
7010
|
+
priceCurrency: message.priceUnit ?? undefined,
|
|
7011
|
+
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
7012
|
+
errorMessage: message.errorMessage ?? undefined
|
|
7013
|
+
};
|
|
7014
|
+
}
|
|
7015
|
+
async getDeliveryStatus(messageId) {
|
|
7016
|
+
const message = await this.client.messages(messageId).fetch();
|
|
7017
|
+
return {
|
|
7018
|
+
status: mapStatus(message.status),
|
|
7019
|
+
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
7020
|
+
errorMessage: message.errorMessage ?? undefined,
|
|
7021
|
+
updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
|
|
7022
|
+
};
|
|
7023
|
+
}
|
|
7024
|
+
}
|
|
7025
|
+
function mapStatus(status) {
|
|
7026
|
+
switch (status) {
|
|
7027
|
+
case "queued":
|
|
7028
|
+
case "accepted":
|
|
7029
|
+
case "scheduled":
|
|
7030
|
+
return "queued";
|
|
7031
|
+
case "sending":
|
|
7032
|
+
case "processing":
|
|
7033
|
+
return "sending";
|
|
7034
|
+
case "sent":
|
|
7035
|
+
return "sent";
|
|
7036
|
+
case "delivered":
|
|
7037
|
+
return "delivered";
|
|
7038
|
+
case "undelivered":
|
|
7039
|
+
return "undelivered";
|
|
7040
|
+
case "failed":
|
|
7041
|
+
case "canceled":
|
|
7042
|
+
return "failed";
|
|
7043
|
+
default:
|
|
7044
|
+
return "queued";
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
|
|
7004
7048
|
// src/impls/provider-factory.ts
|
|
7005
7049
|
import { Buffer as Buffer6 } from "buffer";
|
|
7006
|
-
import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
|
|
7007
|
-
import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
|
|
7008
7050
|
import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
|
|
7051
|
+
import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
|
|
7052
|
+
import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
|
|
7009
7053
|
var SECRET_CACHE = new Map;
|
|
7010
7054
|
|
|
7011
7055
|
class IntegrationProviderFactory {
|
|
@@ -7089,6 +7133,12 @@ class IntegrationProviderFactory {
|
|
|
7089
7133
|
defaultRepo: config?.defaultRepo,
|
|
7090
7134
|
apiBaseUrl: config?.apiBaseUrl
|
|
7091
7135
|
});
|
|
7136
|
+
case "messaging.telegram":
|
|
7137
|
+
return new TelegramMessagingProvider({
|
|
7138
|
+
botToken: requireSecret(secrets, "botToken", "Telegram bot token is required"),
|
|
7139
|
+
defaultChatId: config?.defaultChatId,
|
|
7140
|
+
apiBaseUrl: config?.apiBaseUrl
|
|
7141
|
+
});
|
|
7092
7142
|
case "messaging.whatsapp.meta":
|
|
7093
7143
|
return new MetaWhatsappMessagingProvider({
|
|
7094
7144
|
accessToken: requireSecret(secrets, "accessToken", "Meta WhatsApp access token is required"),
|
|
@@ -7458,34 +7508,34 @@ function requireConfig(context, key, message) {
|
|
|
7458
7508
|
}
|
|
7459
7509
|
return value;
|
|
7460
7510
|
}
|
|
7461
|
-
// src/
|
|
7511
|
+
// src/llm.ts
|
|
7462
7512
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7463
7513
|
|
|
7464
|
-
// src/
|
|
7514
|
+
// src/meeting-recorder.ts
|
|
7465
7515
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7466
7516
|
|
|
7467
|
-
// src/
|
|
7517
|
+
// src/messaging.ts
|
|
7468
7518
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7469
7519
|
|
|
7470
|
-
// src/
|
|
7520
|
+
// src/openbanking.ts
|
|
7471
7521
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7472
7522
|
|
|
7473
|
-
// src/
|
|
7523
|
+
// src/payments.ts
|
|
7474
7524
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7475
7525
|
|
|
7476
|
-
// src/
|
|
7526
|
+
// src/project-management.ts
|
|
7477
7527
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7478
7528
|
|
|
7479
|
-
// src/
|
|
7529
|
+
// src/sms.ts
|
|
7480
7530
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7481
7531
|
|
|
7482
|
-
// src/
|
|
7532
|
+
// src/storage.ts
|
|
7483
7533
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7484
7534
|
|
|
7485
|
-
// src/
|
|
7535
|
+
// src/vector-store.ts
|
|
7486
7536
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7487
7537
|
|
|
7488
|
-
// src/
|
|
7538
|
+
// src/voice.ts
|
|
7489
7539
|
export * from "@contractspec/lib.contracts-integrations";
|
|
7490
7540
|
export {
|
|
7491
7541
|
resolveToolkit,
|
|
@@ -7496,6 +7546,7 @@ export {
|
|
|
7496
7546
|
TwilioWhatsappMessagingProvider,
|
|
7497
7547
|
TwilioSmsProvider,
|
|
7498
7548
|
TldvMeetingRecorderProvider,
|
|
7549
|
+
TelegramMessagingProvider,
|
|
7499
7550
|
SupabaseVectorProvider,
|
|
7500
7551
|
SupabasePostgresProvider,
|
|
7501
7552
|
StripePaymentsProvider,
|