@myscheme/voice-navigation-sdk 0.1.5 → 0.1.6

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.
Files changed (32) hide show
  1. package/dist/actions.js +12 -12
  2. package/dist/microphone-handler.d.ts +2 -0
  3. package/dist/microphone-handler.d.ts.map +1 -1
  4. package/dist/microphone-handler.js +11 -2
  5. package/dist/navigation-controller.d.ts.map +1 -1
  6. package/dist/navigation-controller.js +39 -6
  7. package/dist/server/azure-speech-handler.d.ts +12 -0
  8. package/dist/server/azure-speech-handler.d.ts.map +1 -0
  9. package/dist/server/azure-speech-handler.js +40 -0
  10. package/dist/server/bedrock-embedding-handler.d.ts +15 -0
  11. package/dist/server/bedrock-embedding-handler.d.ts.map +1 -0
  12. package/dist/server/bedrock-embedding-handler.js +123 -0
  13. package/dist/server/bedrock-handler.d.ts +14 -0
  14. package/dist/server/bedrock-handler.d.ts.map +1 -0
  15. package/dist/server/bedrock-handler.js +114 -0
  16. package/dist/server/index.d.ts +4 -0
  17. package/dist/server/index.d.ts.map +1 -1
  18. package/dist/server/index.js +4 -0
  19. package/dist/server/opensearch-env-handler.d.ts +22 -0
  20. package/dist/server/opensearch-env-handler.d.ts.map +1 -0
  21. package/dist/server/opensearch-env-handler.js +221 -0
  22. package/dist/services/azure-speech.d.ts +10 -2
  23. package/dist/services/azure-speech.d.ts.map +1 -1
  24. package/dist/services/azure-speech.js +66 -3
  25. package/dist/services/bedrock.d.ts +13 -4
  26. package/dist/services/bedrock.d.ts.map +1 -1
  27. package/dist/services/bedrock.js +171 -17
  28. package/dist/services/vector-search.d.ts.map +1 -1
  29. package/dist/services/vector-search.js +15 -2
  30. package/dist/types.d.ts +13 -8
  31. package/dist/types.d.ts.map +1 -1
  32. package/package.json +3 -3
