@myscheme/voice-navigation-sdk 0.1.5 → 0.1.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.
Files changed (38) hide show
  1. package/README.md +13 -5
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +184 -44
  4. package/dist/microphone-handler.d.ts +8 -0
  5. package/dist/microphone-handler.d.ts.map +1 -1
  6. package/dist/microphone-handler.js +98 -24
  7. package/dist/navigation-controller.d.ts +4 -0
  8. package/dist/navigation-controller.d.ts.map +1 -1
  9. package/dist/navigation-controller.js +201 -7
  10. package/dist/server/azure-speech-handler.d.ts +13 -0
  11. package/dist/server/azure-speech-handler.d.ts.map +1 -0
  12. package/dist/server/azure-speech-handler.js +79 -0
  13. package/dist/server/bedrock-embedding-handler.d.ts +16 -0
  14. package/dist/server/bedrock-embedding-handler.d.ts.map +1 -0
  15. package/dist/server/bedrock-embedding-handler.js +183 -0
  16. package/dist/server/bedrock-handler.d.ts +15 -0
  17. package/dist/server/bedrock-handler.d.ts.map +1 -0
  18. package/dist/server/bedrock-handler.js +171 -0
  19. package/dist/server/index.d.ts +7 -0
  20. package/dist/server/index.d.ts.map +1 -1
  21. package/dist/server/index.js +7 -0
  22. package/dist/server/opensearch-env-handler.d.ts +22 -0
  23. package/dist/server/opensearch-env-handler.d.ts.map +1 -0
  24. package/dist/server/opensearch-env-handler.js +221 -0
  25. package/dist/services/azure-speech.d.ts +10 -2
  26. package/dist/services/azure-speech.d.ts.map +1 -1
  27. package/dist/services/azure-speech.js +66 -3
  28. package/dist/services/bedrock.d.ts +16 -4
  29. package/dist/services/bedrock.d.ts.map +1 -1
  30. package/dist/services/bedrock.js +293 -17
  31. package/dist/services/vector-search.d.ts.map +1 -1
  32. package/dist/services/vector-search.js +15 -2
  33. package/dist/services/voice-feedback.d.ts +46 -0
  34. package/dist/services/voice-feedback.d.ts.map +1 -0
  35. package/dist/services/voice-feedback.js +332 -0
  36. package/dist/types.d.ts +17 -9
  37. package/dist/types.d.ts.map +1 -1
  38. package/package.json +3 -3
