@contractspec/integration.providers-impls 3.7.6 → 3.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -241
- package/dist/impls/composio-fallback-resolver.d.ts +4 -4
- package/dist/impls/composio-fallback-resolver.js +73 -73
- package/dist/impls/composio-mcp.d.ts +1 -1
- package/dist/impls/composio-proxies.d.ts +5 -5
- package/dist/impls/composio-sdk.d.ts +1 -1
- package/dist/impls/elevenlabs-voice.d.ts +1 -1
- package/dist/impls/fal-voice.d.ts +1 -1
- package/dist/impls/gcs-storage.d.ts +1 -1
- package/dist/impls/gmail-inbound.d.ts +1 -1
- package/dist/impls/gradium-voice.d.ts +2 -2
- package/dist/impls/health/base-health-provider.d.ts +1 -1
- package/dist/impls/health/official-health-providers.d.ts +1 -1
- package/dist/impls/health/providers.d.ts +1 -1
- package/dist/impls/health-provider-factory.d.ts +1 -1
- package/dist/impls/index.d.ts +31 -30
- package/dist/impls/index.js +2189 -2138
- package/dist/impls/messaging-telegram.d.ts +13 -0
- package/dist/impls/messaging-telegram.js +49 -0
- package/dist/impls/mistral-conversational.d.ts +1 -1
- package/dist/impls/mistral-conversational.js +159 -159
- package/dist/impls/posthog-reader.d.ts +1 -1
- package/dist/impls/provider-factory.d.ts +11 -11
- package/dist/impls/provider-factory.js +2116 -2066
- package/dist/index.d.ts +12 -12
- package/dist/index.js +2197 -2146
- package/dist/node/impls/composio-fallback-resolver.js +73 -73
- package/dist/node/impls/index.js +2189 -2138
- package/dist/node/impls/messaging-telegram.js +49 -0
- package/dist/node/impls/mistral-conversational.js +159 -159
- package/dist/node/impls/provider-factory.js +2116 -2066
- package/dist/node/index.js +2197 -2146
- package/dist/node/secrets/provider.js +2 -2
- package/dist/secrets/provider.d.ts +2 -2
- package/dist/secrets/provider.js +2 -2
- package/package.json +25 -13
|
@@ -2816,476 +2816,563 @@ function asString(value) {
|
|
|
2816
2816
|
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
2817
2817
|
}
|
|
2818
2818
|
|
|
2819
|
-
// src/impls/
|
|
2820
|
-
import {
|
|
2819
|
+
// src/impls/jira.ts
|
|
2820
|
+
import { Buffer as Buffer3 } from "node:buffer";
|
|
2821
2821
|
|
|
2822
|
-
class
|
|
2823
|
-
|
|
2824
|
-
|
|
2822
|
+
class JiraProjectManagementProvider {
|
|
2823
|
+
siteUrl;
|
|
2824
|
+
authHeader;
|
|
2825
|
+
defaults;
|
|
2826
|
+
fetchFn;
|
|
2825
2827
|
constructor(options) {
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
+
this.siteUrl = normalizeSiteUrl(options.siteUrl);
|
|
2829
|
+
this.authHeader = buildAuthHeader(options.email, options.apiToken);
|
|
2830
|
+
this.defaults = {
|
|
2831
|
+
projectKey: options.projectKey,
|
|
2832
|
+
issueType: options.issueType,
|
|
2833
|
+
defaultLabels: options.defaultLabels,
|
|
2834
|
+
issueTypeMap: options.issueTypeMap
|
|
2835
|
+
};
|
|
2836
|
+
this.fetchFn = options.fetch ?? fetch;
|
|
2837
|
+
}
|
|
2838
|
+
async createWorkItem(input) {
|
|
2839
|
+
const projectKey = input.projectId ?? this.defaults.projectKey;
|
|
2840
|
+
if (!projectKey) {
|
|
2841
|
+
throw new Error("Jira projectKey is required to create work items.");
|
|
2828
2842
|
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2843
|
+
const issueType = resolveIssueType(input.type, this.defaults);
|
|
2844
|
+
const description = buildJiraDescription(input.description);
|
|
2845
|
+
const labels = mergeLabels(this.defaults.defaultLabels, input.tags);
|
|
2846
|
+
const priority = mapPriority(input.priority);
|
|
2847
|
+
const payload = {
|
|
2848
|
+
fields: {
|
|
2849
|
+
project: { key: projectKey },
|
|
2850
|
+
summary: input.title,
|
|
2851
|
+
description,
|
|
2852
|
+
issuetype: { name: issueType },
|
|
2853
|
+
labels,
|
|
2854
|
+
priority: priority ? { name: priority } : undefined,
|
|
2855
|
+
assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
|
|
2856
|
+
duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
|
|
2857
|
+
}
|
|
2858
|
+
};
|
|
2859
|
+
const response = await this.fetchFn(`${this.siteUrl}/rest/api/3/issue`, {
|
|
2860
|
+
method: "POST",
|
|
2861
|
+
headers: {
|
|
2862
|
+
Authorization: this.authHeader,
|
|
2863
|
+
"Content-Type": "application/json",
|
|
2864
|
+
Accept: "application/json"
|
|
2865
|
+
},
|
|
2866
|
+
body: JSON.stringify(payload)
|
|
2833
2867
|
});
|
|
2834
|
-
|
|
2868
|
+
if (!response.ok) {
|
|
2869
|
+
const body = await response.text();
|
|
2870
|
+
throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
|
|
2871
|
+
}
|
|
2872
|
+
const data = await response.json();
|
|
2873
|
+
return {
|
|
2874
|
+
id: data.id ?? data.key ?? "",
|
|
2875
|
+
title: input.title,
|
|
2876
|
+
url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
|
|
2877
|
+
status: input.status,
|
|
2878
|
+
priority: input.priority,
|
|
2879
|
+
tags: input.tags,
|
|
2880
|
+
projectId: projectKey,
|
|
2881
|
+
externalId: input.externalId,
|
|
2882
|
+
metadata: input.metadata
|
|
2883
|
+
};
|
|
2835
2884
|
}
|
|
2836
|
-
async
|
|
2837
|
-
const
|
|
2838
|
-
const
|
|
2839
|
-
|
|
2885
|
+
async createWorkItems(items) {
|
|
2886
|
+
const created = [];
|
|
2887
|
+
for (const item of items) {
|
|
2888
|
+
created.push(await this.createWorkItem(item));
|
|
2889
|
+
}
|
|
2890
|
+
return created;
|
|
2840
2891
|
}
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2892
|
+
}
|
|
2893
|
+
function normalizeSiteUrl(siteUrl) {
|
|
2894
|
+
return siteUrl.replace(/\/$/, "");
|
|
2895
|
+
}
|
|
2896
|
+
function buildAuthHeader(email, apiToken) {
|
|
2897
|
+
const token = Buffer3.from(`${email}:${apiToken}`).toString("base64");
|
|
2898
|
+
return `Basic ${token}`;
|
|
2899
|
+
}
|
|
2900
|
+
function resolveIssueType(type, defaults) {
|
|
2901
|
+
if (type && defaults.issueTypeMap?.[type]) {
|
|
2902
|
+
return defaults.issueTypeMap[type] ?? defaults.issueType ?? "Task";
|
|
2903
|
+
}
|
|
2904
|
+
return defaults.issueType ?? "Task";
|
|
2905
|
+
}
|
|
2906
|
+
function mapPriority(priority) {
|
|
2907
|
+
switch (priority) {
|
|
2908
|
+
case "urgent":
|
|
2909
|
+
return "Highest";
|
|
2910
|
+
case "high":
|
|
2911
|
+
return "High";
|
|
2912
|
+
case "medium":
|
|
2913
|
+
return "Medium";
|
|
2914
|
+
case "low":
|
|
2915
|
+
return "Low";
|
|
2916
|
+
case "none":
|
|
2917
|
+
default:
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
function mergeLabels(defaults, tags) {
|
|
2922
|
+
const merged = new Set;
|
|
2923
|
+
(defaults ?? []).forEach((label) => merged.add(label));
|
|
2924
|
+
(tags ?? []).forEach((tag) => merged.add(tag));
|
|
2925
|
+
const result = [...merged];
|
|
2926
|
+
return result.length > 0 ? result : undefined;
|
|
2927
|
+
}
|
|
2928
|
+
function buildJiraDescription(description) {
|
|
2929
|
+
if (!description)
|
|
2930
|
+
return;
|
|
2931
|
+
const lines = description.split(/\r?\n/).filter((line) => line.trim());
|
|
2932
|
+
const content = lines.map((line) => ({
|
|
2933
|
+
type: "paragraph",
|
|
2934
|
+
content: [{ type: "text", text: line }]
|
|
2935
|
+
}));
|
|
2936
|
+
if (content.length === 0)
|
|
2937
|
+
return;
|
|
2938
|
+
return { type: "doc", version: 1, content };
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/impls/linear.ts
|
|
2942
|
+
import { LinearClient } from "@linear/sdk";
|
|
2943
|
+
var PRIORITY_MAP = {
|
|
2944
|
+
urgent: 1,
|
|
2945
|
+
high: 2,
|
|
2946
|
+
medium: 3,
|
|
2947
|
+
low: 4,
|
|
2948
|
+
none: 0
|
|
2949
|
+
};
|
|
2950
|
+
|
|
2951
|
+
class LinearProjectManagementProvider {
|
|
2952
|
+
client;
|
|
2953
|
+
defaults;
|
|
2954
|
+
constructor(options) {
|
|
2955
|
+
this.client = options.client ?? new LinearClient({ apiKey: options.apiKey });
|
|
2956
|
+
this.defaults = {
|
|
2957
|
+
teamId: options.teamId,
|
|
2958
|
+
projectId: options.projectId,
|
|
2959
|
+
assigneeId: options.assigneeId,
|
|
2960
|
+
stateId: options.stateId,
|
|
2961
|
+
labelIds: options.labelIds,
|
|
2962
|
+
tagLabelMap: options.tagLabelMap
|
|
2963
|
+
};
|
|
2964
|
+
}
|
|
2965
|
+
async createWorkItem(input) {
|
|
2966
|
+
const teamId = this.defaults.teamId;
|
|
2967
|
+
if (!teamId) {
|
|
2968
|
+
throw new Error("Linear teamId is required to create work items.");
|
|
2897
2969
|
}
|
|
2898
|
-
const
|
|
2899
|
-
|
|
2900
|
-
|
|
2970
|
+
const payload = await this.client.createIssue({
|
|
2971
|
+
teamId,
|
|
2972
|
+
title: input.title,
|
|
2973
|
+
description: input.description,
|
|
2974
|
+
priority: mapPriority2(input.priority),
|
|
2975
|
+
estimate: input.estimate,
|
|
2976
|
+
assigneeId: input.assigneeId ?? this.defaults.assigneeId,
|
|
2977
|
+
projectId: input.projectId ?? this.defaults.projectId,
|
|
2978
|
+
stateId: this.defaults.stateId,
|
|
2979
|
+
labelIds: resolveLabelIds(this.defaults, input.tags)
|
|
2980
|
+
});
|
|
2981
|
+
const issue = await payload.issue;
|
|
2982
|
+
const state = issue ? await issue.state : undefined;
|
|
2983
|
+
return {
|
|
2984
|
+
id: issue?.id ?? "",
|
|
2985
|
+
title: issue?.title ?? input.title,
|
|
2986
|
+
url: issue?.url ?? undefined,
|
|
2987
|
+
status: state?.name ?? undefined,
|
|
2988
|
+
priority: input.priority,
|
|
2989
|
+
tags: input.tags,
|
|
2990
|
+
projectId: input.projectId ?? this.defaults.projectId,
|
|
2991
|
+
externalId: input.externalId,
|
|
2992
|
+
metadata: input.metadata
|
|
2901
2993
|
};
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2994
|
+
}
|
|
2995
|
+
async createWorkItems(items) {
|
|
2996
|
+
const created = [];
|
|
2997
|
+
for (const item of items) {
|
|
2998
|
+
created.push(await this.createWorkItem(item));
|
|
2907
2999
|
}
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
3000
|
+
return created;
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
function mapPriority2(priority) {
|
|
3004
|
+
if (!priority)
|
|
3005
|
+
return;
|
|
3006
|
+
return PRIORITY_MAP[priority] ?? undefined;
|
|
3007
|
+
}
|
|
3008
|
+
function resolveLabelIds(defaults, tags) {
|
|
3009
|
+
const labelIds = new Set;
|
|
3010
|
+
(defaults.labelIds ?? []).forEach((id) => labelIds.add(id));
|
|
3011
|
+
if (tags && defaults.tagLabelMap) {
|
|
3012
|
+
tags.forEach((tag) => {
|
|
3013
|
+
const mapped = defaults.tagLabelMap?.[tag];
|
|
3014
|
+
if (mapped)
|
|
3015
|
+
labelIds.add(mapped);
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
3018
|
+
const merged = [...labelIds];
|
|
3019
|
+
return merged.length > 0 ? merged : undefined;
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
// src/impls/messaging-github.ts
|
|
3023
|
+
class GithubMessagingProvider {
|
|
3024
|
+
token;
|
|
3025
|
+
defaultOwner;
|
|
3026
|
+
defaultRepo;
|
|
3027
|
+
apiBaseUrl;
|
|
3028
|
+
constructor(options) {
|
|
3029
|
+
this.token = options.token;
|
|
3030
|
+
this.defaultOwner = options.defaultOwner;
|
|
3031
|
+
this.defaultRepo = options.defaultRepo;
|
|
3032
|
+
this.apiBaseUrl = options.apiBaseUrl ?? "https://api.github.com";
|
|
3033
|
+
}
|
|
3034
|
+
async sendMessage(input) {
|
|
3035
|
+
const target = this.resolveTarget(input);
|
|
3036
|
+
const response = await fetch(`${this.apiBaseUrl}/repos/${target.owner}/${target.repo}/issues/${target.issueNumber}/comments`, {
|
|
3037
|
+
method: "POST",
|
|
3038
|
+
headers: {
|
|
3039
|
+
authorization: `Bearer ${this.token}`,
|
|
3040
|
+
accept: "application/vnd.github+json",
|
|
3041
|
+
"content-type": "application/json"
|
|
3042
|
+
},
|
|
3043
|
+
body: JSON.stringify({ body: input.text })
|
|
3044
|
+
});
|
|
3045
|
+
const body = await response.json();
|
|
3046
|
+
if (!response.ok || !body.id) {
|
|
3047
|
+
throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
3048
|
+
}
|
|
3049
|
+
return {
|
|
3050
|
+
id: String(body.id),
|
|
3051
|
+
providerMessageId: body.node_id,
|
|
3052
|
+
status: "sent",
|
|
3053
|
+
sentAt: new Date,
|
|
3054
|
+
metadata: {
|
|
3055
|
+
url: body.html_url ?? "",
|
|
3056
|
+
owner: target.owner,
|
|
3057
|
+
repo: target.repo,
|
|
3058
|
+
issueNumber: String(target.issueNumber)
|
|
2914
3059
|
}
|
|
2915
3060
|
};
|
|
2916
3061
|
}
|
|
2917
|
-
async
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
const mappedMessages = messages.map((message) => this.toMistralMessage(message));
|
|
2923
|
-
const request = {
|
|
2924
|
-
model,
|
|
2925
|
-
messages: mappedMessages
|
|
2926
|
-
};
|
|
2927
|
-
if (options.temperature != null) {
|
|
2928
|
-
request.temperature = options.temperature;
|
|
2929
|
-
}
|
|
2930
|
-
if (options.topP != null) {
|
|
2931
|
-
request.topP = options.topP;
|
|
2932
|
-
}
|
|
2933
|
-
if (options.maxOutputTokens != null) {
|
|
2934
|
-
request.maxTokens = options.maxOutputTokens;
|
|
2935
|
-
}
|
|
2936
|
-
if (options.stopSequences?.length) {
|
|
2937
|
-
request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
|
|
2938
|
-
}
|
|
2939
|
-
if (options.tools?.length) {
|
|
2940
|
-
request.tools = options.tools.map((tool) => ({
|
|
2941
|
-
type: "function",
|
|
2942
|
-
function: {
|
|
2943
|
-
name: tool.name,
|
|
2944
|
-
description: tool.description,
|
|
2945
|
-
parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
|
|
2946
|
-
}
|
|
2947
|
-
}));
|
|
2948
|
-
}
|
|
2949
|
-
if (options.responseFormat === "json") {
|
|
2950
|
-
request.responseFormat = { type: "json_object" };
|
|
3062
|
+
async updateMessage(messageId, input) {
|
|
3063
|
+
const owner = input.metadata?.owner ?? this.defaultOwner;
|
|
3064
|
+
const repo = input.metadata?.repo ?? this.defaultRepo;
|
|
3065
|
+
if (!owner || !repo) {
|
|
3066
|
+
throw new Error("GitHub updateMessage requires owner and repo metadata.");
|
|
2951
3067
|
}
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
};
|
|
3068
|
+
const response = await fetch(`${this.apiBaseUrl}/repos/${owner}/${repo}/issues/comments/${messageId}`, {
|
|
3069
|
+
method: "PATCH",
|
|
3070
|
+
headers: {
|
|
3071
|
+
authorization: `Bearer ${this.token}`,
|
|
3072
|
+
accept: "application/vnd.github+json",
|
|
3073
|
+
"content-type": "application/json"
|
|
3074
|
+
},
|
|
3075
|
+
body: JSON.stringify({ body: input.text })
|
|
3076
|
+
});
|
|
3077
|
+
const body = await response.json();
|
|
3078
|
+
if (!response.ok || !body.id) {
|
|
3079
|
+
throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
2965
3080
|
}
|
|
2966
|
-
const message = this.fromAssistantMessage(firstChoice.message);
|
|
2967
3081
|
return {
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
3082
|
+
id: String(body.id),
|
|
3083
|
+
providerMessageId: body.node_id,
|
|
3084
|
+
status: "sent",
|
|
3085
|
+
sentAt: new Date,
|
|
3086
|
+
metadata: {
|
|
3087
|
+
url: body.html_url ?? "",
|
|
3088
|
+
owner,
|
|
3089
|
+
repo
|
|
3090
|
+
}
|
|
2972
3091
|
};
|
|
2973
3092
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3093
|
+
resolveTarget(input) {
|
|
3094
|
+
const parsedRecipient = parseRecipient(input.recipientId);
|
|
3095
|
+
const owner = parsedRecipient?.owner ?? this.defaultOwner;
|
|
3096
|
+
const repo = parsedRecipient?.repo ?? this.defaultRepo;
|
|
3097
|
+
const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
|
|
3098
|
+
if (!owner || !repo || issueNumber == null) {
|
|
3099
|
+
throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
|
|
3100
|
+
}
|
|
2977
3101
|
return {
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3102
|
+
owner,
|
|
3103
|
+
repo,
|
|
3104
|
+
issueNumber
|
|
2981
3105
|
};
|
|
2982
3106
|
}
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3107
|
+
}
|
|
3108
|
+
function parseRecipient(value) {
|
|
3109
|
+
if (!value)
|
|
3110
|
+
return null;
|
|
3111
|
+
const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
3112
|
+
if (!match)
|
|
3113
|
+
return null;
|
|
3114
|
+
const owner = match[1];
|
|
3115
|
+
const repo = match[2];
|
|
3116
|
+
const issueNumber = Number(match[3]);
|
|
3117
|
+
if (!owner || !repo || !Number.isInteger(issueNumber)) {
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
3120
|
+
return { owner, repo, issueNumber };
|
|
3121
|
+
}
|
|
3122
|
+
function parseIssueNumber(value) {
|
|
3123
|
+
if (!value)
|
|
3124
|
+
return null;
|
|
3125
|
+
const numeric = Number(value);
|
|
3126
|
+
return Number.isInteger(numeric) ? numeric : null;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// src/impls/messaging-slack.ts
|
|
3130
|
+
class SlackMessagingProvider {
|
|
3131
|
+
botToken;
|
|
3132
|
+
defaultChannelId;
|
|
3133
|
+
apiBaseUrl;
|
|
3134
|
+
constructor(options) {
|
|
3135
|
+
this.botToken = options.botToken;
|
|
3136
|
+
this.defaultChannelId = options.defaultChannelId;
|
|
3137
|
+
this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
|
|
3138
|
+
}
|
|
3139
|
+
async sendMessage(input) {
|
|
3140
|
+
const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
|
|
3141
|
+
if (!channel) {
|
|
3142
|
+
throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
|
|
3000
3143
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3144
|
+
const payload = {
|
|
3145
|
+
channel,
|
|
3146
|
+
text: input.text,
|
|
3147
|
+
mrkdwn: input.markdown ?? true,
|
|
3148
|
+
thread_ts: input.threadId
|
|
3004
3149
|
};
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3150
|
+
const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
|
|
3151
|
+
method: "POST",
|
|
3152
|
+
headers: {
|
|
3153
|
+
authorization: `Bearer ${this.botToken}`,
|
|
3154
|
+
"content-type": "application/json"
|
|
3155
|
+
},
|
|
3156
|
+
body: JSON.stringify(payload)
|
|
3157
|
+
});
|
|
3158
|
+
const body = await response.json();
|
|
3159
|
+
if (!response.ok || !body.ok || !body.ts) {
|
|
3160
|
+
throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
|
|
3161
|
+
}
|
|
3008
3162
|
return {
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3163
|
+
id: `slack:${body.channel ?? channel}:${body.ts}`,
|
|
3164
|
+
providerMessageId: body.ts,
|
|
3165
|
+
status: "sent",
|
|
3166
|
+
sentAt: new Date,
|
|
3167
|
+
metadata: {
|
|
3168
|
+
channelId: body.channel ?? channel
|
|
3169
|
+
}
|
|
3013
3170
|
};
|
|
3014
3171
|
}
|
|
3015
|
-
|
|
3016
|
-
const
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
case "system":
|
|
3020
|
-
return {
|
|
3021
|
-
role: "system",
|
|
3022
|
-
content: textContent ?? ""
|
|
3023
|
-
};
|
|
3024
|
-
case "user":
|
|
3025
|
-
return {
|
|
3026
|
-
role: "user",
|
|
3027
|
-
content: textContent ?? ""
|
|
3028
|
-
};
|
|
3029
|
-
case "assistant":
|
|
3030
|
-
return {
|
|
3031
|
-
role: "assistant",
|
|
3032
|
-
content: toolCalls.length > 0 ? null : textContent ?? "",
|
|
3033
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
3034
|
-
};
|
|
3035
|
-
case "tool":
|
|
3036
|
-
return {
|
|
3037
|
-
role: "tool",
|
|
3038
|
-
content: textContent ?? "",
|
|
3039
|
-
toolCallId: message.toolCallId ?? toolCalls[0]?.id
|
|
3040
|
-
};
|
|
3041
|
-
default:
|
|
3042
|
-
return {
|
|
3043
|
-
role: "user",
|
|
3044
|
-
content: textContent ?? ""
|
|
3045
|
-
};
|
|
3172
|
+
async updateMessage(messageId, input) {
|
|
3173
|
+
const channel = input.channelId ?? this.defaultChannelId;
|
|
3174
|
+
if (!channel) {
|
|
3175
|
+
throw new Error("Slack updateMessage requires channelId or defaultChannelId.");
|
|
3046
3176
|
}
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3177
|
+
const response = await fetch(`${this.apiBaseUrl}/chat.update`, {
|
|
3178
|
+
method: "POST",
|
|
3179
|
+
headers: {
|
|
3180
|
+
authorization: `Bearer ${this.botToken}`,
|
|
3181
|
+
"content-type": "application/json"
|
|
3182
|
+
},
|
|
3183
|
+
body: JSON.stringify({
|
|
3184
|
+
channel,
|
|
3185
|
+
ts: messageId,
|
|
3186
|
+
text: input.text,
|
|
3187
|
+
mrkdwn: input.markdown ?? true
|
|
3188
|
+
})
|
|
3189
|
+
});
|
|
3190
|
+
const body = await response.json();
|
|
3191
|
+
if (!response.ok || !body.ok || !body.ts) {
|
|
3192
|
+
throw new Error(`Slack updateMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
|
|
3193
|
+
}
|
|
3194
|
+
return {
|
|
3195
|
+
id: `slack:${body.channel ?? channel}:${body.ts}`,
|
|
3196
|
+
providerMessageId: body.ts,
|
|
3197
|
+
status: "sent",
|
|
3198
|
+
sentAt: new Date,
|
|
3199
|
+
metadata: {
|
|
3200
|
+
channelId: body.channel ?? channel
|
|
3063
3201
|
}
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
}
|
|
3067
|
-
function mapFinishReason(reason) {
|
|
3068
|
-
if (!reason)
|
|
3069
|
-
return;
|
|
3070
|
-
const normalized = reason.toLowerCase();
|
|
3071
|
-
switch (normalized) {
|
|
3072
|
-
case "stop":
|
|
3073
|
-
return "stop";
|
|
3074
|
-
case "length":
|
|
3075
|
-
return "length";
|
|
3076
|
-
case "tool_call":
|
|
3077
|
-
case "tool_calls":
|
|
3078
|
-
return "tool_call";
|
|
3079
|
-
case "content_filter":
|
|
3080
|
-
return "content_filter";
|
|
3081
|
-
default:
|
|
3082
|
-
return;
|
|
3202
|
+
};
|
|
3083
3203
|
}
|
|
3084
3204
|
}
|
|
3085
3205
|
|
|
3086
|
-
// src/impls/
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
defaultModel;
|
|
3206
|
+
// src/impls/messaging-telegram.ts
|
|
3207
|
+
class TelegramMessagingProvider {
|
|
3208
|
+
botToken;
|
|
3209
|
+
defaultChatId;
|
|
3210
|
+
apiBaseUrl;
|
|
3092
3211
|
constructor(options) {
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
this.client = options.client ?? new Mistral2({
|
|
3097
|
-
apiKey: options.apiKey,
|
|
3098
|
-
serverURL: options.serverURL
|
|
3099
|
-
});
|
|
3100
|
-
this.defaultModel = options.defaultModel ?? "mistral-embed";
|
|
3212
|
+
this.botToken = options.botToken;
|
|
3213
|
+
this.defaultChatId = options.defaultChatId;
|
|
3214
|
+
this.apiBaseUrl = options.apiBaseUrl ?? "https://api.telegram.org";
|
|
3101
3215
|
}
|
|
3102
|
-
async
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3216
|
+
async sendMessage(input) {
|
|
3217
|
+
const chatId = input.channelId ?? input.recipientId ?? this.defaultChatId ?? undefined;
|
|
3218
|
+
if (!chatId) {
|
|
3219
|
+
throw new Error("Telegram sendMessage requires channelId, recipientId, or defaultChatId.");
|
|
3220
|
+
}
|
|
3221
|
+
const messageThreadId = input.threadId && input.threadId !== chatId ? Number.parseInt(input.threadId, 10) : undefined;
|
|
3222
|
+
const response = await fetch(`${this.apiBaseUrl}/bot${this.botToken}/sendMessage`, {
|
|
3223
|
+
method: "POST",
|
|
3224
|
+
headers: {
|
|
3225
|
+
"content-type": "application/json"
|
|
3226
|
+
},
|
|
3227
|
+
body: JSON.stringify({
|
|
3228
|
+
chat_id: chatId,
|
|
3229
|
+
text: input.text,
|
|
3230
|
+
message_thread_id: Number.isFinite(messageThreadId) ? messageThreadId : undefined
|
|
3231
|
+
})
|
|
3109
3232
|
});
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
model: response.model,
|
|
3115
|
-
metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
|
|
3116
|
-
}));
|
|
3117
|
-
}
|
|
3118
|
-
async embedQuery(query, options) {
|
|
3119
|
-
const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
|
|
3120
|
-
if (!result) {
|
|
3121
|
-
throw new Error("Failed to compute embedding for query");
|
|
3233
|
+
const body = await response.json();
|
|
3234
|
+
const providerMessageId = body.result?.message_id;
|
|
3235
|
+
if (!response.ok || !body.ok || providerMessageId == null) {
|
|
3236
|
+
throw new Error(`Telegram sendMessage failed: ${body.description ?? `HTTP_${response.status}`}`);
|
|
3122
3237
|
}
|
|
3123
|
-
return
|
|
3238
|
+
return {
|
|
3239
|
+
id: `telegram:${chatId}:${providerMessageId}`,
|
|
3240
|
+
providerMessageId: String(providerMessageId),
|
|
3241
|
+
status: "sent",
|
|
3242
|
+
sentAt: new Date,
|
|
3243
|
+
metadata: {
|
|
3244
|
+
chatId: String(body.result?.chat?.id ?? chatId)
|
|
3245
|
+
}
|
|
3246
|
+
};
|
|
3124
3247
|
}
|
|
3125
3248
|
}
|
|
3126
3249
|
|
|
3127
|
-
// src/impls/
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
wav: "audio/wav",
|
|
3133
|
-
ogg: "audio/ogg",
|
|
3134
|
-
pcm: "audio/pcm",
|
|
3135
|
-
opus: "audio/opus"
|
|
3136
|
-
};
|
|
3137
|
-
|
|
3138
|
-
class MistralSttProvider {
|
|
3139
|
-
apiKey;
|
|
3140
|
-
defaultModel;
|
|
3141
|
-
defaultLanguage;
|
|
3142
|
-
baseUrl;
|
|
3143
|
-
fetchImpl;
|
|
3250
|
+
// src/impls/messaging-whatsapp-meta.ts
|
|
3251
|
+
class MetaWhatsappMessagingProvider {
|
|
3252
|
+
accessToken;
|
|
3253
|
+
phoneNumberId;
|
|
3254
|
+
apiVersion;
|
|
3144
3255
|
constructor(options) {
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
this.apiKey = options.apiKey;
|
|
3149
|
-
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
|
|
3150
|
-
this.defaultLanguage = options.defaultLanguage;
|
|
3151
|
-
this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
|
|
3152
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
3256
|
+
this.accessToken = options.accessToken;
|
|
3257
|
+
this.phoneNumberId = options.phoneNumberId;
|
|
3258
|
+
this.apiVersion = options.apiVersion ?? "v22.0";
|
|
3153
3259
|
}
|
|
3154
|
-
async
|
|
3155
|
-
const
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
const fileName = `audio.${input.audio.format}`;
|
|
3159
|
-
const audioBytes = new Uint8Array(input.audio.data);
|
|
3160
|
-
const blob = new Blob([audioBytes], { type: mimeType });
|
|
3161
|
-
formData.append("file", blob, fileName);
|
|
3162
|
-
formData.append("model", model);
|
|
3163
|
-
formData.append("response_format", "verbose_json");
|
|
3164
|
-
const language = input.language ?? this.defaultLanguage;
|
|
3165
|
-
if (language) {
|
|
3166
|
-
formData.append("language", language);
|
|
3260
|
+
async sendMessage(input) {
|
|
3261
|
+
const to = input.recipientId;
|
|
3262
|
+
if (!to) {
|
|
3263
|
+
throw new Error("Meta WhatsApp sendMessage requires recipientId.");
|
|
3167
3264
|
}
|
|
3168
|
-
const response = await this.
|
|
3265
|
+
const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
|
|
3169
3266
|
method: "POST",
|
|
3170
3267
|
headers: {
|
|
3171
|
-
|
|
3268
|
+
authorization: `Bearer ${this.accessToken}`,
|
|
3269
|
+
"content-type": "application/json"
|
|
3172
3270
|
},
|
|
3173
|
-
body:
|
|
3271
|
+
body: JSON.stringify({
|
|
3272
|
+
messaging_product: "whatsapp",
|
|
3273
|
+
to,
|
|
3274
|
+
type: "text",
|
|
3275
|
+
text: {
|
|
3276
|
+
body: input.text,
|
|
3277
|
+
preview_url: false
|
|
3278
|
+
}
|
|
3279
|
+
})
|
|
3174
3280
|
});
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3281
|
+
const body = await response.json();
|
|
3282
|
+
const messageId = body.messages?.[0]?.id;
|
|
3283
|
+
if (!response.ok || !messageId) {
|
|
3284
|
+
const errorCode = body.error?.code != null ? String(body.error.code) : "";
|
|
3285
|
+
throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
|
|
3178
3286
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
if (segments.length === 0 && text.length > 0) {
|
|
3189
|
-
segments.push({
|
|
3190
|
-
text,
|
|
3191
|
-
startMs: 0,
|
|
3192
|
-
endMs: input.audio.durationMs ?? 0
|
|
3193
|
-
});
|
|
3287
|
+
return {
|
|
3288
|
+
id: messageId,
|
|
3289
|
+
providerMessageId: messageId,
|
|
3290
|
+
status: "sent",
|
|
3291
|
+
sentAt: new Date,
|
|
3292
|
+
metadata: {
|
|
3293
|
+
phoneNumberId: this.phoneNumberId
|
|
3294
|
+
}
|
|
3295
|
+
};
|
|
3194
3296
|
}
|
|
3195
|
-
const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
|
|
3196
|
-
const topLevelWords = parseWordTimings(record.words);
|
|
3197
|
-
const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
|
|
3198
|
-
const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
|
|
3199
|
-
const speakers = dedupeSpeakers(segments);
|
|
3200
|
-
return {
|
|
3201
|
-
text,
|
|
3202
|
-
segments,
|
|
3203
|
-
language,
|
|
3204
|
-
durationMs,
|
|
3205
|
-
speakers: speakers.length > 0 ? speakers : undefined,
|
|
3206
|
-
wordTimings
|
|
3207
|
-
};
|
|
3208
3297
|
}
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3298
|
+
|
|
3299
|
+
// src/impls/messaging-whatsapp-twilio.ts
|
|
3300
|
+
import { Buffer as Buffer4 } from "node:buffer";
|
|
3301
|
+
|
|
3302
|
+
class TwilioWhatsappMessagingProvider {
|
|
3303
|
+
accountSid;
|
|
3304
|
+
authToken;
|
|
3305
|
+
fromNumber;
|
|
3306
|
+
constructor(options) {
|
|
3307
|
+
this.accountSid = options.accountSid;
|
|
3308
|
+
this.authToken = options.authToken;
|
|
3309
|
+
this.fromNumber = options.fromNumber;
|
|
3212
3310
|
}
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
const
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
continue;
|
|
3311
|
+
async sendMessage(input) {
|
|
3312
|
+
const to = normalizeWhatsappAddress(input.recipientId);
|
|
3313
|
+
const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
|
|
3314
|
+
if (!to) {
|
|
3315
|
+
throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
|
|
3219
3316
|
}
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
parsed.push({
|
|
3223
|
-
text,
|
|
3224
|
-
startMs: secondsToMs(startSeconds),
|
|
3225
|
-
endMs: secondsToMs(endSeconds),
|
|
3226
|
-
speakerId: readString3(segmentRecord, "speaker") ?? undefined,
|
|
3227
|
-
confidence: readNumber2(segmentRecord, "confidence"),
|
|
3228
|
-
wordTimings: parseWordTimings(segmentRecord.words)
|
|
3229
|
-
});
|
|
3230
|
-
}
|
|
3231
|
-
return parsed;
|
|
3232
|
-
}
|
|
3233
|
-
function parseWordTimings(value) {
|
|
3234
|
-
if (!Array.isArray(value)) {
|
|
3235
|
-
return [];
|
|
3236
|
-
}
|
|
3237
|
-
const words = [];
|
|
3238
|
-
for (const entry of value) {
|
|
3239
|
-
const wordRecord = asRecord2(entry);
|
|
3240
|
-
const word = readString3(wordRecord, "word");
|
|
3241
|
-
const startSeconds = readNumber2(wordRecord, "start");
|
|
3242
|
-
const endSeconds = readNumber2(wordRecord, "end");
|
|
3243
|
-
if (!word || startSeconds == null || endSeconds == null) {
|
|
3244
|
-
continue;
|
|
3317
|
+
if (!from) {
|
|
3318
|
+
throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
|
|
3245
3319
|
}
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3320
|
+
const params = new URLSearchParams;
|
|
3321
|
+
params.set("To", to);
|
|
3322
|
+
params.set("From", from);
|
|
3323
|
+
params.set("Body", input.text);
|
|
3324
|
+
const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
|
|
3325
|
+
const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
|
|
3326
|
+
method: "POST",
|
|
3327
|
+
headers: {
|
|
3328
|
+
authorization: `Basic ${auth}`,
|
|
3329
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
3330
|
+
},
|
|
3331
|
+
body: params.toString()
|
|
3251
3332
|
});
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
}
|
|
3255
|
-
function dedupeSpeakers(segments) {
|
|
3256
|
-
const seen = new Set;
|
|
3257
|
-
const speakers = [];
|
|
3258
|
-
for (const segment of segments) {
|
|
3259
|
-
if (!segment.speakerId || seen.has(segment.speakerId)) {
|
|
3260
|
-
continue;
|
|
3333
|
+
const body = await response.json();
|
|
3334
|
+
if (!response.ok || !body.sid) {
|
|
3335
|
+
throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
|
|
3261
3336
|
}
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3337
|
+
return {
|
|
3338
|
+
id: body.sid,
|
|
3339
|
+
providerMessageId: body.sid,
|
|
3340
|
+
status: mapTwilioStatus(body.status),
|
|
3341
|
+
sentAt: new Date,
|
|
3342
|
+
errorCode: body.error_code != null ? String(body.error_code) : undefined,
|
|
3343
|
+
errorMessage: body.error_message ?? undefined,
|
|
3344
|
+
metadata: {
|
|
3345
|
+
from,
|
|
3346
|
+
to
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3267
3349
|
}
|
|
3268
|
-
return speakers;
|
|
3269
|
-
}
|
|
3270
|
-
function normalizeBaseUrl(url) {
|
|
3271
|
-
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
3272
3350
|
}
|
|
3273
|
-
function
|
|
3274
|
-
if (value
|
|
3351
|
+
function normalizeWhatsappAddress(value) {
|
|
3352
|
+
if (!value)
|
|
3353
|
+
return null;
|
|
3354
|
+
if (value.startsWith("whatsapp:"))
|
|
3275
3355
|
return value;
|
|
3276
|
-
}
|
|
3277
|
-
return {};
|
|
3278
|
-
}
|
|
3279
|
-
function readString3(record, key) {
|
|
3280
|
-
const value = record[key];
|
|
3281
|
-
return typeof value === "string" ? value : undefined;
|
|
3282
|
-
}
|
|
3283
|
-
function readNumber2(record, key) {
|
|
3284
|
-
const value = record[key];
|
|
3285
|
-
return typeof value === "number" ? value : undefined;
|
|
3356
|
+
return `whatsapp:${value}`;
|
|
3286
3357
|
}
|
|
3287
|
-
function
|
|
3288
|
-
|
|
3358
|
+
function mapTwilioStatus(status) {
|
|
3359
|
+
switch (status) {
|
|
3360
|
+
case "queued":
|
|
3361
|
+
case "accepted":
|
|
3362
|
+
case "scheduled":
|
|
3363
|
+
return "queued";
|
|
3364
|
+
case "sending":
|
|
3365
|
+
return "sending";
|
|
3366
|
+
case "delivered":
|
|
3367
|
+
return "delivered";
|
|
3368
|
+
case "failed":
|
|
3369
|
+
case "undelivered":
|
|
3370
|
+
case "canceled":
|
|
3371
|
+
return "failed";
|
|
3372
|
+
case "sent":
|
|
3373
|
+
default:
|
|
3374
|
+
return "sent";
|
|
3375
|
+
}
|
|
3289
3376
|
}
|
|
3290
3377
|
|
|
3291
3378
|
// src/impls/mistral-conversational.session.ts
|
|
@@ -3405,832 +3492,781 @@ class MistralConversationSession {
|
|
|
3405
3492
|
});
|
|
3406
3493
|
this.history.push({ role: "user", content: text });
|
|
3407
3494
|
const assistantStart = Date.now();
|
|
3408
|
-
const assistantText = await this.complete(this.history, {
|
|
3409
|
-
...this.sessionConfig,
|
|
3410
|
-
llmModel: this.sessionConfig.llmModel ?? this.defaultModel
|
|
3411
|
-
});
|
|
3412
|
-
if (this.closed) {
|
|
3413
|
-
return;
|
|
3414
|
-
}
|
|
3415
|
-
const normalizedAssistantText = assistantText.trim();
|
|
3416
|
-
const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
|
|
3417
|
-
this.queue.push({
|
|
3418
|
-
type: "agent_speech_started",
|
|
3419
|
-
text: finalAssistantText
|
|
3420
|
-
});
|
|
3421
|
-
this.queue.push({
|
|
3422
|
-
type: "transcript",
|
|
3423
|
-
role: "agent",
|
|
3424
|
-
text: finalAssistantText,
|
|
3425
|
-
timestamp: assistantStart
|
|
3426
|
-
});
|
|
3427
|
-
this.queue.push({ type: "agent_speech_ended" });
|
|
3428
|
-
this.turns.push({
|
|
3429
|
-
role: "assistant",
|
|
3430
|
-
text: finalAssistantText,
|
|
3431
|
-
startMs: assistantStart,
|
|
3432
|
-
endMs: Date.now()
|
|
3433
|
-
});
|
|
3434
|
-
this.history.push({ role: "assistant", content: finalAssistantText });
|
|
3435
|
-
}
|
|
3436
|
-
emitError(error) {
|
|
3437
|
-
if (this.closed) {
|
|
3438
|
-
return;
|
|
3439
|
-
}
|
|
3440
|
-
this.queue.push({ type: "error", error: toError(error) });
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
function toError(error) {
|
|
3444
|
-
if (error instanceof Error) {
|
|
3445
|
-
return error;
|
|
3446
|
-
}
|
|
3447
|
-
return new Error(String(error));
|
|
3448
|
-
}
|
|
3449
|
-
|
|
3450
|
-
// src/impls/mistral-conversational.ts
|
|
3451
|
-
var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
|
|
3452
|
-
var DEFAULT_MODEL2 = "mistral-small-latest";
|
|
3453
|
-
var DEFAULT_VOICE = "default";
|
|
3454
|
-
|
|
3455
|
-
class MistralConversationalProvider {
|
|
3456
|
-
apiKey;
|
|
3457
|
-
defaultModel;
|
|
3458
|
-
defaultVoiceId;
|
|
3459
|
-
baseUrl;
|
|
3460
|
-
fetchImpl;
|
|
3461
|
-
sttProvider;
|
|
3462
|
-
constructor(options) {
|
|
3463
|
-
if (!options.apiKey) {
|
|
3464
|
-
throw new Error("MistralConversationalProvider requires an apiKey");
|
|
3465
|
-
}
|
|
3466
|
-
this.apiKey = options.apiKey;
|
|
3467
|
-
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
|
|
3468
|
-
this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
|
|
3469
|
-
this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
|
|
3470
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
3471
|
-
this.sttProvider = options.sttProvider ?? new MistralSttProvider({
|
|
3472
|
-
apiKey: options.apiKey,
|
|
3473
|
-
defaultModel: options.sttOptions?.defaultModel,
|
|
3474
|
-
defaultLanguage: options.sttOptions?.defaultLanguage,
|
|
3475
|
-
serverURL: options.sttOptions?.serverURL ?? options.serverURL,
|
|
3476
|
-
fetchImpl: this.fetchImpl
|
|
3477
|
-
});
|
|
3478
|
-
}
|
|
3479
|
-
async startSession(config) {
|
|
3480
|
-
return new MistralConversationSession({
|
|
3481
|
-
sessionConfig: {
|
|
3482
|
-
...config,
|
|
3483
|
-
voiceId: config.voiceId || this.defaultVoiceId
|
|
3484
|
-
},
|
|
3485
|
-
defaultModel: this.defaultModel,
|
|
3486
|
-
complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
|
|
3487
|
-
sttProvider: this.sttProvider
|
|
3488
|
-
});
|
|
3489
|
-
}
|
|
3490
|
-
async listVoices() {
|
|
3491
|
-
return [
|
|
3492
|
-
{
|
|
3493
|
-
id: this.defaultVoiceId,
|
|
3494
|
-
name: "Mistral Default Voice",
|
|
3495
|
-
description: "Default conversational voice profile.",
|
|
3496
|
-
capabilities: ["conversational"]
|
|
3497
|
-
}
|
|
3498
|
-
];
|
|
3499
|
-
}
|
|
3500
|
-
async completeConversation(history, sessionConfig) {
|
|
3501
|
-
const model = sessionConfig.llmModel ?? this.defaultModel;
|
|
3502
|
-
const messages = [];
|
|
3503
|
-
if (sessionConfig.systemPrompt) {
|
|
3504
|
-
messages.push({ role: "system", content: sessionConfig.systemPrompt });
|
|
3505
|
-
}
|
|
3506
|
-
for (const item of history) {
|
|
3507
|
-
messages.push({ role: item.role, content: item.content });
|
|
3508
|
-
}
|
|
3509
|
-
const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
3510
|
-
method: "POST",
|
|
3511
|
-
headers: {
|
|
3512
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
3513
|
-
"Content-Type": "application/json"
|
|
3514
|
-
},
|
|
3515
|
-
body: JSON.stringify({
|
|
3516
|
-
model,
|
|
3517
|
-
messages
|
|
3518
|
-
})
|
|
3519
|
-
});
|
|
3520
|
-
if (!response.ok) {
|
|
3521
|
-
const body = await response.text();
|
|
3522
|
-
throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
|
|
3523
|
-
}
|
|
3524
|
-
const payload = await response.json();
|
|
3525
|
-
return readAssistantText(payload);
|
|
3526
|
-
}
|
|
3527
|
-
}
|
|
3528
|
-
function normalizeBaseUrl2(url) {
|
|
3529
|
-
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
3530
|
-
}
|
|
3531
|
-
function readAssistantText(payload) {
|
|
3532
|
-
const record = asRecord3(payload);
|
|
3533
|
-
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
3534
|
-
const firstChoice = asRecord3(choices[0]);
|
|
3535
|
-
const message = asRecord3(firstChoice.message);
|
|
3536
|
-
if (typeof message.content === "string") {
|
|
3537
|
-
return message.content;
|
|
3538
|
-
}
|
|
3539
|
-
if (Array.isArray(message.content)) {
|
|
3540
|
-
const textParts = message.content.map((part) => {
|
|
3541
|
-
const entry = asRecord3(part);
|
|
3542
|
-
const text = entry.text;
|
|
3543
|
-
return typeof text === "string" ? text : "";
|
|
3544
|
-
}).filter((text) => text.length > 0);
|
|
3545
|
-
return textParts.join("");
|
|
3546
|
-
}
|
|
3547
|
-
return "";
|
|
3548
|
-
}
|
|
3549
|
-
function asRecord3(value) {
|
|
3550
|
-
if (value && typeof value === "object") {
|
|
3551
|
-
return value;
|
|
3552
|
-
}
|
|
3553
|
-
return {};
|
|
3554
|
-
}
|
|
3555
|
-
|
|
3556
|
-
// src/impls/qdrant-vector.ts
|
|
3557
|
-
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
3558
|
-
|
|
3559
|
-
class QdrantVectorProvider {
|
|
3560
|
-
client;
|
|
3561
|
-
createCollectionIfMissing;
|
|
3562
|
-
distance;
|
|
3563
|
-
constructor(options) {
|
|
3564
|
-
this.client = options.client ?? new QdrantClient({
|
|
3565
|
-
url: options.url,
|
|
3566
|
-
apiKey: options.apiKey,
|
|
3567
|
-
...options.clientParams
|
|
3568
|
-
});
|
|
3569
|
-
this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
|
|
3570
|
-
this.distance = options.distance ?? "Cosine";
|
|
3571
|
-
}
|
|
3572
|
-
async upsert(request) {
|
|
3573
|
-
if (request.documents.length === 0)
|
|
3574
|
-
return;
|
|
3575
|
-
const firstDocument = request.documents[0];
|
|
3576
|
-
if (!firstDocument)
|
|
3577
|
-
return;
|
|
3578
|
-
const vectorSize = firstDocument.vector.length;
|
|
3579
|
-
if (this.createCollectionIfMissing) {
|
|
3580
|
-
await this.ensureCollection(request.collection, vectorSize);
|
|
3581
|
-
}
|
|
3582
|
-
const points = request.documents.map((document) => ({
|
|
3583
|
-
id: document.id,
|
|
3584
|
-
vector: document.vector,
|
|
3585
|
-
payload: {
|
|
3586
|
-
...document.payload,
|
|
3587
|
-
...document.namespace ? { namespace: document.namespace } : {},
|
|
3588
|
-
...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
|
|
3589
|
-
}
|
|
3590
|
-
}));
|
|
3591
|
-
await this.client.upsert(request.collection, {
|
|
3592
|
-
wait: true,
|
|
3593
|
-
points
|
|
3495
|
+
const assistantText = await this.complete(this.history, {
|
|
3496
|
+
...this.sessionConfig,
|
|
3497
|
+
llmModel: this.sessionConfig.llmModel ?? this.defaultModel
|
|
3594
3498
|
});
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
with_vector: false
|
|
3499
|
+
if (this.closed) {
|
|
3500
|
+
return;
|
|
3501
|
+
}
|
|
3502
|
+
const normalizedAssistantText = assistantText.trim();
|
|
3503
|
+
const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
|
|
3504
|
+
this.queue.push({
|
|
3505
|
+
type: "agent_speech_started",
|
|
3506
|
+
text: finalAssistantText
|
|
3604
3507
|
});
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
})
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3508
|
+
this.queue.push({
|
|
3509
|
+
type: "transcript",
|
|
3510
|
+
role: "agent",
|
|
3511
|
+
text: finalAssistantText,
|
|
3512
|
+
timestamp: assistantStart
|
|
3513
|
+
});
|
|
3514
|
+
this.queue.push({ type: "agent_speech_ended" });
|
|
3515
|
+
this.turns.push({
|
|
3516
|
+
role: "assistant",
|
|
3517
|
+
text: finalAssistantText,
|
|
3518
|
+
startMs: assistantStart,
|
|
3519
|
+
endMs: Date.now()
|
|
3616
3520
|
});
|
|
3521
|
+
this.history.push({ role: "assistant", content: finalAssistantText });
|
|
3617
3522
|
}
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
} catch (_error) {
|
|
3622
|
-
await this.client.createCollection(collectionName, {
|
|
3623
|
-
vectors: {
|
|
3624
|
-
size: vectorSize,
|
|
3625
|
-
distance: this.distance
|
|
3626
|
-
}
|
|
3627
|
-
});
|
|
3523
|
+
emitError(error) {
|
|
3524
|
+
if (this.closed) {
|
|
3525
|
+
return;
|
|
3628
3526
|
}
|
|
3527
|
+
this.queue.push({ type: "error", error: toError(error) });
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
function toError(error) {
|
|
3531
|
+
if (error instanceof Error) {
|
|
3532
|
+
return error;
|
|
3629
3533
|
}
|
|
3534
|
+
return new Error(String(error));
|
|
3630
3535
|
}
|
|
3631
3536
|
|
|
3632
|
-
// src/impls/
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3537
|
+
// src/impls/mistral-stt.ts
|
|
3538
|
+
var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
|
|
3539
|
+
var DEFAULT_MODEL = "voxtral-mini-latest";
|
|
3540
|
+
var AUDIO_MIME_BY_FORMAT = {
|
|
3541
|
+
mp3: "audio/mpeg",
|
|
3542
|
+
wav: "audio/wav",
|
|
3543
|
+
ogg: "audio/ogg",
|
|
3544
|
+
pcm: "audio/pcm",
|
|
3545
|
+
opus: "audio/opus"
|
|
3546
|
+
};
|
|
3637
3547
|
|
|
3638
|
-
class
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
if (options.
|
|
3646
|
-
|
|
3647
|
-
throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
|
|
3648
|
-
}
|
|
3649
|
-
this.client = options.client;
|
|
3650
|
-
this.db = options.db;
|
|
3651
|
-
this.ownsClient = false;
|
|
3652
|
-
return;
|
|
3653
|
-
}
|
|
3654
|
-
if (options.client) {
|
|
3655
|
-
this.client = options.client;
|
|
3656
|
-
this.ownsClient = false;
|
|
3657
|
-
} else {
|
|
3658
|
-
if (!options.connectionString) {
|
|
3659
|
-
throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
|
|
3660
|
-
}
|
|
3661
|
-
this.client = postgres(options.connectionString, {
|
|
3662
|
-
max: options.maxConnections,
|
|
3663
|
-
prepare: false,
|
|
3664
|
-
ssl: resolveSslMode(options.sslMode)
|
|
3665
|
-
});
|
|
3666
|
-
this.ownsClient = true;
|
|
3548
|
+
class MistralSttProvider {
|
|
3549
|
+
apiKey;
|
|
3550
|
+
defaultModel;
|
|
3551
|
+
defaultLanguage;
|
|
3552
|
+
baseUrl;
|
|
3553
|
+
fetchImpl;
|
|
3554
|
+
constructor(options) {
|
|
3555
|
+
if (!options.apiKey) {
|
|
3556
|
+
throw new Error("MistralSttProvider requires an apiKey");
|
|
3667
3557
|
}
|
|
3668
|
-
this.
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
const rows = asRows(result);
|
|
3674
|
-
return {
|
|
3675
|
-
rows,
|
|
3676
|
-
rowCount: rows.length
|
|
3677
|
-
};
|
|
3678
|
-
}
|
|
3679
|
-
async execute(statement, params = []) {
|
|
3680
|
-
const query = buildParameterizedSql(statement, params);
|
|
3681
|
-
await this.db.execute(query);
|
|
3558
|
+
this.apiKey = options.apiKey;
|
|
3559
|
+
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
|
|
3560
|
+
this.defaultLanguage = options.defaultLanguage;
|
|
3561
|
+
this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
|
|
3562
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
3682
3563
|
}
|
|
3683
|
-
async
|
|
3684
|
-
const
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3564
|
+
async transcribe(input) {
|
|
3565
|
+
const formData = new FormData;
|
|
3566
|
+
const model = input.model ?? this.defaultModel;
|
|
3567
|
+
const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
|
|
3568
|
+
const fileName = `audio.${input.audio.format}`;
|
|
3569
|
+
const audioBytes = new Uint8Array(input.audio.data);
|
|
3570
|
+
const blob = new Blob([audioBytes], { type: mimeType });
|
|
3571
|
+
formData.append("file", blob, fileName);
|
|
3572
|
+
formData.append("model", model);
|
|
3573
|
+
formData.append("response_format", "verbose_json");
|
|
3574
|
+
const language = input.language ?? this.defaultLanguage;
|
|
3575
|
+
if (language) {
|
|
3576
|
+
formData.append("language", language);
|
|
3577
|
+
}
|
|
3578
|
+
const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
|
|
3579
|
+
method: "POST",
|
|
3580
|
+
headers: {
|
|
3581
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
3582
|
+
},
|
|
3583
|
+
body: formData
|
|
3691
3584
|
});
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
if (this.ownsClient) {
|
|
3696
|
-
await this.client.end({ timeout: 5 });
|
|
3585
|
+
if (!response.ok) {
|
|
3586
|
+
const body = await response.text();
|
|
3587
|
+
throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
|
|
3697
3588
|
}
|
|
3589
|
+
const payload = await response.json();
|
|
3590
|
+
return toTranscriptionResult(payload, input);
|
|
3698
3591
|
}
|
|
3699
3592
|
}
|
|
3700
|
-
function
|
|
3701
|
-
const
|
|
3702
|
-
const
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
|
|
3712
|
-
throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
|
|
3713
|
-
}
|
|
3714
|
-
const staticSegment = statement.slice(cursor, start);
|
|
3715
|
-
if (staticSegment.length > 0) {
|
|
3716
|
-
segments.push(drizzleSql.raw(staticSegment));
|
|
3717
|
-
}
|
|
3718
|
-
const parameterValue = params[parameterIndex];
|
|
3719
|
-
if (parameterValue === undefined) {
|
|
3720
|
-
throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
|
|
3721
|
-
}
|
|
3722
|
-
const normalizedValue = normalizeParam(parameterValue);
|
|
3723
|
-
segments.push(drizzleSql`${normalizedValue}`);
|
|
3724
|
-
cursor = start + token.length;
|
|
3725
|
-
}
|
|
3726
|
-
const tailSegment = statement.slice(cursor);
|
|
3727
|
-
if (tailSegment.length > 0) {
|
|
3728
|
-
segments.push(drizzleSql.raw(tailSegment));
|
|
3729
|
-
}
|
|
3730
|
-
if (segments.length === 0) {
|
|
3731
|
-
return drizzleSql.raw("");
|
|
3593
|
+
function toTranscriptionResult(payload, input) {
|
|
3594
|
+
const record = asRecord2(payload);
|
|
3595
|
+
const text = readString3(record, "text") ?? "";
|
|
3596
|
+
const language = readString3(record, "language") ?? input.language ?? "unknown";
|
|
3597
|
+
const segments = parseSegments(record);
|
|
3598
|
+
if (segments.length === 0 && text.length > 0) {
|
|
3599
|
+
segments.push({
|
|
3600
|
+
text,
|
|
3601
|
+
startMs: 0,
|
|
3602
|
+
endMs: input.audio.durationMs ?? 0
|
|
3603
|
+
});
|
|
3732
3604
|
}
|
|
3733
|
-
|
|
3605
|
+
const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
|
|
3606
|
+
const topLevelWords = parseWordTimings(record.words);
|
|
3607
|
+
const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
|
|
3608
|
+
const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
|
|
3609
|
+
const speakers = dedupeSpeakers(segments);
|
|
3610
|
+
return {
|
|
3611
|
+
text,
|
|
3612
|
+
segments,
|
|
3613
|
+
language,
|
|
3614
|
+
durationMs,
|
|
3615
|
+
speakers: speakers.length > 0 ? speakers : undefined,
|
|
3616
|
+
wordTimings
|
|
3617
|
+
};
|
|
3734
3618
|
}
|
|
3735
|
-
function
|
|
3736
|
-
if (
|
|
3737
|
-
return
|
|
3738
|
-
}
|
|
3739
|
-
if (value instanceof Uint8Array) {
|
|
3740
|
-
return Buffer3.from(value);
|
|
3619
|
+
function parseSegments(record) {
|
|
3620
|
+
if (!Array.isArray(record.segments)) {
|
|
3621
|
+
return [];
|
|
3741
3622
|
}
|
|
3742
|
-
|
|
3743
|
-
|
|
3623
|
+
const parsed = [];
|
|
3624
|
+
for (const entry of record.segments) {
|
|
3625
|
+
const segmentRecord = asRecord2(entry);
|
|
3626
|
+
const text = readString3(segmentRecord, "text");
|
|
3627
|
+
if (!text) {
|
|
3628
|
+
continue;
|
|
3629
|
+
}
|
|
3630
|
+
const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
|
|
3631
|
+
const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
|
|
3632
|
+
parsed.push({
|
|
3633
|
+
text,
|
|
3634
|
+
startMs: secondsToMs(startSeconds),
|
|
3635
|
+
endMs: secondsToMs(endSeconds),
|
|
3636
|
+
speakerId: readString3(segmentRecord, "speaker") ?? undefined,
|
|
3637
|
+
confidence: readNumber2(segmentRecord, "confidence"),
|
|
3638
|
+
wordTimings: parseWordTimings(segmentRecord.words)
|
|
3639
|
+
});
|
|
3744
3640
|
}
|
|
3745
|
-
return
|
|
3641
|
+
return parsed;
|
|
3746
3642
|
}
|
|
3747
|
-
function
|
|
3748
|
-
if (!Array.isArray(
|
|
3643
|
+
function parseWordTimings(value) {
|
|
3644
|
+
if (!Array.isArray(value)) {
|
|
3749
3645
|
return [];
|
|
3750
3646
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3647
|
+
const words = [];
|
|
3648
|
+
for (const entry of value) {
|
|
3649
|
+
const wordRecord = asRecord2(entry);
|
|
3650
|
+
const word = readString3(wordRecord, "word");
|
|
3651
|
+
const startSeconds = readNumber2(wordRecord, "start");
|
|
3652
|
+
const endSeconds = readNumber2(wordRecord, "end");
|
|
3653
|
+
if (!word || startSeconds == null || endSeconds == null) {
|
|
3654
|
+
continue;
|
|
3655
|
+
}
|
|
3656
|
+
words.push({
|
|
3657
|
+
word,
|
|
3658
|
+
startMs: secondsToMs(startSeconds),
|
|
3659
|
+
endMs: secondsToMs(endSeconds),
|
|
3660
|
+
confidence: readNumber2(wordRecord, "confidence")
|
|
3661
|
+
});
|
|
3762
3662
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3663
|
+
return words;
|
|
3664
|
+
}
|
|
3665
|
+
function dedupeSpeakers(segments) {
|
|
3666
|
+
const seen = new Set;
|
|
3667
|
+
const speakers = [];
|
|
3668
|
+
for (const segment of segments) {
|
|
3669
|
+
if (!segment.speakerId || seen.has(segment.speakerId)) {
|
|
3670
|
+
continue;
|
|
3671
|
+
}
|
|
3672
|
+
seen.add(segment.speakerId);
|
|
3673
|
+
speakers.push({
|
|
3674
|
+
id: segment.speakerId,
|
|
3675
|
+
name: segment.speakerName
|
|
3676
|
+
});
|
|
3765
3677
|
}
|
|
3766
|
-
return
|
|
3678
|
+
return speakers;
|
|
3767
3679
|
}
|
|
3768
|
-
function
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
case "require":
|
|
3775
|
-
default:
|
|
3776
|
-
return "require";
|
|
3680
|
+
function normalizeBaseUrl(url) {
|
|
3681
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
3682
|
+
}
|
|
3683
|
+
function asRecord2(value) {
|
|
3684
|
+
if (value && typeof value === "object") {
|
|
3685
|
+
return value;
|
|
3777
3686
|
}
|
|
3687
|
+
return {};
|
|
3688
|
+
}
|
|
3689
|
+
function readString3(record, key) {
|
|
3690
|
+
const value = record[key];
|
|
3691
|
+
return typeof value === "string" ? value : undefined;
|
|
3692
|
+
}
|
|
3693
|
+
function readNumber2(record, key) {
|
|
3694
|
+
const value = record[key];
|
|
3695
|
+
return typeof value === "number" ? value : undefined;
|
|
3696
|
+
}
|
|
3697
|
+
function secondsToMs(value) {
|
|
3698
|
+
return Math.round(value * 1000);
|
|
3778
3699
|
}
|
|
3779
3700
|
|
|
3780
|
-
// src/impls/
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3701
|
+
// src/impls/mistral-conversational.ts
|
|
3702
|
+
var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
|
|
3703
|
+
var DEFAULT_MODEL2 = "mistral-small-latest";
|
|
3704
|
+
var DEFAULT_VOICE = "default";
|
|
3705
|
+
|
|
3706
|
+
class MistralConversationalProvider {
|
|
3707
|
+
apiKey;
|
|
3708
|
+
defaultModel;
|
|
3709
|
+
defaultVoiceId;
|
|
3710
|
+
baseUrl;
|
|
3711
|
+
fetchImpl;
|
|
3712
|
+
sttProvider;
|
|
3790
3713
|
constructor(options) {
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
maxConnections: options.maxConnections,
|
|
3794
|
-
sslMode: options.sslMode
|
|
3795
|
-
});
|
|
3796
|
-
this.createTableIfMissing = options.createTableIfMissing ?? true;
|
|
3797
|
-
this.distanceMetric = options.distanceMetric ?? "cosine";
|
|
3798
|
-
const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
|
|
3799
|
-
const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
|
|
3800
|
-
this.quotedSchema = quoteIdentifier(schema);
|
|
3801
|
-
this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
|
|
3802
|
-
this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
|
|
3803
|
-
this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
|
|
3804
|
-
}
|
|
3805
|
-
async upsert(request) {
|
|
3806
|
-
if (request.documents.length === 0) {
|
|
3807
|
-
return;
|
|
3808
|
-
}
|
|
3809
|
-
if (this.createTableIfMissing) {
|
|
3810
|
-
await this.ensureTable();
|
|
3811
|
-
}
|
|
3812
|
-
for (const document of request.documents) {
|
|
3813
|
-
await this.database.execute(`INSERT INTO ${this.qualifiedTable}
|
|
3814
|
-
(collection, id, embedding, payload, namespace, expires_at, updated_at)
|
|
3815
|
-
VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
|
|
3816
|
-
ON CONFLICT (collection, id)
|
|
3817
|
-
DO UPDATE SET
|
|
3818
|
-
embedding = EXCLUDED.embedding,
|
|
3819
|
-
payload = EXCLUDED.payload,
|
|
3820
|
-
namespace = EXCLUDED.namespace,
|
|
3821
|
-
expires_at = EXCLUDED.expires_at,
|
|
3822
|
-
updated_at = now();`, [
|
|
3823
|
-
request.collection,
|
|
3824
|
-
document.id,
|
|
3825
|
-
toVectorLiteral(document.vector),
|
|
3826
|
-
document.payload ? JSON.stringify(document.payload) : null,
|
|
3827
|
-
document.namespace ?? null,
|
|
3828
|
-
document.expiresAt ?? null
|
|
3829
|
-
]);
|
|
3714
|
+
if (!options.apiKey) {
|
|
3715
|
+
throw new Error("MistralConversationalProvider requires an apiKey");
|
|
3830
3716
|
}
|
|
3717
|
+
this.apiKey = options.apiKey;
|
|
3718
|
+
this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
|
|
3719
|
+
this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
|
|
3720
|
+
this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
|
|
3721
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
3722
|
+
this.sttProvider = options.sttProvider ?? new MistralSttProvider({
|
|
3723
|
+
apiKey: options.apiKey,
|
|
3724
|
+
defaultModel: options.sttOptions?.defaultModel,
|
|
3725
|
+
defaultLanguage: options.sttOptions?.defaultLanguage,
|
|
3726
|
+
serverURL: options.sttOptions?.serverURL ?? options.serverURL,
|
|
3727
|
+
fetchImpl: this.fetchImpl
|
|
3728
|
+
});
|
|
3831
3729
|
}
|
|
3832
|
-
async
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
AND ($2::text IS NULL OR namespace = $2)
|
|
3842
|
-
AND (expires_at IS NULL OR expires_at > now())
|
|
3843
|
-
AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
|
|
3844
|
-
ORDER BY embedding ${operator} $3::vector
|
|
3845
|
-
LIMIT $5;`, [
|
|
3846
|
-
query.collection,
|
|
3847
|
-
query.namespace ?? null,
|
|
3848
|
-
toVectorLiteral(query.vector),
|
|
3849
|
-
query.filter ? JSON.stringify(query.filter) : null,
|
|
3850
|
-
query.topK
|
|
3851
|
-
]);
|
|
3852
|
-
const mapped = results.rows.map((row) => {
|
|
3853
|
-
const distance = Number(row.distance);
|
|
3854
|
-
return {
|
|
3855
|
-
id: row.id,
|
|
3856
|
-
score: distanceToScore(distance, this.distanceMetric),
|
|
3857
|
-
payload: isRecord(row.payload) ? row.payload : undefined,
|
|
3858
|
-
namespace: row.namespace ?? undefined
|
|
3859
|
-
};
|
|
3730
|
+
async startSession(config) {
|
|
3731
|
+
return new MistralConversationSession({
|
|
3732
|
+
sessionConfig: {
|
|
3733
|
+
...config,
|
|
3734
|
+
voiceId: config.voiceId || this.defaultVoiceId
|
|
3735
|
+
},
|
|
3736
|
+
defaultModel: this.defaultModel,
|
|
3737
|
+
complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
|
|
3738
|
+
sttProvider: this.sttProvider
|
|
3860
3739
|
});
|
|
3861
|
-
const scoreThreshold = query.scoreThreshold;
|
|
3862
|
-
if (scoreThreshold == null) {
|
|
3863
|
-
return mapped;
|
|
3864
|
-
}
|
|
3865
|
-
return mapped.filter((result) => result.score >= scoreThreshold);
|
|
3866
3740
|
}
|
|
3867
|
-
async
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3741
|
+
async listVoices() {
|
|
3742
|
+
return [
|
|
3743
|
+
{
|
|
3744
|
+
id: this.defaultVoiceId,
|
|
3745
|
+
name: "Mistral Default Voice",
|
|
3746
|
+
description: "Default conversational voice profile.",
|
|
3747
|
+
capabilities: ["conversational"]
|
|
3748
|
+
}
|
|
3875
3749
|
];
|
|
3876
|
-
await this.database.execute(`DELETE FROM ${this.qualifiedTable}
|
|
3877
|
-
WHERE collection = $1
|
|
3878
|
-
AND id = ANY($2::text[])
|
|
3879
|
-
AND ($3::text IS NULL OR namespace = $3);`, params);
|
|
3880
3750
|
}
|
|
3881
|
-
async
|
|
3882
|
-
|
|
3883
|
-
|
|
3751
|
+
async completeConversation(history, sessionConfig) {
|
|
3752
|
+
const model = sessionConfig.llmModel ?? this.defaultModel;
|
|
3753
|
+
const messages = [];
|
|
3754
|
+
if (sessionConfig.systemPrompt) {
|
|
3755
|
+
messages.push({ role: "system", content: sessionConfig.systemPrompt });
|
|
3884
3756
|
}
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
async createTable() {
|
|
3888
|
-
await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
3889
|
-
await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
|
|
3890
|
-
await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
|
|
3891
|
-
collection text NOT NULL,
|
|
3892
|
-
id text NOT NULL,
|
|
3893
|
-
embedding vector NOT NULL,
|
|
3894
|
-
payload jsonb,
|
|
3895
|
-
namespace text,
|
|
3896
|
-
expires_at timestamptz,
|
|
3897
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
3898
|
-
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
3899
|
-
PRIMARY KEY (collection, id)
|
|
3900
|
-
);`);
|
|
3901
|
-
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
|
|
3902
|
-
ON ${this.qualifiedTable} (collection);`);
|
|
3903
|
-
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
|
|
3904
|
-
ON ${this.qualifiedTable} (namespace);`);
|
|
3905
|
-
}
|
|
3906
|
-
get distanceOperator() {
|
|
3907
|
-
switch (this.distanceMetric) {
|
|
3908
|
-
case "l2":
|
|
3909
|
-
return "<->";
|
|
3910
|
-
case "inner_product":
|
|
3911
|
-
return "<#>";
|
|
3912
|
-
case "cosine":
|
|
3913
|
-
default:
|
|
3914
|
-
return "<=>";
|
|
3757
|
+
for (const item of history) {
|
|
3758
|
+
messages.push({ role: item.role, content: item.content });
|
|
3915
3759
|
}
|
|
3760
|
+
const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
3761
|
+
method: "POST",
|
|
3762
|
+
headers: {
|
|
3763
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
3764
|
+
"Content-Type": "application/json"
|
|
3765
|
+
},
|
|
3766
|
+
body: JSON.stringify({
|
|
3767
|
+
model,
|
|
3768
|
+
messages
|
|
3769
|
+
})
|
|
3770
|
+
});
|
|
3771
|
+
if (!response.ok) {
|
|
3772
|
+
const body = await response.text();
|
|
3773
|
+
throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
|
|
3774
|
+
}
|
|
3775
|
+
const payload = await response.json();
|
|
3776
|
+
return readAssistantText(payload);
|
|
3916
3777
|
}
|
|
3917
3778
|
}
|
|
3918
|
-
function
|
|
3919
|
-
|
|
3920
|
-
throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
|
|
3921
|
-
}
|
|
3922
|
-
return value;
|
|
3923
|
-
}
|
|
3924
|
-
function quoteIdentifier(value) {
|
|
3925
|
-
return `"${value.replaceAll('"', '""')}"`;
|
|
3779
|
+
function normalizeBaseUrl2(url) {
|
|
3780
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
3926
3781
|
}
|
|
3927
|
-
function
|
|
3928
|
-
|
|
3929
|
-
|
|
3782
|
+
function readAssistantText(payload) {
|
|
3783
|
+
const record = asRecord3(payload);
|
|
3784
|
+
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
3785
|
+
const firstChoice = asRecord3(choices[0]);
|
|
3786
|
+
const message = asRecord3(firstChoice.message);
|
|
3787
|
+
if (typeof message.content === "string") {
|
|
3788
|
+
return message.content;
|
|
3930
3789
|
}
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3790
|
+
if (Array.isArray(message.content)) {
|
|
3791
|
+
const textParts = message.content.map((part) => {
|
|
3792
|
+
const entry = asRecord3(part);
|
|
3793
|
+
const text = entry.text;
|
|
3794
|
+
return typeof text === "string" ? text : "";
|
|
3795
|
+
}).filter((text) => text.length > 0);
|
|
3796
|
+
return textParts.join("");
|
|
3935
3797
|
}
|
|
3936
|
-
return
|
|
3937
|
-
}
|
|
3938
|
-
function isRecord(value) {
|
|
3939
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3798
|
+
return "";
|
|
3940
3799
|
}
|
|
3941
|
-
function
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
return -distance;
|
|
3945
|
-
case "l2":
|
|
3946
|
-
return 1 / (1 + distance);
|
|
3947
|
-
case "cosine":
|
|
3948
|
-
default:
|
|
3949
|
-
return 1 - distance;
|
|
3800
|
+
function asRecord3(value) {
|
|
3801
|
+
if (value && typeof value === "object") {
|
|
3802
|
+
return value;
|
|
3950
3803
|
}
|
|
3804
|
+
return {};
|
|
3951
3805
|
}
|
|
3952
3806
|
|
|
3953
|
-
// src/impls/
|
|
3954
|
-
import
|
|
3955
|
-
var API_VERSION = "2026-02-25.clover";
|
|
3807
|
+
// src/impls/mistral-embedding.ts
|
|
3808
|
+
import { Mistral } from "@mistralai/mistralai";
|
|
3956
3809
|
|
|
3957
|
-
class
|
|
3958
|
-
|
|
3810
|
+
class MistralEmbeddingProvider {
|
|
3811
|
+
client;
|
|
3812
|
+
defaultModel;
|
|
3959
3813
|
constructor(options) {
|
|
3960
|
-
|
|
3961
|
-
|
|
3814
|
+
if (!options.apiKey) {
|
|
3815
|
+
throw new Error("MistralEmbeddingProvider requires an apiKey");
|
|
3816
|
+
}
|
|
3817
|
+
this.client = options.client ?? new Mistral({
|
|
3818
|
+
apiKey: options.apiKey,
|
|
3819
|
+
serverURL: options.serverURL
|
|
3962
3820
|
});
|
|
3821
|
+
this.defaultModel = options.defaultModel ?? "mistral-embed";
|
|
3963
3822
|
}
|
|
3964
|
-
async
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3823
|
+
async embedDocuments(documents, options) {
|
|
3824
|
+
if (documents.length === 0)
|
|
3825
|
+
return [];
|
|
3826
|
+
const model = options?.model ?? this.defaultModel;
|
|
3827
|
+
const response = await this.client.embeddings.create({
|
|
3828
|
+
model,
|
|
3829
|
+
inputs: documents.map((doc) => doc.text)
|
|
3970
3830
|
});
|
|
3971
|
-
return
|
|
3831
|
+
return response.data.map((item, index) => ({
|
|
3832
|
+
id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
|
|
3833
|
+
vector: item.embedding ?? [],
|
|
3834
|
+
dimensions: item.embedding?.length ?? 0,
|
|
3835
|
+
model: response.model,
|
|
3836
|
+
metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
|
|
3837
|
+
}));
|
|
3972
3838
|
}
|
|
3973
|
-
async
|
|
3974
|
-
const
|
|
3975
|
-
if (
|
|
3976
|
-
|
|
3977
|
-
|
|
3839
|
+
async embedQuery(query, options) {
|
|
3840
|
+
const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
|
|
3841
|
+
if (!result) {
|
|
3842
|
+
throw new Error("Failed to compute embedding for query");
|
|
3843
|
+
}
|
|
3844
|
+
return result;
|
|
3978
3845
|
}
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
// src/impls/mistral-llm.ts
|
|
3849
|
+
import { Mistral as Mistral2 } from "@mistralai/mistralai";
|
|
3850
|
+
|
|
3851
|
+
class MistralLLMProvider {
|
|
3852
|
+
client;
|
|
3853
|
+
defaultModel;
|
|
3854
|
+
constructor(options) {
|
|
3855
|
+
if (!options.apiKey) {
|
|
3856
|
+
throw new Error("MistralLLMProvider requires an apiKey");
|
|
3857
|
+
}
|
|
3858
|
+
this.client = options.client ?? new Mistral2({
|
|
3859
|
+
apiKey: options.apiKey,
|
|
3860
|
+
serverURL: options.serverURL,
|
|
3861
|
+
userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
|
|
3991
3862
|
});
|
|
3992
|
-
|
|
3993
|
-
}
|
|
3994
|
-
async capturePayment(paymentIntentId, input) {
|
|
3995
|
-
const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
|
|
3996
|
-
return this.toPaymentIntent(intent);
|
|
3863
|
+
this.defaultModel = options.defaultModel ?? "mistral-large-latest";
|
|
3997
3864
|
}
|
|
3998
|
-
async
|
|
3999
|
-
const
|
|
4000
|
-
|
|
3865
|
+
async chat(messages, options = {}) {
|
|
3866
|
+
const request = this.buildChatRequest(messages, options);
|
|
3867
|
+
const response = await this.client.chat.complete(request);
|
|
3868
|
+
return this.buildLLMResponse(response);
|
|
4001
3869
|
}
|
|
4002
|
-
async
|
|
4003
|
-
const
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
3870
|
+
async* stream(messages, options = {}) {
|
|
3871
|
+
const request = this.buildChatRequest(messages, options);
|
|
3872
|
+
request.stream = true;
|
|
3873
|
+
const stream = await this.client.chat.stream(request);
|
|
3874
|
+
const aggregatedParts = [];
|
|
3875
|
+
const aggregatedToolCalls = [];
|
|
3876
|
+
let usage;
|
|
3877
|
+
let finishReason;
|
|
3878
|
+
for await (const event of stream) {
|
|
3879
|
+
for (const choice of event.data.choices) {
|
|
3880
|
+
const delta = choice.delta;
|
|
3881
|
+
if (typeof delta.content === "string") {
|
|
3882
|
+
if (delta.content.length > 0) {
|
|
3883
|
+
aggregatedParts.push({ type: "text", text: delta.content });
|
|
3884
|
+
yield {
|
|
3885
|
+
type: "message_delta",
|
|
3886
|
+
delta: { type: "text", text: delta.content },
|
|
3887
|
+
index: choice.index
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
} else if (Array.isArray(delta.content)) {
|
|
3891
|
+
for (const chunk of delta.content) {
|
|
3892
|
+
if (chunk.type === "text" && "text" in chunk) {
|
|
3893
|
+
aggregatedParts.push({ type: "text", text: chunk.text });
|
|
3894
|
+
yield {
|
|
3895
|
+
type: "message_delta",
|
|
3896
|
+
delta: { type: "text", text: chunk.text },
|
|
3897
|
+
index: choice.index
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
if (delta.toolCalls) {
|
|
3903
|
+
let localIndex = 0;
|
|
3904
|
+
for (const call of delta.toolCalls) {
|
|
3905
|
+
const toolCall = this.fromMistralToolCall(call, localIndex);
|
|
3906
|
+
aggregatedToolCalls.push(toolCall);
|
|
3907
|
+
yield {
|
|
3908
|
+
type: "tool_call",
|
|
3909
|
+
call: toolCall,
|
|
3910
|
+
index: choice.index
|
|
3911
|
+
};
|
|
3912
|
+
localIndex += 1;
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
if (choice.finishReason && choice.finishReason !== "null") {
|
|
3916
|
+
finishReason = choice.finishReason;
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
if (event.data.usage) {
|
|
3920
|
+
const usageEntry = this.fromUsage(event.data.usage);
|
|
3921
|
+
if (usageEntry) {
|
|
3922
|
+
usage = usageEntry;
|
|
3923
|
+
yield { type: "usage", usage: usageEntry };
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
const message = {
|
|
3928
|
+
role: "assistant",
|
|
3929
|
+
content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
|
|
3930
|
+
};
|
|
3931
|
+
if (aggregatedToolCalls.length > 0) {
|
|
3932
|
+
message.content = [
|
|
3933
|
+
...aggregatedToolCalls,
|
|
3934
|
+
...aggregatedParts.length ? aggregatedParts : []
|
|
3935
|
+
];
|
|
3936
|
+
}
|
|
3937
|
+
yield {
|
|
3938
|
+
type: "end",
|
|
3939
|
+
response: {
|
|
3940
|
+
message,
|
|
3941
|
+
usage,
|
|
3942
|
+
finishReason: mapFinishReason(finishReason)
|
|
3943
|
+
}
|
|
4021
3944
|
};
|
|
4022
3945
|
}
|
|
4023
|
-
async
|
|
4024
|
-
|
|
4025
|
-
const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
|
|
4026
|
-
const response = await this.stripe.invoices.list({
|
|
4027
|
-
customer: query?.customerId,
|
|
4028
|
-
status: stripeStatus,
|
|
4029
|
-
limit: query?.limit,
|
|
4030
|
-
starting_after: query?.startingAfter
|
|
4031
|
-
});
|
|
4032
|
-
return response.data.map((invoice) => this.toInvoice(invoice));
|
|
4033
|
-
}
|
|
4034
|
-
async listTransactions(query) {
|
|
4035
|
-
const response = await this.stripe.charges.list({
|
|
4036
|
-
customer: query?.customerId,
|
|
4037
|
-
payment_intent: query?.paymentIntentId,
|
|
4038
|
-
limit: query?.limit,
|
|
4039
|
-
starting_after: query?.startingAfter
|
|
4040
|
-
});
|
|
4041
|
-
return response.data.map((charge) => ({
|
|
4042
|
-
id: charge.id,
|
|
4043
|
-
paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
|
|
4044
|
-
amount: {
|
|
4045
|
-
amount: charge.amount,
|
|
4046
|
-
currency: charge.currency?.toUpperCase() ?? "USD"
|
|
4047
|
-
},
|
|
4048
|
-
type: "capture",
|
|
4049
|
-
status: mapChargeStatus(charge.status),
|
|
4050
|
-
description: charge.description ?? undefined,
|
|
4051
|
-
createdAt: new Date(charge.created * 1000),
|
|
4052
|
-
metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
|
|
4053
|
-
balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
|
|
4054
|
-
})
|
|
4055
|
-
}));
|
|
3946
|
+
async countTokens(_messages) {
|
|
3947
|
+
throw new Error("Mistral API does not currently support token counting");
|
|
4056
3948
|
}
|
|
4057
|
-
|
|
4058
|
-
const
|
|
4059
|
-
const
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
name: customer.name ?? undefined,
|
|
4064
|
-
metadata,
|
|
4065
|
-
createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
|
|
4066
|
-
updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
|
|
3949
|
+
buildChatRequest(messages, options) {
|
|
3950
|
+
const model = options.model ?? this.defaultModel;
|
|
3951
|
+
const mappedMessages = messages.map((message) => this.toMistralMessage(message));
|
|
3952
|
+
const request = {
|
|
3953
|
+
model,
|
|
3954
|
+
messages: mappedMessages
|
|
4067
3955
|
};
|
|
3956
|
+
if (options.temperature != null) {
|
|
3957
|
+
request.temperature = options.temperature;
|
|
3958
|
+
}
|
|
3959
|
+
if (options.topP != null) {
|
|
3960
|
+
request.topP = options.topP;
|
|
3961
|
+
}
|
|
3962
|
+
if (options.maxOutputTokens != null) {
|
|
3963
|
+
request.maxTokens = options.maxOutputTokens;
|
|
3964
|
+
}
|
|
3965
|
+
if (options.stopSequences?.length) {
|
|
3966
|
+
request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
|
|
3967
|
+
}
|
|
3968
|
+
if (options.tools?.length) {
|
|
3969
|
+
request.tools = options.tools.map((tool) => ({
|
|
3970
|
+
type: "function",
|
|
3971
|
+
function: {
|
|
3972
|
+
name: tool.name,
|
|
3973
|
+
description: tool.description,
|
|
3974
|
+
parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
|
|
3975
|
+
}
|
|
3976
|
+
}));
|
|
3977
|
+
}
|
|
3978
|
+
if (options.responseFormat === "json") {
|
|
3979
|
+
request.responseFormat = { type: "json_object" };
|
|
3980
|
+
}
|
|
3981
|
+
return request;
|
|
4068
3982
|
}
|
|
4069
|
-
|
|
4070
|
-
const
|
|
3983
|
+
buildLLMResponse(response) {
|
|
3984
|
+
const firstChoice = response.choices[0];
|
|
3985
|
+
if (!firstChoice) {
|
|
3986
|
+
return {
|
|
3987
|
+
message: {
|
|
3988
|
+
role: "assistant",
|
|
3989
|
+
content: [{ type: "text", text: "" }]
|
|
3990
|
+
},
|
|
3991
|
+
usage: this.fromUsage(response.usage),
|
|
3992
|
+
raw: response
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
const message = this.fromAssistantMessage(firstChoice.message);
|
|
4071
3996
|
return {
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
description: intent.description ?? undefined,
|
|
4077
|
-
clientSecret: intent.client_secret ?? undefined,
|
|
4078
|
-
metadata,
|
|
4079
|
-
createdAt: new Date(intent.created * 1000),
|
|
4080
|
-
updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
|
|
3997
|
+
message,
|
|
3998
|
+
usage: this.fromUsage(response.usage),
|
|
3999
|
+
finishReason: mapFinishReason(firstChoice.finishReason),
|
|
4000
|
+
raw: response
|
|
4081
4001
|
};
|
|
4082
4002
|
}
|
|
4083
|
-
|
|
4084
|
-
|
|
4003
|
+
fromUsage(usage) {
|
|
4004
|
+
if (!usage)
|
|
4005
|
+
return;
|
|
4085
4006
|
return {
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
|
|
4090
|
-
amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
|
|
4091
|
-
customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
|
|
4092
|
-
dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
|
|
4093
|
-
hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
|
|
4094
|
-
metadata,
|
|
4095
|
-
createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
|
|
4096
|
-
updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
|
|
4007
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
4008
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
4009
|
+
totalTokens: usage.totalTokens ?? 0
|
|
4097
4010
|
};
|
|
4098
4011
|
}
|
|
4099
|
-
|
|
4012
|
+
fromAssistantMessage(message) {
|
|
4013
|
+
const parts = [];
|
|
4014
|
+
if (typeof message.content === "string") {
|
|
4015
|
+
parts.push({ type: "text", text: message.content });
|
|
4016
|
+
} else if (Array.isArray(message.content)) {
|
|
4017
|
+
message.content.forEach((chunk) => {
|
|
4018
|
+
if (chunk.type === "text" && "text" in chunk) {
|
|
4019
|
+
parts.push({ type: "text", text: chunk.text });
|
|
4020
|
+
}
|
|
4021
|
+
});
|
|
4022
|
+
}
|
|
4023
|
+
const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
|
|
4024
|
+
if (toolCalls.length > 0) {
|
|
4025
|
+
parts.splice(0, 0, ...toolCalls);
|
|
4026
|
+
}
|
|
4027
|
+
if (parts.length === 0) {
|
|
4028
|
+
parts.push({ type: "text", text: "" });
|
|
4029
|
+
}
|
|
4100
4030
|
return {
|
|
4101
|
-
|
|
4102
|
-
|
|
4031
|
+
role: "assistant",
|
|
4032
|
+
content: parts
|
|
4103
4033
|
};
|
|
4104
4034
|
}
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
return;
|
|
4108
|
-
const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
|
|
4109
|
-
if (entries.length === 0)
|
|
4110
|
-
return;
|
|
4111
|
-
return Object.fromEntries(entries);
|
|
4112
|
-
}
|
|
4113
|
-
mergeMetadata(base, extras) {
|
|
4114
|
-
const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
|
|
4115
|
-
if (!base && filteredExtras.length === 0) {
|
|
4116
|
-
return;
|
|
4117
|
-
}
|
|
4035
|
+
fromMistralToolCall(call, index) {
|
|
4036
|
+
const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
|
|
4118
4037
|
return {
|
|
4119
|
-
|
|
4120
|
-
|
|
4038
|
+
type: "tool-call",
|
|
4039
|
+
id: call.id ?? `tool-call-${index}`,
|
|
4040
|
+
name: call.function.name,
|
|
4041
|
+
arguments: args
|
|
4121
4042
|
};
|
|
4122
4043
|
}
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4044
|
+
toMistralMessage(message) {
|
|
4045
|
+
const textContent = this.extractText(message.content);
|
|
4046
|
+
const toolCalls = this.extractToolCalls(message);
|
|
4047
|
+
switch (message.role) {
|
|
4048
|
+
case "system":
|
|
4049
|
+
return {
|
|
4050
|
+
role: "system",
|
|
4051
|
+
content: textContent ?? ""
|
|
4052
|
+
};
|
|
4053
|
+
case "user":
|
|
4054
|
+
return {
|
|
4055
|
+
role: "user",
|
|
4056
|
+
content: textContent ?? ""
|
|
4057
|
+
};
|
|
4058
|
+
case "assistant":
|
|
4059
|
+
return {
|
|
4060
|
+
role: "assistant",
|
|
4061
|
+
content: toolCalls.length > 0 ? null : textContent ?? "",
|
|
4062
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
4063
|
+
};
|
|
4064
|
+
case "tool":
|
|
4065
|
+
return {
|
|
4066
|
+
role: "tool",
|
|
4067
|
+
content: textContent ?? "",
|
|
4068
|
+
toolCallId: message.toolCallId ?? toolCalls[0]?.id
|
|
4069
|
+
};
|
|
4070
|
+
default:
|
|
4071
|
+
return {
|
|
4072
|
+
role: "user",
|
|
4073
|
+
content: textContent ?? ""
|
|
4074
|
+
};
|
|
4075
|
+
}
|
|
4151
4076
|
}
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
case "failed":
|
|
4158
|
-
case "canceled":
|
|
4159
|
-
return status;
|
|
4160
|
-
default:
|
|
4161
|
-
return "pending";
|
|
4077
|
+
extractText(parts) {
|
|
4078
|
+
const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
|
|
4079
|
+
if (textParts.length === 0)
|
|
4080
|
+
return null;
|
|
4081
|
+
return textParts.join("");
|
|
4162
4082
|
}
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4083
|
+
extractToolCalls(message) {
|
|
4084
|
+
const toolCallParts = message.content.filter((part) => part.type === "tool-call");
|
|
4085
|
+
return toolCallParts.map((call, index) => ({
|
|
4086
|
+
id: call.id ?? `call_${index}`,
|
|
4087
|
+
type: "function",
|
|
4088
|
+
index,
|
|
4089
|
+
function: {
|
|
4090
|
+
name: call.name,
|
|
4091
|
+
arguments: call.arguments
|
|
4092
|
+
}
|
|
4093
|
+
}));
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
function mapFinishReason(reason) {
|
|
4097
|
+
if (!reason)
|
|
4098
|
+
return;
|
|
4099
|
+
const normalized = reason.toLowerCase();
|
|
4100
|
+
switch (normalized) {
|
|
4101
|
+
case "stop":
|
|
4102
|
+
return "stop";
|
|
4103
|
+
case "length":
|
|
4104
|
+
return "length";
|
|
4105
|
+
case "tool_call":
|
|
4106
|
+
case "tool_calls":
|
|
4107
|
+
return "tool_call";
|
|
4108
|
+
case "content_filter":
|
|
4109
|
+
return "content_filter";
|
|
4174
4110
|
default:
|
|
4175
|
-
return
|
|
4111
|
+
return;
|
|
4176
4112
|
}
|
|
4177
4113
|
}
|
|
4178
4114
|
|
|
4179
|
-
// src/impls/
|
|
4180
|
-
import {
|
|
4115
|
+
// src/impls/notion.ts
|
|
4116
|
+
import { Client } from "@notionhq/client";
|
|
4181
4117
|
|
|
4182
|
-
class
|
|
4118
|
+
class NotionProjectManagementProvider {
|
|
4183
4119
|
client;
|
|
4184
|
-
|
|
4185
|
-
messageStream;
|
|
4120
|
+
defaults;
|
|
4186
4121
|
constructor(options) {
|
|
4187
|
-
this.client = options.client ?? new
|
|
4188
|
-
|
|
4122
|
+
this.client = options.client ?? new Client({ auth: options.apiKey });
|
|
4123
|
+
this.defaults = {
|
|
4124
|
+
databaseId: options.databaseId,
|
|
4125
|
+
summaryParentPageId: options.summaryParentPageId,
|
|
4126
|
+
titleProperty: options.titleProperty,
|
|
4127
|
+
statusProperty: options.statusProperty,
|
|
4128
|
+
priorityProperty: options.priorityProperty,
|
|
4129
|
+
tagsProperty: options.tagsProperty,
|
|
4130
|
+
dueDateProperty: options.dueDateProperty,
|
|
4131
|
+
descriptionProperty: options.descriptionProperty
|
|
4132
|
+
};
|
|
4133
|
+
}
|
|
4134
|
+
async createWorkItem(input) {
|
|
4135
|
+
if (input.type === "summary" && this.defaults.summaryParentPageId) {
|
|
4136
|
+
return this.createSummaryPage(input);
|
|
4137
|
+
}
|
|
4138
|
+
const databaseId = this.defaults.databaseId;
|
|
4139
|
+
if (!databaseId) {
|
|
4140
|
+
throw new Error("Notion databaseId is required to create work items.");
|
|
4141
|
+
}
|
|
4142
|
+
const titleProperty = this.defaults.titleProperty ?? "Name";
|
|
4143
|
+
const properties = {
|
|
4144
|
+
[titleProperty]: buildTitleProperty(input.title)
|
|
4145
|
+
};
|
|
4146
|
+
applySelect(properties, this.defaults.statusProperty, input.status);
|
|
4147
|
+
applySelect(properties, this.defaults.priorityProperty, input.priority);
|
|
4148
|
+
applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
|
|
4149
|
+
applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
|
|
4150
|
+
applyRichText(properties, this.defaults.descriptionProperty, input.description);
|
|
4151
|
+
const page = await this.client.pages.create({
|
|
4152
|
+
parent: { type: "database_id", database_id: databaseId },
|
|
4153
|
+
properties
|
|
4189
4154
|
});
|
|
4190
|
-
|
|
4191
|
-
|
|
4155
|
+
return {
|
|
4156
|
+
id: page.id,
|
|
4157
|
+
title: input.title,
|
|
4158
|
+
url: "url" in page ? page.url : undefined,
|
|
4159
|
+
status: input.status,
|
|
4160
|
+
priority: input.priority,
|
|
4161
|
+
tags: input.tags,
|
|
4162
|
+
projectId: databaseId,
|
|
4163
|
+
externalId: input.externalId,
|
|
4164
|
+
metadata: input.metadata
|
|
4165
|
+
};
|
|
4192
4166
|
}
|
|
4193
|
-
async
|
|
4194
|
-
const
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
MessageStream: this.messageStream,
|
|
4208
|
-
Attachments: buildAttachments(message)
|
|
4167
|
+
async createWorkItems(items) {
|
|
4168
|
+
const created = [];
|
|
4169
|
+
for (const item of items) {
|
|
4170
|
+
created.push(await this.createWorkItem(item));
|
|
4171
|
+
}
|
|
4172
|
+
return created;
|
|
4173
|
+
}
|
|
4174
|
+
async createSummaryPage(input) {
|
|
4175
|
+
const parentId = this.defaults.summaryParentPageId;
|
|
4176
|
+
if (!parentId) {
|
|
4177
|
+
throw new Error("Notion summaryParentPageId is required for summaries.");
|
|
4178
|
+
}
|
|
4179
|
+
const summaryProperties = {
|
|
4180
|
+
title: buildTitleProperty(input.title)
|
|
4209
4181
|
};
|
|
4210
|
-
const
|
|
4182
|
+
const page = await this.client.pages.create({
|
|
4183
|
+
parent: { type: "page_id", page_id: parentId },
|
|
4184
|
+
properties: summaryProperties
|
|
4185
|
+
});
|
|
4186
|
+
if (input.description) {
|
|
4187
|
+
const children = buildParagraphBlocks(input.description);
|
|
4188
|
+
if (children.length > 0) {
|
|
4189
|
+
await this.client.blocks.children.append({
|
|
4190
|
+
block_id: page.id,
|
|
4191
|
+
children
|
|
4192
|
+
});
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4211
4195
|
return {
|
|
4212
|
-
id:
|
|
4213
|
-
|
|
4214
|
-
|
|
4196
|
+
id: page.id,
|
|
4197
|
+
title: input.title,
|
|
4198
|
+
url: "url" in page ? page.url : undefined,
|
|
4199
|
+
status: input.status,
|
|
4200
|
+
priority: input.priority,
|
|
4201
|
+
tags: input.tags,
|
|
4202
|
+
projectId: parentId,
|
|
4203
|
+
externalId: input.externalId,
|
|
4204
|
+
metadata: input.metadata
|
|
4215
4205
|
};
|
|
4216
4206
|
}
|
|
4217
4207
|
}
|
|
4218
|
-
function
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4208
|
+
function buildTitleProperty(title) {
|
|
4209
|
+
return {
|
|
4210
|
+
title: [
|
|
4211
|
+
{
|
|
4212
|
+
type: "text",
|
|
4213
|
+
text: { content: title }
|
|
4214
|
+
}
|
|
4215
|
+
]
|
|
4216
|
+
};
|
|
4223
4217
|
}
|
|
4224
|
-
function
|
|
4225
|
-
|
|
4218
|
+
function buildRichText(text) {
|
|
4219
|
+
return {
|
|
4220
|
+
rich_text: [
|
|
4221
|
+
{
|
|
4222
|
+
type: "text",
|
|
4223
|
+
text: { content: text }
|
|
4224
|
+
}
|
|
4225
|
+
]
|
|
4226
|
+
};
|
|
4227
|
+
}
|
|
4228
|
+
function applySelect(properties, property, value) {
|
|
4229
|
+
if (!property || !value)
|
|
4226
4230
|
return;
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4231
|
+
const next = {
|
|
4232
|
+
select: { name: value }
|
|
4233
|
+
};
|
|
4234
|
+
properties[property] = next;
|
|
4235
|
+
}
|
|
4236
|
+
function applyMultiSelect(properties, property, values) {
|
|
4237
|
+
if (!property || !values || values.length === 0)
|
|
4238
|
+
return;
|
|
4239
|
+
const next = {
|
|
4240
|
+
multi_select: values.map((value) => ({ name: value }))
|
|
4241
|
+
};
|
|
4242
|
+
properties[property] = next;
|
|
4243
|
+
}
|
|
4244
|
+
function applyDate(properties, property, value) {
|
|
4245
|
+
if (!property || !value)
|
|
4246
|
+
return;
|
|
4247
|
+
const next = {
|
|
4248
|
+
date: { start: value.toISOString() }
|
|
4249
|
+
};
|
|
4250
|
+
properties[property] = next;
|
|
4251
|
+
}
|
|
4252
|
+
function applyRichText(properties, property, value) {
|
|
4253
|
+
if (!property || !value)
|
|
4254
|
+
return;
|
|
4255
|
+
properties[property] = buildRichText(value);
|
|
4256
|
+
}
|
|
4257
|
+
function buildParagraphBlocks(text) {
|
|
4258
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim());
|
|
4259
|
+
return lines.map((line) => ({
|
|
4260
|
+
object: "block",
|
|
4261
|
+
type: "paragraph",
|
|
4262
|
+
paragraph: {
|
|
4263
|
+
rich_text: [
|
|
4264
|
+
{
|
|
4265
|
+
type: "text",
|
|
4266
|
+
text: { content: line }
|
|
4267
|
+
}
|
|
4268
|
+
]
|
|
4269
|
+
}
|
|
4234
4270
|
}));
|
|
4235
4271
|
}
|
|
4236
4272
|
|
|
@@ -4251,682 +4287,365 @@ class PosthogAnalyticsReader {
|
|
|
4251
4287
|
query: {
|
|
4252
4288
|
kind: "HogQLQuery",
|
|
4253
4289
|
query: input.query,
|
|
4254
|
-
values: input.values
|
|
4255
|
-
}
|
|
4256
|
-
}
|
|
4257
|
-
});
|
|
4258
|
-
return response.data;
|
|
4259
|
-
}
|
|
4260
|
-
async getEvents(input) {
|
|
4261
|
-
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4262
|
-
const response = await this.client.request({
|
|
4263
|
-
method: "GET",
|
|
4264
|
-
path: `/api/projects/${projectId}/events/`,
|
|
4265
|
-
query: {
|
|
4266
|
-
event: input.event ?? resolveSingleEvent(input.events),
|
|
4267
|
-
events: resolveEventList(input.events),
|
|
4268
|
-
distinct_id: input.distinctId,
|
|
4269
|
-
order_by: input.orderBy,
|
|
4270
|
-
limit: input.limit,
|
|
4271
|
-
offset: input.offset,
|
|
4272
|
-
properties: input.properties ? JSON.stringify(input.properties) : undefined,
|
|
4273
|
-
...buildEventDateQuery(input.dateRange)
|
|
4274
|
-
}
|
|
4275
|
-
});
|
|
4276
|
-
return response.data;
|
|
4277
|
-
}
|
|
4278
|
-
async getPersons(input) {
|
|
4279
|
-
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4280
|
-
const response = await this.client.request({
|
|
4281
|
-
method: "GET",
|
|
4282
|
-
path: `/api/projects/${projectId}/persons/`,
|
|
4283
|
-
query: {
|
|
4284
|
-
cohort_id: input.cohortId,
|
|
4285
|
-
search: input.search,
|
|
4286
|
-
limit: input.limit,
|
|
4287
|
-
offset: input.offset,
|
|
4288
|
-
properties: input.properties ? JSON.stringify(input.properties) : undefined
|
|
4289
|
-
}
|
|
4290
|
-
});
|
|
4291
|
-
return response.data;
|
|
4292
|
-
}
|
|
4293
|
-
async getInsights(input) {
|
|
4294
|
-
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4295
|
-
const response = await this.client.request({
|
|
4296
|
-
method: "GET",
|
|
4297
|
-
path: `/api/projects/${projectId}/insights/`,
|
|
4298
|
-
query: {
|
|
4299
|
-
insight: input.insightType,
|
|
4300
|
-
limit: input.limit,
|
|
4301
|
-
offset: input.offset
|
|
4290
|
+
values: input.values
|
|
4291
|
+
}
|
|
4302
4292
|
}
|
|
4303
4293
|
});
|
|
4304
4294
|
return response.data;
|
|
4305
4295
|
}
|
|
4306
|
-
async
|
|
4307
|
-
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4308
|
-
const response = await this.client.request({
|
|
4309
|
-
method: "GET",
|
|
4310
|
-
path: `/api/projects/${projectId}/insights/${input.insightId}/`
|
|
4311
|
-
});
|
|
4312
|
-
return response.data;
|
|
4313
|
-
}
|
|
4314
|
-
async getCohorts(input) {
|
|
4296
|
+
async getEvents(input) {
|
|
4315
4297
|
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4316
4298
|
const response = await this.client.request({
|
|
4317
4299
|
method: "GET",
|
|
4318
|
-
path: `/api/projects/${projectId}/
|
|
4300
|
+
path: `/api/projects/${projectId}/events/`,
|
|
4319
4301
|
query: {
|
|
4302
|
+
event: input.event ?? resolveSingleEvent(input.events),
|
|
4303
|
+
events: resolveEventList(input.events),
|
|
4304
|
+
distinct_id: input.distinctId,
|
|
4305
|
+
order_by: input.orderBy,
|
|
4320
4306
|
limit: input.limit,
|
|
4321
|
-
offset: input.offset
|
|
4307
|
+
offset: input.offset,
|
|
4308
|
+
properties: input.properties ? JSON.stringify(input.properties) : undefined,
|
|
4309
|
+
...buildEventDateQuery(input.dateRange)
|
|
4322
4310
|
}
|
|
4323
4311
|
});
|
|
4324
4312
|
return response.data;
|
|
4325
4313
|
}
|
|
4326
|
-
async
|
|
4314
|
+
async getPersons(input) {
|
|
4327
4315
|
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4328
4316
|
const response = await this.client.request({
|
|
4329
4317
|
method: "GET",
|
|
4330
|
-
path: `/api/projects/${projectId}/
|
|
4318
|
+
path: `/api/projects/${projectId}/persons/`,
|
|
4331
4319
|
query: {
|
|
4332
|
-
|
|
4320
|
+
cohort_id: input.cohortId,
|
|
4321
|
+
search: input.search,
|
|
4333
4322
|
limit: input.limit,
|
|
4334
|
-
offset: input.offset
|
|
4323
|
+
offset: input.offset,
|
|
4324
|
+
properties: input.properties ? JSON.stringify(input.properties) : undefined
|
|
4335
4325
|
}
|
|
4336
4326
|
});
|
|
4337
4327
|
return response.data;
|
|
4338
4328
|
}
|
|
4339
|
-
async
|
|
4329
|
+
async getInsights(input) {
|
|
4340
4330
|
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4341
4331
|
const response = await this.client.request({
|
|
4342
4332
|
method: "GET",
|
|
4343
|
-
path: `/api/projects/${projectId}/
|
|
4333
|
+
path: `/api/projects/${projectId}/insights/`,
|
|
4344
4334
|
query: {
|
|
4335
|
+
insight: input.insightType,
|
|
4345
4336
|
limit: input.limit,
|
|
4346
|
-
offset: input.offset
|
|
4347
|
-
...buildAnnotationDateQuery(input.dateRange)
|
|
4348
|
-
}
|
|
4349
|
-
});
|
|
4350
|
-
return response.data;
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
|
-
function resolveProjectId(inputProjectId, defaultProjectId) {
|
|
4354
|
-
const projectId = inputProjectId ?? defaultProjectId;
|
|
4355
|
-
if (!projectId) {
|
|
4356
|
-
throw new Error("PostHog projectId is required for API reads.");
|
|
4357
|
-
}
|
|
4358
|
-
return projectId;
|
|
4359
|
-
}
|
|
4360
|
-
function resolveSingleEvent(events) {
|
|
4361
|
-
if (!events || events.length !== 1)
|
|
4362
|
-
return;
|
|
4363
|
-
return events[0];
|
|
4364
|
-
}
|
|
4365
|
-
function resolveEventList(events) {
|
|
4366
|
-
if (!events || events.length <= 1)
|
|
4367
|
-
return;
|
|
4368
|
-
return events.join(",");
|
|
4369
|
-
}
|
|
4370
|
-
function buildEventDateQuery(range) {
|
|
4371
|
-
const after = formatDate(range?.from);
|
|
4372
|
-
const before = formatDate(range?.to);
|
|
4373
|
-
return {
|
|
4374
|
-
after,
|
|
4375
|
-
before,
|
|
4376
|
-
timezone: range?.timezone
|
|
4377
|
-
};
|
|
4378
|
-
}
|
|
4379
|
-
function buildAnnotationDateQuery(range) {
|
|
4380
|
-
const dateFrom = formatDate(range?.from);
|
|
4381
|
-
const dateTo = formatDate(range?.to);
|
|
4382
|
-
return {
|
|
4383
|
-
date_from: dateFrom,
|
|
4384
|
-
date_to: dateTo,
|
|
4385
|
-
timezone: range?.timezone
|
|
4386
|
-
};
|
|
4387
|
-
}
|
|
4388
|
-
function formatDate(value) {
|
|
4389
|
-
if (!value)
|
|
4390
|
-
return;
|
|
4391
|
-
return typeof value === "string" ? value : value.toISOString();
|
|
4392
|
-
}
|
|
4393
|
-
|
|
4394
|
-
// src/impls/posthog-utils.ts
|
|
4395
|
-
function normalizeHost(host) {
|
|
4396
|
-
return host.replace(/\/$/, "");
|
|
4397
|
-
}
|
|
4398
|
-
function buildUrl(host, path, query) {
|
|
4399
|
-
if (/^https?:\/\//.test(path)) {
|
|
4400
|
-
return appendQuery(path, query);
|
|
4401
|
-
}
|
|
4402
|
-
const normalizedPath = path.replace(/^\/+/, "");
|
|
4403
|
-
return appendQuery(`${host}/${normalizedPath}`, query);
|
|
4404
|
-
}
|
|
4405
|
-
function appendQuery(url, query) {
|
|
4406
|
-
if (!query)
|
|
4407
|
-
return url;
|
|
4408
|
-
const params = new URLSearchParams;
|
|
4409
|
-
Object.entries(query).forEach(([key, value]) => {
|
|
4410
|
-
if (value === undefined)
|
|
4411
|
-
return;
|
|
4412
|
-
params.set(key, String(value));
|
|
4413
|
-
});
|
|
4414
|
-
const suffix = params.toString();
|
|
4415
|
-
return suffix ? `${url}?${suffix}` : url;
|
|
4416
|
-
}
|
|
4417
|
-
function parseJson(value) {
|
|
4418
|
-
if (!value)
|
|
4419
|
-
return {};
|
|
4420
|
-
try {
|
|
4421
|
-
return JSON.parse(value);
|
|
4422
|
-
} catch {
|
|
4423
|
-
return value;
|
|
4424
|
-
}
|
|
4425
|
-
}
|
|
4426
|
-
|
|
4427
|
-
// src/impls/posthog.ts
|
|
4428
|
-
import { PostHog } from "posthog-node";
|
|
4429
|
-
var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
|
|
4430
|
-
|
|
4431
|
-
class PosthogAnalyticsProvider {
|
|
4432
|
-
host;
|
|
4433
|
-
projectId;
|
|
4434
|
-
projectApiKey;
|
|
4435
|
-
personalApiKey;
|
|
4436
|
-
mcpUrl;
|
|
4437
|
-
fetchFn;
|
|
4438
|
-
client;
|
|
4439
|
-
reader;
|
|
4440
|
-
constructor(options) {
|
|
4441
|
-
this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
|
|
4442
|
-
this.projectId = options.projectId;
|
|
4443
|
-
this.projectApiKey = options.projectApiKey;
|
|
4444
|
-
this.personalApiKey = options.personalApiKey;
|
|
4445
|
-
this.mcpUrl = options.mcpUrl;
|
|
4446
|
-
this.fetchFn = options.fetch ?? fetch;
|
|
4447
|
-
this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
|
|
4448
|
-
host: this.host,
|
|
4449
|
-
requestTimeout: options.requestTimeoutMs ?? 1e4
|
|
4450
|
-
}) : undefined);
|
|
4451
|
-
this.reader = new PosthogAnalyticsReader({
|
|
4452
|
-
projectId: this.projectId,
|
|
4453
|
-
client: { request: this.request.bind(this) }
|
|
4454
|
-
});
|
|
4455
|
-
}
|
|
4456
|
-
async capture(event) {
|
|
4457
|
-
if (!this.client) {
|
|
4458
|
-
throw new Error("PostHog projectApiKey is required for capture.");
|
|
4459
|
-
}
|
|
4460
|
-
await this.client.capture({
|
|
4461
|
-
distinctId: event.distinctId,
|
|
4462
|
-
event: event.event,
|
|
4463
|
-
properties: event.properties,
|
|
4464
|
-
timestamp: event.timestamp,
|
|
4465
|
-
groups: event.groups
|
|
4466
|
-
});
|
|
4467
|
-
}
|
|
4468
|
-
async identify(input) {
|
|
4469
|
-
if (!this.client) {
|
|
4470
|
-
throw new Error("PostHog projectApiKey is required for identify.");
|
|
4471
|
-
}
|
|
4472
|
-
await this.client.identify({
|
|
4473
|
-
distinctId: input.distinctId,
|
|
4474
|
-
properties: {
|
|
4475
|
-
...input.properties ? { $set: input.properties } : {},
|
|
4476
|
-
...input.setOnce ? { $set_once: input.setOnce } : {}
|
|
4337
|
+
offset: input.offset
|
|
4477
4338
|
}
|
|
4478
4339
|
});
|
|
4340
|
+
return response.data;
|
|
4479
4341
|
}
|
|
4480
|
-
async
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
}
|
|
4486
|
-
async getPersons(input) {
|
|
4487
|
-
return this.reader.getPersons(input);
|
|
4488
|
-
}
|
|
4489
|
-
async getInsights(input) {
|
|
4490
|
-
return this.reader.getInsights(input);
|
|
4491
|
-
}
|
|
4492
|
-
async getInsightResult(input) {
|
|
4493
|
-
return this.reader.getInsightResult(input);
|
|
4494
|
-
}
|
|
4495
|
-
async getCohorts(input) {
|
|
4496
|
-
return this.reader.getCohorts(input);
|
|
4497
|
-
}
|
|
4498
|
-
async getFeatureFlags(input) {
|
|
4499
|
-
return this.reader.getFeatureFlags(input);
|
|
4500
|
-
}
|
|
4501
|
-
async getAnnotations(input) {
|
|
4502
|
-
return this.reader.getAnnotations(input);
|
|
4503
|
-
}
|
|
4504
|
-
async request(request) {
|
|
4505
|
-
if (!this.personalApiKey) {
|
|
4506
|
-
throw new Error("PostHog personalApiKey is required for API requests.");
|
|
4507
|
-
}
|
|
4508
|
-
const url = buildUrl(this.host, request.path, request.query);
|
|
4509
|
-
const response = await this.fetchFn(url, {
|
|
4510
|
-
method: request.method,
|
|
4511
|
-
headers: {
|
|
4512
|
-
Authorization: `Bearer ${this.personalApiKey}`,
|
|
4513
|
-
"Content-Type": "application/json",
|
|
4514
|
-
...request.headers ?? {}
|
|
4515
|
-
},
|
|
4516
|
-
body: request.body ? JSON.stringify(request.body) : undefined
|
|
4517
|
-
});
|
|
4518
|
-
const text = await response.text();
|
|
4519
|
-
const data = parseJson(text);
|
|
4520
|
-
return {
|
|
4521
|
-
status: response.status,
|
|
4522
|
-
data,
|
|
4523
|
-
headers: Object.fromEntries(response.headers.entries())
|
|
4524
|
-
};
|
|
4525
|
-
}
|
|
4526
|
-
async callMcpTool(call) {
|
|
4527
|
-
if (!this.mcpUrl) {
|
|
4528
|
-
throw new Error("PostHog MCP URL is not configured.");
|
|
4529
|
-
}
|
|
4530
|
-
const response = await this.fetchFn(this.mcpUrl, {
|
|
4531
|
-
method: "POST",
|
|
4532
|
-
headers: {
|
|
4533
|
-
"Content-Type": "application/json"
|
|
4534
|
-
},
|
|
4535
|
-
body: JSON.stringify({
|
|
4536
|
-
jsonrpc: "2.0",
|
|
4537
|
-
id: 1,
|
|
4538
|
-
method: "tools/call",
|
|
4539
|
-
params: {
|
|
4540
|
-
name: call.name,
|
|
4541
|
-
arguments: call.arguments ?? {}
|
|
4542
|
-
}
|
|
4543
|
-
})
|
|
4544
|
-
});
|
|
4545
|
-
if (!response.ok) {
|
|
4546
|
-
const body = await response.text();
|
|
4547
|
-
throw new Error(`PostHog MCP error (${response.status}): ${body}`);
|
|
4548
|
-
}
|
|
4549
|
-
const result = await response.json();
|
|
4550
|
-
if (result.error) {
|
|
4551
|
-
throw new Error(result.error.message ?? "PostHog MCP error");
|
|
4552
|
-
}
|
|
4553
|
-
return result.result ?? null;
|
|
4554
|
-
}
|
|
4555
|
-
}
|
|
4556
|
-
|
|
4557
|
-
// src/impls/twilio-sms.ts
|
|
4558
|
-
import Twilio from "twilio";
|
|
4559
|
-
|
|
4560
|
-
class TwilioSmsProvider {
|
|
4561
|
-
client;
|
|
4562
|
-
fromNumber;
|
|
4563
|
-
constructor(options) {
|
|
4564
|
-
this.client = options.client ?? Twilio(options.accountSid, options.authToken);
|
|
4565
|
-
this.fromNumber = options.fromNumber;
|
|
4566
|
-
}
|
|
4567
|
-
async sendSms(input) {
|
|
4568
|
-
const message = await this.client.messages.create({
|
|
4569
|
-
to: input.to,
|
|
4570
|
-
from: input.from ?? this.fromNumber,
|
|
4571
|
-
body: input.body
|
|
4342
|
+
async getInsightResult(input) {
|
|
4343
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4344
|
+
const response = await this.client.request({
|
|
4345
|
+
method: "GET",
|
|
4346
|
+
path: `/api/projects/${projectId}/insights/${input.insightId}/`
|
|
4572
4347
|
});
|
|
4573
|
-
return
|
|
4574
|
-
id: message.sid,
|
|
4575
|
-
to: message.to ?? input.to,
|
|
4576
|
-
from: message.from ?? input.from ?? this.fromNumber ?? "",
|
|
4577
|
-
body: message.body ?? input.body,
|
|
4578
|
-
status: mapStatus(message.status),
|
|
4579
|
-
sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
|
|
4580
|
-
deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
|
|
4581
|
-
price: message.price ? Number(message.price) : undefined,
|
|
4582
|
-
priceCurrency: message.priceUnit ?? undefined,
|
|
4583
|
-
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
4584
|
-
errorMessage: message.errorMessage ?? undefined
|
|
4585
|
-
};
|
|
4348
|
+
return response.data;
|
|
4586
4349
|
}
|
|
4587
|
-
async
|
|
4588
|
-
const
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4350
|
+
async getCohorts(input) {
|
|
4351
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4352
|
+
const response = await this.client.request({
|
|
4353
|
+
method: "GET",
|
|
4354
|
+
path: `/api/projects/${projectId}/cohorts/`,
|
|
4355
|
+
query: {
|
|
4356
|
+
limit: input.limit,
|
|
4357
|
+
offset: input.offset
|
|
4358
|
+
}
|
|
4359
|
+
});
|
|
4360
|
+
return response.data;
|
|
4361
|
+
}
|
|
4362
|
+
async getFeatureFlags(input) {
|
|
4363
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4364
|
+
const response = await this.client.request({
|
|
4365
|
+
method: "GET",
|
|
4366
|
+
path: `/api/projects/${projectId}/feature_flags/`,
|
|
4367
|
+
query: {
|
|
4368
|
+
active: input.active,
|
|
4369
|
+
limit: input.limit,
|
|
4370
|
+
offset: input.offset
|
|
4371
|
+
}
|
|
4372
|
+
});
|
|
4373
|
+
return response.data;
|
|
4374
|
+
}
|
|
4375
|
+
async getAnnotations(input) {
|
|
4376
|
+
const projectId = resolveProjectId(input.projectId, this.projectId);
|
|
4377
|
+
const response = await this.client.request({
|
|
4378
|
+
method: "GET",
|
|
4379
|
+
path: `/api/projects/${projectId}/annotations/`,
|
|
4380
|
+
query: {
|
|
4381
|
+
limit: input.limit,
|
|
4382
|
+
offset: input.offset,
|
|
4383
|
+
...buildAnnotationDateQuery(input.dateRange)
|
|
4384
|
+
}
|
|
4385
|
+
});
|
|
4386
|
+
return response.data;
|
|
4595
4387
|
}
|
|
4596
4388
|
}
|
|
4597
|
-
function
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
case "scheduled":
|
|
4602
|
-
return "queued";
|
|
4603
|
-
case "sending":
|
|
4604
|
-
case "processing":
|
|
4605
|
-
return "sending";
|
|
4606
|
-
case "sent":
|
|
4607
|
-
return "sent";
|
|
4608
|
-
case "delivered":
|
|
4609
|
-
return "delivered";
|
|
4610
|
-
case "undelivered":
|
|
4611
|
-
return "undelivered";
|
|
4612
|
-
case "failed":
|
|
4613
|
-
case "canceled":
|
|
4614
|
-
return "failed";
|
|
4615
|
-
default:
|
|
4616
|
-
return "queued";
|
|
4389
|
+
function resolveProjectId(inputProjectId, defaultProjectId) {
|
|
4390
|
+
const projectId = inputProjectId ?? defaultProjectId;
|
|
4391
|
+
if (!projectId) {
|
|
4392
|
+
throw new Error("PostHog projectId is required for API reads.");
|
|
4617
4393
|
}
|
|
4394
|
+
return projectId;
|
|
4395
|
+
}
|
|
4396
|
+
function resolveSingleEvent(events) {
|
|
4397
|
+
if (!events || events.length !== 1)
|
|
4398
|
+
return;
|
|
4399
|
+
return events[0];
|
|
4400
|
+
}
|
|
4401
|
+
function resolveEventList(events) {
|
|
4402
|
+
if (!events || events.length <= 1)
|
|
4403
|
+
return;
|
|
4404
|
+
return events.join(",");
|
|
4405
|
+
}
|
|
4406
|
+
function buildEventDateQuery(range) {
|
|
4407
|
+
const after = formatDate(range?.from);
|
|
4408
|
+
const before = formatDate(range?.to);
|
|
4409
|
+
return {
|
|
4410
|
+
after,
|
|
4411
|
+
before,
|
|
4412
|
+
timezone: range?.timezone
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4415
|
+
function buildAnnotationDateQuery(range) {
|
|
4416
|
+
const dateFrom = formatDate(range?.from);
|
|
4417
|
+
const dateTo = formatDate(range?.to);
|
|
4418
|
+
return {
|
|
4419
|
+
date_from: dateFrom,
|
|
4420
|
+
date_to: dateTo,
|
|
4421
|
+
timezone: range?.timezone
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
function formatDate(value) {
|
|
4425
|
+
if (!value)
|
|
4426
|
+
return;
|
|
4427
|
+
return typeof value === "string" ? value : value.toISOString();
|
|
4618
4428
|
}
|
|
4619
4429
|
|
|
4620
|
-
// src/impls/
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
this.defaultChannelId = options.defaultChannelId;
|
|
4628
|
-
this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
|
|
4629
|
-
}
|
|
4630
|
-
async sendMessage(input) {
|
|
4631
|
-
const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
|
|
4632
|
-
if (!channel) {
|
|
4633
|
-
throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
|
|
4634
|
-
}
|
|
4635
|
-
const payload = {
|
|
4636
|
-
channel,
|
|
4637
|
-
text: input.text,
|
|
4638
|
-
mrkdwn: input.markdown ?? true,
|
|
4639
|
-
thread_ts: input.threadId
|
|
4640
|
-
};
|
|
4641
|
-
const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
|
|
4642
|
-
method: "POST",
|
|
4643
|
-
headers: {
|
|
4644
|
-
authorization: `Bearer ${this.botToken}`,
|
|
4645
|
-
"content-type": "application/json"
|
|
4646
|
-
},
|
|
4647
|
-
body: JSON.stringify(payload)
|
|
4648
|
-
});
|
|
4649
|
-
const body = await response.json();
|
|
4650
|
-
if (!response.ok || !body.ok || !body.ts) {
|
|
4651
|
-
throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
|
|
4652
|
-
}
|
|
4653
|
-
return {
|
|
4654
|
-
id: `slack:${body.channel ?? channel}:${body.ts}`,
|
|
4655
|
-
providerMessageId: body.ts,
|
|
4656
|
-
status: "sent",
|
|
4657
|
-
sentAt: new Date,
|
|
4658
|
-
metadata: {
|
|
4659
|
-
channelId: body.channel ?? channel
|
|
4660
|
-
}
|
|
4661
|
-
};
|
|
4430
|
+
// src/impls/posthog-utils.ts
|
|
4431
|
+
function normalizeHost(host) {
|
|
4432
|
+
return host.replace(/\/$/, "");
|
|
4433
|
+
}
|
|
4434
|
+
function buildUrl(host, path, query) {
|
|
4435
|
+
if (/^https?:\/\//.test(path)) {
|
|
4436
|
+
return appendQuery(path, query);
|
|
4662
4437
|
}
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
}
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
return {
|
|
4686
|
-
id: `slack:${body.channel ?? channel}:${body.ts}`,
|
|
4687
|
-
providerMessageId: body.ts,
|
|
4688
|
-
status: "sent",
|
|
4689
|
-
sentAt: new Date,
|
|
4690
|
-
metadata: {
|
|
4691
|
-
channelId: body.channel ?? channel
|
|
4692
|
-
}
|
|
4693
|
-
};
|
|
4438
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
4439
|
+
return appendQuery(`${host}/${normalizedPath}`, query);
|
|
4440
|
+
}
|
|
4441
|
+
function appendQuery(url, query) {
|
|
4442
|
+
if (!query)
|
|
4443
|
+
return url;
|
|
4444
|
+
const params = new URLSearchParams;
|
|
4445
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
4446
|
+
if (value === undefined)
|
|
4447
|
+
return;
|
|
4448
|
+
params.set(key, String(value));
|
|
4449
|
+
});
|
|
4450
|
+
const suffix = params.toString();
|
|
4451
|
+
return suffix ? `${url}?${suffix}` : url;
|
|
4452
|
+
}
|
|
4453
|
+
function parseJson(value) {
|
|
4454
|
+
if (!value)
|
|
4455
|
+
return {};
|
|
4456
|
+
try {
|
|
4457
|
+
return JSON.parse(value);
|
|
4458
|
+
} catch {
|
|
4459
|
+
return value;
|
|
4694
4460
|
}
|
|
4695
4461
|
}
|
|
4696
4462
|
|
|
4697
|
-
// src/impls/
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4463
|
+
// src/impls/posthog.ts
|
|
4464
|
+
import { PostHog } from "posthog-node";
|
|
4465
|
+
var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
|
|
4466
|
+
|
|
4467
|
+
class PosthogAnalyticsProvider {
|
|
4468
|
+
host;
|
|
4469
|
+
projectId;
|
|
4470
|
+
projectApiKey;
|
|
4471
|
+
personalApiKey;
|
|
4472
|
+
mcpUrl;
|
|
4473
|
+
fetchFn;
|
|
4474
|
+
client;
|
|
4475
|
+
reader;
|
|
4703
4476
|
constructor(options) {
|
|
4704
|
-
this.
|
|
4705
|
-
this.
|
|
4706
|
-
this.
|
|
4707
|
-
this.
|
|
4477
|
+
this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
|
|
4478
|
+
this.projectId = options.projectId;
|
|
4479
|
+
this.projectApiKey = options.projectApiKey;
|
|
4480
|
+
this.personalApiKey = options.personalApiKey;
|
|
4481
|
+
this.mcpUrl = options.mcpUrl;
|
|
4482
|
+
this.fetchFn = options.fetch ?? fetch;
|
|
4483
|
+
this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
|
|
4484
|
+
host: this.host,
|
|
4485
|
+
requestTimeout: options.requestTimeoutMs ?? 1e4
|
|
4486
|
+
}) : undefined);
|
|
4487
|
+
this.reader = new PosthogAnalyticsReader({
|
|
4488
|
+
projectId: this.projectId,
|
|
4489
|
+
client: { request: this.request.bind(this) }
|
|
4490
|
+
});
|
|
4708
4491
|
}
|
|
4709
|
-
async
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4492
|
+
async capture(event) {
|
|
4493
|
+
if (!this.client) {
|
|
4494
|
+
throw new Error("PostHog projectApiKey is required for capture.");
|
|
4495
|
+
}
|
|
4496
|
+
await this.client.capture({
|
|
4497
|
+
distinctId: event.distinctId,
|
|
4498
|
+
event: event.event,
|
|
4499
|
+
properties: event.properties,
|
|
4500
|
+
timestamp: event.timestamp,
|
|
4501
|
+
groups: event.groups
|
|
4719
4502
|
});
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4503
|
+
}
|
|
4504
|
+
async identify(input) {
|
|
4505
|
+
if (!this.client) {
|
|
4506
|
+
throw new Error("PostHog projectApiKey is required for identify.");
|
|
4723
4507
|
}
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
metadata: {
|
|
4730
|
-
url: body.html_url ?? "",
|
|
4731
|
-
owner: target.owner,
|
|
4732
|
-
repo: target.repo,
|
|
4733
|
-
issueNumber: String(target.issueNumber)
|
|
4508
|
+
await this.client.identify({
|
|
4509
|
+
distinctId: input.distinctId,
|
|
4510
|
+
properties: {
|
|
4511
|
+
...input.properties ? { $set: input.properties } : {},
|
|
4512
|
+
...input.setOnce ? { $set_once: input.setOnce } : {}
|
|
4734
4513
|
}
|
|
4735
|
-
};
|
|
4514
|
+
});
|
|
4736
4515
|
}
|
|
4737
|
-
async
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4516
|
+
async queryHogQL(input) {
|
|
4517
|
+
return this.reader.queryHogQL(input);
|
|
4518
|
+
}
|
|
4519
|
+
async getEvents(input) {
|
|
4520
|
+
return this.reader.getEvents(input);
|
|
4521
|
+
}
|
|
4522
|
+
async getPersons(input) {
|
|
4523
|
+
return this.reader.getPersons(input);
|
|
4524
|
+
}
|
|
4525
|
+
async getInsights(input) {
|
|
4526
|
+
return this.reader.getInsights(input);
|
|
4527
|
+
}
|
|
4528
|
+
async getInsightResult(input) {
|
|
4529
|
+
return this.reader.getInsightResult(input);
|
|
4530
|
+
}
|
|
4531
|
+
async getCohorts(input) {
|
|
4532
|
+
return this.reader.getCohorts(input);
|
|
4533
|
+
}
|
|
4534
|
+
async getFeatureFlags(input) {
|
|
4535
|
+
return this.reader.getFeatureFlags(input);
|
|
4536
|
+
}
|
|
4537
|
+
async getAnnotations(input) {
|
|
4538
|
+
return this.reader.getAnnotations(input);
|
|
4539
|
+
}
|
|
4540
|
+
async request(request) {
|
|
4541
|
+
if (!this.personalApiKey) {
|
|
4542
|
+
throw new Error("PostHog personalApiKey is required for API requests.");
|
|
4742
4543
|
}
|
|
4743
|
-
const
|
|
4744
|
-
|
|
4544
|
+
const url = buildUrl(this.host, request.path, request.query);
|
|
4545
|
+
const response = await this.fetchFn(url, {
|
|
4546
|
+
method: request.method,
|
|
4745
4547
|
headers: {
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4548
|
+
Authorization: `Bearer ${this.personalApiKey}`,
|
|
4549
|
+
"Content-Type": "application/json",
|
|
4550
|
+
...request.headers ?? {}
|
|
4749
4551
|
},
|
|
4750
|
-
body: JSON.stringify(
|
|
4552
|
+
body: request.body ? JSON.stringify(request.body) : undefined
|
|
4751
4553
|
});
|
|
4752
|
-
const
|
|
4753
|
-
|
|
4754
|
-
throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
|
|
4755
|
-
}
|
|
4756
|
-
return {
|
|
4757
|
-
id: String(body.id),
|
|
4758
|
-
providerMessageId: body.node_id,
|
|
4759
|
-
status: "sent",
|
|
4760
|
-
sentAt: new Date,
|
|
4761
|
-
metadata: {
|
|
4762
|
-
url: body.html_url ?? "",
|
|
4763
|
-
owner,
|
|
4764
|
-
repo
|
|
4765
|
-
}
|
|
4766
|
-
};
|
|
4767
|
-
}
|
|
4768
|
-
resolveTarget(input) {
|
|
4769
|
-
const parsedRecipient = parseRecipient(input.recipientId);
|
|
4770
|
-
const owner = parsedRecipient?.owner ?? this.defaultOwner;
|
|
4771
|
-
const repo = parsedRecipient?.repo ?? this.defaultRepo;
|
|
4772
|
-
const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
|
|
4773
|
-
if (!owner || !repo || issueNumber == null) {
|
|
4774
|
-
throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
|
|
4775
|
-
}
|
|
4554
|
+
const text = await response.text();
|
|
4555
|
+
const data = parseJson(text);
|
|
4776
4556
|
return {
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4557
|
+
status: response.status,
|
|
4558
|
+
data,
|
|
4559
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
4780
4560
|
};
|
|
4781
4561
|
}
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
return null;
|
|
4786
|
-
const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
4787
|
-
if (!match)
|
|
4788
|
-
return null;
|
|
4789
|
-
const owner = match[1];
|
|
4790
|
-
const repo = match[2];
|
|
4791
|
-
const issueNumber = Number(match[3]);
|
|
4792
|
-
if (!owner || !repo || !Number.isInteger(issueNumber)) {
|
|
4793
|
-
return null;
|
|
4794
|
-
}
|
|
4795
|
-
return { owner, repo, issueNumber };
|
|
4796
|
-
}
|
|
4797
|
-
function parseIssueNumber(value) {
|
|
4798
|
-
if (!value)
|
|
4799
|
-
return null;
|
|
4800
|
-
const numeric = Number(value);
|
|
4801
|
-
return Number.isInteger(numeric) ? numeric : null;
|
|
4802
|
-
}
|
|
4803
|
-
|
|
4804
|
-
// src/impls/messaging-whatsapp-meta.ts
|
|
4805
|
-
class MetaWhatsappMessagingProvider {
|
|
4806
|
-
accessToken;
|
|
4807
|
-
phoneNumberId;
|
|
4808
|
-
apiVersion;
|
|
4809
|
-
constructor(options) {
|
|
4810
|
-
this.accessToken = options.accessToken;
|
|
4811
|
-
this.phoneNumberId = options.phoneNumberId;
|
|
4812
|
-
this.apiVersion = options.apiVersion ?? "v22.0";
|
|
4813
|
-
}
|
|
4814
|
-
async sendMessage(input) {
|
|
4815
|
-
const to = input.recipientId;
|
|
4816
|
-
if (!to) {
|
|
4817
|
-
throw new Error("Meta WhatsApp sendMessage requires recipientId.");
|
|
4562
|
+
async callMcpTool(call) {
|
|
4563
|
+
if (!this.mcpUrl) {
|
|
4564
|
+
throw new Error("PostHog MCP URL is not configured.");
|
|
4818
4565
|
}
|
|
4819
|
-
const response = await
|
|
4566
|
+
const response = await this.fetchFn(this.mcpUrl, {
|
|
4820
4567
|
method: "POST",
|
|
4821
4568
|
headers: {
|
|
4822
|
-
|
|
4823
|
-
"content-type": "application/json"
|
|
4569
|
+
"Content-Type": "application/json"
|
|
4824
4570
|
},
|
|
4825
4571
|
body: JSON.stringify({
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4572
|
+
jsonrpc: "2.0",
|
|
4573
|
+
id: 1,
|
|
4574
|
+
method: "tools/call",
|
|
4575
|
+
params: {
|
|
4576
|
+
name: call.name,
|
|
4577
|
+
arguments: call.arguments ?? {}
|
|
4832
4578
|
}
|
|
4833
4579
|
})
|
|
4834
4580
|
});
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
const errorCode = body.error?.code != null ? String(body.error.code) : "";
|
|
4839
|
-
throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
|
|
4581
|
+
if (!response.ok) {
|
|
4582
|
+
const body = await response.text();
|
|
4583
|
+
throw new Error(`PostHog MCP error (${response.status}): ${body}`);
|
|
4840
4584
|
}
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
metadata: {
|
|
4847
|
-
phoneNumberId: this.phoneNumberId
|
|
4848
|
-
}
|
|
4849
|
-
};
|
|
4585
|
+
const result = await response.json();
|
|
4586
|
+
if (result.error) {
|
|
4587
|
+
throw new Error(result.error.message ?? "PostHog MCP error");
|
|
4588
|
+
}
|
|
4589
|
+
return result.result ?? null;
|
|
4850
4590
|
}
|
|
4851
4591
|
}
|
|
4852
4592
|
|
|
4853
|
-
// src/impls/
|
|
4854
|
-
import {
|
|
4593
|
+
// src/impls/postmark-email.ts
|
|
4594
|
+
import { ServerClient } from "postmark";
|
|
4855
4595
|
|
|
4856
|
-
class
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4596
|
+
class PostmarkEmailProvider {
|
|
4597
|
+
client;
|
|
4598
|
+
defaultFromEmail;
|
|
4599
|
+
messageStream;
|
|
4860
4600
|
constructor(options) {
|
|
4861
|
-
this.
|
|
4862
|
-
|
|
4863
|
-
this.fromNumber = options.fromNumber;
|
|
4864
|
-
}
|
|
4865
|
-
async sendMessage(input) {
|
|
4866
|
-
const to = normalizeWhatsappAddress(input.recipientId);
|
|
4867
|
-
const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
|
|
4868
|
-
if (!to) {
|
|
4869
|
-
throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
|
|
4870
|
-
}
|
|
4871
|
-
if (!from) {
|
|
4872
|
-
throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
|
|
4873
|
-
}
|
|
4874
|
-
const params = new URLSearchParams;
|
|
4875
|
-
params.set("To", to);
|
|
4876
|
-
params.set("From", from);
|
|
4877
|
-
params.set("Body", input.text);
|
|
4878
|
-
const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
|
|
4879
|
-
const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
|
|
4880
|
-
method: "POST",
|
|
4881
|
-
headers: {
|
|
4882
|
-
authorization: `Basic ${auth}`,
|
|
4883
|
-
"content-type": "application/x-www-form-urlencoded"
|
|
4884
|
-
},
|
|
4885
|
-
body: params.toString()
|
|
4601
|
+
this.client = options.client ?? new ServerClient(options.serverToken, {
|
|
4602
|
+
useHttps: true
|
|
4886
4603
|
});
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4604
|
+
this.defaultFromEmail = options.defaultFromEmail;
|
|
4605
|
+
this.messageStream = options.messageStream;
|
|
4606
|
+
}
|
|
4607
|
+
async sendEmail(message) {
|
|
4608
|
+
const request = {
|
|
4609
|
+
From: formatAddress(message.from) ?? this.defaultFromEmail,
|
|
4610
|
+
To: message.to.map((addr) => formatAddress(addr)).join(", "),
|
|
4611
|
+
Cc: message.cc?.map((addr) => formatAddress(addr)).join(", ") || undefined,
|
|
4612
|
+
Bcc: message.bcc?.map((addr) => formatAddress(addr)).join(", ") || undefined,
|
|
4613
|
+
ReplyTo: message.replyTo ? formatAddress(message.replyTo) : undefined,
|
|
4614
|
+
Subject: message.subject,
|
|
4615
|
+
TextBody: message.textBody,
|
|
4616
|
+
HtmlBody: message.htmlBody,
|
|
4617
|
+
Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
|
|
4618
|
+
Name: name,
|
|
4619
|
+
Value: value
|
|
4620
|
+
})) : undefined,
|
|
4621
|
+
MessageStream: this.messageStream,
|
|
4622
|
+
Attachments: buildAttachments(message)
|
|
4623
|
+
};
|
|
4624
|
+
const response = await this.client.sendEmail(request);
|
|
4891
4625
|
return {
|
|
4892
|
-
id:
|
|
4893
|
-
providerMessageId:
|
|
4894
|
-
|
|
4895
|
-
sentAt: new Date,
|
|
4896
|
-
errorCode: body.error_code != null ? String(body.error_code) : undefined,
|
|
4897
|
-
errorMessage: body.error_message ?? undefined,
|
|
4898
|
-
metadata: {
|
|
4899
|
-
from,
|
|
4900
|
-
to
|
|
4901
|
-
}
|
|
4626
|
+
id: response.MessageID,
|
|
4627
|
+
providerMessageId: response.MessageID,
|
|
4628
|
+
queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
|
|
4902
4629
|
};
|
|
4903
4630
|
}
|
|
4904
4631
|
}
|
|
4905
|
-
function
|
|
4906
|
-
if (
|
|
4907
|
-
return
|
|
4908
|
-
if (value.startsWith("whatsapp:"))
|
|
4909
|
-
return value;
|
|
4910
|
-
return `whatsapp:${value}`;
|
|
4911
|
-
}
|
|
4912
|
-
function mapTwilioStatus(status) {
|
|
4913
|
-
switch (status) {
|
|
4914
|
-
case "queued":
|
|
4915
|
-
case "accepted":
|
|
4916
|
-
case "scheduled":
|
|
4917
|
-
return "queued";
|
|
4918
|
-
case "sending":
|
|
4919
|
-
return "sending";
|
|
4920
|
-
case "delivered":
|
|
4921
|
-
return "delivered";
|
|
4922
|
-
case "failed":
|
|
4923
|
-
case "undelivered":
|
|
4924
|
-
case "canceled":
|
|
4925
|
-
return "failed";
|
|
4926
|
-
case "sent":
|
|
4927
|
-
default:
|
|
4928
|
-
return "sent";
|
|
4632
|
+
function formatAddress(address) {
|
|
4633
|
+
if (address.name) {
|
|
4634
|
+
return `"${address.name}" <${address.email}>`;
|
|
4929
4635
|
}
|
|
4636
|
+
return address.email;
|
|
4637
|
+
}
|
|
4638
|
+
function buildAttachments(message) {
|
|
4639
|
+
if (!message.attachments?.length)
|
|
4640
|
+
return;
|
|
4641
|
+
return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
|
|
4642
|
+
Name: attachment.filename,
|
|
4643
|
+
Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
|
|
4644
|
+
ContentType: attachment.contentType,
|
|
4645
|
+
ContentID: null,
|
|
4646
|
+
ContentLength: attachment.sizeBytes,
|
|
4647
|
+
Disposition: "attachment"
|
|
4648
|
+
}));
|
|
4930
4649
|
}
|
|
4931
4650
|
|
|
4932
4651
|
// src/impls/powens-client.ts
|
|
@@ -5351,367 +5070,629 @@ class PowensOpenBankingProvider {
|
|
|
5351
5070
|
this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
|
|
5352
5071
|
throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
|
|
5353
5072
|
}
|
|
5354
|
-
}
|
|
5355
|
-
|
|
5356
|
-
// src/impls/
|
|
5357
|
-
import {
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
this.
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5073
|
+
}
|
|
5074
|
+
|
|
5075
|
+
// src/impls/qdrant-vector.ts
|
|
5076
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
5077
|
+
|
|
5078
|
+
class QdrantVectorProvider {
|
|
5079
|
+
client;
|
|
5080
|
+
createCollectionIfMissing;
|
|
5081
|
+
distance;
|
|
5082
|
+
constructor(options) {
|
|
5083
|
+
this.client = options.client ?? new QdrantClient({
|
|
5084
|
+
url: options.url,
|
|
5085
|
+
apiKey: options.apiKey,
|
|
5086
|
+
...options.clientParams
|
|
5087
|
+
});
|
|
5088
|
+
this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
|
|
5089
|
+
this.distance = options.distance ?? "Cosine";
|
|
5090
|
+
}
|
|
5091
|
+
async upsert(request) {
|
|
5092
|
+
if (request.documents.length === 0)
|
|
5093
|
+
return;
|
|
5094
|
+
const firstDocument = request.documents[0];
|
|
5095
|
+
if (!firstDocument)
|
|
5096
|
+
return;
|
|
5097
|
+
const vectorSize = firstDocument.vector.length;
|
|
5098
|
+
if (this.createCollectionIfMissing) {
|
|
5099
|
+
await this.ensureCollection(request.collection, vectorSize);
|
|
5100
|
+
}
|
|
5101
|
+
const points = request.documents.map((document) => ({
|
|
5102
|
+
id: document.id,
|
|
5103
|
+
vector: document.vector,
|
|
5104
|
+
payload: {
|
|
5105
|
+
...document.payload,
|
|
5106
|
+
...document.namespace ? { namespace: document.namespace } : {},
|
|
5107
|
+
...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
|
|
5108
|
+
}
|
|
5109
|
+
}));
|
|
5110
|
+
await this.client.upsert(request.collection, {
|
|
5111
|
+
wait: true,
|
|
5112
|
+
points
|
|
5113
|
+
});
|
|
5114
|
+
}
|
|
5115
|
+
async search(query) {
|
|
5116
|
+
const results = await this.client.search(query.collection, {
|
|
5117
|
+
vector: query.vector,
|
|
5118
|
+
limit: query.topK,
|
|
5119
|
+
filter: query.filter,
|
|
5120
|
+
score_threshold: query.scoreThreshold,
|
|
5121
|
+
with_payload: true,
|
|
5122
|
+
with_vector: false
|
|
5123
|
+
});
|
|
5124
|
+
return results.map((item) => ({
|
|
5125
|
+
id: String(item.id),
|
|
5126
|
+
score: item.score,
|
|
5127
|
+
payload: item.payload ?? undefined,
|
|
5128
|
+
namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
|
|
5129
|
+
}));
|
|
5130
|
+
}
|
|
5131
|
+
async delete(request) {
|
|
5132
|
+
await this.client.delete(request.collection, {
|
|
5133
|
+
wait: true,
|
|
5134
|
+
points: request.ids
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
5137
|
+
async ensureCollection(collectionName, vectorSize) {
|
|
5138
|
+
try {
|
|
5139
|
+
await this.client.getCollection(collectionName);
|
|
5140
|
+
} catch (_error) {
|
|
5141
|
+
await this.client.createCollection(collectionName, {
|
|
5142
|
+
vectors: {
|
|
5143
|
+
size: vectorSize,
|
|
5144
|
+
distance: this.distance
|
|
5145
|
+
}
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
// src/impls/stripe-payments.ts
|
|
5152
|
+
import Stripe from "stripe";
|
|
5153
|
+
var API_VERSION = "2026-02-25.clover";
|
|
5154
|
+
|
|
5155
|
+
class StripePaymentsProvider {
|
|
5156
|
+
stripe;
|
|
5157
|
+
constructor(options) {
|
|
5158
|
+
this.stripe = options.stripe ?? new Stripe(options.apiKey, {
|
|
5159
|
+
apiVersion: API_VERSION
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
async createCustomer(input) {
|
|
5163
|
+
const customer = await this.stripe.customers.create({
|
|
5164
|
+
email: input.email,
|
|
5165
|
+
name: input.name,
|
|
5166
|
+
description: input.description,
|
|
5167
|
+
metadata: input.metadata
|
|
5168
|
+
});
|
|
5169
|
+
return this.toCustomer(customer);
|
|
5170
|
+
}
|
|
5171
|
+
async getCustomer(customerId) {
|
|
5172
|
+
const customer = await this.stripe.customers.retrieve(customerId);
|
|
5173
|
+
if (customer.deleted)
|
|
5174
|
+
return null;
|
|
5175
|
+
return this.toCustomer(customer);
|
|
5176
|
+
}
|
|
5177
|
+
async createPaymentIntent(input) {
|
|
5178
|
+
const intent = await this.stripe.paymentIntents.create({
|
|
5179
|
+
amount: input.amount.amount,
|
|
5180
|
+
currency: input.amount.currency,
|
|
5181
|
+
customer: input.customerId,
|
|
5182
|
+
description: input.description,
|
|
5183
|
+
capture_method: input.captureMethod ?? "automatic",
|
|
5184
|
+
confirmation_method: input.confirmationMethod ?? "automatic",
|
|
5185
|
+
automatic_payment_methods: { enabled: true },
|
|
5186
|
+
metadata: input.metadata,
|
|
5187
|
+
return_url: input.returnUrl,
|
|
5188
|
+
statement_descriptor: input.statementDescriptor
|
|
5189
|
+
});
|
|
5190
|
+
return this.toPaymentIntent(intent);
|
|
5191
|
+
}
|
|
5192
|
+
async capturePayment(paymentIntentId, input) {
|
|
5193
|
+
const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
|
|
5194
|
+
return this.toPaymentIntent(intent);
|
|
5195
|
+
}
|
|
5196
|
+
async cancelPaymentIntent(paymentIntentId) {
|
|
5197
|
+
const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
|
|
5198
|
+
return this.toPaymentIntent(intent);
|
|
5199
|
+
}
|
|
5200
|
+
async refundPayment(input) {
|
|
5201
|
+
const refund = await this.stripe.refunds.create({
|
|
5202
|
+
payment_intent: input.paymentIntentId,
|
|
5203
|
+
amount: input.amount?.amount,
|
|
5204
|
+
reason: mapRefundReason(input.reason),
|
|
5205
|
+
metadata: input.metadata
|
|
5206
|
+
});
|
|
5207
|
+
const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
|
|
5208
|
+
return {
|
|
5209
|
+
id: refund.id,
|
|
5210
|
+
paymentIntentId,
|
|
5211
|
+
amount: {
|
|
5212
|
+
amount: refund.amount ?? 0,
|
|
5213
|
+
currency: refund.currency?.toUpperCase() ?? "USD"
|
|
5214
|
+
},
|
|
5215
|
+
status: mapRefundStatus(refund.status),
|
|
5216
|
+
reason: refund.reason ?? undefined,
|
|
5217
|
+
metadata: this.toMetadata(refund.metadata),
|
|
5218
|
+
createdAt: refund.created ? new Date(refund.created * 1000) : undefined
|
|
5219
|
+
};
|
|
5220
|
+
}
|
|
5221
|
+
async listInvoices(query) {
|
|
5222
|
+
const requestedStatus = query?.status?.[0];
|
|
5223
|
+
const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
|
|
5224
|
+
const response = await this.stripe.invoices.list({
|
|
5225
|
+
customer: query?.customerId,
|
|
5226
|
+
status: stripeStatus,
|
|
5227
|
+
limit: query?.limit,
|
|
5228
|
+
starting_after: query?.startingAfter
|
|
5229
|
+
});
|
|
5230
|
+
return response.data.map((invoice) => this.toInvoice(invoice));
|
|
5231
|
+
}
|
|
5232
|
+
async listTransactions(query) {
|
|
5233
|
+
const response = await this.stripe.charges.list({
|
|
5234
|
+
customer: query?.customerId,
|
|
5235
|
+
payment_intent: query?.paymentIntentId,
|
|
5236
|
+
limit: query?.limit,
|
|
5237
|
+
starting_after: query?.startingAfter
|
|
5238
|
+
});
|
|
5239
|
+
return response.data.map((charge) => ({
|
|
5240
|
+
id: charge.id,
|
|
5241
|
+
paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
|
|
5242
|
+
amount: {
|
|
5243
|
+
amount: charge.amount,
|
|
5244
|
+
currency: charge.currency?.toUpperCase() ?? "USD"
|
|
5245
|
+
},
|
|
5246
|
+
type: "capture",
|
|
5247
|
+
status: mapChargeStatus(charge.status),
|
|
5248
|
+
description: charge.description ?? undefined,
|
|
5249
|
+
createdAt: new Date(charge.created * 1000),
|
|
5250
|
+
metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
|
|
5251
|
+
balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
|
|
5252
|
+
})
|
|
5253
|
+
}));
|
|
5254
|
+
}
|
|
5255
|
+
toCustomer(customer) {
|
|
5256
|
+
const metadata = this.toMetadata(customer.metadata);
|
|
5257
|
+
const updatedAtValue = metadata?.updatedAt;
|
|
5258
|
+
return {
|
|
5259
|
+
id: customer.id,
|
|
5260
|
+
email: customer.email ?? undefined,
|
|
5261
|
+
name: customer.name ?? undefined,
|
|
5262
|
+
metadata,
|
|
5263
|
+
createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
|
|
5264
|
+
updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
|
|
5265
|
+
};
|
|
5266
|
+
}
|
|
5267
|
+
toPaymentIntent(intent) {
|
|
5268
|
+
const metadata = this.toMetadata(intent.metadata);
|
|
5269
|
+
return {
|
|
5270
|
+
id: intent.id,
|
|
5271
|
+
amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
|
|
5272
|
+
status: mapPaymentIntentStatus(intent.status),
|
|
5273
|
+
customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
|
|
5274
|
+
description: intent.description ?? undefined,
|
|
5275
|
+
clientSecret: intent.client_secret ?? undefined,
|
|
5276
|
+
metadata,
|
|
5277
|
+
createdAt: new Date(intent.created * 1000),
|
|
5278
|
+
updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
|
|
5378
5279
|
};
|
|
5379
5280
|
}
|
|
5380
|
-
|
|
5381
|
-
const
|
|
5382
|
-
if (!teamId) {
|
|
5383
|
-
throw new Error("Linear teamId is required to create work items.");
|
|
5384
|
-
}
|
|
5385
|
-
const payload = await this.client.createIssue({
|
|
5386
|
-
teamId,
|
|
5387
|
-
title: input.title,
|
|
5388
|
-
description: input.description,
|
|
5389
|
-
priority: mapPriority(input.priority),
|
|
5390
|
-
estimate: input.estimate,
|
|
5391
|
-
assigneeId: input.assigneeId ?? this.defaults.assigneeId,
|
|
5392
|
-
projectId: input.projectId ?? this.defaults.projectId,
|
|
5393
|
-
stateId: this.defaults.stateId,
|
|
5394
|
-
labelIds: resolveLabelIds(this.defaults, input.tags)
|
|
5395
|
-
});
|
|
5396
|
-
const issue = await payload.issue;
|
|
5397
|
-
const state = issue ? await issue.state : undefined;
|
|
5281
|
+
toInvoice(invoice) {
|
|
5282
|
+
const metadata = this.toMetadata(invoice.metadata);
|
|
5398
5283
|
return {
|
|
5399
|
-
id:
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
metadata
|
|
5284
|
+
id: invoice.id,
|
|
5285
|
+
number: invoice.number ?? undefined,
|
|
5286
|
+
status: invoice.status ?? "draft",
|
|
5287
|
+
amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
|
|
5288
|
+
amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
|
|
5289
|
+
customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
|
|
5290
|
+
dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
|
|
5291
|
+
hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
|
|
5292
|
+
metadata,
|
|
5293
|
+
createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
|
|
5294
|
+
updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
|
|
5408
5295
|
};
|
|
5409
5296
|
}
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5297
|
+
toMoney(amount, currency) {
|
|
5298
|
+
return {
|
|
5299
|
+
amount,
|
|
5300
|
+
currency: currency?.toUpperCase() ?? "USD"
|
|
5301
|
+
};
|
|
5302
|
+
}
|
|
5303
|
+
toMetadata(metadata) {
|
|
5304
|
+
if (!metadata)
|
|
5305
|
+
return;
|
|
5306
|
+
const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
|
|
5307
|
+
if (entries.length === 0)
|
|
5308
|
+
return;
|
|
5309
|
+
return Object.fromEntries(entries);
|
|
5310
|
+
}
|
|
5311
|
+
mergeMetadata(base, extras) {
|
|
5312
|
+
const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
|
|
5313
|
+
if (!base && filteredExtras.length === 0) {
|
|
5314
|
+
return;
|
|
5414
5315
|
}
|
|
5415
|
-
return
|
|
5316
|
+
return {
|
|
5317
|
+
...base ?? {},
|
|
5318
|
+
...Object.fromEntries(filteredExtras)
|
|
5319
|
+
};
|
|
5416
5320
|
}
|
|
5417
5321
|
}
|
|
5418
|
-
function
|
|
5419
|
-
if (!
|
|
5322
|
+
function mapRefundReason(reason) {
|
|
5323
|
+
if (!reason)
|
|
5420
5324
|
return;
|
|
5421
|
-
|
|
5325
|
+
const allowed = [
|
|
5326
|
+
"duplicate",
|
|
5327
|
+
"fraudulent",
|
|
5328
|
+
"requested_by_customer"
|
|
5329
|
+
];
|
|
5330
|
+
return allowed.includes(reason) ? reason : undefined;
|
|
5422
5331
|
}
|
|
5423
|
-
function
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5332
|
+
function mapPaymentIntentStatus(status) {
|
|
5333
|
+
switch (status) {
|
|
5334
|
+
case "requires_payment_method":
|
|
5335
|
+
return "requires_payment_method";
|
|
5336
|
+
case "requires_confirmation":
|
|
5337
|
+
return "requires_confirmation";
|
|
5338
|
+
case "requires_action":
|
|
5339
|
+
case "requires_capture":
|
|
5340
|
+
return "requires_action";
|
|
5341
|
+
case "processing":
|
|
5342
|
+
return "processing";
|
|
5343
|
+
case "succeeded":
|
|
5344
|
+
return "succeeded";
|
|
5345
|
+
case "canceled":
|
|
5346
|
+
return "canceled";
|
|
5347
|
+
default:
|
|
5348
|
+
return "requires_payment_method";
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
function mapRefundStatus(status) {
|
|
5352
|
+
switch (status) {
|
|
5353
|
+
case "pending":
|
|
5354
|
+
case "succeeded":
|
|
5355
|
+
case "failed":
|
|
5356
|
+
case "canceled":
|
|
5357
|
+
return status;
|
|
5358
|
+
default:
|
|
5359
|
+
return "pending";
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
function mapChargeStatus(status) {
|
|
5363
|
+
switch (status) {
|
|
5364
|
+
case "pending":
|
|
5365
|
+
case "processing":
|
|
5366
|
+
return "pending";
|
|
5367
|
+
case "succeeded":
|
|
5368
|
+
return "succeeded";
|
|
5369
|
+
case "failed":
|
|
5370
|
+
case "canceled":
|
|
5371
|
+
return "failed";
|
|
5372
|
+
default:
|
|
5373
|
+
return "pending";
|
|
5432
5374
|
}
|
|
5433
|
-
const merged = [...labelIds];
|
|
5434
|
-
return merged.length > 0 ? merged : undefined;
|
|
5435
5375
|
}
|
|
5436
5376
|
|
|
5437
|
-
// src/impls/
|
|
5377
|
+
// src/impls/supabase-psql.ts
|
|
5438
5378
|
import { Buffer as Buffer5 } from "node:buffer";
|
|
5379
|
+
import { sql as drizzleSql } from "drizzle-orm";
|
|
5380
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
5381
|
+
import postgres from "postgres";
|
|
5439
5382
|
|
|
5440
|
-
class
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
constructor(options) {
|
|
5446
|
-
this.
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
}
|
|
5456
|
-
async createWorkItem(input) {
|
|
5457
|
-
const projectKey = input.projectId ?? this.defaults.projectKey;
|
|
5458
|
-
if (!projectKey) {
|
|
5459
|
-
throw new Error("Jira projectKey is required to create work items.");
|
|
5383
|
+
class SupabasePostgresProvider {
|
|
5384
|
+
client;
|
|
5385
|
+
db;
|
|
5386
|
+
ownsClient;
|
|
5387
|
+
createDrizzle;
|
|
5388
|
+
constructor(options = {}) {
|
|
5389
|
+
this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
|
|
5390
|
+
if (options.db) {
|
|
5391
|
+
if (!options.client) {
|
|
5392
|
+
throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
|
|
5393
|
+
}
|
|
5394
|
+
this.client = options.client;
|
|
5395
|
+
this.db = options.db;
|
|
5396
|
+
this.ownsClient = false;
|
|
5397
|
+
return;
|
|
5460
5398
|
}
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
project: { key: projectKey },
|
|
5468
|
-
summary: input.title,
|
|
5469
|
-
description,
|
|
5470
|
-
issuetype: { name: issueType },
|
|
5471
|
-
labels,
|
|
5472
|
-
priority: priority ? { name: priority } : undefined,
|
|
5473
|
-
assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
|
|
5474
|
-
duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
|
|
5399
|
+
if (options.client) {
|
|
5400
|
+
this.client = options.client;
|
|
5401
|
+
this.ownsClient = false;
|
|
5402
|
+
} else {
|
|
5403
|
+
if (!options.connectionString) {
|
|
5404
|
+
throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
|
|
5475
5405
|
}
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
Accept: "application/json"
|
|
5483
|
-
},
|
|
5484
|
-
body: JSON.stringify(payload)
|
|
5485
|
-
});
|
|
5486
|
-
if (!response.ok) {
|
|
5487
|
-
const body = await response.text();
|
|
5488
|
-
throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
|
|
5406
|
+
this.client = postgres(options.connectionString, {
|
|
5407
|
+
max: options.maxConnections,
|
|
5408
|
+
prepare: false,
|
|
5409
|
+
ssl: resolveSslMode(options.sslMode)
|
|
5410
|
+
});
|
|
5411
|
+
this.ownsClient = true;
|
|
5489
5412
|
}
|
|
5490
|
-
|
|
5413
|
+
this.db = this.createDrizzle(this.client);
|
|
5414
|
+
}
|
|
5415
|
+
async query(statement, params = []) {
|
|
5416
|
+
const query = buildParameterizedSql(statement, params);
|
|
5417
|
+
const result = await this.db.execute(query);
|
|
5418
|
+
const rows = asRows(result);
|
|
5491
5419
|
return {
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
|
|
5495
|
-
status: input.status,
|
|
5496
|
-
priority: input.priority,
|
|
5497
|
-
tags: input.tags,
|
|
5498
|
-
projectId: projectKey,
|
|
5499
|
-
externalId: input.externalId,
|
|
5500
|
-
metadata: input.metadata
|
|
5420
|
+
rows,
|
|
5421
|
+
rowCount: rows.length
|
|
5501
5422
|
};
|
|
5502
5423
|
}
|
|
5503
|
-
async
|
|
5504
|
-
const
|
|
5505
|
-
|
|
5506
|
-
|
|
5424
|
+
async execute(statement, params = []) {
|
|
5425
|
+
const query = buildParameterizedSql(statement, params);
|
|
5426
|
+
await this.db.execute(query);
|
|
5427
|
+
}
|
|
5428
|
+
async transaction(run) {
|
|
5429
|
+
const transactionResult = this.client.begin(async (transactionClient) => {
|
|
5430
|
+
const transactionalProvider = new SupabasePostgresProvider({
|
|
5431
|
+
client: transactionClient,
|
|
5432
|
+
db: this.createDrizzle(transactionClient),
|
|
5433
|
+
createDrizzle: this.createDrizzle
|
|
5434
|
+
});
|
|
5435
|
+
return run(transactionalProvider);
|
|
5436
|
+
});
|
|
5437
|
+
return transactionResult;
|
|
5438
|
+
}
|
|
5439
|
+
async close() {
|
|
5440
|
+
if (this.ownsClient) {
|
|
5441
|
+
await this.client.end({ timeout: 5 });
|
|
5507
5442
|
}
|
|
5508
|
-
return created;
|
|
5509
5443
|
}
|
|
5510
5444
|
}
|
|
5511
|
-
function
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
const
|
|
5516
|
-
|
|
5445
|
+
function buildParameterizedSql(statement, params) {
|
|
5446
|
+
const segments = [];
|
|
5447
|
+
const pattern = /\$(\d+)/g;
|
|
5448
|
+
let cursor = 0;
|
|
5449
|
+
for (const match of statement.matchAll(pattern)) {
|
|
5450
|
+
const token = match[0];
|
|
5451
|
+
const indexPart = match[1];
|
|
5452
|
+
const start = match.index;
|
|
5453
|
+
if (indexPart == null || start == null)
|
|
5454
|
+
continue;
|
|
5455
|
+
const parameterIndex = Number(indexPart) - 1;
|
|
5456
|
+
if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
|
|
5457
|
+
throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
|
|
5458
|
+
}
|
|
5459
|
+
const staticSegment = statement.slice(cursor, start);
|
|
5460
|
+
if (staticSegment.length > 0) {
|
|
5461
|
+
segments.push(drizzleSql.raw(staticSegment));
|
|
5462
|
+
}
|
|
5463
|
+
const parameterValue = params[parameterIndex];
|
|
5464
|
+
if (parameterValue === undefined) {
|
|
5465
|
+
throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
|
|
5466
|
+
}
|
|
5467
|
+
const normalizedValue = normalizeParam(parameterValue);
|
|
5468
|
+
segments.push(drizzleSql`${normalizedValue}`);
|
|
5469
|
+
cursor = start + token.length;
|
|
5470
|
+
}
|
|
5471
|
+
const tailSegment = statement.slice(cursor);
|
|
5472
|
+
if (tailSegment.length > 0) {
|
|
5473
|
+
segments.push(drizzleSql.raw(tailSegment));
|
|
5474
|
+
}
|
|
5475
|
+
if (segments.length === 0) {
|
|
5476
|
+
return drizzleSql.raw("");
|
|
5477
|
+
}
|
|
5478
|
+
return drizzleSql.join(segments);
|
|
5517
5479
|
}
|
|
5518
|
-
function
|
|
5519
|
-
if (
|
|
5520
|
-
return
|
|
5480
|
+
function normalizeParam(value) {
|
|
5481
|
+
if (typeof value === "bigint") {
|
|
5482
|
+
return value.toString();
|
|
5521
5483
|
}
|
|
5522
|
-
|
|
5484
|
+
if (value instanceof Uint8Array) {
|
|
5485
|
+
return Buffer5.from(value);
|
|
5486
|
+
}
|
|
5487
|
+
if (isPlainObject(value)) {
|
|
5488
|
+
return JSON.stringify(value);
|
|
5489
|
+
}
|
|
5490
|
+
return value;
|
|
5523
5491
|
}
|
|
5524
|
-
function
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
return "Highest";
|
|
5528
|
-
case "high":
|
|
5529
|
-
return "High";
|
|
5530
|
-
case "medium":
|
|
5531
|
-
return "Medium";
|
|
5532
|
-
case "low":
|
|
5533
|
-
return "Low";
|
|
5534
|
-
case "none":
|
|
5535
|
-
default:
|
|
5536
|
-
return;
|
|
5492
|
+
function asRows(result) {
|
|
5493
|
+
if (!Array.isArray(result)) {
|
|
5494
|
+
return [];
|
|
5537
5495
|
}
|
|
5496
|
+
return result;
|
|
5538
5497
|
}
|
|
5539
|
-
function
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5498
|
+
function isPlainObject(value) {
|
|
5499
|
+
if (value == null || typeof value !== "object") {
|
|
5500
|
+
return false;
|
|
5501
|
+
}
|
|
5502
|
+
if (Array.isArray(value)) {
|
|
5503
|
+
return false;
|
|
5504
|
+
}
|
|
5505
|
+
if (value instanceof Date) {
|
|
5506
|
+
return false;
|
|
5507
|
+
}
|
|
5508
|
+
if (value instanceof Uint8Array) {
|
|
5509
|
+
return false;
|
|
5510
|
+
}
|
|
5511
|
+
return true;
|
|
5545
5512
|
}
|
|
5546
|
-
function
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
return { type: "doc", version: 1, content };
|
|
5513
|
+
function resolveSslMode(mode) {
|
|
5514
|
+
switch (mode) {
|
|
5515
|
+
case "allow":
|
|
5516
|
+
return false;
|
|
5517
|
+
case "prefer":
|
|
5518
|
+
return "prefer";
|
|
5519
|
+
case "require":
|
|
5520
|
+
default:
|
|
5521
|
+
return "require";
|
|
5522
|
+
}
|
|
5557
5523
|
}
|
|
5558
5524
|
|
|
5559
|
-
// src/impls/
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5525
|
+
// src/impls/supabase-vector.ts
|
|
5526
|
+
class SupabaseVectorProvider {
|
|
5527
|
+
database;
|
|
5528
|
+
createTableIfMissing;
|
|
5529
|
+
distanceMetric;
|
|
5530
|
+
quotedSchema;
|
|
5531
|
+
qualifiedTable;
|
|
5532
|
+
collectionIndex;
|
|
5533
|
+
namespaceIndex;
|
|
5534
|
+
ensureTablePromise;
|
|
5565
5535
|
constructor(options) {
|
|
5566
|
-
this.
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
}
|
|
5536
|
+
this.database = options.database ?? new SupabasePostgresProvider({
|
|
5537
|
+
connectionString: options.connectionString,
|
|
5538
|
+
maxConnections: options.maxConnections,
|
|
5539
|
+
sslMode: options.sslMode
|
|
5540
|
+
});
|
|
5541
|
+
this.createTableIfMissing = options.createTableIfMissing ?? true;
|
|
5542
|
+
this.distanceMetric = options.distanceMetric ?? "cosine";
|
|
5543
|
+
const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
|
|
5544
|
+
const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
|
|
5545
|
+
this.quotedSchema = quoteIdentifier(schema);
|
|
5546
|
+
this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
|
|
5547
|
+
this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
|
|
5548
|
+
this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
|
|
5577
5549
|
}
|
|
5578
|
-
async
|
|
5579
|
-
if (
|
|
5580
|
-
return
|
|
5550
|
+
async upsert(request) {
|
|
5551
|
+
if (request.documents.length === 0) {
|
|
5552
|
+
return;
|
|
5581
5553
|
}
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
throw new Error("Notion databaseId is required to create work items.");
|
|
5554
|
+
if (this.createTableIfMissing) {
|
|
5555
|
+
await this.ensureTable();
|
|
5585
5556
|
}
|
|
5586
|
-
const
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5557
|
+
for (const document of request.documents) {
|
|
5558
|
+
await this.database.execute(`INSERT INTO ${this.qualifiedTable}
|
|
5559
|
+
(collection, id, embedding, payload, namespace, expires_at, updated_at)
|
|
5560
|
+
VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
|
|
5561
|
+
ON CONFLICT (collection, id)
|
|
5562
|
+
DO UPDATE SET
|
|
5563
|
+
embedding = EXCLUDED.embedding,
|
|
5564
|
+
payload = EXCLUDED.payload,
|
|
5565
|
+
namespace = EXCLUDED.namespace,
|
|
5566
|
+
expires_at = EXCLUDED.expires_at,
|
|
5567
|
+
updated_at = now();`, [
|
|
5568
|
+
request.collection,
|
|
5569
|
+
document.id,
|
|
5570
|
+
toVectorLiteral(document.vector),
|
|
5571
|
+
document.payload ? JSON.stringify(document.payload) : null,
|
|
5572
|
+
document.namespace ?? null,
|
|
5573
|
+
document.expiresAt ?? null
|
|
5574
|
+
]);
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
async search(query) {
|
|
5578
|
+
const operator = this.distanceOperator;
|
|
5579
|
+
const results = await this.database.query(`SELECT
|
|
5580
|
+
id,
|
|
5581
|
+
payload,
|
|
5582
|
+
namespace,
|
|
5583
|
+
(embedding ${operator} $3::vector) AS distance
|
|
5584
|
+
FROM ${this.qualifiedTable}
|
|
5585
|
+
WHERE collection = $1
|
|
5586
|
+
AND ($2::text IS NULL OR namespace = $2)
|
|
5587
|
+
AND (expires_at IS NULL OR expires_at > now())
|
|
5588
|
+
AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
|
|
5589
|
+
ORDER BY embedding ${operator} $3::vector
|
|
5590
|
+
LIMIT $5;`, [
|
|
5591
|
+
query.collection,
|
|
5592
|
+
query.namespace ?? null,
|
|
5593
|
+
toVectorLiteral(query.vector),
|
|
5594
|
+
query.filter ? JSON.stringify(query.filter) : null,
|
|
5595
|
+
query.topK
|
|
5596
|
+
]);
|
|
5597
|
+
const mapped = results.rows.map((row) => {
|
|
5598
|
+
const distance = Number(row.distance);
|
|
5599
|
+
return {
|
|
5600
|
+
id: row.id,
|
|
5601
|
+
score: distanceToScore(distance, this.distanceMetric),
|
|
5602
|
+
payload: isRecord(row.payload) ? row.payload : undefined,
|
|
5603
|
+
namespace: row.namespace ?? undefined
|
|
5604
|
+
};
|
|
5598
5605
|
});
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
priority: input.priority,
|
|
5605
|
-
tags: input.tags,
|
|
5606
|
-
projectId: databaseId,
|
|
5607
|
-
externalId: input.externalId,
|
|
5608
|
-
metadata: input.metadata
|
|
5609
|
-
};
|
|
5606
|
+
const scoreThreshold = query.scoreThreshold;
|
|
5607
|
+
if (scoreThreshold == null) {
|
|
5608
|
+
return mapped;
|
|
5609
|
+
}
|
|
5610
|
+
return mapped.filter((result) => result.score >= scoreThreshold);
|
|
5610
5611
|
}
|
|
5611
|
-
async
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
created.push(await this.createWorkItem(item));
|
|
5612
|
+
async delete(request) {
|
|
5613
|
+
if (request.ids.length === 0) {
|
|
5614
|
+
return;
|
|
5615
5615
|
}
|
|
5616
|
-
|
|
5616
|
+
const params = [
|
|
5617
|
+
request.collection,
|
|
5618
|
+
request.ids,
|
|
5619
|
+
request.namespace ?? null
|
|
5620
|
+
];
|
|
5621
|
+
await this.database.execute(`DELETE FROM ${this.qualifiedTable}
|
|
5622
|
+
WHERE collection = $1
|
|
5623
|
+
AND id = ANY($2::text[])
|
|
5624
|
+
AND ($3::text IS NULL OR namespace = $3);`, params);
|
|
5617
5625
|
}
|
|
5618
|
-
async
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
throw new Error("Notion summaryParentPageId is required for summaries.");
|
|
5626
|
+
async ensureTable() {
|
|
5627
|
+
if (!this.ensureTablePromise) {
|
|
5628
|
+
this.ensureTablePromise = this.createTable();
|
|
5622
5629
|
}
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5630
|
+
await this.ensureTablePromise;
|
|
5631
|
+
}
|
|
5632
|
+
async createTable() {
|
|
5633
|
+
await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
5634
|
+
await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
|
|
5635
|
+
await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
|
|
5636
|
+
collection text NOT NULL,
|
|
5637
|
+
id text NOT NULL,
|
|
5638
|
+
embedding vector NOT NULL,
|
|
5639
|
+
payload jsonb,
|
|
5640
|
+
namespace text,
|
|
5641
|
+
expires_at timestamptz,
|
|
5642
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
5643
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
5644
|
+
PRIMARY KEY (collection, id)
|
|
5645
|
+
);`);
|
|
5646
|
+
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
|
|
5647
|
+
ON ${this.qualifiedTable} (collection);`);
|
|
5648
|
+
await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
|
|
5649
|
+
ON ${this.qualifiedTable} (namespace);`);
|
|
5650
|
+
}
|
|
5651
|
+
get distanceOperator() {
|
|
5652
|
+
switch (this.distanceMetric) {
|
|
5653
|
+
case "l2":
|
|
5654
|
+
return "<->";
|
|
5655
|
+
case "inner_product":
|
|
5656
|
+
return "<#>";
|
|
5657
|
+
case "cosine":
|
|
5658
|
+
default:
|
|
5659
|
+
return "<=>";
|
|
5638
5660
|
}
|
|
5639
|
-
return {
|
|
5640
|
-
id: page.id,
|
|
5641
|
-
title: input.title,
|
|
5642
|
-
url: "url" in page ? page.url : undefined,
|
|
5643
|
-
status: input.status,
|
|
5644
|
-
priority: input.priority,
|
|
5645
|
-
tags: input.tags,
|
|
5646
|
-
projectId: parentId,
|
|
5647
|
-
externalId: input.externalId,
|
|
5648
|
-
metadata: input.metadata
|
|
5649
|
-
};
|
|
5650
5661
|
}
|
|
5651
5662
|
}
|
|
5652
|
-
function
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
text: { content: title }
|
|
5658
|
-
}
|
|
5659
|
-
]
|
|
5660
|
-
};
|
|
5661
|
-
}
|
|
5662
|
-
function buildRichText(text) {
|
|
5663
|
-
return {
|
|
5664
|
-
rich_text: [
|
|
5665
|
-
{
|
|
5666
|
-
type: "text",
|
|
5667
|
-
text: { content: text }
|
|
5668
|
-
}
|
|
5669
|
-
]
|
|
5670
|
-
};
|
|
5671
|
-
}
|
|
5672
|
-
function applySelect(properties, property, value) {
|
|
5673
|
-
if (!property || !value)
|
|
5674
|
-
return;
|
|
5675
|
-
const next = {
|
|
5676
|
-
select: { name: value }
|
|
5677
|
-
};
|
|
5678
|
-
properties[property] = next;
|
|
5663
|
+
function sanitizeIdentifier(value, label) {
|
|
5664
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
5665
|
+
throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
|
|
5666
|
+
}
|
|
5667
|
+
return value;
|
|
5679
5668
|
}
|
|
5680
|
-
function
|
|
5681
|
-
|
|
5682
|
-
return;
|
|
5683
|
-
const next = {
|
|
5684
|
-
multi_select: values.map((value) => ({ name: value }))
|
|
5685
|
-
};
|
|
5686
|
-
properties[property] = next;
|
|
5669
|
+
function quoteIdentifier(value) {
|
|
5670
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
5687
5671
|
}
|
|
5688
|
-
function
|
|
5689
|
-
if (
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5672
|
+
function toVectorLiteral(vector) {
|
|
5673
|
+
if (vector.length === 0) {
|
|
5674
|
+
throw new Error("Supabase vectors must contain at least one dimension.");
|
|
5675
|
+
}
|
|
5676
|
+
for (const value of vector) {
|
|
5677
|
+
if (!Number.isFinite(value)) {
|
|
5678
|
+
throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
return `[${vector.join(",")}]`;
|
|
5695
5682
|
}
|
|
5696
|
-
function
|
|
5697
|
-
|
|
5698
|
-
return;
|
|
5699
|
-
properties[property] = buildRichText(value);
|
|
5683
|
+
function isRecord(value) {
|
|
5684
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5700
5685
|
}
|
|
5701
|
-
function
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
}
|
|
5712
|
-
]
|
|
5713
|
-
}
|
|
5714
|
-
}));
|
|
5686
|
+
function distanceToScore(distance, metric) {
|
|
5687
|
+
switch (metric) {
|
|
5688
|
+
case "inner_product":
|
|
5689
|
+
return -distance;
|
|
5690
|
+
case "l2":
|
|
5691
|
+
return 1 / (1 + distance);
|
|
5692
|
+
case "cosine":
|
|
5693
|
+
default:
|
|
5694
|
+
return 1 - distance;
|
|
5695
|
+
}
|
|
5715
5696
|
}
|
|
5716
5697
|
|
|
5717
5698
|
// src/impls/tldv-meeting-recorder.ts
|
|
@@ -5857,11 +5838,74 @@ async function safeReadError4(response) {
|
|
|
5857
5838
|
}
|
|
5858
5839
|
}
|
|
5859
5840
|
|
|
5841
|
+
// src/impls/twilio-sms.ts
|
|
5842
|
+
import Twilio from "twilio";
|
|
5843
|
+
|
|
5844
|
+
class TwilioSmsProvider {
|
|
5845
|
+
client;
|
|
5846
|
+
fromNumber;
|
|
5847
|
+
constructor(options) {
|
|
5848
|
+
this.client = options.client ?? Twilio(options.accountSid, options.authToken);
|
|
5849
|
+
this.fromNumber = options.fromNumber;
|
|
5850
|
+
}
|
|
5851
|
+
async sendSms(input) {
|
|
5852
|
+
const message = await this.client.messages.create({
|
|
5853
|
+
to: input.to,
|
|
5854
|
+
from: input.from ?? this.fromNumber,
|
|
5855
|
+
body: input.body
|
|
5856
|
+
});
|
|
5857
|
+
return {
|
|
5858
|
+
id: message.sid,
|
|
5859
|
+
to: message.to ?? input.to,
|
|
5860
|
+
from: message.from ?? input.from ?? this.fromNumber ?? "",
|
|
5861
|
+
body: message.body ?? input.body,
|
|
5862
|
+
status: mapStatus(message.status),
|
|
5863
|
+
sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
|
|
5864
|
+
deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
|
|
5865
|
+
price: message.price ? Number(message.price) : undefined,
|
|
5866
|
+
priceCurrency: message.priceUnit ?? undefined,
|
|
5867
|
+
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
5868
|
+
errorMessage: message.errorMessage ?? undefined
|
|
5869
|
+
};
|
|
5870
|
+
}
|
|
5871
|
+
async getDeliveryStatus(messageId) {
|
|
5872
|
+
const message = await this.client.messages(messageId).fetch();
|
|
5873
|
+
return {
|
|
5874
|
+
status: mapStatus(message.status),
|
|
5875
|
+
errorCode: message.errorCode ? String(message.errorCode) : undefined,
|
|
5876
|
+
errorMessage: message.errorMessage ?? undefined,
|
|
5877
|
+
updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
|
|
5878
|
+
};
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
function mapStatus(status) {
|
|
5882
|
+
switch (status) {
|
|
5883
|
+
case "queued":
|
|
5884
|
+
case "accepted":
|
|
5885
|
+
case "scheduled":
|
|
5886
|
+
return "queued";
|
|
5887
|
+
case "sending":
|
|
5888
|
+
case "processing":
|
|
5889
|
+
return "sending";
|
|
5890
|
+
case "sent":
|
|
5891
|
+
return "sent";
|
|
5892
|
+
case "delivered":
|
|
5893
|
+
return "delivered";
|
|
5894
|
+
case "undelivered":
|
|
5895
|
+
return "undelivered";
|
|
5896
|
+
case "failed":
|
|
5897
|
+
case "canceled":
|
|
5898
|
+
return "failed";
|
|
5899
|
+
default:
|
|
5900
|
+
return "queued";
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
|
|
5860
5904
|
// src/impls/provider-factory.ts
|
|
5861
5905
|
import { Buffer as Buffer6 } from "node:buffer";
|
|
5862
|
-
import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
|
|
5863
|
-
import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
|
|
5864
5906
|
import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
|
|
5907
|
+
import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
|
|
5908
|
+
import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
|
|
5865
5909
|
var SECRET_CACHE = new Map;
|
|
5866
5910
|
|
|
5867
5911
|
class IntegrationProviderFactory {
|
|
@@ -5945,6 +5989,12 @@ class IntegrationProviderFactory {
|
|
|
5945
5989
|
defaultRepo: config?.defaultRepo,
|
|
5946
5990
|
apiBaseUrl: config?.apiBaseUrl
|
|
5947
5991
|
});
|
|
5992
|
+
case "messaging.telegram":
|
|
5993
|
+
return new TelegramMessagingProvider({
|
|
5994
|
+
botToken: requireSecret(secrets, "botToken", "Telegram bot token is required"),
|
|
5995
|
+
defaultChatId: config?.defaultChatId,
|
|
5996
|
+
apiBaseUrl: config?.apiBaseUrl
|
|
5997
|
+
});
|
|
5948
5998
|
case "messaging.whatsapp.meta":
|
|
5949
5999
|
return new MetaWhatsappMessagingProvider({
|
|
5950
6000
|
accessToken: requireSecret(secrets, "accessToken", "Meta WhatsApp access token is required"),
|