@@ -0,0 +1,221 @@
1
+ const rateLimitMap = new Map();
2
+ function checkRateLimit(identifier, maxRequests, windowMs) {
3
+ const now = Date.now();
4
+ const record = rateLimitMap.get(identifier);
5
+ if (!record || now > record.resetTime) {
6
+ rateLimitMap.set(identifier, {
7
+ count: 1,
8
+ resetTime: now + windowMs,
9
+ });
10
+ return true;
11
+ }
12
+ if (record.count >= maxRequests) {
13
+ return false;
14
+ }
15
+ record.count++;
16
+ return true;
17
+ }
18
+ function getClientIdentifier(req) {
19
+ const forwardedFor = req.headers?.["x-forwarded-for"];
20
+ if (forwardedFor) {
21
+ const ip = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor;
22
+ return ip.split(",")[0].trim();
23
+ }
24
+ const realIp = req.headers?.["x-real-ip"];
25
+ if (realIp) {
26
+ return Array.isArray(realIp) ? realIp[0] : realIp;
27
+ }
28
+ return req.socket?.remoteAddress || "unknown";
29
+ }
30
+ let opensearchClient = null;
31
+ async function getOpenSearchClient() {
32
+ if (!opensearchClient) {
33
+ const node = process.env.OPENSEARCH_NODE;
34
+ const username = process.env.OPENSEARCH_USERNAME;
35
+ const password = process.env.OPENSEARCH_PASSWORD;
36
+ if (!node || !username || !password) {
37
+ throw new Error("OpenSearch configuration is missing. Set OPENSEARCH_NODE, OPENSEARCH_USERNAME, and OPENSEARCH_PASSWORD in your .env file");
38
+ }
39
+ const { Client } = await import("@opensearch-project/opensearch");
40
+ opensearchClient = new Client({
41
+ node: node,
42
+ auth: {
43
+ username: username,
44
+ password: password,
45
+ },
46
+ ssl: {
47
+ rejectUnauthorized: true,
48
+ },
49
+ });
50
+ }
51
+ return opensearchClient;
52
+ }
53
+ export async function openSearchSearchHandler(req, res) {
54
+ if (req.method !== "POST") {
55
+ res.setHeader("Allow", ["POST"]);
56
+ res.status(405).json({ error: "Method not allowed" });
57
+ return;
58
+ }
59
+ const clientId = getClientIdentifier(req);
60
+ if (!checkRateLimit(clientId, 20, 60 * 1000)) {
61
+ res.status(429).json({
62
+ error: "Too many requests",
63
+ message: "Rate limit exceeded. Please try again later.",
64
+ });
65
+ return;
66
+ }
67
+ const { query, vector, size = 10 } = req.body || {};
68
+ if (!query || !vector) {
69
+ res.status(400).json({
70
+ error: "Missing required parameters",
71
+ hint: "Provide both 'query' and 'vector' in request body",
72
+ });
73
+ return;
74
+ }
75
+ if (typeof query !== "string" || query.length > 500) {
76
+ res.status(400).json({
77
+ error: "Invalid query parameter",
78
+ hint: "Query must be a string with max 500 characters",
79
+ });
80
+ return;
81
+ }
82
+ if (!Array.isArray(vector) || vector.length === 0) {
83
+ res.status(400).json({
84
+ error: "Invalid vector parameter",
85
+ hint: "Vector must be a non-empty array of numbers",
86
+ });
87
+ return;
88
+ }
89
+ try {
90
+ const client = await getOpenSearchClient();
91
+ const indexName = process.env.OPENSEARCH_INDEX;
92
+ const vectorField = process.env.OPENSEARCH_VECTOR_FIELD || "embedding";
93
+ if (!indexName) {
94
+ throw new Error("OPENSEARCH_INDEX environment variable is not set");
95
+ }
96
+ let searchResponse;
97
+ try {
98
+ searchResponse = await client.search({
99
+ index: indexName,
100
+ body: {
101
+ size: Math.min(size, 100),
102
+ query: {
103
+ knn: {
104
+ [vectorField]: {
105
+ vector: vector,
106
+ k: Math.min(size * 2, 200),
107
+ },
108
+ },
109
+ },
110
+ _source: ["name", "path", "keywords", "description"],
111
+ },
112
+ });
113
+ }
114
+ catch (knnError) {
115
+ const errorMessage = knnError?.message || "";
116
+ if (errorMessage.includes("is not knn_vector type") ||
117
+ errorMessage.includes("failed to create query") ||
118
+ errorMessage.includes("knn")) {
119
+ console.log(`[VoiceNavigation] Using text-based search (kNN vector search not available for field '${vectorField}')`);
120
+ searchResponse = await client.search({
121
+ index: indexName,
122
+ body: {
123
+ size: Math.min(size, 100),
124
+ query: {
125
+ query_string: {
126
+ query: query,
127
+ default_operator: "OR",
128
+ analyze_wildcard: true,
129
+ lenient: true,
130
+ },
131
+ },
132
+ _source: true,
133
+ },
134
+ });
135
+ }
136
+ else {
137
+ throw knnError;
138
+ }
139
+ }
140
+ const hits = searchResponse.body.hits.hits.map((hit) => ({
141
+ id: hit._id,
142
+ score: hit._score,
143
+ source: hit._source,
144
+ }));
145
+ res.status(200).json({ hits });
146
+ }
147
+ catch (error) {
148
+ console.error("[VoiceNavigation] OpenSearch search error:", error);
149
+ res.status(500).json({
150
+ error: "Search failed",
151
+ message: error instanceof Error ? error.message : "Unknown error",
152
+ });
153
+ }
154
+ }
155
+ export async function openSearchSuggestHandler(req, res) {
156
+ if (req.method !== "POST") {
157
+ res.setHeader("Allow", ["POST"]);
158
+ res.status(405).json({ error: "Method not allowed" });
159
+ return;
160
+ }
161
+ const clientId = getClientIdentifier(req);
162
+ if (!checkRateLimit(clientId, 30, 60 * 1000)) {
163
+ res.status(429).json({
164
+ error: "Too many requests",
165
+ message: "Rate limit exceeded. Please try again later.",
166
+ });
167
+ return;
168
+ }
169
+ const { query } = req.body || {};
170
+ if (!query || typeof query !== "string") {
171
+ res.status(400).json({
172
+ error: "Missing or invalid query parameter",
173
+ hint: "Provide 'query' as a string in request body",
174
+ });
175
+ return;
176
+ }
177
+ if (query.length > 200) {
178
+ res.status(400).json({
179
+ error: "Query too long",
180
+ hint: "Query must be max 200 characters for suggestions",
181
+ });
182
+ return;
183
+ }
184
+ try {
185
+ const client = await getOpenSearchClient();
186
+ const indexName = process.env.OPENSEARCH_INDEX;
187
+ if (!indexName) {
188
+ throw new Error("OPENSEARCH_INDEX environment variable is not set");
189
+ }
190
+ const searchResponse = await client.search({
191
+ index: indexName,
192
+ body: {
193
+ size: 5,
194
+ query: {
195
+ multi_match: {
196
+ query: query,
197
+ fields: ["name^3", "keywords^2", "description"],
198
+ type: "bool_prefix",
199
+ fuzziness: "AUTO",
200
+ },
201
+ },
202
+ _source: ["name", "path", "keywords"],
203
+ },
204
+ });
205
+ const suggestions = searchResponse.body.hits.hits.map((hit) => ({
206
+ id: hit._id,
207
+ score: hit._score,
208
+ name: hit._source.name,
209
+ path: hit._source.path,
210
+ keywords: hit._source.keywords || [],
211
+ }));
212
+ res.status(200).json({ suggestions });
213
+ }
214
+ catch (error) {
215
+ console.error("[VoiceNavigation] OpenSearch suggest error:", error);
216
+ res.status(500).json({
217
+ error: "Suggestion failed",
218
+ message: error instanceof Error ? error.message : "Unknown error",
219
+ });
220
+ }
221
+ }
@@ -1,13 +1,21 @@
1
1
  import type { AzureTokenResponse } from "../types.js";