@@ -0,0 +1,183 @@
1
+ import crypto from "crypto";
2
+ function sign(key, msg) {
3
+ return crypto.createHmac("sha256", key).update(msg, "utf8").digest();
4
+ }
5
+ function getSignatureKey(key, dateStamp, region, service) {
6
+ const kDate = sign("AWS4" + key, dateStamp);
7
+ const kRegion = sign(kDate, region);
8
+ const kService = sign(kRegion, service);
9
+ const kSigning = sign(kService, "aws4_request");
10
+ return kSigning;
11
+ }
12
+ async function generateBedrockEmbedding(text, inputType = "search_query") {
13
+ const region = process.env.AWS_REGION || "ap-south-1";
14
+ const accessKey = process.env.AWS_ACCESS_KEY_ID;
15
+ const secretKey = process.env.AWS_SECRET_ACCESS_KEY;
16
+ const sessionToken = process.env.AWS_SESSION_TOKEN;
17
+ const embeddingModelId = process.env.AWS_EMBEDDING_MODEL_ID || "cohere.embed-multilingual-v3";
18
+ if (!accessKey || !secretKey) {
19
+ console.error("[VoiceNavigation] Missing AWS credentials. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in your .env file");
20
+ return {
21
+ success: false,
22
+ statusCode: 500,
23
+ error: {
24
+ error: "Missing AWS credentials",
25
+ hint: "Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables",
26
+ },
27
+ };
28
+ }
29
+ if (!embeddingModelId) {
30
+ return {
31
+ success: false,
32
+ statusCode: 500,
33
+ error: { error: "Missing AWS_EMBEDDING_MODEL_ID" },
34
+ };
35
+ }
36
+ if (!text) {
37
+ return {
38
+ success: false,
39
+ statusCode: 400,
40
+ error: { error: "Missing text parameter" },
41
+ };
42
+ }
43
+ const requestBody = {
44
+ texts: Array.isArray(text) ? text : [text],
45
+ input_type: inputType,
46
+ truncate: "END",
47
+ };
48
+ const body = JSON.stringify(requestBody);
49
+ const service = "bedrock";
50
+ const host = `bedrock-runtime.${region}.amazonaws.com`;
51
+ const encodedModelId = encodeURIComponent(embeddingModelId);
52
+ const path = `/model/${encodedModelId}/invoke`;
53
+ const endpoint = `https://${host}${path}`;
54
+ const canonicalPath = path.replace(/%/g, "%25");
55
+ const amzDate = new Date().toISOString().replace(/[:-]|\.\d{3}/g, "");
56
+ const dateStamp = amzDate.slice(0, 8);
57
+ const headers = {
58
+ "content-type": "application/json",
59
+ host: host,
60
+ "x-amz-date": amzDate,
61
+ };
62
+ if (sessionToken) {
63
+ headers["x-amz-security-token"] = sessionToken;
64
+ }
65
+ const signedHeaders = Object.keys(headers)
66
+ .map((h) => h.toLowerCase())
67
+ .sort()
68
+ .join(";");
69
+ const canonicalHeaders = Object.keys(headers)
70
+ .map((h) => h.toLowerCase())
71
+ .sort()
72
+ .map((k) => `${k}:${headers[k]}\n`)
73
+ .join("");
74
+ const canonicalRequest = "POST\n" +
75
+ canonicalPath +
76
+ "\n" +
77
+ "\n" +
78
+ canonicalHeaders +
79
+ "\n" +
80
+ signedHeaders +
81
+ "\n" +
82
+ crypto.createHash("sha256").update(body, "utf8").digest("hex");
83
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
84
+ const stringToSign = "AWS4-HMAC-SHA256\n" +
85
+ amzDate +
86
+ "\n" +
87
+ credentialScope +
88
+ "\n" +
89
+ crypto.createHash("sha256").update(canonicalRequest, "utf8").digest("hex");
90
+ const signingKey = getSignatureKey(secretKey, dateStamp, region, service);
91
+ const signature = crypto
92
+ .createHmac("sha256", signingKey)
93
+ .update(stringToSign, "utf8")
94
+ .digest("hex");
95
+ const authorizationHeader = `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, ` +
96
+ `SignedHeaders=${signedHeaders}, Signature=${signature}`;
97
+ const fetchHeaders = {
98
+ ...headers,
99
+ Authorization: authorizationHeader,
100
+ };
101
+ try {
102
+ const response = await fetch(endpoint, {
103
+ method: "POST",
104
+ headers: fetchHeaders,
105
+ body,
106
+ });
107
+ if (!response.ok) {
108
+ const errorText = await response.text();
109
+ console.error("[VoiceNavigation] Bedrock Embedding API error:", response.status, errorText);
110
+ return {
111
+ success: false,
112
+ statusCode: response.status,
113
+ error: {
114
+ error: "Bedrock Embedding API error",
115
+ details: errorText,
116
+ },
117
+ };
118
+ }
119
+ const json = await response.json();
120
+ return {
121
+ success: true,
122
+ statusCode: 200,
123
+ data: json,
124
+ };
125
+ }
126
+ catch (error) {
127
+ console.error("[VoiceNavigation] Bedrock embedding generation error:", error);
128
+ return {
129
+ success: false,
130
+ statusCode: 500,
131
+ error: {
132
+ error: "Failed to generate embeddings",
133
+ message: error instanceof Error ? error.message : "Unknown error",
134
+ },
135
+ };
136
+ }
137
+ }
138
+ export async function bedrockEmbeddingHandler(req, res) {
139
+ if (req.method !== "POST") {
140
+ res.setHeader("Allow", ["POST"]);
141
+ res.status(405).json({ error: "Method not allowed" });
142
+ return;
143
+ }
144
+ const { text, inputType = "search_query" } = req.body || {};
145
+ const result = await generateBedrockEmbedding(text, inputType);
146
+ if (result.success && result.data) {
147
+ res.status(result.statusCode).json(result.data);
148
+ }
149
+ else {
150
+ res.status(result.statusCode).json(result.error);
151
+ }
152
+ }
153
+ export async function BedrockEmbeddingPOST(request) {
154
+ try {
155
+ const body = await request.json();
156
+ const { text, inputType = "search_query" } = body || {};
157
+ const result = await generateBedrockEmbedding(text, inputType);
158
+ const headers = {
159
+ "Content-Type": "application/json",
160
+ };
161
+ if (result.success && result.data) {
162
+ return new Response(JSON.stringify(result.data), {
163
+ status: result.statusCode,
164
+ headers,
165
+ });
166
+ }
167
+ else {
168
+ return new Response(JSON.stringify(result.error), {
169
+ status: result.statusCode,
170
+ headers,
171
+ });
172
+ }
173
+ }
174
+ catch (error) {
175
+ return new Response(JSON.stringify({
176
+ error: "Invalid request",
177
+ message: error instanceof Error ? error.message : "Unknown error",
178
+ }), {
179
+ status: 400,
180
+ headers: { "Content-Type": "application/json" },
181
+ });
182
+ }
183
+ }
@@ -0,0 +1,15 @@
1
+ type NextApiRequest = {
2
+ method?: string;
3
+ body?: {
4
+ body?: unknown;
5
+ };
6
+ };
7
+ type NextApiResponse = {
8
+ status: (code: number) => NextApiResponse;
9
+ json: (data: unknown) => void;
10
+ setHeader: (name: string, value: string | string[]) => void;
11
+ };
12
+ export declare function bedrockHandler(req: NextApiRequest, res: NextApiResponse): Promise<void>;
13
+ export declare function BedrockPOST(request: Request): Promise<Response>;
14
+ export {};
15
+ //# sourceMappingURL=bedrock-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bedrock-handler.d.ts","sourceRoot":"","sources":["../../src/server/bedrock-handler.ts"],"names":[],"mappings":"AA4BA,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;IAC1C,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7D,CAAC;AAqLF,wBAAsB,cAAc,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAkBf;AAMD,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkCrE"}
@@ -0,0 +1,171 @@
1
+ import crypto from "crypto";
2
+ function sign(key, msg) {
3
+ return crypto.createHmac("sha256", key).update(msg, "utf8").digest();
4
+ }
5
+ function getSignatureKey(key, dateStamp, region, service) {
6
+ const kDate = sign("AWS4" + key, dateStamp);
7
+ const kRegion = sign(kDate, region);
8
+ const kService = sign(kRegion, service);
9
+ const kSigning = sign(kService, "aws4_request");
10
+ return kSigning;
11
+ }
12
+ async function invokeBedrock(requestBody) {
13
+ const region = process.env.AWS_REGION || "ap-south-1";
14
+ const accessKey = process.env.AWS_ACCESS_KEY_ID;
15
+ const secretKey = process.env.AWS_SECRET_ACCESS_KEY;
16
+ const sessionToken = process.env.AWS_SESSION_TOKEN;
17
+ const modelId = process.env.AWS_MODEL_ID || "anthropic.claude-3-sonnet-20240229-v1:0";
18
+ if (!accessKey || !secretKey) {
19
+ console.error("[VoiceNavigation] Missing AWS credentials. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in your .env file");
20
+ return {
21
+ success: false,
22
+ statusCode: 500,
23
+ error: {
24
+ error: "Missing AWS credentials",
25
+ hint: "Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables",
26
+ },
27
+ };
28
+ }
29
+ if (!requestBody) {
30
+ return {
31
+ success: false,
32
+ statusCode: 400,
33
+ error: { error: "Missing request body" },
34
+ };
35
+ }
36
+ const body = JSON.stringify(requestBody);
37
+ const service = "bedrock";
38
+ const host = `bedrock-runtime.${region}.amazonaws.com`;
39
+ const encodedModelId = encodeURIComponent(modelId);
40
+ const path = `/model/${encodedModelId}/invoke`;
41
+ const endpoint = `https://${host}${path}`;
42
+ const canonicalPath = path.replace(/%/g, "%25");
43
+ const amzDate = new Date().toISOString().replace(/[:-]|\.\d{3}/g, "");
44
+ const dateStamp = amzDate.slice(0, 8);
45
+ const headers = {
46
+ "content-type": "application/json",
47
+ host: host,
48
+ "x-amz-date": amzDate,
49
+ };
50
+ if (sessionToken) {
51
+ headers["x-amz-security-token"] = sessionToken;
52
+ }
53
+ const signedHeaders = Object.keys(headers)
54
+ .map((h) => h.toLowerCase())
55
+ .sort()
56
+ .join(";");
57
+ const canonicalHeaders = Object.keys(headers)
58
+ .map((h) => h.toLowerCase())
59
+ .sort()
60
+ .map((k) => `${k}:${headers[k]}\n`)
61
+ .join("");
62
+ const canonicalRequest = "POST\n" +
63
+ canonicalPath +
64
+ "\n" +
65
+ "\n" +
66
+ canonicalHeaders +
67
+ "\n" +
68
+ signedHeaders +
69
+ "\n" +
70
+ crypto.createHash("sha256").update(body, "utf8").digest("hex");
71
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
72
+ const stringToSign = "AWS4-HMAC-SHA256\n" +
73
+ amzDate +
74
+ "\n" +
75
+ credentialScope +
76
+ "\n" +
77
+ crypto.createHash("sha256").update(canonicalRequest, "utf8").digest("hex");
78
+ const signingKey = getSignatureKey(secretKey, dateStamp, region, service);
79
+ const signature = crypto
80
+ .createHmac("sha256", signingKey)
81
+ .update(stringToSign, "utf8")
82
+ .digest("hex");
83
+ const authorizationHeader = `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, ` +
84
+ `SignedHeaders=${signedHeaders}, Signature=${signature}`;
85
+ const fetchHeaders = {
86
+ ...headers,
87
+ Authorization: authorizationHeader,
88
+ };
89
+ try {
90
+ const response = await fetch(endpoint, {
91
+ method: "POST",
92
+ headers: fetchHeaders,
93
+ body,
94
+ });
95
+ if (!response.ok) {
96
+ const errorText = await response.text();
97
+ console.error("[VoiceNavigation] Bedrock API error:", response.status, errorText);
98
+ return {
99
+ success: false,
100
+ statusCode: response.status,
101
+ error: {
102
+ error: "Bedrock API error",
103
+ details: errorText,
104
+ },
105
+ };
106
+ }
107
+ const json = await response.json();
108
+ return {
109
+ success: true,
110
+ statusCode: 200,
111
+ data: json,
112
+ };
113
+ }
114
+ catch (error) {
115
+ console.error("[VoiceNavigation] Bedrock action extraction error:", error);
116
+ return {
117
+ success: false,
118
+ statusCode: 500,
119
+ error: {
120
+ error: "Failed to extract action",
121
+ message: error instanceof Error ? error.message : "Unknown error",
122
+ },
123
+ };
124
+ }
125
+ }
126
+ export async function bedrockHandler(req, res) {
127
+ if (req.method !== "POST") {
128
+ res.setHeader("Allow", ["POST"]);
129
+ res.status(405).json({ error: "Method not allowed" });
130
+ return;
131
+ }
132
+ const requestBody = req.body?.body;
133
+ const result = await invokeBedrock(requestBody);
134
+ if (result.success && result.data) {
135
+ res.status(result.statusCode).json(result.data);
136
+ }
137
+ else {
138
+ res.status(result.statusCode).json(result.error);
139
+ }
140
+ }
141
+ export async function BedrockPOST(request) {
142
+ try {
143
+ const body = await request.json();
144
+ const requestBody = body?.body;
145
+ const result = await invokeBedrock(requestBody);
146
+ const headers = {
147
+ "Content-Type": "application/json",
148
+ };
149
+ if (result.success && result.data) {
150
+ return new Response(JSON.stringify(result.data), {
151
+ status: result.statusCode,
152
+ headers,
153
+ });
154
+ }
155
+ else {
156
+ return new Response(JSON.stringify(result.error), {
157
+ status: result.statusCode,
158
+ headers,
159
+ });
160
+ }
161
+ }
162
+ catch (error) {
163
+ return new Response(JSON.stringify({
164
+ error: "Invalid request",
165
+ message: error instanceof Error ? error.message : "Unknown error",
166
+ }), {
167
+ status: 400,
168
+ headers: { "Content-Type": "application/json" },
169
+ });
170
+ }
171
+ }
@@ -1,3 +1,10 @@
1
+ export { azureSpeechTokenHandler } from "./azure-speech-handler.js";
2
+ export { bedrockHandler } from "./bedrock-handler.js";
3
+ export { bedrockEmbeddingHandler } from "./bedrock-embedding-handler.js";
4
+ export { openSearchSearchHandler, openSearchSuggestHandler, } from "./opensearch-env-handler.js";
5
+ export { POST } from "./azure-speech-handler.js";
6
+ export { BedrockPOST } from "./bedrock-handler.js";
7
+ export { BedrockEmbeddingPOST } from "./bedrock-embedding-handler.js";
1
8
  export { createOpenSearchProxyHandler, DEFAULT_OPENSEARCH_PROXY_PATH, } from "./opensearch-handler.js";
