@naanlang/naan 1.4.3 → 1.4.4
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/LICENSE.md +1 -1
- package/README.md +1 -1
- package/bin/index.js +4 -3
- package/dist/naan.min.js +5 -5
- package/frameworks/browser/https_request.nlg +39 -10
- package/frameworks/browser/sworker.js +23 -23
- package/frameworks/browser/terminals.nlg +19 -16
- package/frameworks/node/http_request.nlg +103 -0
- package/frameworks/node/https_request.nlg +42 -9
- package/frameworks/node/node.nlg +1 -0
- package/frameworks/node/worker.nlg +1 -0
- package/frameworks/project/projects.nlg +1 -1
- package/frameworks/running/executors.nlg +26 -7
- package/frameworks/running/taskexec.nlg +48 -14
- package/lib/browser/env_web.js +7 -7
- package/lib/core/naanlib.js +5 -5
- package/lib/env_node.js +9 -3
- package/lib/node_repl_init.nlg +2 -2
- package/package.json +1 -1
- package/plugins/nodeLib/node_util.nlg +14 -7
- package/plugins/openSearch/os_dynamo.nlg +5 -5
- package/plugins/serviceAws/aws_dynamo.nlg +25 -8
- package/plugins/serviceAws/aws_vm.nlg +198 -0
- package/plugins/serviceGC/gc_api.nlg +1 -0
- package/plugins/serviceNexara/serviceNexara.nlg +32 -0
- package/plugins/serviceNexara/speechrec.nlg +160 -0
- package/plugins/serviceYC/serviceYC.nlg +2 -3
- package/plugins/serviceYC/yc_speechrec.nlg +4 -3
- package/plugins/serviceYC/yc_vm.nlg +44 -54
- package/test/test_01_core.nlg +42 -33
- package/test/test_03_jsinterop.nlg +3 -3
package/lib/env_node.js
CHANGED
|
@@ -110,11 +110,13 @@ exports.NaanNodeREPL = function NaanNodeREPL(options) {
|
|
|
110
110
|
{ // Node REPL does too much crap
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
if (process.stdin.isTTY
|
|
113
|
+
if (process.stdin.isTTY)
|
|
114
114
|
{ // must have TTY for local REPL
|
|
115
115
|
replServer = repl.start({ prompt: '', eval: myEval});
|
|
116
116
|
|
|
117
117
|
replServer.on('line', function(cmd) { // process raw command line
|
|
118
|
+
if (options.replDisable)
|
|
119
|
+
return;
|
|
118
120
|
if (cmd.charAt(0) != ".") {
|
|
119
121
|
textToRemoteTerm("\x1b[90m\x1b[3m".concat(cmd, "\x1b[0m\n"));
|
|
120
122
|
naanlib.textLine(cmd + "\n");
|
|
@@ -126,9 +128,13 @@ exports.NaanNodeREPL = function NaanNodeREPL(options) {
|
|
|
126
128
|
//
|
|
127
129
|
|
|
128
130
|
replServer._events.SIGINT = function () {
|
|
129
|
-
|
|
131
|
+
if (options.replDisable) {
|
|
132
|
+
console.log("^C");
|
|
133
|
+
process.exit();
|
|
134
|
+
} else
|
|
135
|
+
naanlib.escape();
|
|
130
136
|
};
|
|
131
|
-
|
|
137
|
+
|
|
132
138
|
//
|
|
133
139
|
// Hook an exit command
|
|
134
140
|
//
|
package/lib/node_repl_init.nlg
CHANGED
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* column positioning: // // !
|
|
7
7
|
*
|
|
8
|
-
* Copyright (c) 2021-
|
|
8
|
+
* Copyright (c) 2021-2025 by Richard C. Zulch
|
|
9
9
|
*
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -13,12 +13,11 @@
|
|
|
13
13
|
/*
|
|
14
14
|
* MultipartBody
|
|
15
15
|
*
|
|
16
|
-
* column positioning: // // !
|
|
17
16
|
* Create a multipart Form body from the specified dictionary, returning a buffer. Any xobject in
|
|
18
17
|
* the dictionary is coded as an octet. If you want a different filename, include an options object
|
|
19
18
|
* that defines that filename.
|
|
20
19
|
*
|
|
21
|
-
*The
|
|
20
|
+
*The return value is a dictionary as follows:
|
|
22
21
|
* {
|
|
23
22
|
* body: <Buffer>
|
|
24
23
|
* header: {
|
|
@@ -29,11 +28,17 @@
|
|
|
29
28
|
*
|
|
30
29
|
*/
|
|
31
30
|
|
|
32
|
-
function MultipartBody(dict, options,
|
|
31
|
+
function MultipartBody(dict, options,
|
|
32
|
+
local boundary, parts, key, data, item, filename) {
|
|
33
33
|
boundary = "xx-xx-xx-xx-xx"
|
|
34
34
|
parts = []
|
|
35
35
|
for `(key, data) in dict {
|
|
36
|
-
if
|
|
36
|
+
if Array.isArray(data) { // PHP-style arrays
|
|
37
|
+
for item in data
|
|
38
|
+
parts.push(xnew(js.g.Buffer.from, "--".concat(boundary, "\r\n",
|
|
39
|
+
"Content-Disposition: form-data; name=\"", key, "[]",
|
|
40
|
+
"\"; \r\n\r\n", item, "\r\n"), "utf8"))
|
|
41
|
+
} else if xobject(data) { // binary file data
|
|
37
42
|
if options[key]
|
|
38
43
|
filename = options[key]
|
|
39
44
|
else
|
|
@@ -43,8 +48,7 @@ function MultipartBody(dict, options, local boundary, parts, key, data, filename
|
|
|
43
48
|
"Content-Type:application/octet-stream\r\n\r\n"), "utf8"))
|
|
44
49
|
parts.push(xnew(js.g.Buffer.from, data, "binary"))
|
|
45
50
|
parts.push(xnew(js.g.Buffer.from, "\r\n", "utf8"))
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
51
|
+
} else { // ordinary data
|
|
48
52
|
parts.push(xnew(js.g.Buffer.from, "--".concat(boundary, "\r\n",
|
|
49
53
|
"Content-Disposition: form-data; name=\"", key,
|
|
50
54
|
"\"; \r\n\r\n", data, "\r\n"), "utf8"))
|
|
@@ -165,8 +169,11 @@ function nodeUtilInit(local manifest) {
|
|
|
165
169
|
Naan.module.build(module.id, "node_util", function(modobj, compobj) {
|
|
166
170
|
require("nodeLib.nlg")
|
|
167
171
|
compobj.manifest = manifest
|
|
172
|
+
modobj.exports.MultipartBody = MultipartBody
|
|
173
|
+
modobj.exports.EncodeQuery = EncodeQuery
|
|
168
174
|
modobj.exports.JsonParse = JsonParse
|
|
169
175
|
modobj.exports.JsonStringify = JsonStringify
|
|
176
|
+
modobj.exports.RandomBytes = RandomBytes
|
|
170
177
|
modobj.exports.UUID = UUID
|
|
171
178
|
function postload() {
|
|
172
179
|
nodeFs = js.r("fs")
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2023 by Richard C. Zulch
|
|
9
|
+
* Copyright (c) 2023-2025 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -65,7 +65,7 @@ closure OSDynIndexer(index, table, options,
|
|
|
65
65
|
// Update ids into the database, returning a nonce signaled with error array.
|
|
66
66
|
//
|
|
67
67
|
closure updateRecordIDs(docs, ids, writeID) {
|
|
68
|
-
asyncArray(docs, 10,
|
|
68
|
+
asyncArray(docs, 10, closure(entry, index, cancel, local error) {
|
|
69
69
|
ignore(error)
|
|
70
70
|
`(error) = table.updateRecord(
|
|
71
71
|
entry[table.hashKey],
|
|
@@ -78,11 +78,11 @@ closure OSDynIndexer(index, table, options,
|
|
|
78
78
|
//
|
|
79
79
|
// Batch index documents into OpenSearch, returning a nonce signaled with ids array.
|
|
80
80
|
//
|
|
81
|
-
closure batchIndex(records, local pending
|
|
81
|
+
closure batchIndex(records, local pending) {
|
|
82
82
|
pending = new(nonce)
|
|
83
|
-
docs = records
|
|
83
|
+
docs = asyncArray(records, 10, closure(record) {
|
|
84
84
|
options.converter(record)
|
|
85
|
-
})
|
|
85
|
+
}).wait()
|
|
86
86
|
future(function(local error, ids) {
|
|
87
87
|
`(error, ids) = index.write(docs)
|
|
88
88
|
pending.signal(list(error, records, ids))
|
|
@@ -946,7 +946,7 @@ closure DynaTable(dyna, tablename, options, local table) {
|
|
|
946
946
|
// range: <key-expression> // range of keys to retrieve
|
|
947
947
|
// filter: <key-expression> // conditions for items to return
|
|
948
948
|
// reverse: <boolean> // reverse order
|
|
949
|
-
// project: <array>
|
|
949
|
+
// project: <array> | `count // project only specified attributes
|
|
950
950
|
// paging: <exclusive start key> // for paging; updated each call
|
|
951
951
|
// consistentRead: <boolean> // true to use consistent read
|
|
952
952
|
// limit: <number> // maximum count of results
|
|
@@ -962,6 +962,7 @@ closure DynaTable(dyna, tablename, options, local table) {
|
|
|
962
962
|
TableName: tablename
|
|
963
963
|
}
|
|
964
964
|
hashKey = table.hashKey
|
|
965
|
+
output = []
|
|
965
966
|
if options.indexName {
|
|
966
967
|
params.IndexName = options.indexName
|
|
967
968
|
if options.indexHashKey
|
|
@@ -989,15 +990,23 @@ closure DynaTable(dyna, tablename, options, local table) {
|
|
|
989
990
|
}
|
|
990
991
|
if options.reverse
|
|
991
992
|
params.ScanIndexForward = false
|
|
992
|
-
if options.project
|
|
993
|
+
if options.project == `count {
|
|
994
|
+
params.Select = "COUNT"
|
|
995
|
+
output = {
|
|
996
|
+
Count: 0
|
|
997
|
+
ScannedCount: 0
|
|
998
|
+
}
|
|
999
|
+
} else if options.project
|
|
993
1000
|
params.ProjectionExpression = options.project.join(", ")
|
|
994
1001
|
if options.paging
|
|
995
1002
|
params.ExclusiveStartKey = options.paging
|
|
996
1003
|
if options.consistentRead
|
|
997
1004
|
params.ConsistentRead = options.consistentRead
|
|
998
|
-
output = []
|
|
999
1005
|
`(error) = dynQueryPager(`query, params, options.limit, function(data) {
|
|
1000
|
-
if
|
|
1006
|
+
if dictionary(output) {
|
|
1007
|
+
output.Count += data.Count
|
|
1008
|
+
output.ScannedCount += data.ScannedCount
|
|
1009
|
+
} else if data.Count > 0
|
|
1001
1010
|
output = output.concat(dyna.conv.recordDynToNaan(data.Items))
|
|
1002
1011
|
data.Count
|
|
1003
1012
|
})
|
|
@@ -1016,7 +1025,7 @@ closure DynaTable(dyna, tablename, options, local table) {
|
|
|
1016
1025
|
// {
|
|
1017
1026
|
// index: <string> // index name in table
|
|
1018
1027
|
// filter: <key-expression> // conditions for items to return
|
|
1019
|
-
// project: <array>
|
|
1028
|
+
// project: <array> | `count // project only specified attributes
|
|
1020
1029
|
// paging: <exclusive start key> // for paging; updated each call
|
|
1021
1030
|
// consistentRead: <boolean> // true to use consistent read
|
|
1022
1031
|
// limit: <number> // maximum count of results
|
|
@@ -1040,15 +1049,23 @@ closure DynaTable(dyna, tablename, options, local table) {
|
|
|
1040
1049
|
if error
|
|
1041
1050
|
return (list(Error("table.scanRecords filter:", error)))
|
|
1042
1051
|
}
|
|
1043
|
-
if options.project
|
|
1052
|
+
if options.project == `count {
|
|
1053
|
+
params.Select = "COUNT"
|
|
1054
|
+
output = {
|
|
1055
|
+
Count: 0
|
|
1056
|
+
ScannedCount: 0
|
|
1057
|
+
}
|
|
1058
|
+
} else if options.project
|
|
1044
1059
|
params.ProjectionExpression = options.project.join(", ")
|
|
1045
1060
|
if options.paging
|
|
1046
1061
|
params.ExclusiveStartKey = options.paging
|
|
1047
1062
|
if options.consistentRead
|
|
1048
1063
|
params.ConsistentRead = options.consistentRead
|
|
1049
|
-
output = []
|
|
1050
1064
|
`(error) = dynQueryPager(`scan, params, options.limit, function(data) {
|
|
1051
|
-
if
|
|
1065
|
+
if dictionary(output) {
|
|
1066
|
+
output.Count += data.Count
|
|
1067
|
+
output.ScannedCount += data.ScannedCount
|
|
1068
|
+
} else if data.Count >= 0
|
|
1052
1069
|
output = output.concat(dyna.conv.recordDynToNaan(data.Items))
|
|
1053
1070
|
data.Count
|
|
1054
1071
|
})
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* aws_vm.nlg
|
|
3
|
+
* serviceAws
|
|
4
|
+
*
|
|
5
|
+
* VM controller under AWS.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2024-2025 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
vmState = false;
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* VmControl
|
|
17
|
+
*
|
|
18
|
+
* VM control object for AWS.
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
closure VmControl(creds, local dokon) {
|
|
23
|
+
global(js, http, vmState)
|
|
24
|
+
if vmState.dokon
|
|
25
|
+
return (vmState.dokon) // singleton
|
|
26
|
+
dokon = new(object, this)
|
|
27
|
+
vmState = new(nonce) // don't save with state
|
|
28
|
+
vmState.dokon = dokon
|
|
29
|
+
|
|
30
|
+
// detachVM
|
|
31
|
+
//
|
|
32
|
+
// Tell AWS to detach a VM from its scalability group. Returns a tuple with:
|
|
33
|
+
// {
|
|
34
|
+
// Activities: [
|
|
35
|
+
// {
|
|
36
|
+
// ActivityId: "STRING_VALUE",
|
|
37
|
+
// AutoScalingGroupName: "STRING_VALUE",
|
|
38
|
+
// Description: "STRING_VALUE",
|
|
39
|
+
// Cause: "STRING_VALUE",
|
|
40
|
+
// StartTime: new Date("TIMESTAMP"),
|
|
41
|
+
// EndTime: new Date("TIMESTAMP"),
|
|
42
|
+
// StatusCode: "PendingSpotBidPlacement" || "WaitingForSpotInstanceRequestId"
|
|
43
|
+
// || "WaitingForSpotInstanceId" || "WaitingForInstanceId" || "PreInService"
|
|
44
|
+
// || "InProgress" || "WaitingForELBConnectionDraining" || "MidLifecycleAction"
|
|
45
|
+
// || "WaitingForInstanceWarmup" || "Successful" || "Failed" || "Cancelled"
|
|
46
|
+
// || "WaitingForConnectionDraining",
|
|
47
|
+
// StatusMessage: "STRING_VALUE",
|
|
48
|
+
// Progress: Number("int"),
|
|
49
|
+
// Details: "STRING_VALUE",
|
|
50
|
+
// AutoScalingGroupState: "STRING_VALUE",
|
|
51
|
+
// AutoScalingGroupARN: "STRING_VALUE",
|
|
52
|
+
// }
|
|
53
|
+
// ]
|
|
54
|
+
// }
|
|
55
|
+
closure detachVM(instanceID, local error, ascSdk, params, asclient, response) {
|
|
56
|
+
`(error, ascSdk) = await(js.i("@aws-sdk/client-auto-scaling"))
|
|
57
|
+
if error
|
|
58
|
+
return (list(error))
|
|
59
|
+
params = { }
|
|
60
|
+
params.region = creds.region
|
|
61
|
+
params.credentials = {
|
|
62
|
+
accessKeyId: creds.keyID
|
|
63
|
+
secretAccessKey: creds.keySecret
|
|
64
|
+
}
|
|
65
|
+
if creds.endpoint
|
|
66
|
+
params.endpoint = creds.endpoint
|
|
67
|
+
asclient = xnew(ascSdk.AutoScalingClient, params)
|
|
68
|
+
`(error, response) = await(asclient.send(xnew(ascSdk.DetachInstancesCommand, {
|
|
69
|
+
AutoScalingGroupName: creds.ec2AsgName
|
|
70
|
+
InstanceIds: [ instanceID ]
|
|
71
|
+
ShouldDecrementDesiredCapacity: false
|
|
72
|
+
})))
|
|
73
|
+
list(error, response)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// terminateVM
|
|
77
|
+
//
|
|
78
|
+
// Tell AWS to terminate a VM, ours in point of fact. Returns a tuple with:
|
|
79
|
+
// {
|
|
80
|
+
// TerminatingInstances: [
|
|
81
|
+
// {
|
|
82
|
+
// InstanceId: "STRING_VALUE"
|
|
83
|
+
// CurrentState: { // InstanceState
|
|
84
|
+
// Code: Number("int"),
|
|
85
|
+
// Name: "pending" || "running" || "shutting-down" || "terminated"
|
|
86
|
+
// || "stopping" || "stopped",
|
|
87
|
+
// }
|
|
88
|
+
// PreviousState: {
|
|
89
|
+
// Code: Number("int"),
|
|
90
|
+
// Name: "pending" || "running" || "shutting-down" || "terminated"
|
|
91
|
+
// || "stopping" || "stopped",
|
|
92
|
+
// }
|
|
93
|
+
// }
|
|
94
|
+
// ]
|
|
95
|
+
// }
|
|
96
|
+
//
|
|
97
|
+
closure terminateVM(instanceID, local error, ec2sdk, params, ec2client, response) {
|
|
98
|
+
`(error, ec2sdk) = await(js.i("@aws-sdk/client-ec2"))
|
|
99
|
+
if error
|
|
100
|
+
return (list(error))
|
|
101
|
+
params = { }
|
|
102
|
+
params.region = creds.region
|
|
103
|
+
params.credentials = {
|
|
104
|
+
accessKeyId: creds.keyID
|
|
105
|
+
secretAccessKey: creds.keySecret
|
|
106
|
+
}
|
|
107
|
+
if creds.endpoint
|
|
108
|
+
params.endpoint = creds.endpoint
|
|
109
|
+
ec2client = xnew(ec2sdk.EC2Client, params)
|
|
110
|
+
`(error, response) = await(ec2client.send(xnew(ec2sdk.TerminateInstancesCommand,
|
|
111
|
+
{ InstanceIds: [ instanceID ] })))
|
|
112
|
+
list(error, response)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// getHostname
|
|
116
|
+
//
|
|
117
|
+
// Ask the VM for its FQDN.
|
|
118
|
+
//
|
|
119
|
+
// TOKEN="`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`"
|
|
120
|
+
// curl -H "X-aws-ec2-metadata-token: $TOKEN" 'http://169.254.169.254/latest/meta-data/hostname'
|
|
121
|
+
// => ip-172-31-80-96.ec2.internal
|
|
122
|
+
//
|
|
123
|
+
dokon.getHostname = closure getHostname(local error, token, hostname) {
|
|
124
|
+
`(error, token) = http.HttpRequest("http://169.254.169.254/latest/api/token", {
|
|
125
|
+
headers: {
|
|
126
|
+
"X-aws-ec2-metadata-token-ttl-seconds": "2160"
|
|
127
|
+
}
|
|
128
|
+
method: "PUT"
|
|
129
|
+
})
|
|
130
|
+
if !error
|
|
131
|
+
`(error, hostname) = http.HttpRequest("http://169.254.169.254/latest/meta-data/hostname", {
|
|
132
|
+
headers: {
|
|
133
|
+
"X-aws-ec2-metadata-token": token
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
if error
|
|
137
|
+
list(error)
|
|
138
|
+
else
|
|
139
|
+
list(false, hostname)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// vmShutdown
|
|
143
|
+
//
|
|
144
|
+
// Get our AWS EC2 VM instance ID and then request that it be shut down.
|
|
145
|
+
//
|
|
146
|
+
// How to get auth token:
|
|
147
|
+
// curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
|
|
148
|
+
// => AQAEAJHVrb_zRdju_A3tYBJLtKHsjej5GFVsDu1VuBCErP8MNVOr0Q==
|
|
149
|
+
//
|
|
150
|
+
// How to get instance-id:
|
|
151
|
+
// curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id
|
|
152
|
+
// => i-008dc3846da316a93
|
|
153
|
+
//
|
|
154
|
+
|
|
155
|
+
dokon.shutdown = closure shutdown(local error, token, instanceID, response) {
|
|
156
|
+
`(error, token) = http.HttpRequest("http://169.254.169.254/latest/api/token", {
|
|
157
|
+
headers: {
|
|
158
|
+
"X-aws-ec2-metadata-token-ttl-seconds": "2160"
|
|
159
|
+
}
|
|
160
|
+
method: "PUT"
|
|
161
|
+
})
|
|
162
|
+
if !error
|
|
163
|
+
`(error, instanceID) = http.HttpRequest("http://169.254.169.254/latest/meta-data/instance-id", {
|
|
164
|
+
headers: {
|
|
165
|
+
"X-aws-ec2-metadata-token": token
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
if !error
|
|
169
|
+
`(error, response) = detachVM(instanceID)
|
|
170
|
+
if !error
|
|
171
|
+
`(error, response) = terminateVM(instanceID)
|
|
172
|
+
if error
|
|
173
|
+
list(error)
|
|
174
|
+
else
|
|
175
|
+
list(false, response)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// finis
|
|
179
|
+
dokon
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* vmConInit
|
|
185
|
+
*
|
|
186
|
+
* Initialize the component.
|
|
187
|
+
*
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
function vmConInit(local manifest) {
|
|
191
|
+
manifest = `(VmControl, vmConInit)
|
|
192
|
+
Naan.module.build(module.id, "aws_vm", function(modobj, compobj) {
|
|
193
|
+
require("./serviceAws.nlg")
|
|
194
|
+
compobj.manifest = manifest
|
|
195
|
+
http = require("naanlib:frameworks/node/http_request.nlg")
|
|
196
|
+
module.exports.VmControl = VmControl
|
|
197
|
+
})
|
|
198
|
+
} ();
|
|
@@ -83,6 +83,7 @@ closure GcApi(options, local gcapi, cacert, renew, access_token, expires_at) {
|
|
|
83
83
|
`(error, data, status) = https.HttpsApiRequest(url, {
|
|
84
84
|
putdata: put || undefined
|
|
85
85
|
cacert: cacert || undefined
|
|
86
|
+
debug: options.debug || undefined
|
|
86
87
|
headers: [
|
|
87
88
|
`(Accept, "application/json"),
|
|
88
89
|
list(`Authorization, "Bearer ".concat(access_token))
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* serviceNexara.nlg
|
|
3
|
+
* serviceNexara
|
|
4
|
+
*
|
|
5
|
+
* Nexara speech recognition.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2025 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* senexInit
|
|
16
|
+
*
|
|
17
|
+
* Initialize the module.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function senexInit(local manifest) {
|
|
22
|
+
manifest = `(senexInit)
|
|
23
|
+
Naan.module.build(module.id, "serviceNexara", function(modobj, compobj) {
|
|
24
|
+
compobj.manifest = manifest
|
|
25
|
+
require("naanlib:frameworks/common").LiveImport()
|
|
26
|
+
nodeLib = require("naanlib:plugins/nodeLib/node_util.nlg")
|
|
27
|
+
if js.g
|
|
28
|
+
https = require("naanlib:frameworks/node/https_request.nlg")
|
|
29
|
+
else
|
|
30
|
+
https = require("naanlib:frameworks/browser/https_request.nlg")
|
|
31
|
+
})
|
|
32
|
+
} ();
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* speechrec.nlg
|
|
3
|
+
* Nexara
|
|
4
|
+
*
|
|
5
|
+
* Access to Nexara speech to text recognition.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2025 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* NexaraSpeechRec
|
|
16
|
+
*
|
|
17
|
+
* Speech recognition object.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
closure NexaraSpeechRec(local nxspr) {
|
|
22
|
+
global(https, nodeLib)
|
|
23
|
+
nxspr = new(object, this)
|
|
24
|
+
nxspr.recogURL = "https://api.nexara.ru/api/v1/audio/transcriptions"
|
|
25
|
+
|
|
26
|
+
// login
|
|
27
|
+
//
|
|
28
|
+
// creds:
|
|
29
|
+
// {
|
|
30
|
+
// apiKey: <string> // e.g. nx-xxxxx
|
|
31
|
+
// }
|
|
32
|
+
//
|
|
33
|
+
nxspr.login = closure login(creds) {
|
|
34
|
+
nxspr.creds = creds
|
|
35
|
+
list(false, { ok: true })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// recog
|
|
39
|
+
//
|
|
40
|
+
// Recognize text from the specified audio.
|
|
41
|
+
//
|
|
42
|
+
// Available options are:
|
|
43
|
+
// {
|
|
44
|
+
// language: <string> // optional: e.g. "ru-RU", "en-US"
|
|
45
|
+
// model: <string> // optional: e.g. "whisper-1"
|
|
46
|
+
// debug: <boolean> // extra debug logging for https request
|
|
47
|
+
// }
|
|
48
|
+
//
|
|
49
|
+
// This returns an Operation object containing the ID of the asynchronous recognition operation:
|
|
50
|
+
// {
|
|
51
|
+
// done: true // recognition complete
|
|
52
|
+
// createdAt: <date> // when operation started
|
|
53
|
+
// modifiedAt: <date> // last modified
|
|
54
|
+
// response: {
|
|
55
|
+
// "@type": <string> // gRPC type
|
|
56
|
+
// chunks: <array> // array of chunks
|
|
57
|
+
// }
|
|
58
|
+
// }
|
|
59
|
+
//
|
|
60
|
+
nxspr.recog = closure recog(audioBlob, options, local start, params, form, error, data) {
|
|
61
|
+
start = Date()
|
|
62
|
+
params = {
|
|
63
|
+
language: options.language.substring(0,2) || undefined
|
|
64
|
+
model: options.model || "whisper-1"
|
|
65
|
+
response_format: "verbose_json",
|
|
66
|
+
timestamp_granularities: ["word", "segment"]
|
|
67
|
+
file: audioBlob
|
|
68
|
+
}
|
|
69
|
+
form = nodeLib.MultipartBody(params)
|
|
70
|
+
`(error, data) = https.HttpsApiRequest(nxspr.recogURL, {
|
|
71
|
+
method: "POST"
|
|
72
|
+
putdata: form.body
|
|
73
|
+
headers: [
|
|
74
|
+
list("Authorization", "Bearer ".concat(nxspr.creds.apiKey))
|
|
75
|
+
list("Content-Type", form.headers."Content-Type")
|
|
76
|
+
list("Content-Length", form.headers."Content-Length")
|
|
77
|
+
]
|
|
78
|
+
debug: options.debug || undefined
|
|
79
|
+
})
|
|
80
|
+
if error
|
|
81
|
+
list(Error("NexaraSpeechRec.recog failed:", error))
|
|
82
|
+
else if !(data.segments.length > 0) {
|
|
83
|
+
debuglog("NexaraSpeechRec.recog: no valid segments returned.")
|
|
84
|
+
printline("Nexara response:", Dialect.print(new(data)))
|
|
85
|
+
list(Error("NexaraSpeechRec.recog empty result"))
|
|
86
|
+
} else {
|
|
87
|
+
if options.dump
|
|
88
|
+
printline(Dialect.print(new(data)))
|
|
89
|
+
list(false, {
|
|
90
|
+
done: true
|
|
91
|
+
createdAt: start
|
|
92
|
+
modifiedAt: Date()
|
|
93
|
+
response: nexaraToYCSK(data)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// nexaraToYCSK
|
|
99
|
+
//
|
|
100
|
+
// Convert a Nexara response to a Yandex SpeechKit structure. Hey, it was first.
|
|
101
|
+
//
|
|
102
|
+
function nexaraToYCSK(resp,
|
|
103
|
+
local output, wordx, segment, chunk, word, wordet) {
|
|
104
|
+
output = {
|
|
105
|
+
\@type: "type.googleapis.com/yandex.cloud.ai.stt.v2.LongRunningRecognitionResponse"
|
|
106
|
+
chunks: []
|
|
107
|
+
language: resp.language
|
|
108
|
+
}
|
|
109
|
+
wordx = 0
|
|
110
|
+
for segment in resp.segments {
|
|
111
|
+
if segment.text == ""
|
|
112
|
+
continue // empty segment
|
|
113
|
+
chunk = {
|
|
114
|
+
channelTag: "1"
|
|
115
|
+
alternatives: [
|
|
116
|
+
{
|
|
117
|
+
words: []
|
|
118
|
+
text: segment.text
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
for word in segment.text.split(" ") {
|
|
123
|
+
wordet = resp.words[wordx++]
|
|
124
|
+
if wordet.word != word {
|
|
125
|
+
debuglog("nexaraToYCSK: expected ${word} but got ${wordet.word}")
|
|
126
|
+
return (false)
|
|
127
|
+
}
|
|
128
|
+
chunk.alternatives.0.words.push({
|
|
129
|
+
word: wordet.word
|
|
130
|
+
startTime: wordet.start.toFixed(3).concat("s")
|
|
131
|
+
endTime: wordet.end.toFixed(3).concat("s")
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
output.chunks.push(chunk)
|
|
135
|
+
}
|
|
136
|
+
output
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// finis
|
|
140
|
+
nxspr
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
* nexaraInit
|
|
146
|
+
*
|
|
147
|
+
* Initialize the module.
|
|
148
|
+
*
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
function nexaraInit(local manifest) {
|
|
152
|
+
manifest = `(NexaraSpeechRec, nexaraInit)
|
|
153
|
+
|
|
154
|
+
Naan.module.build(module.id, "speechrec", function(modobj, compobj) {
|
|
155
|
+
require("./serviceNexara.nlg")
|
|
156
|
+
compobj.manifest = manifest
|
|
157
|
+
modobj.exports.NexaraSpeechRec = NexaraSpeechRec
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
} ();
|
|
@@ -50,9 +50,8 @@ function seycInit(local manifest) {
|
|
|
50
50
|
Naan.module.build(module.id, "serviceYC", function(modobj, compobj) {
|
|
51
51
|
compobj.manifest = manifest
|
|
52
52
|
require("naanlib:frameworks/common").LiveImport()
|
|
53
|
-
if js.g
|
|
54
|
-
|
|
55
|
-
https = require("naanlib:frameworks/node/https_request.nlg") }
|
|
53
|
+
if js.g
|
|
54
|
+
https = require("naanlib:frameworks/node/https_request.nlg")
|
|
56
55
|
else
|
|
57
56
|
https = require("naanlib:frameworks/browser/https_request.nlg")
|
|
58
57
|
module.exports.YcRequest = YcRequest
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2024 by Richard C. Zulch
|
|
9
|
+
* Copyright (c) 2024-2025 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
*
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
closure YCSpeechRec(
|
|
21
|
+
closure YCSpeechRec(local ycspr) {
|
|
22
|
+
global(https)
|
|
22
23
|
ycspr = new(object, this)
|
|
23
24
|
ycspr.recogURL = "https://transcribe.api.cloud.yandex.net/speech/stt/v2/longRunningRecognize"
|
|
24
25
|
ycspr.checkURL = "https://operation.api.cloud.yandex.net/operations/"
|
|
@@ -55,7 +56,7 @@ closure YCSpeechRec(options, local ycspr) {
|
|
|
55
56
|
// }
|
|
56
57
|
// }
|
|
57
58
|
//
|
|
58
|
-
ycspr.startRecog = closure startRecog(audioURI, options, local error, data) {
|
|
59
|
+
ycspr.startRecog = closure startRecog(audioURI, options, local params, error, data) {
|
|
59
60
|
params = {
|
|
60
61
|
config: {
|
|
61
62
|
specification: {
|