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