2
9
  export type { VectorSearchProxyRequest, VectorSearchProxyResponse, } from "./opensearch-handler.js";
3
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAGtE,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC"}
@@ -1 +1,8 @@
1
+ export { azureSpeechTokenHandler } from "./azure-speech-handler.js";
2
+ export { bedrockHandler } from "./bedrock-handler.js";
3
+ export { bedrockEmbeddingHandler } from "./bedrock-embedding-handler.js";
4
+ export { openSearchSearchHandler, openSearchSuggestHandler, } from "./opensearch-env-handler.js";
5
+ export { POST } from "./azure-speech-handler.js";
6
+ export { BedrockPOST } from "./bedrock-handler.js";
7
+ export { BedrockEmbeddingPOST } from "./bedrock-embedding-handler.js";
1
8
  export { createOpenSearchProxyHandler, DEFAULT_OPENSEARCH_PROXY_PATH, } from "./opensearch-handler.js";
@@ -0,0 +1,22 @@
1
+ type NextApiRequest = {
2
+ method?: string;
3
+ headers?: {
4
+ [key: string]: string | string[] | undefined;
5
+ };
6
+ body?: any;
7
+ query?: {
8
+ [key: string]: string | string[];
9
+ };
10
+ socket?: {
11
+ remoteAddress?: string;
12
+ };
13
+ };
14
+ type NextApiResponse = {
15
+ status: (code: number) => NextApiResponse;
16
+ json: (data: any) => void;
17
+ setHeader: (name: string, value: string | string[]) => void;
18
+ };
19
+ export declare function openSearchSearchHandler(req: NextApiRequest, res: NextApiResponse): Promise<void>;
20
+ export declare function openSearchSuggestHandler(req: NextApiRequest, res: NextApiResponse): Promise<void>;
21
+ export {};
22
+ //# sourceMappingURL=opensearch-env-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opensearch-env-handler.d.ts","sourceRoot":"","sources":["../../src/server/opensearch-env-handler.ts"],"names":[],"mappings":"AAuBA,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3D,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAA;KAAE,CAAC;IAC7C,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;IAC1C,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC1B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7D,CAAC;AAmFF,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CA6Hf;AAMD,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAgFf"}
@@ -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"}