2
2
  export interface AzureSpeechConfig {
3
- subscriptionKey: string;
4
- region: string;
3
+ tokenEndpoint?: string;
4
+ subscriptionKey?: string;
5
+ region?: string;
5
6
  }
6
7
  export declare class AzureSpeechService {
8
+ private tokenEndpoint;
7
9
  private subscriptionKey;
8
10
  private region;
11
+ private useProxy;
12
+ private cachedToken;
13
+ private tokenExpiration;
9
14
  constructor(config: AzureSpeechConfig);
10
15
  fetchToken(): Promise<AzureTokenResponse>;
16
+ private fetchTokenViaProxy;
17
+ private fetchTokenDirect;
18
+ clearTokenCache(): void;
11
19
  static create(config: AzureSpeechConfig): AzureSpeechService;
12
20
  }
13
21
  //# sourceMappingURL=azure-speech.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"azure-speech.d.ts","sourceRoot":"","sources":["../../src/services/azure-speech.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,iBAAiB;IAQ/B,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAiC/C,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,kBAAkB;CAG7D"}
1
+ {"version":3,"file":"azure-speech.d.ts","sourceRoot":"","sources":["../../src/services/azure-speech.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAEhC,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,eAAe,CAAa;gBAExB,MAAM,EAAE,iBAAiB;IAoB/B,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;YAmBjC,kBAAkB;YAoClB,gBAAgB;IAyC9B,eAAe,IAAI,IAAI;IAQvB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,kBAAkB;CAG7D"}
@@ -1,9 +1,66 @@
1
1
  export class AzureSpeechService {
2
2
  constructor(config) {
3
- this.subscriptionKey = config.subscriptionKey;
4
- this.region = config.region;
3
+ this.tokenEndpoint = null;
4
+ this.subscriptionKey = null;
5
+ this.region = null;
6
+ this.useProxy = false;
7
+ this.cachedToken = null;
8
+ this.tokenExpiration = 0;
9
+ this.useProxy = !!config.tokenEndpoint;
10
+ if (this.useProxy) {
11
+ this.tokenEndpoint = config.tokenEndpoint || null;
12
+ }
13
+ else {
14
+ if (!config.subscriptionKey || !config.region) {
15
+ throw new Error("Azure subscription key and region required for direct mode");
16
+ }
17
+ this.subscriptionKey = config.subscriptionKey;
18
+ this.region = config.region;
19
+ }
5
20
  }
6
21
  async fetchToken() {
22
+ if (this.cachedToken && Date.now() < this.tokenExpiration) {
23
+ return {
24
+ token: this.cachedToken,
25
+ region: this.region || "centralindia",
26
+ };
27
+ }
28
+ if (this.useProxy) {
29
+ return this.fetchTokenViaProxy();
30
+ }
31
+ else {
32
+ return this.fetchTokenDirect();
33
+ }
34
+ }
35
+ async fetchTokenViaProxy() {
36
+ if (!this.tokenEndpoint) {
37
+ throw new Error("Token endpoint not configured");
38
+ }
39
+ try {
40
+ const response = await fetch(this.tokenEndpoint, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ },
45
+ });
46
+ if (!response.ok) {
47
+ throw new Error(`Failed to fetch token: ${response.status} ${response.statusText}`);
48
+ }
49
+ const data = (await response.json());
50
+ this.cachedToken = data.token;
51
+ this.tokenExpiration = Date.now() + 9 * 60 * 1000;
52
+ this.region = data.region;
53
+ return data;
54
+ }
55
+ catch (error) {
56
+ console.error("Failed to fetch Azure speech token via proxy:", error);
57
+ throw error;
58
+ }
59
+ }
60
+ async fetchTokenDirect() {
61
+ if (!this.subscriptionKey || !this.region) {
62
+ throw new Error("Azure subscription key and region not configured");
63
+ }
7
64
  const url = `https://${this.region}.api.cognitive.microsoft.com/sts/v1.0/issueToken`;
8
65
  try {
9
66
  const response = await fetch(url, {
@@ -17,9 +74,11 @@ export class AzureSpeechService {
17
74
  throw new Error(`Failed to fetch token: ${response.status} ${response.statusText}`);
18
75
  }
19
76
  const token = await response.text();
77
+ this.cachedToken = token;
78
+ this.tokenExpiration = Date.now() + 9 * 60 * 1000;
20
79
  return {
21
80
  token,
22
- region: this.region,
81
+ region: this.region || "centralindia",
23
82
  };
24
83
  }
25
84
  catch (error) {
@@ -27,6 +86,10 @@ export class AzureSpeechService {
27
86
  throw error;
28
87
  }
29
88
  }
89
+ clearTokenCache() {
90
+ this.cachedToken = null;
91
+ this.tokenExpiration = 0;
92
+ }
30
93
  static create(config) {
31
94
  return new AzureSpeechService(config);
32
95
  }
@@ -1,10 +1,12 @@
1
1
  import type { AgentActionResponse } from "../types.js";
2
2
  import type { PageRegistry } from "./page-registry.js";
3
3
  export interface BedrockConfig {
4
- region: string;
5
- accessKeyId: string;
6
- secretAccessKey: string;
7
- modelId: string;
4
+ bedrockEndpoint?: string;
5
+ embeddingEndpoint?: string;
6
+ region?: string;
7
+ accessKeyId?: string;
8
+ secretAccessKey?: string;
9
+ modelId?: string;
8
10
  embeddingModelId?: string | null;
9
11
  }
10
12
  export declare class BedrockService {
@@ -12,11 +14,18 @@ export declare class BedrockService {
12
14
  private actionModelId;
13
15
  private embeddingModelId;
14
16
  private pageRegistry;
17
+ private bedrockEndpoint;
18
+ private embeddingEndpointUrl;
19
+ private useProxy;
15
20
  constructor(config: BedrockConfig);
16
21
  setPageRegistry(registry: PageRegistry): void;
17
22
  private buildSystemPrompt;
18
23
  extractAction(userInput: string): Promise<AgentActionResponse>;
24
+ private extractActionViaProxy;
25
+ private extractActionDirect;
19
26
  static create(config: BedrockConfig): BedrockService;
20
27
  embedText(text: string): Promise<number[]>;
28
+ private embedTextViaProxy;
29
+ private embedTextDirect;
21
30
  }
22
31
  //# sourceMappingURL=bedrock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bedrock.d.ts","sourceRoot":"","sources":["../../src/services/bedrock.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAmB,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAiCD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,YAAY,CAA6B;gBAErC,MAAM,EAAE,aAAa;IAwBjC,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAO7C,OAAO,CAAC,iBAAiB;IA+BnB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAmEpE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,cAAc;IAO9C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAoFjD"}
1
+ {"version":3,"file":"bedrock.d.ts","sourceRoot":"","sources":["../../src/services/bedrock.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAmB,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAE5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAmCD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,QAAQ,CAAkB;gBAEtB,MAAM,EAAE,aAAa;IA6CjC,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAO7C,OAAO,CAAC,iBAAiB;IA+BnB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;YAWtD,qBAAqB;YA2ErB,mBAAmB;IAyEjC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,cAAc;IAO9C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAWlC,iBAAiB;YAkFjB,eAAe;CAoF9B"}
@@ -1,5 +1,5 @@
1
1
  import { BedrockRuntimeClient, InvokeModelCommand, } from "@aws-sdk/client-bedrock-runtime";
2
- const CORE_ACTIONS_LIST = `"zoom_in", "zoom_out", "scroll_up", "scroll_down", "scroll_left", "scroll_right", "page_up", "page_down", "scroll_top", "scroll_bottom", "go_back", "go_forward", "reload_page", "print_page", "copy_url", "open_menu", "close_menu", "focus_search", "toggle_fullscreen", "exit_fullscreen", "play_media", "pause_media", "mute_media", "unmute_media", "search_content", "stop"`;
2
+ const CORE_ACTIONS_LIST = `"zoom_in", "zoom_out", "scroll_up", "scroll_down", "scroll_left", "scroll_right", "page_up", "page_down", "scroll_top", "scroll_bottom", "go_back", "go_forward", "reload_page", "print_page", "copy_url", "open_menu", "close_menu", "focus_search", "toggle_fullscreen", "exit_fullscreen", "play_media", "pause_media", "mute_media", "unmute_media", "search_content", "stop", "quit", "tab_next", "tab_back", "click", "enter", "submit", "move_left", "move_right", "move_up", "move_down", "move_beginning", "move_end", "select_all", "select_line", "type_text", "cut", "copy", "paste", "clear"`;
3
3
  const BASE_SYSTEM_PROMPT = `You are an action extraction assistant.
4
4
 
5
5
  CRITICAL RULES:
@@ -14,6 +14,7 @@ CRITICAL RULES:
14
14
  RESPONSE FORMAT:
15
15
  - Core actions: {"action": "action_name"}
16
16
  - Search: {"action": "search_content", "query": "keywords"}
17
+ - Type text: {"action": "type_text", "text": "text to type"}
17
18
  - Page navigation: {"action": "navigate_<page_id>"}
18
19
 
19
20
  EXAMPLES:
@@ -21,6 +22,7 @@ EXAMPLES:
21
22
  - User says "show me the team page" → {"action": "navigate_team"}
22
23
  - User says "scroll down" → {"action": "scroll_down"}
23
24
  - User says "find schemes" → {"action": "search_content", "query": "schemes"}
25
+ - User says "type hello world" → {"action": "type_text", "text": "hello world"}
24
26
 
25
27
  MATCHING RULES FOR PAGE NAVIGATION:
26
28
  - Match user's words to page names and keywords
@@ -29,25 +31,45 @@ MATCHING RULES FOR PAGE NAVIGATION:
29
31
  const DEFAULT_EMBEDDING_MODEL_ID = "cohere.embed-multilingual-v3";
30
32
  export class BedrockService {
31
33
  constructor(config) {
34
+ this.client = null;
35
+ this.actionModelId = null;
32
36
  this.pageRegistry = null;
33
- this.client = new BedrockRuntimeClient({
34
- region: config.region,
35
- credentials: {
36
- accessKeyId: config.accessKeyId,
37
- secretAccessKey: config.secretAccessKey,
38
- },
39
- });
40
- this.actionModelId = config.modelId;
41
- if (config.embeddingModelId === null) {
37
+ this.bedrockEndpoint = null;
38
+ this.embeddingEndpointUrl = null;
39
+ this.useProxy = false;
40
+ this.useProxy = !!(config.bedrockEndpoint || config.embeddingEndpoint);
41
+ if (this.useProxy) {
42
+ this.bedrockEndpoint = config.bedrockEndpoint || null;
43
+ this.embeddingEndpointUrl = config.embeddingEndpoint || null;
44
+ this.actionModelId = null;
42
45
  this.embeddingModelId = null;
43
46
  }
44
- else if (typeof config.embeddingModelId === "string") {
45
- const trimmed = config.embeddingModelId.trim();
46
- this.embeddingModelId =
47
- trimmed.length > 0 ? trimmed : DEFAULT_EMBEDDING_MODEL_ID;
48
- }
49
47
  else {
50
- this.embeddingModelId = DEFAULT_EMBEDDING_MODEL_ID;
48
+ if (!config.region ||
49
+ !config.accessKeyId ||
50
+ !config.secretAccessKey ||
51
+ !config.modelId) {
52
+ throw new Error("AWS credentials required for direct mode");
53
+ }
54
+ this.client = new BedrockRuntimeClient({
55
+ region: config.region,
56
+ credentials: {
57
+ accessKeyId: config.accessKeyId,
58
+ secretAccessKey: config.secretAccessKey,
59
+ },
60
+ });
61
+ this.actionModelId = config.modelId;
62
+ if (config.embeddingModelId === null) {
63
+ this.embeddingModelId = null;
64
+ }
65
+ else if (typeof config.embeddingModelId === "string") {
66
+ const trimmed = config.embeddingModelId.trim();
67
+ this.embeddingModelId =
68
+ trimmed.length > 0 ? trimmed : DEFAULT_EMBEDDING_MODEL_ID;
69
+ }
70
+ else {
71
+ this.embeddingModelId = DEFAULT_EMBEDDING_MODEL_ID;
72
+ }
51
73
  }
52
74
  }
53
75
  setPageRegistry(registry) {
@@ -71,6 +93,73 @@ export class BedrockService {
71
93
  return prompt;
72
94
  }
73
95
  async extractAction(userInput) {
96
+ if (this.useProxy) {
97
+ return this.extractActionViaProxy(userInput);
98
+ }
99
+ else {
100
+ return this.extractActionDirect(userInput);
101
+ }
102
+ }
103
+ async extractActionViaProxy(userInput) {
104
+ if (!this.bedrockEndpoint) {
105
+ throw new Error("Bedrock endpoint not configured");
106
+ }
107
+ try {
108
+ const systemPrompt = this.buildSystemPrompt();
109
+ const requestBody = {
110
+ anthropic_version: "bedrock-2023-05-31",
111
+ max_tokens: 70,
112
+ system: systemPrompt,
113
+ messages: [
114
+ {
115
+ role: "user",
116
+ content: [
117
+ {
118
+ type: "text",
119
+ text: `User input: "${userInput}". Identify the intended action from the allowed list and respond ONLY with a JSON object. If the user input is unclear, respond with {"action": "unknown"}. Include no additional text.`,
120
+ },
121
+ ],
122
+ },
123
+ ],
124
+ };
125
+ const response = await fetch(this.bedrockEndpoint, {
126
+ method: "POST",
127
+ headers: {
128
+ "Content-Type": "application/json",
129
+ },
130
+ body: JSON.stringify({
131
+ body: requestBody,
132
+ }),
133
+ });
134
+ if (!response.ok) {
135
+ throw new Error(`Bedrock proxy error: ${response.status} ${response.statusText}`);
136
+ }
137
+ const data = (await response.json());
138
+ let rawText = "";
139
+ if (data.content && data.content.length > 0) {
140
+ rawText = data.content[0].text || "";
141
+ }
142
+ if (!rawText) {
143
+ return { action: "unknown" };
144
+ }
145
+ try {
146
+ const parsed = JSON.parse(rawText);
147
+ return parsed;
148
+ }
149
+ catch (parseError) {
150
+ console.error("Failed to parse Bedrock response:", rawText, parseError);
151
+ return { action: "unknown" };
152
+ }
153
+ }
154
+ catch (error) {
155
+ console.error("Bedrock proxy API error:", error);
156
+ throw error;
157
+ }
158
+ }
159
+ async extractActionDirect(userInput) {
160
+ if (!this.client || !this.actionModelId) {
161
+ throw new Error("AWS Bedrock client not initialized");
162
+ }
74
163
  const systemPrompt = this.buildSystemPrompt();
75
164
  const body = {
76
165
  anthropic_version: "bedrock-2023-05-31",
@@ -126,11 +215,76 @@ export class BedrockService {
126
215
  return new BedrockService(config);
127
216
  }
128
217
  async embedText(text) {
218
+ if (this.useProxy) {
219
+ return this.embedTextViaProxy(text);
220
+ }
221
+ else {
222
+ return this.embedTextDirect(text);
223
+ }
224
+ }
225
+ async embedTextViaProxy(text) {
226
+ const prompt = text.trim();
227
+ if (!prompt) {
228
+ return [];
229
+ }
230
+ if (!this.embeddingEndpointUrl) {
231
+ throw new Error("Embedding endpoint not configured");
232
+ }
233
+ try {
234
+ const response = await fetch(this.embeddingEndpointUrl, {
235
+ method: "POST",
236
+ headers: {
237
+ "Content-Type": "application/json",
238
+ },
239
+ body: JSON.stringify({
240
+ text: prompt,
241
+ inputType: "search_query",
242
+ }),
243
+ });
244
+ if (!response.ok) {
245
+ throw new Error(`Embedding proxy error: ${response.status} ${response.statusText}`);
246
+ }
247
+ const decoded = (await response.json());
248
+ let embedding;
249
+ if (decoded.embeddings) {
250
+ if (Array.isArray(decoded.embeddings)) {
251
+ if (decoded.embeddings.length > 0 &&
252
+ Array.isArray(decoded.embeddings[0])) {
253
+ embedding = decoded.embeddings[0];
254
+ }
255
+ else if (decoded.embeddings.length > 0 &&
256
+ typeof decoded.embeddings[0] === "number") {
257
+ embedding = decoded.embeddings;
258
+ }
259
+ }
260
+ else if (typeof decoded.embeddings === "object" &&
261
+ "values" in decoded.embeddings) {
262
+ embedding = decoded.embeddings.values;
263
+ }
264
+ }
265
+ if (!embedding) {
266
+ embedding =
267
+ decoded.embedding ||
268
+ decoded.vector ||
269
+ decoded.results?.find((item) => Array.isArray(item.embedding))
270
+ ?.embedding;
271
+ }
272
+ if (!Array.isArray(embedding)) {
273
+ throw new Error("Unexpected embedding response structure");
274
+ }
275
+ return embedding.map((value) => Number(value));
276
+ }
277
+ catch (error) {
278
+ console.error("Embedding proxy error:", error);
279
+ throw error;
280
+ }
281
+ }
282
+ async embedTextDirect(text) {
129
283
  const prompt = text.trim();
130
284
  if (!prompt) {
131
285
  return [];
132
286
  }
133
- if (!this.embeddingModelId) {
287
+ if (!this.client || !this.embeddingModelId) {
134
288
  throw new Error("Bedrock embedding model is not configured");
135
289
  }
136
290
  const payload = {
@@ -1 +1 @@
1
- {"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../src/services/vector-search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAwCD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,MAAM,EAAE,gBAAgB;IA6B9B,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAyF9E,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI;IAoCjD,oBAAoB,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG;QAC7C,GAAG,EAAE,eAAe,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,IAAI;IAaR,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,eAAe;YAkBT,YAAY;CAQ3B"}
1
+ {"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../src/services/vector-search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAyCD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,MAAM,EAAE,gBAAgB;IAuC9B,MAAM,CACV,MAAM,EAAE,MAAM,EAAE,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,EAAE,CAAC;IA6F7B,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI;IAoCjD,oBAAoB,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG;QAC7C,GAAG,EAAE,eAAe,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,IAAI;IAaR,OAAO,CAAC,mBAAmB;IAqC3B,OAAO,CAAC,eAAe;YAqBT,YAAY;CAQ3B"}
@@ -11,6 +11,7 @@ export class VectorSearchService {
11
11
  const requestTimeoutMs = typeof config.requestTimeoutMs === "number" && config.requestTimeoutMs > 0
12
12
  ? Math.floor(config.requestTimeoutMs)
13
13
  : 15000;
14
+ const isEndpointOnlyMode = !!(config.searchEndpoint && !config.node);
14
15
  this.config = {
15
16
  ...config,
16
17
  vectorField: config.vectorField || "embedding",
@@ -19,9 +20,12 @@ export class VectorSearchService {
19
20
  numCandidates,
20
21
  requestTimeoutMs,
21
22
  sourceFields: config.sourceFields,
22
- apiPath: config.apiPath || DEFAULT_VECTOR_SEARCH_API_PATH,
23
+ apiPath: config.apiPath ||
24
+ config.searchEndpoint ||
25
+ DEFAULT_VECTOR_SEARCH_API_PATH,
26
+ isEndpointOnlyMode,
23
27
  };
24
- this.endpoint = this.resolveEndpoint(this.config.apiPath);
28
+ this.endpoint = this.resolveEndpoint(config.searchEndpoint || config.apiPath || DEFAULT_VECTOR_SEARCH_API_PATH);
25
29
  }
26
30
  async search(vector, queryText) {
27
31
  if (!Array.isArray(vector) || vector.length === 0) {
@@ -133,6 +137,15 @@ export class VectorSearchService {
133
137
  return null;
134
138
  }
135
139
  buildRequestPayload(vector, queryText) {
140
+ if (this.config.isEndpointOnlyMode) {
141
+ return {
142
+ vector,
143
+ query: queryText,
144
+ size: this.config.size,
145
+ minScore: this.config.minScore,
146
+ sourceFields: this.config.sourceFields,
147
+ };
148
+ }
136
149
  return {
137
150
  vector,
138
151
  queryText,