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