@contractspec/integration.providers-impls 3.7.5 → 3.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -241
- package/dist/impls/composio-fallback-resolver.d.ts +4 -4
- package/dist/impls/composio-fallback-resolver.js +73 -73
- package/dist/impls/composio-mcp.d.ts +1 -1
- package/dist/impls/composio-proxies.d.ts +5 -5
- package/dist/impls/composio-sdk.d.ts +1 -1
- package/dist/impls/elevenlabs-voice.d.ts +1 -1
- package/dist/impls/fal-voice.d.ts +1 -1
- package/dist/impls/gcs-storage.d.ts +1 -1
- package/dist/impls/gmail-inbound.d.ts +1 -1
- package/dist/impls/gradium-voice.d.ts +2 -2
- package/dist/impls/health/base-health-provider.d.ts +1 -1
- package/dist/impls/health/official-health-providers.d.ts +1 -1
- package/dist/impls/health/providers.d.ts +1 -1
- package/dist/impls/health-provider-factory.d.ts +1 -1
- package/dist/impls/index.d.ts +30 -30
- package/dist/impls/index.js +2150 -2150
- package/dist/impls/mistral-conversational.d.ts +1 -1
- package/dist/impls/mistral-conversational.js +159 -159
- package/dist/impls/posthog-reader.d.ts +1 -1
- package/dist/impls/provider-factory.d.ts +11 -11
- package/dist/impls/provider-factory.js +2073 -2073
- package/dist/index.d.ts +12 -12
- package/dist/index.js +2057 -2057
- package/dist/node/impls/composio-fallback-resolver.js +73 -73
- package/dist/node/impls/index.js +2150 -2150
- package/dist/node/impls/mistral-conversational.js +159 -159
- package/dist/node/impls/provider-factory.js +2073 -2073
- package/dist/node/index.js +2057 -2057
- package/dist/node/secrets/provider.js +2 -2
- package/dist/secrets/provider.d.ts +2 -2
- package/dist/secrets/provider.js +2 -2
- package/package.json +13 -13
package/dist/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,519 @@ 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
|
-
};
|
|
4042
|
-
}
|
|
4043
|
-
async countTokens(_messages) {
|
|
4044
|
-
throw new Error("Mistral API does not currently support token counting");
|
|
4045
|
-
}
|
|
4046
|
-
buildChatRequest(messages, options) {
|
|
4047
|
-
const model = options.model ?? this.defaultModel;
|
|
4048
|
-
const mappedMessages = messages.map((message) => this.toMistralMessage(message));
|
|
4049
|
-
const request = {
|
|
4050
|
-
model,
|
|
4051
|
-
messages: mappedMessages
|
|
4052
|
-
};
|
|
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
|
-
}
|
|
4080
|
-
buildLLMResponse(response) {
|
|
4081
|
-
const firstChoice = response.choices[0];
|
|
4082
|
-
if (!firstChoice) {
|
|
4083
|
-
return {
|
|
4084
|
-
message: {
|
|
4085
|
-
role: "assistant",
|
|
4086
|
-
content: [{ type: "text", text: "" }]
|
|
4087
|
-
},
|
|
4088
|
-
usage: this.fromUsage(response.usage),
|
|
4089
|
-
raw: response
|
|
4090
|
-
};
|
|
4091
|
-
}
|
|
4092
|
-
const message = this.fromAssistantMessage(firstChoice.message);
|
|
4093
|
-
return {
|
|
4094
|
-
message,
|
|
4095
|
-
usage: this.fromUsage(response.usage),
|
|
4096
|
-
finishReason: mapFinishReason(firstChoice.finishReason),
|
|
4097
|
-
raw: response
|
|
4098
|
-
};
|
|
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";
|
|
4099
4029
|
}
|
|
4100
|
-
|
|
4101
|
-
|
|
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:
|
|
4102
4044
|
return;
|
|
4103
|
-
return {
|
|
4104
|
-
promptTokens: usage.promptTokens ?? 0,
|
|
4105
|
-
completionTokens: usage.completionTokens ?? 0,
|
|
4106
|
-
totalTokens: usage.totalTokens ?? 0
|
|
4107
|
-
};
|
|
4108
4045
|
}
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
}
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
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
|
|
4130
4089
|
};
|
|
4131
4090
|
}
|
|
4132
|
-
|
|
4133
|
-
const
|
|
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.");
|
|
4095
|
+
}
|
|
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;
|
|
4134
4109
|
return {
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
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
|
|
4139
4119
|
};
|
|
4140
4120
|
}
|
|
4141
|
-
|
|
4142
|
-
const
|
|
4143
|
-
const
|
|
4144
|
-
|
|
4145
|
-
case "system":
|
|
4146
|
-
return {
|
|
4147
|
-
role: "system",
|
|
4148
|
-
content: textContent ?? ""
|
|
4149
|
-
};
|
|
4150
|
-
case "user":
|
|
4151
|
-
return {
|
|
4152
|
-
role: "user",
|
|
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
|
-
};
|
|
4121
|
+
async createWorkItems(items) {
|
|
4122
|
+
const created = [];
|
|
4123
|
+
for (const item of items) {
|
|
4124
|
+
created.push(await this.createWorkItem(item));
|
|
4172
4125
|
}
|
|
4173
|
-
|
|
4174
|
-
extractText(parts) {
|
|
4175
|
-
const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
|
|
4176
|
-
if (textParts.length === 0)
|
|
4177
|
-
return null;
|
|
4178
|
-
return textParts.join("");
|
|
4179
|
-
}
|
|
4180
|
-
extractToolCalls(message) {
|
|
4181
|
-
const toolCallParts = message.content.filter((part) => part.type === "tool-call");
|
|
4182
|
-
return toolCallParts.map((call, index) => ({
|
|
4183
|
-
id: call.id ?? `call_${index}`,
|
|
4184
|
-
type: "function",
|
|
4185
|
-
index,
|
|
4186
|
-
function: {
|
|
4187
|
-
name: call.name,
|
|
4188
|
-
arguments: call.arguments
|
|
4189
|
-
}
|
|
4190
|
-
}));
|
|
4126
|
+
return created;
|
|
4191
4127
|
}
|
|
4192
4128
|
}
|
|
4193
|
-
function
|
|
4194
|
-
if (!
|
|
4129
|
+
function mapPriority2(priority) {
|
|
4130
|
+
if (!priority)
|
|
4195
4131
|
return;
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
default:
|
|
4208
|
-
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
|
+
});
|
|
4209
4143
|
}
|
|
4144
|
+
const merged = [...labelIds];
|
|
4145
|
+
return merged.length > 0 ? merged : undefined;
|
|
4210
4146
|
}
|
|
4211
4147
|
|
|
4212
|
-
// src/impls/
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4148
|
+
// src/impls/messaging-github.ts
|
|
4149
|
+
class GithubMessagingProvider {
|
|
4150
|
+
token;
|
|
4151
|
+
defaultOwner;
|
|
4152
|
+
defaultRepo;
|
|
4153
|
+
apiBaseUrl;
|
|
4218
4154
|
constructor(options) {
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
this.
|
|
4223
|
-
|
|
4224
|
-
|
|
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 })
|
|
4225
4170
|
});
|
|
4226
|
-
|
|
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
|
+
}
|
|
4175
|
+
return {
|
|
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
|
+
}
|
|
4186
|
+
};
|
|
4227
4187
|
}
|
|
4228
|
-
async
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
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.");
|
|
4193
|
+
}
|
|
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 })
|
|
4235
4202
|
});
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4203
|
+
const body = await response.json();
|
|
4204
|
+
if (!response.ok || !body.id) {
|
|
4205
|
+
throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
4206
|
+
}
|
|
4207
|
+
return {
|
|
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
|
+
}
|
|
4217
|
+
};
|
|
4243
4218
|
}
|
|
4244
|
-
|
|
4245
|
-
const
|
|
4246
|
-
|
|
4247
|
-
|
|
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).");
|
|
4248
4226
|
}
|
|
4249
|
-
return
|
|
4227
|
+
return {
|
|
4228
|
+
owner,
|
|
4229
|
+
repo,
|
|
4230
|
+
issueNumber
|
|
4231
|
+
};
|
|
4250
4232
|
}
|
|
4251
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;
|
|
4245
|
+
}
|
|
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
|
+
}
|
|
4252
4254
|
|
|
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;
|
|
4255
|
+
// src/impls/messaging-slack.ts
|
|
4256
|
+
class SlackMessagingProvider {
|
|
4257
|
+
botToken;
|
|
4258
|
+
defaultChannelId;
|
|
4259
|
+
apiBaseUrl;
|
|
4270
4260
|
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;
|
|
4261
|
+
this.botToken = options.botToken;
|
|
4262
|
+
this.defaultChannelId = options.defaultChannelId;
|
|
4263
|
+
this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
|
|
4279
4264
|
}
|
|
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);
|
|
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.");
|
|
4293
4269
|
}
|
|
4294
|
-
const
|
|
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`, {
|
|
4295
4277
|
method: "POST",
|
|
4296
4278
|
headers: {
|
|
4297
|
-
|
|
4279
|
+
authorization: `Bearer ${this.botToken}`,
|
|
4280
|
+
"content-type": "application/json"
|
|
4298
4281
|
},
|
|
4299
|
-
body:
|
|
4282
|
+
body: JSON.stringify(payload)
|
|
4300
4283
|
});
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
throw new Error(`
|
|
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}`}`);
|
|
4304
4287
|
}
|
|
4305
|
-
|
|
4306
|
-
|
|
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
|
+
};
|
|
4307
4297
|
}
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
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
|
+
})
|
|
4319
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
|
|
4327
|
+
}
|
|
4328
|
+
};
|
|
4320
4329
|
}
|
|
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
4330
|
}
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4331
|
+
|
|
4332
|
+
// src/impls/messaging-whatsapp-meta.ts
|
|
4333
|
+
class MetaWhatsappMessagingProvider {
|
|
4334
|
+
accessToken;
|
|
4335
|
+
phoneNumberId;
|
|
4336
|
+
apiVersion;
|
|
4337
|
+
constructor(options) {
|
|
4338
|
+
this.accessToken = options.accessToken;
|
|
4339
|
+
this.phoneNumberId = options.phoneNumberId;
|
|
4340
|
+
this.apiVersion = options.apiVersion ?? "v22.0";
|
|
4338
4341
|
}
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
if (!text) {
|
|
4344
|
-
continue;
|
|
4342
|
+
async sendMessage(input) {
|
|
4343
|
+
const to = input.recipientId;
|
|
4344
|
+
if (!to) {
|
|
4345
|
+
throw new Error("Meta WhatsApp sendMessage requires recipientId.");
|
|
4345
4346
|
}
|
|
4346
|
-
const
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4347
|
+
const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
|
|
4348
|
+
method: "POST",
|
|
4349
|
+
headers: {
|
|
4350
|
+
authorization: `Bearer ${this.accessToken}`,
|
|
4351
|
+
"content-type": "application/json"
|
|
4352
|
+
},
|
|
4353
|
+
body: JSON.stringify({
|
|
4354
|
+
messaging_product: "whatsapp",
|
|
4355
|
+
to,
|
|
4356
|
+
type: "text",
|
|
4357
|
+
text: {
|
|
4358
|
+
body: input.text,
|
|
4359
|
+
preview_url: false
|
|
4360
|
+
}
|
|
4361
|
+
})
|
|
4355
4362
|
});
|
|
4363
|
+
const body = await response.json();
|
|
4364
|
+
const messageId = body.messages?.[0]?.id;
|
|
4365
|
+
if (!response.ok || !messageId) {
|
|
4366
|
+
const errorCode = body.error?.code != null ? String(body.error.code) : "";
|
|
4367
|
+
throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
|
|
4368
|
+
}
|
|
4369
|
+
return {
|
|
4370
|
+
id: messageId,
|
|
4371
|
+
providerMessageId: messageId,
|
|
4372
|
+
status: "sent",
|
|
4373
|
+
sentAt: new Date,
|
|
4374
|
+
metadata: {
|
|
4375
|
+
phoneNumberId: this.phoneNumberId
|
|
4376
|
+
}
|
|
4377
|
+
};
|
|
4356
4378
|
}
|
|
4357
|
-
return parsed;
|
|
4358
4379
|
}
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4380
|
+
|
|
4381
|
+
// src/impls/messaging-whatsapp-twilio.ts
|
|
4382
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
4383
|
+
|
|
4384
|
+
class TwilioWhatsappMessagingProvider {
|
|
4385
|
+
accountSid;
|
|
4386
|
+
authToken;
|
|
4387
|
+
fromNumber;
|
|
4388
|
+
constructor(options) {
|
|
4389
|
+
this.accountSid = options.accountSid;
|
|
4390
|
+
this.authToken = options.authToken;
|
|
4391
|
+
this.fromNumber = options.fromNumber;
|
|
4362
4392
|
}
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
const
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
const endSeconds = readNumber2(wordRecord, "end");
|
|
4369
|
-
if (!word || startSeconds == null || endSeconds == null) {
|
|
4370
|
-
continue;
|
|
4393
|
+
async sendMessage(input) {
|
|
4394
|
+
const to = normalizeWhatsappAddress(input.recipientId);
|
|
4395
|
+
const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
|
|
4396
|
+
if (!to) {
|
|
4397
|
+
throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
|
|
4371
4398
|
}
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
startMs: secondsToMs(startSeconds),
|
|
4375
|
-
endMs: secondsToMs(endSeconds),
|
|
4376
|
-
confidence: readNumber2(wordRecord, "confidence")
|
|
4377
|
-
});
|
|
4378
|
-
}
|
|
4379
|
-
return words;
|
|
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;
|
|
4399
|
+
if (!from) {
|
|
4400
|
+
throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
|
|
4387
4401
|
}
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4402
|
+
const params = new URLSearchParams;
|
|
4403
|
+
params.set("To", to);
|
|
4404
|
+
params.set("From", from);
|
|
4405
|
+
params.set("Body", input.text);
|
|
4406
|
+
const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
|
|
4407
|
+
const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
|
|
4408
|
+
method: "POST",
|
|
4409
|
+
headers: {
|
|
4410
|
+
authorization: `Basic ${auth}`,
|
|
4411
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
4412
|
+
},
|
|
4413
|
+
body: params.toString()
|
|
4392
4414
|
});
|
|
4415
|
+
const body = await response.json();
|
|
4416
|
+
if (!response.ok || !body.sid) {
|
|
4417
|
+
throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
|
|
4418
|
+
}
|
|
4419
|
+
return {
|
|
4420
|
+
id: body.sid,
|
|
4421
|
+
providerMessageId: body.sid,
|
|
4422
|
+
status: mapTwilioStatus(body.status),
|
|
4423
|
+
sentAt: new Date,
|
|
4424
|
+
errorCode: body.error_code != null ? String(body.error_code) : undefined,
|
|
4425
|
+
errorMessage: body.error_message ?? undefined,
|
|
4426
|
+
metadata: {
|
|
4427
|
+
from,
|
|
4428
|
+
to
|
|
4429
|
+
}
|
|
4430
|
+
};
|
|
4393
4431
|
}
|
|
4394
|
-
return speakers;
|
|
4395
4432
|
}
|
|
4396
|
-
function
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
if (value && typeof value === "object") {
|
|
4433
|
+
function normalizeWhatsappAddress(value) {
|
|
4434
|
+
if (!value)
|
|
4435
|
+
return null;
|
|
4436
|
+
if (value.startsWith("whatsapp:"))
|
|
4401
4437
|
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;
|
|
4438
|
+
return `whatsapp:${value}`;
|
|
4412
4439
|
}
|
|
4413
|
-
function
|
|
4414
|
-
|
|
4440
|
+
function mapTwilioStatus(status) {
|
|
4441
|
+
switch (status) {
|
|
4442
|
+
case "queued":
|
|
4443
|
+
case "accepted":
|
|
4444
|
+
case "scheduled":
|
|
4445
|
+
return "queued";
|
|
4446
|
+
case "sending":
|
|
4447
|
+
return "sending";
|
|
4448
|
+
case "delivered":
|
|
4449
|
+
return "delivered";
|
|
4450
|
+
case "failed":
|
|
4451
|
+
case "undelivered":
|
|
4452
|
+
case "canceled":
|
|
4453
|
+
return "failed";
|
|
4454
|
+
case "sent":
|
|
4455
|
+
default:
|
|
4456
|
+
return "sent";
|
|
4457
|
+
}
|
|
4415
4458
|
}
|
|
4416
4459
|
|
|
4417
4460
|
// src/impls/mistral-conversational.session.ts
|
|
@@ -4487,876 +4530,825 @@ class MistralConversationSession {
|
|
|
4487
4530
|
return this.closedSummary;
|
|
4488
4531
|
}
|
|
4489
4532
|
this.closed = true;
|
|
4490
|
-
await this.pending;
|
|
4491
|
-
const durationMs = Date.now() - this.startedAt;
|
|
4492
|
-
const summary = {
|
|
4493
|
-
sessionId: this.sessionId,
|
|
4494
|
-
durationMs,
|
|
4495
|
-
turns: this.turns.map((turn) => ({
|
|
4496
|
-
role: turn.role === "assistant" ? "agent" : turn.role,
|
|
4497
|
-
text: turn.text,
|
|
4498
|
-
startMs: turn.startMs,
|
|
4499
|
-
endMs: turn.endMs
|
|
4500
|
-
})),
|
|
4501
|
-
transcript: this.turns.map((turn) => `${turn.role}: ${turn.text}`).join(`
|
|
4502
|
-
`)
|
|
4503
|
-
};
|
|
4504
|
-
this.closedSummary = summary;
|
|
4505
|
-
this.queue.push({
|
|
4506
|
-
type: "session_ended",
|
|
4507
|
-
reason: "closed_by_client",
|
|
4508
|
-
durationMs
|
|
4509
|
-
});
|
|
4510
|
-
this.queue.close();
|
|
4511
|
-
return summary;
|
|
4512
|
-
}
|
|
4513
|
-
async handleUserText(text) {
|
|
4514
|
-
if (this.closed) {
|
|
4515
|
-
return;
|
|
4516
|
-
}
|
|
4517
|
-
const userStart = Date.now();
|
|
4518
|
-
this.queue.push({ type: "user_speech_started" });
|
|
4519
|
-
this.queue.push({ type: "user_speech_ended", transcript: text });
|
|
4520
|
-
this.queue.push({
|
|
4521
|
-
type: "transcript",
|
|
4522
|
-
role: "user",
|
|
4523
|
-
text,
|
|
4524
|
-
timestamp: userStart
|
|
4525
|
-
});
|
|
4526
|
-
this.turns.push({
|
|
4527
|
-
role: "user",
|
|
4528
|
-
text,
|
|
4529
|
-
startMs: userStart,
|
|
4530
|
-
endMs: Date.now()
|
|
4531
|
-
});
|
|
4532
|
-
this.history.push({ role: "user", content: text });
|
|
4533
|
-
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
|
|
4533
|
+
await this.pending;
|
|
4534
|
+
const durationMs = Date.now() - this.startedAt;
|
|
4535
|
+
const summary = {
|
|
4536
|
+
sessionId: this.sessionId,
|
|
4537
|
+
durationMs,
|
|
4538
|
+
turns: this.turns.map((turn) => ({
|
|
4539
|
+
role: turn.role === "assistant" ? "agent" : turn.role,
|
|
4540
|
+
text: turn.text,
|
|
4541
|
+
startMs: turn.startMs,
|
|
4542
|
+
endMs: turn.endMs
|
|
4543
|
+
})),
|
|
4544
|
+
transcript: this.turns.map((turn) => `${turn.role}: ${turn.text}`).join(`
|
|
4545
|
+
`)
|
|
4546
|
+
};
|
|
4547
|
+
this.closedSummary = summary;
|
|
4548
|
+
this.queue.push({
|
|
4549
|
+
type: "session_ended",
|
|
4550
|
+
reason: "closed_by_client",
|
|
4551
|
+
durationMs
|
|
4694
4552
|
});
|
|
4695
|
-
this.
|
|
4696
|
-
|
|
4553
|
+
this.queue.close();
|
|
4554
|
+
return summary;
|
|
4697
4555
|
}
|
|
4698
|
-
async
|
|
4699
|
-
if (
|
|
4556
|
+
async handleUserText(text) {
|
|
4557
|
+
if (this.closed) {
|
|
4700
4558
|
return;
|
|
4701
|
-
|
|
4702
|
-
|
|
4559
|
+
}
|
|
4560
|
+
const userStart = Date.now();
|
|
4561
|
+
this.queue.push({ type: "user_speech_started" });
|
|
4562
|
+
this.queue.push({ type: "user_speech_ended", transcript: text });
|
|
4563
|
+
this.queue.push({
|
|
4564
|
+
type: "transcript",
|
|
4565
|
+
role: "user",
|
|
4566
|
+
text,
|
|
4567
|
+
timestamp: userStart
|
|
4568
|
+
});
|
|
4569
|
+
this.turns.push({
|
|
4570
|
+
role: "user",
|
|
4571
|
+
text,
|
|
4572
|
+
startMs: userStart,
|
|
4573
|
+
endMs: Date.now()
|
|
4574
|
+
});
|
|
4575
|
+
this.history.push({ role: "user", content: text });
|
|
4576
|
+
const assistantStart = Date.now();
|
|
4577
|
+
const assistantText = await this.complete(this.history, {
|
|
4578
|
+
...this.sessionConfig,
|
|
4579
|
+
llmModel: this.sessionConfig.llmModel ?? this.defaultModel
|
|
4580
|
+
});
|
|
4581
|
+
if (this.closed) {
|
|
4703
4582
|
return;
|
|
4704
|
-
const vectorSize = firstDocument.vector.length;
|
|
4705
|
-
if (this.createCollectionIfMissing) {
|
|
4706
|
-
await this.ensureCollection(request.collection, vectorSize);
|
|
4707
4583
|
}
|
|
4708
|
-
const
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
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
|
|
4584
|
+
const normalizedAssistantText = assistantText.trim();
|
|
4585
|
+
const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
|
|
4586
|
+
this.queue.push({
|
|
4587
|
+
type: "agent_speech_started",
|
|
4588
|
+
text: finalAssistantText
|
|
4720
4589
|
});
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
filter: query.filter,
|
|
4727
|
-
score_threshold: query.scoreThreshold,
|
|
4728
|
-
with_payload: true,
|
|
4729
|
-
with_vector: false
|
|
4590
|
+
this.queue.push({
|
|
4591
|
+
type: "transcript",
|
|
4592
|
+
role: "agent",
|
|
4593
|
+
text: finalAssistantText,
|
|
4594
|
+
timestamp: assistantStart
|
|
4730
4595
|
});
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
}
|
|
4738
|
-
async delete(request) {
|
|
4739
|
-
await this.client.delete(request.collection, {
|
|
4740
|
-
wait: true,
|
|
4741
|
-
points: request.ids
|
|
4596
|
+
this.queue.push({ type: "agent_speech_ended" });
|
|
4597
|
+
this.turns.push({
|
|
4598
|
+
role: "assistant",
|
|
4599
|
+
text: finalAssistantText,
|
|
4600
|
+
startMs: assistantStart,
|
|
4601
|
+
endMs: Date.now()
|
|
4742
4602
|
});
|
|
4603
|
+
this.history.push({ role: "assistant", content: finalAssistantText });
|
|
4743
4604
|
}
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
} catch (_error) {
|
|
4748
|
-
await this.client.createCollection(collectionName, {
|
|
4749
|
-
vectors: {
|
|
4750
|
-
size: vectorSize,
|
|
4751
|
-
distance: this.distance
|
|
4752
|
-
}
|
|
4753
|
-
});
|
|
4605
|
+
emitError(error) {
|
|
4606
|
+
if (this.closed) {
|
|
4607
|
+
return;
|
|
4754
4608
|
}
|
|
4609
|
+
this.queue.push({ type: "error", error: toError(error) });
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
function toError(error) {
|
|
4613
|
+
if (error instanceof Error) {
|
|
4614
|
+
return error;
|
|
4755
4615
|
}
|
|
4616
|
+
return new Error(String(error));
|
|
4756
4617
|
}
|
|
4757
4618
|
|
|
4758
|
-
// src/impls/
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4619
|
+
// src/impls/mistral-stt.ts
|
|
4620
|
+
var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
|
|
4621
|
+
var DEFAULT_MODEL = "voxtral-mini-latest";
|
|
4622
|
+
var AUDIO_MIME_BY_FORMAT = {
|
|
4623
|
+
mp3: "audio/mpeg",
|
|
4624
|
+
wav: "audio/wav",
|
|
4625
|
+
ogg: "audio/ogg",
|
|
4626
|
+
pcm: "audio/pcm",
|
|
4627
|
+
opus: "audio/opus"
|
|
4628
|
+
};
|
|
4763
4629
|
|
|
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;
|
|
4630
|
+
class MistralSttProvider {
|
|
4631
|
+
apiKey;
|
|
4632
|
+
defaultModel;
|
|
4633
|
+
defaultLanguage;
|
|
4634
|
+
baseUrl;
|
|
4635
|
+
fetchImpl;
|
|
4636
|
+
constructor(options) {
|
|
4637
|
+
if (!options.apiKey) {
|
|
4638
|
+
throw new Error("MistralSttProvider requires an apiKey");
|
|
4793
4639
|
}
|
|
4794
|
-
this.
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
const rows = asRows(result);
|
|
4800
|
-
return {
|
|
4801
|
-
rows,
|
|
4802
|
-
rowCount: rows.length
|
|
4803
|
-
};
|
|
4640
|
+
this.apiKey = options.apiKey;
|
|
4641
|
+
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
|
|
4642
|
+
this.defaultLanguage = options.defaultLanguage;
|
|
4643
|
+
this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
|
|
4644
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
4804
4645
|
}
|
|
4805
|
-
async
|
|
4806
|
-
const
|
|
4807
|
-
|
|
4646
|
+
async transcribe(input) {
|
|
4647
|
+
const formData = new FormData;
|
|
4648
|
+
const model = input.model ?? this.defaultModel;
|
|
4649
|
+
const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
|
|
4650
|
+
const fileName = `audio.${input.audio.format}`;
|
|
4651
|
+
const audioBytes = new Uint8Array(input.audio.data);
|
|
4652
|
+
const blob = new Blob([audioBytes], { type: mimeType });
|
|
4653
|
+
formData.append("file", blob, fileName);
|
|
4654
|
+
formData.append("model", model);
|
|
4655
|
+
formData.append("response_format", "verbose_json");
|
|
4656
|
+
const language = input.language ?? this.defaultLanguage;
|
|
4657
|
+
if (language) {
|
|
4658
|
+
formData.append("language", language);
|
|
4659
|
+
}
|
|
4660
|
+
const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
|
|
4661
|
+
method: "POST",
|
|
4662
|
+
headers: {
|
|
4663
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
4664
|
+
},
|
|
4665
|
+
body: formData
|
|
4666
|
+
});
|
|
4667
|
+
if (!response.ok) {
|
|
4668
|
+
const body = await response.text();
|
|
4669
|
+
throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
|
|
4670
|
+
}
|
|
4671
|
+
const payload = await response.json();
|
|
4672
|
+
return toTranscriptionResult(payload, input);
|
|
4808
4673
|
}
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4674
|
+
}
|
|
4675
|
+
function toTranscriptionResult(payload, input) {
|
|
4676
|
+
const record = asRecord2(payload);
|
|
4677
|
+
const text = readString3(record, "text") ?? "";
|
|
4678
|
+
const language = readString3(record, "language") ?? input.language ?? "unknown";
|
|
4679
|
+
const segments = parseSegments(record);
|
|
4680
|
+
if (segments.length === 0 && text.length > 0) {
|
|
4681
|
+
segments.push({
|
|
4682
|
+
text,
|
|
4683
|
+
startMs: 0,
|
|
4684
|
+
endMs: input.audio.durationMs ?? 0
|
|
4817
4685
|
});
|
|
4818
|
-
return transactionResult;
|
|
4819
4686
|
}
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4687
|
+
const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
|
|
4688
|
+
const topLevelWords = parseWordTimings(record.words);
|
|
4689
|
+
const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
|
|
4690
|
+
const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
|
|
4691
|
+
const speakers = dedupeSpeakers(segments);
|
|
4692
|
+
return {
|
|
4693
|
+
text,
|
|
4694
|
+
segments,
|
|
4695
|
+
language,
|
|
4696
|
+
durationMs,
|
|
4697
|
+
speakers: speakers.length > 0 ? speakers : undefined,
|
|
4698
|
+
wordTimings
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
function parseSegments(record) {
|
|
4702
|
+
if (!Array.isArray(record.segments)) {
|
|
4703
|
+
return [];
|
|
4704
|
+
}
|
|
4705
|
+
const parsed = [];
|
|
4706
|
+
for (const entry of record.segments) {
|
|
4707
|
+
const segmentRecord = asRecord2(entry);
|
|
4708
|
+
const text = readString3(segmentRecord, "text");
|
|
4709
|
+
if (!text) {
|
|
4710
|
+
continue;
|
|
4823
4711
|
}
|
|
4712
|
+
const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
|
|
4713
|
+
const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
|
|
4714
|
+
parsed.push({
|
|
4715
|
+
text,
|
|
4716
|
+
startMs: secondsToMs(startSeconds),
|
|
4717
|
+
endMs: secondsToMs(endSeconds),
|
|
4718
|
+
speakerId: readString3(segmentRecord, "speaker") ?? undefined,
|
|
4719
|
+
confidence: readNumber2(segmentRecord, "confidence"),
|
|
4720
|
+
wordTimings: parseWordTimings(segmentRecord.words)
|
|
4721
|
+
});
|
|
4824
4722
|
}
|
|
4723
|
+
return parsed;
|
|
4825
4724
|
}
|
|
4826
|
-
function
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
const
|
|
4833
|
-
const
|
|
4834
|
-
|
|
4725
|
+
function parseWordTimings(value) {
|
|
4726
|
+
if (!Array.isArray(value)) {
|
|
4727
|
+
return [];
|
|
4728
|
+
}
|
|
4729
|
+
const words = [];
|
|
4730
|
+
for (const entry of value) {
|
|
4731
|
+
const wordRecord = asRecord2(entry);
|
|
4732
|
+
const word = readString3(wordRecord, "word");
|
|
4733
|
+
const startSeconds = readNumber2(wordRecord, "start");
|
|
4734
|
+
const endSeconds = readNumber2(wordRecord, "end");
|
|
4735
|
+
if (!word || startSeconds == null || endSeconds == null) {
|
|
4835
4736
|
continue;
|
|
4836
|
-
const parameterIndex = Number(indexPart) - 1;
|
|
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
4737
|
}
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
segments.push(drizzleSql.raw(tailSegment));
|
|
4855
|
-
}
|
|
4856
|
-
if (segments.length === 0) {
|
|
4857
|
-
return drizzleSql.raw("");
|
|
4738
|
+
words.push({
|
|
4739
|
+
word,
|
|
4740
|
+
startMs: secondsToMs(startSeconds),
|
|
4741
|
+
endMs: secondsToMs(endSeconds),
|
|
4742
|
+
confidence: readNumber2(wordRecord, "confidence")
|
|
4743
|
+
});
|
|
4858
4744
|
}
|
|
4859
|
-
return
|
|
4745
|
+
return words;
|
|
4860
4746
|
}
|
|
4861
|
-
function
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4747
|
+
function dedupeSpeakers(segments) {
|
|
4748
|
+
const seen = new Set;
|
|
4749
|
+
const speakers = [];
|
|
4750
|
+
for (const segment of segments) {
|
|
4751
|
+
if (!segment.speakerId || seen.has(segment.speakerId)) {
|
|
4752
|
+
continue;
|
|
4753
|
+
}
|
|
4754
|
+
seen.add(segment.speakerId);
|
|
4755
|
+
speakers.push({
|
|
4756
|
+
id: segment.speakerId,
|
|
4757
|
+
name: segment.speakerName
|
|
4758
|
+
});
|
|
4870
4759
|
}
|
|
4871
|
-
return
|
|
4760
|
+
return speakers;
|
|
4872
4761
|
}
|
|
4873
|
-
function
|
|
4874
|
-
|
|
4875
|
-
return [];
|
|
4876
|
-
}
|
|
4877
|
-
return result;
|
|
4762
|
+
function normalizeBaseUrl(url) {
|
|
4763
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
4878
4764
|
}
|
|
4879
|
-
function
|
|
4880
|
-
if (value
|
|
4881
|
-
return
|
|
4882
|
-
}
|
|
4883
|
-
if (Array.isArray(value)) {
|
|
4884
|
-
return false;
|
|
4885
|
-
}
|
|
4886
|
-
if (value instanceof Date) {
|
|
4887
|
-
return false;
|
|
4888
|
-
}
|
|
4889
|
-
if (value instanceof Uint8Array) {
|
|
4890
|
-
return false;
|
|
4765
|
+
function asRecord2(value) {
|
|
4766
|
+
if (value && typeof value === "object") {
|
|
4767
|
+
return value;
|
|
4891
4768
|
}
|
|
4892
|
-
return
|
|
4769
|
+
return {};
|
|
4893
4770
|
}
|
|
4894
|
-
function
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4771
|
+
function readString3(record, key) {
|
|
4772
|
+
const value = record[key];
|
|
4773
|
+
return typeof value === "string" ? value : undefined;
|
|
4774
|
+
}
|
|
4775
|
+
function readNumber2(record, key) {
|
|
4776
|
+
const value = record[key];
|
|
4777
|
+
return typeof value === "number" ? value : undefined;
|
|
4778
|
+
}
|
|
4779
|
+
function secondsToMs(value) {
|
|
4780
|
+
return Math.round(value * 1000);
|
|
4904
4781
|
}
|
|
4905
4782
|
|
|
4906
|
-
// src/impls/
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4783
|
+
// src/impls/mistral-conversational.ts
|
|
4784
|
+
var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
|
|
4785
|
+
var DEFAULT_MODEL2 = "mistral-small-latest";
|
|
4786
|
+
var DEFAULT_VOICE = "default";
|
|
4787
|
+
|
|
4788
|
+
class MistralConversationalProvider {
|
|
4789
|
+
apiKey;
|
|
4790
|
+
defaultModel;
|
|
4791
|
+
defaultVoiceId;
|
|
4792
|
+
baseUrl;
|
|
4793
|
+
fetchImpl;
|
|
4794
|
+
sttProvider;
|
|
4916
4795
|
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
|
-
]);
|
|
4796
|
+
if (!options.apiKey) {
|
|
4797
|
+
throw new Error("MistralConversationalProvider requires an apiKey");
|
|
4956
4798
|
}
|
|
4799
|
+
this.apiKey = options.apiKey;
|
|
4800
|
+
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
|
|
4801
|
+
this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
|
|
4802
|
+
this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
|
|
4803
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
4804
|
+
this.sttProvider = options.sttProvider ?? new MistralSttProvider({
|
|
4805
|
+
apiKey: options.apiKey,
|
|
4806
|
+
defaultModel: options.sttOptions?.defaultModel,
|
|
4807
|
+
defaultLanguage: options.sttOptions?.defaultLanguage,
|
|
4808
|
+
serverURL: options.sttOptions?.serverURL ?? options.serverURL,
|
|
4809
|
+
fetchImpl: this.fetchImpl
|
|
4810
|
+
});
|
|
4957
4811
|
}
|
|
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
|
-
};
|
|
4812
|
+
async startSession(config) {
|
|
4813
|
+
return new MistralConversationSession({
|
|
4814
|
+
sessionConfig: {
|
|
4815
|
+
...config,
|
|
4816
|
+
voiceId: config.voiceId || this.defaultVoiceId
|
|
4817
|
+
},
|
|
4818
|
+
defaultModel: this.defaultModel,
|
|
4819
|
+
complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
|
|
4820
|
+
sttProvider: this.sttProvider
|
|
4986
4821
|
});
|
|
4987
|
-
const scoreThreshold = query.scoreThreshold;
|
|
4988
|
-
if (scoreThreshold == null) {
|
|
4989
|
-
return mapped;
|
|
4990
|
-
}
|
|
4991
|
-
return mapped.filter((result) => result.score >= scoreThreshold);
|
|
4992
4822
|
}
|
|
4993
|
-
async
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
4823
|
+
async listVoices() {
|
|
4824
|
+
return [
|
|
4825
|
+
{
|
|
4826
|
+
id: this.defaultVoiceId,
|
|
4827
|
+
name: "Mistral Default Voice",
|
|
4828
|
+
description: "Default conversational voice profile.",
|
|
4829
|
+
capabilities: ["conversational"]
|
|
4830
|
+
}
|
|
5001
4831
|
];
|
|
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
4832
|
}
|
|
5007
|
-
async
|
|
5008
|
-
|
|
5009
|
-
|
|
4833
|
+
async completeConversation(history, sessionConfig) {
|
|
4834
|
+
const model = sessionConfig.llmModel ?? this.defaultModel;
|
|
4835
|
+
const messages = [];
|
|
4836
|
+
if (sessionConfig.systemPrompt) {
|
|
4837
|
+
messages.push({ role: "system", content: sessionConfig.systemPrompt });
|
|
5010
4838
|
}
|
|
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 "<=>";
|
|
4839
|
+
for (const item of history) {
|
|
4840
|
+
messages.push({ role: item.role, content: item.content });
|
|
5041
4841
|
}
|
|
4842
|
+
const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
4843
|
+
method: "POST",
|
|
4844
|
+
headers: {
|
|
4845
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
4846
|
+
"Content-Type": "application/json"
|
|
4847
|
+
},
|
|
4848
|
+
body: JSON.stringify({
|
|
4849
|
+
model,
|
|
4850
|
+
messages
|
|
4851
|
+
})
|
|
4852
|
+
});
|
|
4853
|
+
if (!response.ok) {
|
|
4854
|
+
const body = await response.text();
|
|
4855
|
+
throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
|
|
4856
|
+
}
|
|
4857
|
+
const payload = await response.json();
|
|
4858
|
+
return readAssistantText(payload);
|
|
5042
4859
|
}
|
|
5043
4860
|
}
|
|
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('"', '""')}"`;
|
|
4861
|
+
function normalizeBaseUrl2(url) {
|
|
4862
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5052
4863
|
}
|
|
5053
|
-
function
|
|
5054
|
-
|
|
5055
|
-
|
|
4864
|
+
function readAssistantText(payload) {
|
|
4865
|
+
const record = asRecord3(payload);
|
|
4866
|
+
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
4867
|
+
const firstChoice = asRecord3(choices[0]);
|
|
4868
|
+
const message = asRecord3(firstChoice.message);
|
|
4869
|
+
if (typeof message.content === "string") {
|
|
4870
|
+
return message.content;
|
|
5056
4871
|
}
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
4872
|
+
if (Array.isArray(message.content)) {
|
|
4873
|
+
const textParts = message.content.map((part) => {
|
|
4874
|
+
const entry = asRecord3(part);
|
|
4875
|
+
const text = entry.text;
|
|
4876
|
+
return typeof text === "string" ? text : "";
|
|
4877
|
+
}).filter((text) => text.length > 0);
|
|
4878
|
+
return textParts.join("");
|
|
5061
4879
|
}
|
|
5062
|
-
return
|
|
5063
|
-
}
|
|
5064
|
-
function isRecord(value) {
|
|
5065
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4880
|
+
return "";
|
|
5066
4881
|
}
|
|
5067
|
-
function
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
return -distance;
|
|
5071
|
-
case "l2":
|
|
5072
|
-
return 1 / (1 + distance);
|
|
5073
|
-
case "cosine":
|
|
5074
|
-
default:
|
|
5075
|
-
return 1 - distance;
|
|
4882
|
+
function asRecord3(value) {
|
|
4883
|
+
if (value && typeof value === "object") {
|
|
4884
|
+
return value;
|
|
5076
4885
|
}
|
|
4886
|
+
return {};
|
|
5077
4887
|
}
|
|
5078
4888
|
|
|
5079
|
-
// src/impls/
|
|
5080
|
-
import
|
|
5081
|
-
var API_VERSION = "2026-02-25.clover";
|
|
4889
|
+
// src/impls/mistral-embedding.ts
|
|
4890
|
+
import { Mistral } from "@mistralai/mistralai";
|
|
5082
4891
|
|
|
5083
|
-
class
|
|
5084
|
-
|
|
4892
|
+
class MistralEmbeddingProvider {
|
|
4893
|
+
client;
|
|
4894
|
+
defaultModel;
|
|
5085
4895
|
constructor(options) {
|
|
5086
|
-
|
|
5087
|
-
|
|
4896
|
+
if (!options.apiKey) {
|
|
4897
|
+
throw new Error("MistralEmbeddingProvider requires an apiKey");
|
|
4898
|
+
}
|
|
4899
|
+
this.client = options.client ?? new Mistral({
|
|
4900
|
+
apiKey: options.apiKey,
|
|
4901
|
+
serverURL: options.serverURL
|
|
5088
4902
|
});
|
|
4903
|
+
this.defaultModel = options.defaultModel ?? "mistral-embed";
|
|
5089
4904
|
}
|
|
5090
|
-
async
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
4905
|
+
async embedDocuments(documents, options) {
|
|
4906
|
+
if (documents.length === 0)
|
|
4907
|
+
return [];
|
|
4908
|
+
const model = options?.model ?? this.defaultModel;
|
|
4909
|
+
const response = await this.client.embeddings.create({
|
|
4910
|
+
model,
|
|
4911
|
+
inputs: documents.map((doc) => doc.text)
|
|
5096
4912
|
});
|
|
5097
|
-
return
|
|
4913
|
+
return response.data.map((item, index) => ({
|
|
4914
|
+
id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
|
|
4915
|
+
vector: item.embedding ?? [],
|
|
4916
|
+
dimensions: item.embedding?.length ?? 0,
|
|
4917
|
+
model: response.model,
|
|
4918
|
+
metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
|
|
4919
|
+
}));
|
|
5098
4920
|
}
|
|
5099
|
-
async
|
|
5100
|
-
const
|
|
5101
|
-
if (
|
|
5102
|
-
|
|
5103
|
-
|
|
4921
|
+
async embedQuery(query, options) {
|
|
4922
|
+
const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
|
|
4923
|
+
if (!result) {
|
|
4924
|
+
throw new Error("Failed to compute embedding for query");
|
|
4925
|
+
}
|
|
4926
|
+
return result;
|
|
5104
4927
|
}
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
// src/impls/mistral-llm.ts
|
|
4931
|
+
import { Mistral as Mistral2 } from "@mistralai/mistralai";
|
|
4932
|
+
|
|
4933
|
+
class MistralLLMProvider {
|
|
4934
|
+
client;
|
|
4935
|
+
defaultModel;
|
|
4936
|
+
constructor(options) {
|
|
4937
|
+
if (!options.apiKey) {
|
|
4938
|
+
throw new Error("MistralLLMProvider requires an apiKey");
|
|
4939
|
+
}
|
|
4940
|
+
this.client = options.client ?? new Mistral2({
|
|
4941
|
+
apiKey: options.apiKey,
|
|
4942
|
+
serverURL: options.serverURL,
|
|
4943
|
+
userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
|
|
5117
4944
|
});
|
|
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);
|
|
4945
|
+
this.defaultModel = options.defaultModel ?? "mistral-large-latest";
|
|
5123
4946
|
}
|
|
5124
|
-
async
|
|
5125
|
-
const
|
|
5126
|
-
|
|
4947
|
+
async chat(messages, options = {}) {
|
|
4948
|
+
const request = this.buildChatRequest(messages, options);
|
|
4949
|
+
const response = await this.client.chat.complete(request);
|
|
4950
|
+
return this.buildLLMResponse(response);
|
|
5127
4951
|
}
|
|
5128
|
-
async
|
|
5129
|
-
const
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
4952
|
+
async* stream(messages, options = {}) {
|
|
4953
|
+
const request = this.buildChatRequest(messages, options);
|
|
4954
|
+
request.stream = true;
|
|
4955
|
+
const stream = await this.client.chat.stream(request);
|
|
4956
|
+
const aggregatedParts = [];
|
|
4957
|
+
const aggregatedToolCalls = [];
|
|
4958
|
+
let usage;
|
|
4959
|
+
let finishReason;
|
|
4960
|
+
for await (const event of stream) {
|
|
4961
|
+
for (const choice of event.data.choices) {
|
|
4962
|
+
const delta = choice.delta;
|
|
4963
|
+
if (typeof delta.content === "string") {
|
|
4964
|
+
if (delta.content.length > 0) {
|
|
4965
|
+
aggregatedParts.push({ type: "text", text: delta.content });
|
|
4966
|
+
yield {
|
|
4967
|
+
type: "message_delta",
|
|
4968
|
+
delta: { type: "text", text: delta.content },
|
|
4969
|
+
index: choice.index
|
|
4970
|
+
};
|
|
4971
|
+
}
|
|
4972
|
+
} else if (Array.isArray(delta.content)) {
|
|
4973
|
+
for (const chunk of delta.content) {
|
|
4974
|
+
if (chunk.type === "text" && "text" in chunk) {
|
|
4975
|
+
aggregatedParts.push({ type: "text", text: chunk.text });
|
|
4976
|
+
yield {
|
|
4977
|
+
type: "message_delta",
|
|
4978
|
+
delta: { type: "text", text: chunk.text },
|
|
4979
|
+
index: choice.index
|
|
4980
|
+
};
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
4984
|
+
if (delta.toolCalls) {
|
|
4985
|
+
let localIndex = 0;
|
|
4986
|
+
for (const call of delta.toolCalls) {
|
|
4987
|
+
const toolCall = this.fromMistralToolCall(call, localIndex);
|
|
4988
|
+
aggregatedToolCalls.push(toolCall);
|
|
4989
|
+
yield {
|
|
4990
|
+
type: "tool_call",
|
|
4991
|
+
call: toolCall,
|
|
4992
|
+
index: choice.index
|
|
4993
|
+
};
|
|
4994
|
+
localIndex += 1;
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
if (choice.finishReason && choice.finishReason !== "null") {
|
|
4998
|
+
finishReason = choice.finishReason;
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
if (event.data.usage) {
|
|
5002
|
+
const usageEntry = this.fromUsage(event.data.usage);
|
|
5003
|
+
if (usageEntry) {
|
|
5004
|
+
usage = usageEntry;
|
|
5005
|
+
yield { type: "usage", usage: usageEntry };
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
const message = {
|
|
5010
|
+
role: "assistant",
|
|
5011
|
+
content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
|
|
5012
|
+
};
|
|
5013
|
+
if (aggregatedToolCalls.length > 0) {
|
|
5014
|
+
message.content = [
|
|
5015
|
+
...aggregatedToolCalls,
|
|
5016
|
+
...aggregatedParts.length ? aggregatedParts : []
|
|
5017
|
+
];
|
|
5018
|
+
}
|
|
5019
|
+
yield {
|
|
5020
|
+
type: "end",
|
|
5021
|
+
response: {
|
|
5022
|
+
message,
|
|
5023
|
+
usage,
|
|
5024
|
+
finishReason: mapFinishReason(finishReason)
|
|
5025
|
+
}
|
|
5147
5026
|
};
|
|
5148
5027
|
}
|
|
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));
|
|
5028
|
+
async countTokens(_messages) {
|
|
5029
|
+
throw new Error("Mistral API does not currently support token counting");
|
|
5159
5030
|
}
|
|
5160
|
-
|
|
5161
|
-
const
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
}
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5031
|
+
buildChatRequest(messages, options) {
|
|
5032
|
+
const model = options.model ?? this.defaultModel;
|
|
5033
|
+
const mappedMessages = messages.map((message) => this.toMistralMessage(message));
|
|
5034
|
+
const request = {
|
|
5035
|
+
model,
|
|
5036
|
+
messages: mappedMessages
|
|
5037
|
+
};
|
|
5038
|
+
if (options.temperature != null) {
|
|
5039
|
+
request.temperature = options.temperature;
|
|
5040
|
+
}
|
|
5041
|
+
if (options.topP != null) {
|
|
5042
|
+
request.topP = options.topP;
|
|
5043
|
+
}
|
|
5044
|
+
if (options.maxOutputTokens != null) {
|
|
5045
|
+
request.maxTokens = options.maxOutputTokens;
|
|
5046
|
+
}
|
|
5047
|
+
if (options.stopSequences?.length) {
|
|
5048
|
+
request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
|
|
5049
|
+
}
|
|
5050
|
+
if (options.tools?.length) {
|
|
5051
|
+
request.tools = options.tools.map((tool) => ({
|
|
5052
|
+
type: "function",
|
|
5053
|
+
function: {
|
|
5054
|
+
name: tool.name,
|
|
5055
|
+
description: tool.description,
|
|
5056
|
+
parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
|
|
5057
|
+
}
|
|
5058
|
+
}));
|
|
5059
|
+
}
|
|
5060
|
+
if (options.responseFormat === "json") {
|
|
5061
|
+
request.responseFormat = { type: "json_object" };
|
|
5062
|
+
}
|
|
5063
|
+
return request;
|
|
5182
5064
|
}
|
|
5183
|
-
|
|
5184
|
-
const
|
|
5185
|
-
|
|
5065
|
+
buildLLMResponse(response) {
|
|
5066
|
+
const firstChoice = response.choices[0];
|
|
5067
|
+
if (!firstChoice) {
|
|
5068
|
+
return {
|
|
5069
|
+
message: {
|
|
5070
|
+
role: "assistant",
|
|
5071
|
+
content: [{ type: "text", text: "" }]
|
|
5072
|
+
},
|
|
5073
|
+
usage: this.fromUsage(response.usage),
|
|
5074
|
+
raw: response
|
|
5075
|
+
};
|
|
5076
|
+
}
|
|
5077
|
+
const message = this.fromAssistantMessage(firstChoice.message);
|
|
5186
5078
|
return {
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
|
|
5192
|
-
updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
|
|
5079
|
+
message,
|
|
5080
|
+
usage: this.fromUsage(response.usage),
|
|
5081
|
+
finishReason: mapFinishReason(firstChoice.finishReason),
|
|
5082
|
+
raw: response
|
|
5193
5083
|
};
|
|
5194
5084
|
}
|
|
5195
|
-
|
|
5196
|
-
|
|
5085
|
+
fromUsage(usage) {
|
|
5086
|
+
if (!usage)
|
|
5087
|
+
return;
|
|
5197
5088
|
return {
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
|
|
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)
|
|
5089
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
5090
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
5091
|
+
totalTokens: usage.totalTokens ?? 0
|
|
5207
5092
|
};
|
|
5208
5093
|
}
|
|
5209
|
-
|
|
5210
|
-
const
|
|
5094
|
+
fromAssistantMessage(message) {
|
|
5095
|
+
const parts = [];
|
|
5096
|
+
if (typeof message.content === "string") {
|
|
5097
|
+
parts.push({ type: "text", text: message.content });
|
|
5098
|
+
} else if (Array.isArray(message.content)) {
|
|
5099
|
+
message.content.forEach((chunk) => {
|
|
5100
|
+
if (chunk.type === "text" && "text" in chunk) {
|
|
5101
|
+
parts.push({ type: "text", text: chunk.text });
|
|
5102
|
+
}
|
|
5103
|
+
});
|
|
5104
|
+
}
|
|
5105
|
+
const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
|
|
5106
|
+
if (toolCalls.length > 0) {
|
|
5107
|
+
parts.splice(0, 0, ...toolCalls);
|
|
5108
|
+
}
|
|
5109
|
+
if (parts.length === 0) {
|
|
5110
|
+
parts.push({ type: "text", text: "" });
|
|
5111
|
+
}
|
|
5211
5112
|
return {
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
status: invoice.status ?? "draft",
|
|
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
|
|
5113
|
+
role: "assistant",
|
|
5114
|
+
content: parts
|
|
5223
5115
|
};
|
|
5224
5116
|
}
|
|
5225
|
-
|
|
5117
|
+
fromMistralToolCall(call, index) {
|
|
5118
|
+
const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
|
|
5226
5119
|
return {
|
|
5227
|
-
|
|
5228
|
-
|
|
5120
|
+
type: "tool-call",
|
|
5121
|
+
id: call.id ?? `tool-call-${index}`,
|
|
5122
|
+
name: call.function.name,
|
|
5123
|
+
arguments: args
|
|
5229
5124
|
};
|
|
5230
5125
|
}
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5126
|
+
toMistralMessage(message) {
|
|
5127
|
+
const textContent = this.extractText(message.content);
|
|
5128
|
+
const toolCalls = this.extractToolCalls(message);
|
|
5129
|
+
switch (message.role) {
|
|
5130
|
+
case "system":
|
|
5131
|
+
return {
|
|
5132
|
+
role: "system",
|
|
5133
|
+
content: textContent ?? ""
|
|
5134
|
+
};
|
|
5135
|
+
case "user":
|
|
5136
|
+
return {
|
|
5137
|
+
role: "user",
|
|
5138
|
+
content: textContent ?? ""
|
|
5139
|
+
};
|
|
5140
|
+
case "assistant":
|
|
5141
|
+
return {
|
|
5142
|
+
role: "assistant",
|
|
5143
|
+
content: toolCalls.length > 0 ? null : textContent ?? "",
|
|
5144
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
5145
|
+
};
|
|
5146
|
+
case "tool":
|
|
5147
|
+
return {
|
|
5148
|
+
role: "tool",
|
|
5149
|
+
content: textContent ?? "",
|
|
5150
|
+
toolCallId: message.toolCallId ?? toolCalls[0]?.id
|
|
5151
|
+
};
|
|
5152
|
+
default:
|
|
5153
|
+
return {
|
|
5154
|
+
role: "user",
|
|
5155
|
+
content: textContent ?? ""
|
|
5156
|
+
};
|
|
5243
5157
|
}
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5158
|
+
}
|
|
5159
|
+
extractText(parts) {
|
|
5160
|
+
const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
|
|
5161
|
+
if (textParts.length === 0)
|
|
5162
|
+
return null;
|
|
5163
|
+
return textParts.join("");
|
|
5164
|
+
}
|
|
5165
|
+
extractToolCalls(message) {
|
|
5166
|
+
const toolCallParts = message.content.filter((part) => part.type === "tool-call");
|
|
5167
|
+
return toolCallParts.map((call, index) => ({
|
|
5168
|
+
id: call.id ?? `call_${index}`,
|
|
5169
|
+
type: "function",
|
|
5170
|
+
index,
|
|
5171
|
+
function: {
|
|
5172
|
+
name: call.name,
|
|
5173
|
+
arguments: call.arguments
|
|
5174
|
+
}
|
|
5175
|
+
}));
|
|
5248
5176
|
}
|
|
5249
5177
|
}
|
|
5250
|
-
function
|
|
5178
|
+
function mapFinishReason(reason) {
|
|
5251
5179
|
if (!reason)
|
|
5252
5180
|
return;
|
|
5253
|
-
const
|
|
5254
|
-
|
|
5255
|
-
"
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
case "
|
|
5263
|
-
return "
|
|
5264
|
-
case "requires_confirmation":
|
|
5265
|
-
return "requires_confirmation";
|
|
5266
|
-
case "requires_action":
|
|
5267
|
-
case "requires_capture":
|
|
5268
|
-
return "requires_action";
|
|
5269
|
-
case "processing":
|
|
5270
|
-
return "processing";
|
|
5271
|
-
case "succeeded":
|
|
5272
|
-
return "succeeded";
|
|
5273
|
-
case "canceled":
|
|
5274
|
-
return "canceled";
|
|
5275
|
-
default:
|
|
5276
|
-
return "requires_payment_method";
|
|
5277
|
-
}
|
|
5278
|
-
}
|
|
5279
|
-
function mapRefundStatus(status) {
|
|
5280
|
-
switch (status) {
|
|
5281
|
-
case "pending":
|
|
5282
|
-
case "succeeded":
|
|
5283
|
-
case "failed":
|
|
5284
|
-
case "canceled":
|
|
5285
|
-
return status;
|
|
5286
|
-
default:
|
|
5287
|
-
return "pending";
|
|
5288
|
-
}
|
|
5289
|
-
}
|
|
5290
|
-
function mapChargeStatus(status) {
|
|
5291
|
-
switch (status) {
|
|
5292
|
-
case "pending":
|
|
5293
|
-
case "processing":
|
|
5294
|
-
return "pending";
|
|
5295
|
-
case "succeeded":
|
|
5296
|
-
return "succeeded";
|
|
5297
|
-
case "failed":
|
|
5298
|
-
case "canceled":
|
|
5299
|
-
return "failed";
|
|
5181
|
+
const normalized = reason.toLowerCase();
|
|
5182
|
+
switch (normalized) {
|
|
5183
|
+
case "stop":
|
|
5184
|
+
return "stop";
|
|
5185
|
+
case "length":
|
|
5186
|
+
return "length";
|
|
5187
|
+
case "tool_call":
|
|
5188
|
+
case "tool_calls":
|
|
5189
|
+
return "tool_call";
|
|
5190
|
+
case "content_filter":
|
|
5191
|
+
return "content_filter";
|
|
5300
5192
|
default:
|
|
5301
|
-
return
|
|
5193
|
+
return;
|
|
5302
5194
|
}
|
|
5303
5195
|
}
|
|
5304
5196
|
|
|
5305
|
-
// src/impls/
|
|
5306
|
-
import {
|
|
5197
|
+
// src/impls/notion.ts
|
|
5198
|
+
import { Client } from "@notionhq/client";
|
|
5307
5199
|
|
|
5308
|
-
class
|
|
5200
|
+
class NotionProjectManagementProvider {
|
|
5309
5201
|
client;
|
|
5310
|
-
|
|
5311
|
-
messageStream;
|
|
5202
|
+
defaults;
|
|
5312
5203
|
constructor(options) {
|
|
5313
|
-
this.client = options.client ?? new
|
|
5314
|
-
|
|
5204
|
+
this.client = options.client ?? new Client({ auth: options.apiKey });
|
|
5205
|
+
this.defaults = {
|
|
5206
|
+
databaseId: options.databaseId,
|
|
5207
|
+
summaryParentPageId: options.summaryParentPageId,
|
|
5208
|
+
titleProperty: options.titleProperty,
|
|
5209
|
+
statusProperty: options.statusProperty,
|
|
5210
|
+
priorityProperty: options.priorityProperty,
|
|
5211
|
+
tagsProperty: options.tagsProperty,
|
|
5212
|
+
dueDateProperty: options.dueDateProperty,
|
|
5213
|
+
descriptionProperty: options.descriptionProperty
|
|
5214
|
+
};
|
|
5215
|
+
}
|
|
5216
|
+
async createWorkItem(input) {
|
|
5217
|
+
if (input.type === "summary" && this.defaults.summaryParentPageId) {
|
|
5218
|
+
return this.createSummaryPage(input);
|
|
5219
|
+
}
|
|
5220
|
+
const databaseId = this.defaults.databaseId;
|
|
5221
|
+
if (!databaseId) {
|
|
5222
|
+
throw new Error("Notion databaseId is required to create work items.");
|
|
5223
|
+
}
|
|
5224
|
+
const titleProperty = this.defaults.titleProperty ?? "Name";
|
|
5225
|
+
const properties = {
|
|
5226
|
+
[titleProperty]: buildTitleProperty(input.title)
|
|
5227
|
+
};
|
|
5228
|
+
applySelect(properties, this.defaults.statusProperty, input.status);
|
|
5229
|
+
applySelect(properties, this.defaults.priorityProperty, input.priority);
|
|
5230
|
+
applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
|
|
5231
|
+
applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
|
|
5232
|
+
applyRichText(properties, this.defaults.descriptionProperty, input.description);
|
|
5233
|
+
const page = await this.client.pages.create({
|
|
5234
|
+
parent: { type: "database_id", database_id: databaseId },
|
|
5235
|
+
properties
|
|
5315
5236
|
});
|
|
5316
|
-
|
|
5317
|
-
|
|
5237
|
+
return {
|
|
5238
|
+
id: page.id,
|
|
5239
|
+
title: input.title,
|
|
5240
|
+
url: "url" in page ? page.url : undefined,
|
|
5241
|
+
status: input.status,
|
|
5242
|
+
priority: input.priority,
|
|
5243
|
+
tags: input.tags,
|
|
5244
|
+
projectId: databaseId,
|
|
5245
|
+
externalId: input.externalId,
|
|
5246
|
+
metadata: input.metadata
|
|
5247
|
+
};
|
|
5318
5248
|
}
|
|
5319
|
-
async
|
|
5320
|
-
const
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
MessageStream: this.messageStream,
|
|
5334
|
-
Attachments: buildAttachments(message)
|
|
5249
|
+
async createWorkItems(items) {
|
|
5250
|
+
const created = [];
|
|
5251
|
+
for (const item of items) {
|
|
5252
|
+
created.push(await this.createWorkItem(item));
|
|
5253
|
+
}
|
|
5254
|
+
return created;
|
|
5255
|
+
}
|
|
5256
|
+
async createSummaryPage(input) {
|
|
5257
|
+
const parentId = this.defaults.summaryParentPageId;
|
|
5258
|
+
if (!parentId) {
|
|
5259
|
+
throw new Error("Notion summaryParentPageId is required for summaries.");
|
|
5260
|
+
}
|
|
5261
|
+
const summaryProperties = {
|
|
5262
|
+
title: buildTitleProperty(input.title)
|
|
5335
5263
|
};
|
|
5336
|
-
const
|
|
5264
|
+
const page = await this.client.pages.create({
|
|
5265
|
+
parent: { type: "page_id", page_id: parentId },
|
|
5266
|
+
properties: summaryProperties
|
|
5267
|
+
});
|
|
5268
|
+
if (input.description) {
|
|
5269
|
+
const children = buildParagraphBlocks(input.description);
|
|
5270
|
+
if (children.length > 0) {
|
|
5271
|
+
await this.client.blocks.children.append({
|
|
5272
|
+
block_id: page.id,
|
|
5273
|
+
children
|
|
5274
|
+
});
|
|
5275
|
+
}
|
|
5276
|
+
}
|
|
5337
5277
|
return {
|
|
5338
|
-
id:
|
|
5339
|
-
|
|
5340
|
-
|
|
5278
|
+
id: page.id,
|
|
5279
|
+
title: input.title,
|
|
5280
|
+
url: "url" in page ? page.url : undefined,
|
|
5281
|
+
status: input.status,
|
|
5282
|
+
priority: input.priority,
|
|
5283
|
+
tags: input.tags,
|
|
5284
|
+
projectId: parentId,
|
|
5285
|
+
externalId: input.externalId,
|
|
5286
|
+
metadata: input.metadata
|
|
5341
5287
|
};
|
|
5342
5288
|
}
|
|
5343
5289
|
}
|
|
5344
|
-
function
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5290
|
+
function buildTitleProperty(title) {
|
|
5291
|
+
return {
|
|
5292
|
+
title: [
|
|
5293
|
+
{
|
|
5294
|
+
type: "text",
|
|
5295
|
+
text: { content: title }
|
|
5296
|
+
}
|
|
5297
|
+
]
|
|
5298
|
+
};
|
|
5349
5299
|
}
|
|
5350
|
-
function
|
|
5351
|
-
|
|
5300
|
+
function buildRichText(text) {
|
|
5301
|
+
return {
|
|
5302
|
+
rich_text: [
|
|
5303
|
+
{
|
|
5304
|
+
type: "text",
|
|
5305
|
+
text: { content: text }
|
|
5306
|
+
}
|
|
5307
|
+
]
|
|
5308
|
+
};
|
|
5309
|
+
}
|
|
5310
|
+
function applySelect(properties, property, value) {
|
|
5311
|
+
if (!property || !value)
|
|
5352
5312
|
return;
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5313
|
+
const next = {
|
|
5314
|
+
select: { name: value }
|
|
5315
|
+
};
|
|
5316
|
+
properties[property] = next;
|
|
5317
|
+
}
|
|
5318
|
+
function applyMultiSelect(properties, property, values) {
|
|
5319
|
+
if (!property || !values || values.length === 0)
|
|
5320
|
+
return;
|
|
5321
|
+
const next = {
|
|
5322
|
+
multi_select: values.map((value) => ({ name: value }))
|
|
5323
|
+
};
|
|
5324
|
+
properties[property] = next;
|
|
5325
|
+
}
|
|
5326
|
+
function applyDate(properties, property, value) {
|
|
5327
|
+
if (!property || !value)
|
|
5328
|
+
return;
|
|
5329
|
+
const next = {
|
|
5330
|
+
date: { start: value.toISOString() }
|
|
5331
|
+
};
|
|
5332
|
+
properties[property] = next;
|
|
5333
|
+
}
|
|
5334
|
+
function applyRichText(properties, property, value) {
|
|
5335
|
+
if (!property || !value)
|
|
5336
|
+
return;
|
|
5337
|
+
properties[property] = buildRichText(value);
|
|
5338
|
+
}
|
|
5339
|
+
function buildParagraphBlocks(text) {
|
|
5340
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim());
|
|
5341
|
+
return lines.map((line) => ({
|
|
5342
|
+
object: "block",
|
|
5343
|
+
type: "paragraph",
|
|
5344
|
+
paragraph: {
|
|
5345
|
+
rich_text: [
|
|
5346
|
+
{
|
|
5347
|
+
type: "text",
|
|
5348
|
+
text: { content: line }
|
|
5349
|
+
}
|
|
5350
|
+
]
|
|
5351
|
+
}
|
|
5360
5352
|
}));
|
|
5361
5353
|
}
|
|
5362
5354
|
|
|
@@ -5447,612 +5439,295 @@ class PosthogAnalyticsReader {
|
|
|
5447
5439
|
offset: input.offset
|
|
5448
5440
|
}
|
|
5449
5441
|
});
|
|
5450
|
-
return response.data;
|
|
5451
|
-
}
|
|
5452
|
-
async getFeatureFlags(input) {
|
|
5453
|
-
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5454
|
-
const response = await this.client.request({
|
|
5455
|
-
method: "GET",
|
|
5456
|
-
path: `/api/projects/${projectId}/feature_flags/`,
|
|
5457
|
-
query: {
|
|
5458
|
-
active: input.active,
|
|
5459
|
-
limit: input.limit,
|
|
5460
|
-
offset: input.offset
|
|
5461
|
-
}
|
|
5462
|
-
});
|
|
5463
|
-
return response.data;
|
|
5464
|
-
}
|
|
5465
|
-
async getAnnotations(input) {
|
|
5466
|
-
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5467
|
-
const response = await this.client.request({
|
|
5468
|
-
method: "GET",
|
|
5469
|
-
path: `/api/projects/${projectId}/annotations/`,
|
|
5470
|
-
query: {
|
|
5471
|
-
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 } : {}
|
|
5603
|
-
}
|
|
5604
|
-
});
|
|
5605
|
-
}
|
|
5606
|
-
async queryHogQL(input) {
|
|
5607
|
-
return this.reader.queryHogQL(input);
|
|
5608
|
-
}
|
|
5609
|
-
async getEvents(input) {
|
|
5610
|
-
return this.reader.getEvents(input);
|
|
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
|
|
5442
|
+
return response.data;
|
|
5443
|
+
}
|
|
5444
|
+
async getFeatureFlags(input) {
|
|
5445
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5446
|
+
const response = await this.client.request({
|
|
5447
|
+
method: "GET",
|
|
5448
|
+
path: `/api/projects/${projectId}/feature_flags/`,
|
|
5449
|
+
query: {
|
|
5450
|
+
active: input.active,
|
|
5451
|
+
limit: input.limit,
|
|
5452
|
+
offset: input.offset
|
|
5453
|
+
}
|
|
5698
5454
|
});
|
|
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
|
-
};
|
|
5455
|
+
return response.data;
|
|
5712
5456
|
}
|
|
5713
|
-
async
|
|
5714
|
-
const
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5457
|
+
async getAnnotations(input) {
|
|
5458
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
5459
|
+
const response = await this.client.request({
|
|
5460
|
+
method: "GET",
|
|
5461
|
+
path: `/api/projects/${projectId}/annotations/`,
|
|
5462
|
+
query: {
|
|
5463
|
+
limit: input.limit,
|
|
5464
|
+
offset: input.offset,
|
|
5465
|
+
...buildAnnotationDateQuery(input.dateRange)
|
|
5466
|
+
}
|
|
5467
|
+
});
|
|
5468
|
+
return response.data;
|
|
5721
5469
|
}
|
|
5722
5470
|
}
|
|
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";
|
|
5471
|
+
function resolveProjectId(inputProjectId, defaultProjectId) {
|
|
5472
|
+
const projectId = inputProjectId ?? defaultProjectId;
|
|
5473
|
+
if (!projectId) {
|
|
5474
|
+
throw new Error("PostHog projectId is required for API reads.");
|
|
5743
5475
|
}
|
|
5476
|
+
return projectId;
|
|
5477
|
+
}
|
|
5478
|
+
function resolveSingleEvent(events) {
|
|
5479
|
+
if (!events || events.length !== 1)
|
|
5480
|
+
return;
|
|
5481
|
+
return events[0];
|
|
5482
|
+
}
|
|
5483
|
+
function resolveEventList(events) {
|
|
5484
|
+
if (!events || events.length <= 1)
|
|
5485
|
+
return;
|
|
5486
|
+
return events.join(",");
|
|
5487
|
+
}
|
|
5488
|
+
function buildEventDateQuery(range) {
|
|
5489
|
+
const after = formatDate(range?.from);
|
|
5490
|
+
const before = formatDate(range?.to);
|
|
5491
|
+
return {
|
|
5492
|
+
after,
|
|
5493
|
+
before,
|
|
5494
|
+
timezone: range?.timezone
|
|
5495
|
+
};
|
|
5496
|
+
}
|
|
5497
|
+
function buildAnnotationDateQuery(range) {
|
|
5498
|
+
const dateFrom = formatDate(range?.from);
|
|
5499
|
+
const dateTo = formatDate(range?.to);
|
|
5500
|
+
return {
|
|
5501
|
+
date_from: dateFrom,
|
|
5502
|
+
date_to: dateTo,
|
|
5503
|
+
timezone: range?.timezone
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
function formatDate(value) {
|
|
5507
|
+
if (!value)
|
|
5508
|
+
return;
|
|
5509
|
+
return typeof value === "string" ? value : value.toISOString();
|
|
5744
5510
|
}
|
|
5745
5511
|
|
|
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
|
-
};
|
|
5512
|
+
// src/impls/posthog-utils.ts
|
|
5513
|
+
function normalizeHost(host) {
|
|
5514
|
+
return host.replace(/\/$/, "");
|
|
5515
|
+
}
|
|
5516
|
+
function buildUrl(host, path, query) {
|
|
5517
|
+
if (/^https?:\/\//.test(path)) {
|
|
5518
|
+
return appendQuery(path, query);
|
|
5788
5519
|
}
|
|
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
|
-
};
|
|
5520
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
5521
|
+
return appendQuery(`${host}/${normalizedPath}`, query);
|
|
5522
|
+
}
|
|
5523
|
+
function appendQuery(url, query) {
|
|
5524
|
+
if (!query)
|
|
5525
|
+
return url;
|
|
5526
|
+
const params = new URLSearchParams;
|
|
5527
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
5528
|
+
if (value === undefined)
|
|
5529
|
+
return;
|
|
5530
|
+
params.set(key, String(value));
|
|
5531
|
+
});
|
|
5532
|
+
const suffix = params.toString();
|
|
5533
|
+
return suffix ? `${url}?${suffix}` : url;
|
|
5534
|
+
}
|
|
5535
|
+
function parseJson(value) {
|
|
5536
|
+
if (!value)
|
|
5537
|
+
return {};
|
|
5538
|
+
try {
|
|
5539
|
+
return JSON.parse(value);
|
|
5540
|
+
} catch {
|
|
5541
|
+
return value;
|
|
5820
5542
|
}
|
|
5821
5543
|
}
|
|
5822
5544
|
|
|
5823
|
-
// src/impls/
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5545
|
+
// src/impls/posthog.ts
|
|
5546
|
+
import { PostHog } from "posthog-node";
|
|
5547
|
+
var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
|
|
5548
|
+
|
|
5549
|
+
class PosthogAnalyticsProvider {
|
|
5550
|
+
host;
|
|
5551
|
+
projectId;
|
|
5552
|
+
projectApiKey;
|
|
5553
|
+
personalApiKey;
|
|
5554
|
+
mcpUrl;
|
|
5555
|
+
fetchFn;
|
|
5556
|
+
client;
|
|
5557
|
+
reader;
|
|
5829
5558
|
constructor(options) {
|
|
5830
|
-
this.
|
|
5831
|
-
this.
|
|
5832
|
-
this.
|
|
5833
|
-
this.
|
|
5559
|
+
this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
|
|
5560
|
+
this.projectId = options.projectId;
|
|
5561
|
+
this.projectApiKey = options.projectApiKey;
|
|
5562
|
+
this.personalApiKey = options.personalApiKey;
|
|
5563
|
+
this.mcpUrl = options.mcpUrl;
|
|
5564
|
+
this.fetchFn = options.fetch ?? fetch;
|
|
5565
|
+
this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
|
|
5566
|
+
host: this.host,
|
|
5567
|
+
requestTimeout: options.requestTimeoutMs ?? 1e4
|
|
5568
|
+
}) : undefined);
|
|
5569
|
+
this.reader = new PosthogAnalyticsReader({
|
|
5570
|
+
projectId: this.projectId,
|
|
5571
|
+
client: { request: this.request.bind(this) }
|
|
5572
|
+
});
|
|
5834
5573
|
}
|
|
5835
|
-
async
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5574
|
+
async capture(event) {
|
|
5575
|
+
if (!this.client) {
|
|
5576
|
+
throw new Error("PostHog projectApiKey is required for capture.");
|
|
5577
|
+
}
|
|
5578
|
+
await this.client.capture({
|
|
5579
|
+
distinctId: event.distinctId,
|
|
5580
|
+
event: event.event,
|
|
5581
|
+
properties: event.properties,
|
|
5582
|
+
timestamp: event.timestamp,
|
|
5583
|
+
groups: event.groups
|
|
5845
5584
|
});
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5585
|
+
}
|
|
5586
|
+
async identify(input) {
|
|
5587
|
+
if (!this.client) {
|
|
5588
|
+
throw new Error("PostHog projectApiKey is required for identify.");
|
|
5849
5589
|
}
|
|
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)
|
|
5590
|
+
await this.client.identify({
|
|
5591
|
+
distinctId: input.distinctId,
|
|
5592
|
+
properties: {
|
|
5593
|
+
...input.properties ? { $set: input.properties } : {},
|
|
5594
|
+
...input.setOnce ? { $set_once: input.setOnce } : {}
|
|
5860
5595
|
}
|
|
5861
|
-
};
|
|
5596
|
+
});
|
|
5862
5597
|
}
|
|
5863
|
-
async
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5598
|
+
async queryHogQL(input) {
|
|
5599
|
+
return this.reader.queryHogQL(input);
|
|
5600
|
+
}
|
|
5601
|
+
async getEvents(input) {
|
|
5602
|
+
return this.reader.getEvents(input);
|
|
5603
|
+
}
|
|
5604
|
+
async getPersons(input) {
|
|
5605
|
+
return this.reader.getPersons(input);
|
|
5606
|
+
}
|
|
5607
|
+
async getInsights(input) {
|
|
5608
|
+
return this.reader.getInsights(input);
|
|
5609
|
+
}
|
|
5610
|
+
async getInsightResult(input) {
|
|
5611
|
+
return this.reader.getInsightResult(input);
|
|
5612
|
+
}
|
|
5613
|
+
async getCohorts(input) {
|
|
5614
|
+
return this.reader.getCohorts(input);
|
|
5615
|
+
}
|
|
5616
|
+
async getFeatureFlags(input) {
|
|
5617
|
+
return this.reader.getFeatureFlags(input);
|
|
5618
|
+
}
|
|
5619
|
+
async getAnnotations(input) {
|
|
5620
|
+
return this.reader.getAnnotations(input);
|
|
5621
|
+
}
|
|
5622
|
+
async request(request) {
|
|
5623
|
+
if (!this.personalApiKey) {
|
|
5624
|
+
throw new Error("PostHog personalApiKey is required for API requests.");
|
|
5868
5625
|
}
|
|
5869
|
-
const
|
|
5870
|
-
|
|
5626
|
+
const url = buildUrl(this.host, request.path, request.query);
|
|
5627
|
+
const response = await this.fetchFn(url, {
|
|
5628
|
+
method: request.method,
|
|
5871
5629
|
headers: {
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5630
|
+
Authorization: `Bearer ${this.personalApiKey}`,
|
|
5631
|
+
"Content-Type": "application/json",
|
|
5632
|
+
...request.headers ?? {}
|
|
5875
5633
|
},
|
|
5876
|
-
body: JSON.stringify(
|
|
5634
|
+
body: request.body ? JSON.stringify(request.body) : undefined
|
|
5877
5635
|
});
|
|
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
|
-
}
|
|
5636
|
+
const text = await response.text();
|
|
5637
|
+
const data = parseJson(text);
|
|
5902
5638
|
return {
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5639
|
+
status: response.status,
|
|
5640
|
+
data,
|
|
5641
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
5906
5642
|
};
|
|
5907
5643
|
}
|
|
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.");
|
|
5644
|
+
async callMcpTool(call) {
|
|
5645
|
+
if (!this.mcpUrl) {
|
|
5646
|
+
throw new Error("PostHog MCP URL is not configured.");
|
|
5944
5647
|
}
|
|
5945
|
-
const response = await
|
|
5648
|
+
const response = await this.fetchFn(this.mcpUrl, {
|
|
5946
5649
|
method: "POST",
|
|
5947
5650
|
headers: {
|
|
5948
|
-
|
|
5949
|
-
"content-type": "application/json"
|
|
5651
|
+
"Content-Type": "application/json"
|
|
5950
5652
|
},
|
|
5951
5653
|
body: JSON.stringify({
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5654
|
+
jsonrpc: "2.0",
|
|
5655
|
+
id: 1,
|
|
5656
|
+
method: "tools/call",
|
|
5657
|
+
params: {
|
|
5658
|
+
name: call.name,
|
|
5659
|
+
arguments: call.arguments ?? {}
|
|
5958
5660
|
}
|
|
5959
5661
|
})
|
|
5960
5662
|
});
|
|
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})` : ""}`);
|
|
5663
|
+
if (!response.ok) {
|
|
5664
|
+
const body = await response.text();
|
|
5665
|
+
throw new Error(`PostHog MCP error (${response.status}): ${body}`);
|
|
5966
5666
|
}
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
metadata: {
|
|
5973
|
-
phoneNumberId: this.phoneNumberId
|
|
5974
|
-
}
|
|
5975
|
-
};
|
|
5667
|
+
const result = await response.json();
|
|
5668
|
+
if (result.error) {
|
|
5669
|
+
throw new Error(result.error.message ?? "PostHog MCP error");
|
|
5670
|
+
}
|
|
5671
|
+
return result.result ?? null;
|
|
5976
5672
|
}
|
|
5977
5673
|
}
|
|
5978
5674
|
|
|
5979
|
-
// src/impls/
|
|
5980
|
-
import {
|
|
5675
|
+
// src/impls/postmark-email.ts
|
|
5676
|
+
import { ServerClient } from "postmark";
|
|
5981
5677
|
|
|
5982
|
-
class
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5678
|
+
class PostmarkEmailProvider {
|
|
5679
|
+
client;
|
|
5680
|
+
defaultFromEmail;
|
|
5681
|
+
messageStream;
|
|
5986
5682
|
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()
|
|
5683
|
+
this.client = options.client ?? new ServerClient(options.serverToken, {
|
|
5684
|
+
useHttps: true
|
|
6012
5685
|
});
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
5686
|
+
this.defaultFromEmail = options.defaultFromEmail;
|
|
5687
|
+
this.messageStream = options.messageStream;
|
|
5688
|
+
}
|
|
5689
|
+
async sendEmail(message) {
|
|
5690
|
+
const request = {
|
|
5691
|
+
From: formatAddress2(message.from) ?? this.defaultFromEmail,
|
|
5692
|
+
To: message.to.map((addr) => formatAddress2(addr)).join(", "),
|
|
5693
|
+
Cc: message.cc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
|
|
5694
|
+
Bcc: message.bcc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
|
|
5695
|
+
ReplyTo: message.replyTo ? formatAddress2(message.replyTo) : undefined,
|
|
5696
|
+
Subject: message.subject,
|
|
5697
|
+
TextBody: message.textBody,
|
|
5698
|
+
HtmlBody: message.htmlBody,
|
|
5699
|
+
Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
|
|
5700
|
+
Name: name,
|
|
5701
|
+
Value: value
|
|
5702
|
+
})) : undefined,
|
|
5703
|
+
MessageStream: this.messageStream,
|
|
5704
|
+
Attachments: buildAttachments(message)
|
|
5705
|
+
};
|
|
5706
|
+
const response = await this.client.sendEmail(request);
|
|
5707
|
+
return {
|
|
5708
|
+
id: response.MessageID,
|
|
5709
|
+
providerMessageId: response.MessageID,
|
|
5710
|
+
queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
|
|
6028
5711
|
};
|
|
6029
5712
|
}
|
|
6030
5713
|
}
|
|
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";
|
|
5714
|
+
function formatAddress2(address) {
|
|
5715
|
+
if (address.name) {
|
|
5716
|
+
return `"${address.name}" <${address.email}>`;
|
|
6055
5717
|
}
|
|
5718
|
+
return address.email;
|
|
5719
|
+
}
|
|
5720
|
+
function buildAttachments(message) {
|
|
5721
|
+
if (!message.attachments?.length)
|
|
5722
|
+
return;
|
|
5723
|
+
return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
|
|
5724
|
+
Name: attachment.filename,
|
|
5725
|
+
Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
|
|
5726
|
+
ContentType: attachment.contentType,
|
|
5727
|
+
ContentID: null,
|
|
5728
|
+
ContentLength: attachment.sizeBytes,
|
|
5729
|
+
Disposition: "attachment"
|
|
5730
|
+
}));
|
|
6056
5731
|
}
|
|
6057
5732
|
|
|
6058
5733
|
// src/impls/powens-client.ts
|
|
@@ -6464,380 +6139,642 @@ class PowensOpenBankingProvider {
|
|
|
6464
6139
|
return "degraded";
|
|
6465
6140
|
}
|
|
6466
6141
|
}
|
|
6467
|
-
handleError(operation, error) {
|
|
6468
|
-
if (error instanceof PowensClientError) {
|
|
6469
|
-
this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed`, {
|
|
6470
|
-
status: error.status,
|
|
6471
|
-
code: error.code,
|
|
6472
|
-
requestId: error.requestId,
|
|
6473
|
-
message: error.message
|
|
6474
|
-
});
|
|
6475
|
-
throw error;
|
|
6476
|
-
}
|
|
6477
|
-
this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
|
|
6478
|
-
throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
|
|
6142
|
+
handleError(operation, error) {
|
|
6143
|
+
if (error instanceof PowensClientError) {
|
|
6144
|
+
this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed`, {
|
|
6145
|
+
status: error.status,
|
|
6146
|
+
code: error.code,
|
|
6147
|
+
requestId: error.requestId,
|
|
6148
|
+
message: error.message
|
|
6149
|
+
});
|
|
6150
|
+
throw error;
|
|
6151
|
+
}
|
|
6152
|
+
this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
|
|
6153
|
+
throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
// src/impls/qdrant-vector.ts
|
|
6158
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
6159
|
+
|
|
6160
|
+
class QdrantVectorProvider {
|
|
6161
|
+
client;
|
|
6162
|
+
createCollectionIfMissing;
|
|
6163
|
+
distance;
|
|
6164
|
+
constructor(options) {
|
|
6165
|
+
this.client = options.client ?? new QdrantClient({
|
|
6166
|
+
url: options.url,
|
|
6167
|
+
apiKey: options.apiKey,
|
|
6168
|
+
...options.clientParams
|
|
6169
|
+
});
|
|
6170
|
+
this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
|
|
6171
|
+
this.distance = options.distance ?? "Cosine";
|
|
6172
|
+
}
|
|
6173
|
+
async upsert(request) {
|
|
6174
|
+
if (request.documents.length === 0)
|
|
6175
|
+
return;
|
|
6176
|
+
const firstDocument = request.documents[0];
|
|
6177
|
+
if (!firstDocument)
|
|
6178
|
+
return;
|
|
6179
|
+
const vectorSize = firstDocument.vector.length;
|
|
6180
|
+
if (this.createCollectionIfMissing) {
|
|
6181
|
+
await this.ensureCollection(request.collection, vectorSize);
|
|
6182
|
+
}
|
|
6183
|
+
const points = request.documents.map((document) => ({
|
|
6184
|
+
id: document.id,
|
|
6185
|
+
vector: document.vector,
|
|
6186
|
+
payload: {
|
|
6187
|
+
...document.payload,
|
|
6188
|
+
...document.namespace ? { namespace: document.namespace } : {},
|
|
6189
|
+
...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
|
|
6190
|
+
}
|
|
6191
|
+
}));
|
|
6192
|
+
await this.client.upsert(request.collection, {
|
|
6193
|
+
wait: true,
|
|
6194
|
+
points
|
|
6195
|
+
});
|
|
6196
|
+
}
|
|
6197
|
+
async search(query) {
|
|
6198
|
+
const results = await this.client.search(query.collection, {
|
|
6199
|
+
vector: query.vector,
|
|
6200
|
+
limit: query.topK,
|
|
6201
|
+
filter: query.filter,
|
|
6202
|
+
score_threshold: query.scoreThreshold,
|
|
6203
|
+
with_payload: true,
|
|
6204
|
+
with_vector: false
|
|
6205
|
+
});
|
|
6206
|
+
return results.map((item) => ({
|
|
6207
|
+
id: String(item.id),
|
|
6208
|
+
score: item.score,
|
|
6209
|
+
payload: item.payload ?? undefined,
|
|
6210
|
+
namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
|
|
6211
|
+
}));
|
|
6212
|
+
}
|
|
6213
|
+
async delete(request) {
|
|
6214
|
+
await this.client.delete(request.collection, {
|
|
6215
|
+
wait: true,
|
|
6216
|
+
points: request.ids
|
|
6217
|
+
});
|
|
6218
|
+
}
|
|
6219
|
+
async ensureCollection(collectionName, vectorSize) {
|
|
6220
|
+
try {
|
|
6221
|
+
await this.client.getCollection(collectionName);
|
|
6222
|
+
} catch (_error) {
|
|
6223
|
+
await this.client.createCollection(collectionName, {
|
|
6224
|
+
vectors: {
|
|
6225
|
+
size: vectorSize,
|
|
6226
|
+
distance: this.distance
|
|
6227
|
+
}
|
|
6228
|
+
});
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
|
|
6233
|
+
// src/impls/stripe-payments.ts
|
|
6234
|
+
import Stripe from "stripe";
|
|
6235
|
+
var API_VERSION = "2026-02-25.clover";
|
|
6236
|
+
|
|
6237
|
+
class StripePaymentsProvider {
|
|
6238
|
+
stripe;
|
|
6239
|
+
constructor(options) {
|
|
6240
|
+
this.stripe = options.stripe ?? new Stripe(options.apiKey, {
|
|
6241
|
+
apiVersion: API_VERSION
|
|
6242
|
+
});
|
|
6243
|
+
}
|
|
6244
|
+
async createCustomer(input) {
|
|
6245
|
+
const customer = await this.stripe.customers.create({
|
|
6246
|
+
email: input.email,
|
|
6247
|
+
name: input.name,
|
|
6248
|
+
description: input.description,
|
|
6249
|
+
metadata: input.metadata
|
|
6250
|
+
});
|
|
6251
|
+
return this.toCustomer(customer);
|
|
6252
|
+
}
|
|
6253
|
+
async getCustomer(customerId) {
|
|
6254
|
+
const customer = await this.stripe.customers.retrieve(customerId);
|
|
6255
|
+
if (customer.deleted)
|
|
6256
|
+
return null;
|
|
6257
|
+
return this.toCustomer(customer);
|
|
6258
|
+
}
|
|
6259
|
+
async createPaymentIntent(input) {
|
|
6260
|
+
const intent = await this.stripe.paymentIntents.create({
|
|
6261
|
+
amount: input.amount.amount,
|
|
6262
|
+
currency: input.amount.currency,
|
|
6263
|
+
customer: input.customerId,
|
|
6264
|
+
description: input.description,
|
|
6265
|
+
capture_method: input.captureMethod ?? "automatic",
|
|
6266
|
+
confirmation_method: input.confirmationMethod ?? "automatic",
|
|
6267
|
+
automatic_payment_methods: { enabled: true },
|
|
6268
|
+
metadata: input.metadata,
|
|
6269
|
+
return_url: input.returnUrl,
|
|
6270
|
+
statement_descriptor: input.statementDescriptor
|
|
6271
|
+
});
|
|
6272
|
+
return this.toPaymentIntent(intent);
|
|
6273
|
+
}
|
|
6274
|
+
async capturePayment(paymentIntentId, input) {
|
|
6275
|
+
const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
|
|
6276
|
+
return this.toPaymentIntent(intent);
|
|
6277
|
+
}
|
|
6278
|
+
async cancelPaymentIntent(paymentIntentId) {
|
|
6279
|
+
const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
|
|
6280
|
+
return this.toPaymentIntent(intent);
|
|
6281
|
+
}
|
|
6282
|
+
async refundPayment(input) {
|
|
6283
|
+
const refund = await this.stripe.refunds.create({
|
|
6284
|
+
payment_intent: input.paymentIntentId,
|
|
6285
|
+
amount: input.amount?.amount,
|
|
6286
|
+
reason: mapRefundReason(input.reason),
|
|
6287
|
+
metadata: input.metadata
|
|
6288
|
+
});
|
|
6289
|
+
const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
|
|
6290
|
+
return {
|
|
6291
|
+
id: refund.id,
|
|
6292
|
+
paymentIntentId,
|
|
6293
|
+
amount: {
|
|
6294
|
+
amount: refund.amount ?? 0,
|
|
6295
|
+
currency: refund.currency?.toUpperCase() ?? "USD"
|
|
6296
|
+
},
|
|
6297
|
+
status: mapRefundStatus(refund.status),
|
|
6298
|
+
reason: refund.reason ?? undefined,
|
|
6299
|
+
metadata: this.toMetadata(refund.metadata),
|
|
6300
|
+
createdAt: refund.created ? new Date(refund.created * 1000) : undefined
|
|
6301
|
+
};
|
|
6302
|
+
}
|
|
6303
|
+
async listInvoices(query) {
|
|
6304
|
+
const requestedStatus = query?.status?.[0];
|
|
6305
|
+
const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
|
|
6306
|
+
const response = await this.stripe.invoices.list({
|
|
6307
|
+
customer: query?.customerId,
|
|
6308
|
+
status: stripeStatus,
|
|
6309
|
+
limit: query?.limit,
|
|
6310
|
+
starting_after: query?.startingAfter
|
|
6311
|
+
});
|
|
6312
|
+
return response.data.map((invoice) => this.toInvoice(invoice));
|
|
6313
|
+
}
|
|
6314
|
+
async listTransactions(query) {
|
|
6315
|
+
const response = await this.stripe.charges.list({
|
|
6316
|
+
customer: query?.customerId,
|
|
6317
|
+
payment_intent: query?.paymentIntentId,
|
|
6318
|
+
limit: query?.limit,
|
|
6319
|
+
starting_after: query?.startingAfter
|
|
6320
|
+
});
|
|
6321
|
+
return response.data.map((charge) => ({
|
|
6322
|
+
id: charge.id,
|
|
6323
|
+
paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
|
|
6324
|
+
amount: {
|
|
6325
|
+
amount: charge.amount,
|
|
6326
|
+
currency: charge.currency?.toUpperCase() ?? "USD"
|
|
6327
|
+
},
|
|
6328
|
+
type: "capture",
|
|
6329
|
+
status: mapChargeStatus(charge.status),
|
|
6330
|
+
description: charge.description ?? undefined,
|
|
6331
|
+
createdAt: new Date(charge.created * 1000),
|
|
6332
|
+
metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
|
|
6333
|
+
balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
|
|
6334
|
+
})
|
|
6335
|
+
}));
|
|
6336
|
+
}
|
|
6337
|
+
toCustomer(customer) {
|
|
6338
|
+
const metadata = this.toMetadata(customer.metadata);
|
|
6339
|
+
const updatedAtValue = metadata?.updatedAt;
|
|
6340
|
+
return {
|
|
6341
|
+
id: customer.id,
|
|
6342
|
+
email: customer.email ?? undefined,
|
|
6343
|
+
name: customer.name ?? undefined,
|
|
6344
|
+
metadata,
|
|
6345
|
+
createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
|
|
6346
|
+
updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
|
|
6347
|
+
};
|
|
6348
|
+
}
|
|
6349
|
+
toPaymentIntent(intent) {
|
|
6350
|
+
const metadata = this.toMetadata(intent.metadata);
|
|
6351
|
+
return {
|
|
6352
|
+
id: intent.id,
|
|
6353
|
+
amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
|
|
6354
|
+
status: mapPaymentIntentStatus(intent.status),
|
|
6355
|
+
customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
|
|
6356
|
+
description: intent.description ?? undefined,
|
|
6357
|
+
clientSecret: intent.client_secret ?? undefined,
|
|
6358
|
+
metadata,
|
|
6359
|
+
createdAt: new Date(intent.created * 1000),
|
|
6360
|
+
updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
|
|
6361
|
+
};
|
|
6479
6362
|
}
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
defaults;
|
|
6495
|
-
constructor(options) {
|
|
6496
|
-
this.client = options.client ?? new LinearClient({ apiKey: options.apiKey });
|
|
6497
|
-
this.defaults = {
|
|
6498
|
-
teamId: options.teamId,
|
|
6499
|
-
projectId: options.projectId,
|
|
6500
|
-
assigneeId: options.assigneeId,
|
|
6501
|
-
stateId: options.stateId,
|
|
6502
|
-
labelIds: options.labelIds,
|
|
6503
|
-
tagLabelMap: options.tagLabelMap
|
|
6363
|
+
toInvoice(invoice) {
|
|
6364
|
+
const metadata = this.toMetadata(invoice.metadata);
|
|
6365
|
+
return {
|
|
6366
|
+
id: invoice.id,
|
|
6367
|
+
number: invoice.number ?? undefined,
|
|
6368
|
+
status: invoice.status ?? "draft",
|
|
6369
|
+
amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
|
|
6370
|
+
amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
|
|
6371
|
+
customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
|
|
6372
|
+
dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
|
|
6373
|
+
hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
|
|
6374
|
+
metadata,
|
|
6375
|
+
createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
|
|
6376
|
+
updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
|
|
6504
6377
|
};
|
|
6505
6378
|
}
|
|
6506
|
-
|
|
6507
|
-
const teamId = this.defaults.teamId;
|
|
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;
|
|
6379
|
+
toMoney(amount, currency) {
|
|
6524
6380
|
return {
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
url: issue?.url ?? undefined,
|
|
6528
|
-
status: state?.name ?? undefined,
|
|
6529
|
-
priority: input.priority,
|
|
6530
|
-
tags: input.tags,
|
|
6531
|
-
projectId: input.projectId ?? this.defaults.projectId,
|
|
6532
|
-
externalId: input.externalId,
|
|
6533
|
-
metadata: input.metadata
|
|
6381
|
+
amount,
|
|
6382
|
+
currency: currency?.toUpperCase() ?? "USD"
|
|
6534
6383
|
};
|
|
6535
6384
|
}
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6385
|
+
toMetadata(metadata) {
|
|
6386
|
+
if (!metadata)
|
|
6387
|
+
return;
|
|
6388
|
+
const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
|
|
6389
|
+
if (entries.length === 0)
|
|
6390
|
+
return;
|
|
6391
|
+
return Object.fromEntries(entries);
|
|
6392
|
+
}
|
|
6393
|
+
mergeMetadata(base, extras) {
|
|
6394
|
+
const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
|
|
6395
|
+
if (!base && filteredExtras.length === 0) {
|
|
6396
|
+
return;
|
|
6540
6397
|
}
|
|
6541
|
-
return
|
|
6398
|
+
return {
|
|
6399
|
+
...base ?? {},
|
|
6400
|
+
...Object.fromEntries(filteredExtras)
|
|
6401
|
+
};
|
|
6542
6402
|
}
|
|
6543
6403
|
}
|
|
6544
|
-
function
|
|
6545
|
-
if (!
|
|
6404
|
+
function mapRefundReason(reason) {
|
|
6405
|
+
if (!reason)
|
|
6546
6406
|
return;
|
|
6547
|
-
|
|
6407
|
+
const allowed = [
|
|
6408
|
+
"duplicate",
|
|
6409
|
+
"fraudulent",
|
|
6410
|
+
"requested_by_customer"
|
|
6411
|
+
];
|
|
6412
|
+
return allowed.includes(reason) ? reason : undefined;
|
|
6548
6413
|
}
|
|
6549
|
-
function
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6414
|
+
function mapPaymentIntentStatus(status) {
|
|
6415
|
+
switch (status) {
|
|
6416
|
+
case "requires_payment_method":
|
|
6417
|
+
return "requires_payment_method";
|
|
6418
|
+
case "requires_confirmation":
|
|
6419
|
+
return "requires_confirmation";
|
|
6420
|
+
case "requires_action":
|
|
6421
|
+
case "requires_capture":
|
|
6422
|
+
return "requires_action";
|
|
6423
|
+
case "processing":
|
|
6424
|
+
return "processing";
|
|
6425
|
+
case "succeeded":
|
|
6426
|
+
return "succeeded";
|
|
6427
|
+
case "canceled":
|
|
6428
|
+
return "canceled";
|
|
6429
|
+
default:
|
|
6430
|
+
return "requires_payment_method";
|
|
6431
|
+
}
|
|
6432
|
+
}
|
|
6433
|
+
function mapRefundStatus(status) {
|
|
6434
|
+
switch (status) {
|
|
6435
|
+
case "pending":
|
|
6436
|
+
case "succeeded":
|
|
6437
|
+
case "failed":
|
|
6438
|
+
case "canceled":
|
|
6439
|
+
return status;
|
|
6440
|
+
default:
|
|
6441
|
+
return "pending";
|
|
6442
|
+
}
|
|
6443
|
+
}
|
|
6444
|
+
function mapChargeStatus(status) {
|
|
6445
|
+
switch (status) {
|
|
6446
|
+
case "pending":
|
|
6447
|
+
case "processing":
|
|
6448
|
+
return "pending";
|
|
6449
|
+
case "succeeded":
|
|
6450
|
+
return "succeeded";
|
|
6451
|
+
case "failed":
|
|
6452
|
+
case "canceled":
|
|
6453
|
+
return "failed";
|
|
6454
|
+
default:
|
|
6455
|
+
return "pending";
|
|
6558
6456
|
}
|
|
6559
|
-
const merged = [...labelIds];
|
|
6560
|
-
return merged.length > 0 ? merged : undefined;
|
|
6561
6457
|
}
|
|
6562
6458
|
|
|
6563
|
-
// src/impls/
|
|
6459
|
+
// src/impls/supabase-psql.ts
|
|
6564
6460
|
import { Buffer as Buffer5 } from "buffer";
|
|
6461
|
+
import { sql as drizzleSql } from "drizzle-orm";
|
|
6462
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
6463
|
+
import postgres from "postgres";
|
|
6565
6464
|
|
|
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.");
|
|
6465
|
+
class SupabasePostgresProvider {
|
|
6466
|
+
client;
|
|
6467
|
+
db;
|
|
6468
|
+
ownsClient;
|
|
6469
|
+
createDrizzle;
|
|
6470
|
+
constructor(options = {}) {
|
|
6471
|
+
this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
|
|
6472
|
+
if (options.db) {
|
|
6473
|
+
if (!options.client) {
|
|
6474
|
+
throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
|
|
6475
|
+
}
|
|
6476
|
+
this.client = options.client;
|
|
6477
|
+
this.db = options.db;
|
|
6478
|
+
this.ownsClient = false;
|
|
6479
|
+
return;
|
|
6586
6480
|
}
|
|
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
|
|
6481
|
+
if (options.client) {
|
|
6482
|
+
this.client = options.client;
|
|
6483
|
+
this.ownsClient = false;
|
|
6484
|
+
} else {
|
|
6485
|
+
if (!options.connectionString) {
|
|
6486
|
+
throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
|
|
6601
6487
|
}
|
|
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}`);
|
|
6488
|
+
this.client = postgres(options.connectionString, {
|
|
6489
|
+
max: options.maxConnections,
|
|
6490
|
+
prepare: false,
|
|
6491
|
+
ssl: resolveSslMode(options.sslMode)
|
|
6492
|
+
});
|
|
6493
|
+
this.ownsClient = true;
|
|
6615
6494
|
}
|
|
6616
|
-
|
|
6495
|
+
this.db = this.createDrizzle(this.client);
|
|
6496
|
+
}
|
|
6497
|
+
async query(statement, params = []) {
|
|
6498
|
+
const query = buildParameterizedSql(statement, params);
|
|
6499
|
+
const result = await this.db.execute(query);
|
|
6500
|
+
const rows = asRows(result);
|
|
6617
6501
|
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
|
|
6502
|
+
rows,
|
|
6503
|
+
rowCount: rows.length
|
|
6627
6504
|
};
|
|
6628
6505
|
}
|
|
6629
|
-
async
|
|
6630
|
-
const
|
|
6631
|
-
|
|
6632
|
-
|
|
6506
|
+
async execute(statement, params = []) {
|
|
6507
|
+
const query = buildParameterizedSql(statement, params);
|
|
6508
|
+
await this.db.execute(query);
|
|
6509
|
+
}
|
|
6510
|
+
async transaction(run) {
|
|
6511
|
+
const transactionResult = this.client.begin(async (transactionClient) => {
|
|
6512
|
+
const transactionalProvider = new SupabasePostgresProvider({
|
|
6513
|
+
client: transactionClient,
|
|
6514
|
+
db: this.createDrizzle(transactionClient),
|
|
6515
|
+
createDrizzle: this.createDrizzle
|
|
6516
|
+
});
|
|
6517
|
+
return run(transactionalProvider);
|
|
6518
|
+
});
|
|
6519
|
+
return transactionResult;
|
|
6520
|
+
}
|
|
6521
|
+
async close() {
|
|
6522
|
+
if (this.ownsClient) {
|
|
6523
|
+
await this.client.end({ timeout: 5 });
|
|
6633
6524
|
}
|
|
6634
|
-
return created;
|
|
6635
6525
|
}
|
|
6636
6526
|
}
|
|
6637
|
-
function
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
const
|
|
6642
|
-
|
|
6527
|
+
function buildParameterizedSql(statement, params) {
|
|
6528
|
+
const segments = [];
|
|
6529
|
+
const pattern = /\$(\d+)/g;
|
|
6530
|
+
let cursor = 0;
|
|
6531
|
+
for (const match of statement.matchAll(pattern)) {
|
|
6532
|
+
const token = match[0];
|
|
6533
|
+
const indexPart = match[1];
|
|
6534
|
+
const start = match.index;
|
|
6535
|
+
if (indexPart == null || start == null)
|
|
6536
|
+
continue;
|
|
6537
|
+
const parameterIndex = Number(indexPart) - 1;
|
|
6538
|
+
if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
|
|
6539
|
+
throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
|
|
6540
|
+
}
|
|
6541
|
+
const staticSegment = statement.slice(cursor, start);
|
|
6542
|
+
if (staticSegment.length > 0) {
|
|
6543
|
+
segments.push(drizzleSql.raw(staticSegment));
|
|
6544
|
+
}
|
|
6545
|
+
const parameterValue = params[parameterIndex];
|
|
6546
|
+
if (parameterValue === undefined) {
|
|
6547
|
+
throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
|
|
6548
|
+
}
|
|
6549
|
+
const normalizedValue = normalizeParam(parameterValue);
|
|
6550
|
+
segments.push(drizzleSql`${normalizedValue}`);
|
|
6551
|
+
cursor = start + token.length;
|
|
6552
|
+
}
|
|
6553
|
+
const tailSegment = statement.slice(cursor);
|
|
6554
|
+
if (tailSegment.length > 0) {
|
|
6555
|
+
segments.push(drizzleSql.raw(tailSegment));
|
|
6556
|
+
}
|
|
6557
|
+
if (segments.length === 0) {
|
|
6558
|
+
return drizzleSql.raw("");
|
|
6559
|
+
}
|
|
6560
|
+
return drizzleSql.join(segments);
|
|
6643
6561
|
}
|
|
6644
|
-
function
|
|
6645
|
-
if (
|
|
6646
|
-
return
|
|
6562
|
+
function normalizeParam(value) {
|
|
6563
|
+
if (typeof value === "bigint") {
|
|
6564
|
+
return value.toString();
|
|
6647
6565
|
}
|
|
6648
|
-
|
|
6566
|
+
if (value instanceof Uint8Array) {
|
|
6567
|
+
return Buffer5.from(value);
|
|
6568
|
+
}
|
|
6569
|
+
if (isPlainObject(value)) {
|
|
6570
|
+
return JSON.stringify(value);
|
|
6571
|
+
}
|
|
6572
|
+
return value;
|
|
6649
6573
|
}
|
|
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;
|
|
6574
|
+
function asRows(result) {
|
|
6575
|
+
if (!Array.isArray(result)) {
|
|
6576
|
+
return [];
|
|
6663
6577
|
}
|
|
6578
|
+
return result;
|
|
6664
6579
|
}
|
|
6665
|
-
function
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6580
|
+
function isPlainObject(value) {
|
|
6581
|
+
if (value == null || typeof value !== "object") {
|
|
6582
|
+
return false;
|
|
6583
|
+
}
|
|
6584
|
+
if (Array.isArray(value)) {
|
|
6585
|
+
return false;
|
|
6586
|
+
}
|
|
6587
|
+
if (value instanceof Date) {
|
|
6588
|
+
return false;
|
|
6589
|
+
}
|
|
6590
|
+
if (value instanceof Uint8Array) {
|
|
6591
|
+
return false;
|
|
6592
|
+
}
|
|
6593
|
+
return true;
|
|
6671
6594
|
}
|
|
6672
|
-
function
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
return { type: "doc", version: 1, content };
|
|
6595
|
+
function resolveSslMode(mode) {
|
|
6596
|
+
switch (mode) {
|
|
6597
|
+
case "allow":
|
|
6598
|
+
return false;
|
|
6599
|
+
case "prefer":
|
|
6600
|
+
return "prefer";
|
|
6601
|
+
case "require":
|
|
6602
|
+
default:
|
|
6603
|
+
return "require";
|
|
6604
|
+
}
|
|
6683
6605
|
}
|
|
6684
6606
|
|
|
6685
|
-
// src/impls/
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6607
|
+
// src/impls/supabase-vector.ts
|
|
6608
|
+
class SupabaseVectorProvider {
|
|
6609
|
+
database;
|
|
6610
|
+
createTableIfMissing;
|
|
6611
|
+
distanceMetric;
|
|
6612
|
+
quotedSchema;
|
|
6613
|
+
qualifiedTable;
|
|
6614
|
+
collectionIndex;
|
|
6615
|
+
namespaceIndex;
|
|
6616
|
+
ensureTablePromise;
|
|
6691
6617
|
constructor(options) {
|
|
6692
|
-
this.
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
}
|
|
6618
|
+
this.database = options.database ?? new SupabasePostgresProvider({
|
|
6619
|
+
connectionString: options.connectionString,
|
|
6620
|
+
maxConnections: options.maxConnections,
|
|
6621
|
+
sslMode: options.sslMode
|
|
6622
|
+
});
|
|
6623
|
+
this.createTableIfMissing = options.createTableIfMissing ?? true;
|
|
6624
|
+
this.distanceMetric = options.distanceMetric ?? "cosine";
|
|
6625
|
+
const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
|
|
6626
|
+
const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
|
|
6627
|
+
this.quotedSchema = quoteIdentifier(schema);
|
|
6628
|
+
this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
|
|
6629
|
+
this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
|
|
6630
|
+
this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
|
|
6703
6631
|
}
|
|
6704
|
-
async
|
|
6705
|
-
if (
|
|
6706
|
-
return
|
|
6632
|
+
async upsert(request) {
|
|
6633
|
+
if (request.documents.length === 0) {
|
|
6634
|
+
return;
|
|
6707
6635
|
}
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
throw new Error("Notion databaseId is required to create work items.");
|
|
6636
|
+
if (this.createTableIfMissing) {
|
|
6637
|
+
await this.ensureTable();
|
|
6711
6638
|
}
|
|
6712
|
-
const
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6639
|
+
for (const document of request.documents) {
|
|
6640
|
+
await this.database.execute(`INSERT INTO ${this.qualifiedTable}
|
|
6641
|
+
(collection, id, embedding, payload, namespace, expires_at, updated_at)
|
|
6642
|
+
VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
|
|
6643
|
+
ON CONFLICT (collection, id)
|
|
6644
|
+
DO UPDATE SET
|
|
6645
|
+
embedding = EXCLUDED.embedding,
|
|
6646
|
+
payload = EXCLUDED.payload,
|
|
6647
|
+
namespace = EXCLUDED.namespace,
|
|
6648
|
+
expires_at = EXCLUDED.expires_at,
|
|
6649
|
+
updated_at = now();`, [
|
|
6650
|
+
request.collection,
|
|
6651
|
+
document.id,
|
|
6652
|
+
toVectorLiteral(document.vector),
|
|
6653
|
+
document.payload ? JSON.stringify(document.payload) : null,
|
|
6654
|
+
document.namespace ?? null,
|
|
6655
|
+
document.expiresAt ?? null
|
|
6656
|
+
]);
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
async search(query) {
|
|
6660
|
+
const operator = this.distanceOperator;
|
|
6661
|
+
const results = await this.database.query(`SELECT
|
|
6662
|
+
id,
|
|
6663
|
+
payload,
|
|
6664
|
+
namespace,
|
|
6665
|
+
(embedding ${operator} $3::vector) AS distance
|
|
6666
|
+
FROM ${this.qualifiedTable}
|
|
6667
|
+
WHERE collection = $1
|
|
6668
|
+
AND ($2::text IS NULL OR namespace = $2)
|
|
6669
|
+
AND (expires_at IS NULL OR expires_at > now())
|
|
6670
|
+
AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
|
|
6671
|
+
ORDER BY embedding ${operator} $3::vector
|
|
6672
|
+
LIMIT $5;`, [
|
|
6673
|
+
query.collection,
|
|
6674
|
+
query.namespace ?? null,
|
|
6675
|
+
toVectorLiteral(query.vector),
|
|
6676
|
+
query.filter ? JSON.stringify(query.filter) : null,
|
|
6677
|
+
query.topK
|
|
6678
|
+
]);
|
|
6679
|
+
const mapped = results.rows.map((row) => {
|
|
6680
|
+
const distance = Number(row.distance);
|
|
6681
|
+
return {
|
|
6682
|
+
id: row.id,
|
|
6683
|
+
score: distanceToScore(distance, this.distanceMetric),
|
|
6684
|
+
payload: isRecord(row.payload) ? row.payload : undefined,
|
|
6685
|
+
namespace: row.namespace ?? undefined
|
|
6686
|
+
};
|
|
6724
6687
|
});
|
|
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
|
-
};
|
|
6688
|
+
const scoreThreshold = query.scoreThreshold;
|
|
6689
|
+
if (scoreThreshold == null) {
|
|
6690
|
+
return mapped;
|
|
6691
|
+
}
|
|
6692
|
+
return mapped.filter((result) => result.score >= scoreThreshold);
|
|
6736
6693
|
}
|
|
6737
|
-
async
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
created.push(await this.createWorkItem(item));
|
|
6694
|
+
async delete(request) {
|
|
6695
|
+
if (request.ids.length === 0) {
|
|
6696
|
+
return;
|
|
6741
6697
|
}
|
|
6742
|
-
|
|
6698
|
+
const params = [
|
|
6699
|
+
request.collection,
|
|
6700
|
+
request.ids,
|
|
6701
|
+
request.namespace ?? null
|
|
6702
|
+
];
|
|
6703
|
+
await this.database.execute(`DELETE FROM ${this.qualifiedTable}
|
|
6704
|
+
WHERE collection = $1
|
|
6705
|
+
AND id = ANY($2::text[])
|
|
6706
|
+
AND ($3::text IS NULL OR namespace = $3);`, params);
|
|
6743
6707
|
}
|
|
6744
|
-
async
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
throw new Error("Notion summaryParentPageId is required for summaries.");
|
|
6708
|
+
async ensureTable() {
|
|
6709
|
+
if (!this.ensureTablePromise) {
|
|
6710
|
+
this.ensureTablePromise = this.createTable();
|
|
6748
6711
|
}
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6712
|
+
await this.ensureTablePromise;
|
|
6713
|
+
}
|
|
6714
|
+
async createTable() {
|
|
6715
|
+
await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
6716
|
+
await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
|
|
6717
|
+
await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
|
|
6718
|
+
collection text NOT NULL,
|
|
6719
|
+
id text NOT NULL,
|
|
6720
|
+
embedding vector NOT NULL,
|
|
6721
|
+
payload jsonb,
|
|
6722
|
+
namespace text,
|
|
6723
|
+
expires_at timestamptz,
|
|
6724
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
6725
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
6726
|
+
PRIMARY KEY (collection, id)
|
|
6727
|
+
);`);
|
|
6728
|
+
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
|
|
6729
|
+
ON ${this.qualifiedTable} (collection);`);
|
|
6730
|
+
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
|
|
6731
|
+
ON ${this.qualifiedTable} (namespace);`);
|
|
6732
|
+
}
|
|
6733
|
+
get distanceOperator() {
|
|
6734
|
+
switch (this.distanceMetric) {
|
|
6735
|
+
case "l2":
|
|
6736
|
+
return "<->";
|
|
6737
|
+
case "inner_product":
|
|
6738
|
+
return "<#>";
|
|
6739
|
+
case "cosine":
|
|
6740
|
+
default:
|
|
6741
|
+
return "<=>";
|
|
6764
6742
|
}
|
|
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
6743
|
}
|
|
6777
6744
|
}
|
|
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;
|
|
6745
|
+
function sanitizeIdentifier(value, label) {
|
|
6746
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
6747
|
+
throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
|
|
6748
|
+
}
|
|
6749
|
+
return value;
|
|
6805
6750
|
}
|
|
6806
|
-
function
|
|
6807
|
-
|
|
6808
|
-
return;
|
|
6809
|
-
const next = {
|
|
6810
|
-
multi_select: values.map((value) => ({ name: value }))
|
|
6811
|
-
};
|
|
6812
|
-
properties[property] = next;
|
|
6751
|
+
function quoteIdentifier(value) {
|
|
6752
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
6813
6753
|
}
|
|
6814
|
-
function
|
|
6815
|
-
if (
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6754
|
+
function toVectorLiteral(vector) {
|
|
6755
|
+
if (vector.length === 0) {
|
|
6756
|
+
throw new Error("Supabase vectors must contain at least one dimension.");
|
|
6757
|
+
}
|
|
6758
|
+
for (const value of vector) {
|
|
6759
|
+
if (!Number.isFinite(value)) {
|
|
6760
|
+
throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
return `[${vector.join(",")}]`;
|
|
6821
6764
|
}
|
|
6822
|
-
function
|
|
6823
|
-
|
|
6824
|
-
return;
|
|
6825
|
-
properties[property] = buildRichText(value);
|
|
6765
|
+
function isRecord(value) {
|
|
6766
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6826
6767
|
}
|
|
6827
|
-
function
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
}
|
|
6838
|
-
]
|
|
6839
|
-
}
|
|
6840
|
-
}));
|
|
6768
|
+
function distanceToScore(distance, metric) {
|
|
6769
|
+
switch (metric) {
|
|
6770
|
+
case "inner_product":
|
|
6771
|
+
return -distance;
|
|
6772
|
+
case "l2":
|
|
6773
|
+
return 1 / (1 + distance);
|
|
6774
|
+
case "cosine":
|
|
6775
|
+
default:
|
|
6776
|
+
return 1 - distance;
|
|
6777
|
+
}
|
|
6841
6778
|
}
|
|
6842
6779
|
|
|
6843
6780
|
// src/impls/tldv-meeting-recorder.ts
|
|
@@ -6983,11 +6920,74 @@ async function safeReadError4(response) {
|
|
|
6983
6920
|
}
|
|
6984
6921
|
}
|
|
6985
6922
|
|
|
6923
|
+
// src/impls/twilio-sms.ts
|
|
6924
|
+
import Twilio from "twilio";
|
|
6925
|
+
|
|
6926
|
+
class TwilioSmsProvider {
|
|
6927
|
+
client;
|
|
6928
|
+
fromNumber;
|
|
6929
|
+
constructor(options) {
|
|
6930
|
+
this.client = options.client ?? Twilio(options.accountSid, options.authToken);
|
|
6931
|
+
this.fromNumber = options.fromNumber;
|
|
6932
|
+
}
|
|
6933
|
+
async sendSms(input) {
|
|
6934
|
+
const message = await this.client.messages.create({
|
|
6935
|
+
to: input.to,
|
|
6936
|
+
from: input.from ?? this.fromNumber,
|
|
6937
|
+
body: input.body
|
|
6938
|
+
});
|
|
6939
|
+
return {
|
|
6940
|
+
id: message.sid,
|
|
6941
|
+
to: message.to ?? input.to,
|
|
6942
|
+
from: message.from ?? input.from ?? this.fromNumber ?? "",
|
|
6943
|
+
body: message.body ?? input.body,
|
|
6944
|
+
status: mapStatus(message.status),
|
|
6945
|
+
sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
|
|
6946
|
+
deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
|
|
6947
|
+
price: message.price ? Number(message.price) : undefined,
|
|
6948
|
+
priceCurrency: message.priceUnit ?? undefined,
|
|
6949
|
+
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
6950
|
+
errorMessage: message.errorMessage ?? undefined
|
|
6951
|
+
};
|
|
6952
|
+
}
|
|
6953
|
+
async getDeliveryStatus(messageId) {
|
|
6954
|
+
const message = await this.client.messages(messageId).fetch();
|
|
6955
|
+
return {
|
|
6956
|
+
status: mapStatus(message.status),
|
|
6957
|
+
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
6958
|
+
errorMessage: message.errorMessage ?? undefined,
|
|
6959
|
+
updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
|
|
6960
|
+
};
|
|
6961
|
+
}
|
|
6962
|
+
}
|
|
6963
|
+
function mapStatus(status) {
|
|
6964
|
+
switch (status) {
|
|
6965
|
+
case "queued":
|
|
6966
|
+
case "accepted":
|
|
6967
|
+
case "scheduled":
|
|
6968
|
+
return "queued";
|
|
6969
|
+
case "sending":
|
|
6970
|
+
case "processing":
|
|
6971
|
+
return "sending";
|
|
6972
|
+
case "sent":
|
|
6973
|
+
return "sent";
|
|
6974
|
+
case "delivered":
|
|
6975
|
+
return "delivered";
|
|
6976
|
+
case "undelivered":
|
|
6977
|
+
return "undelivered";
|
|
6978
|
+
case "failed":
|
|
6979
|
+
case "canceled":
|
|
6980
|
+
return "failed";
|
|
6981
|
+
default:
|
|
6982
|
+
return "queued";
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6985
|
+
|
|
6986
6986
|
// src/impls/provider-factory.ts
|
|
6987
6987
|
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
6988
|
import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
|
|
6989
|
+
import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
|
|
6990
|
+
import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
|
|
6991
6991
|
var SECRET_CACHE = new Map;
|
|
6992
6992
|
|
|
6993
6993
|
class IntegrationProviderFactory {
|