@translated/lara 1.7.1 → 1.7.3
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/lib/net/browser-client.d.ts +1 -0
- package/lib/net/browser-client.js +77 -0
- package/lib/net/client.d.ts +3 -0
- package/lib/net/client.js +50 -0
- package/lib/net/node-client.d.ts +1 -0
- package/lib/net/node-client.js +131 -0
- package/lib/translator.d.ts +2 -1
- package/lib/translator.js +13 -3
- package/lib/utils/sdk-version.d.ts +1 -1
- package/lib/utils/sdk-version.js +1 -1
- package/package.json +1 -1
|
@@ -4,5 +4,6 @@ export declare class BrowserLaraClient extends LaraClient {
|
|
|
4
4
|
private readonly baseUrl;
|
|
5
5
|
constructor(baseUrl: BaseURL, accessKeyId: string, accessKeySecret: string);
|
|
6
6
|
protected send(path: string, headers: Record<string, string>, body?: Record<string, any>): Promise<ClientResponse>;
|
|
7
|
+
protected sendAndGetStream(path: string, headers: Record<string, string>, body?: Record<string, any>): AsyncGenerator<ClientResponse>;
|
|
7
8
|
protected wrapMultiPartFile(file: MultiPartFile): File;
|
|
8
9
|
}
|
|
@@ -53,6 +53,83 @@ class BrowserLaraClient extends client_1.LaraClient {
|
|
|
53
53
|
}
|
|
54
54
|
return { statusCode: response.status, body: await response.json() };
|
|
55
55
|
}
|
|
56
|
+
async *sendAndGetStream(path, headers, body) {
|
|
57
|
+
let requestBody;
|
|
58
|
+
if (body) {
|
|
59
|
+
if (headers["Content-Type"] === "multipart/form-data") {
|
|
60
|
+
delete headers["Content-Type"]; // browser will set it automatically
|
|
61
|
+
const formBody = new FormData();
|
|
62
|
+
for (const [key, value] of Object.entries(body)) {
|
|
63
|
+
if (!value)
|
|
64
|
+
continue;
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
for (const v of value)
|
|
67
|
+
formBody.append(key, v);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
formBody.append(key, value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
requestBody = formBody;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
requestBody = JSON.stringify(body, undefined, 0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const response = await fetch(this.baseUrl + path, {
|
|
80
|
+
headers,
|
|
81
|
+
method: "POST",
|
|
82
|
+
body: requestBody
|
|
83
|
+
});
|
|
84
|
+
if (!response.body) {
|
|
85
|
+
throw new Error("Response body is not available for streaming");
|
|
86
|
+
}
|
|
87
|
+
const reader = response.body.getReader();
|
|
88
|
+
const decoder = new TextDecoder();
|
|
89
|
+
let buffer = "";
|
|
90
|
+
try {
|
|
91
|
+
while (true) {
|
|
92
|
+
const readResult = await reader.read();
|
|
93
|
+
const { done, value } = readResult;
|
|
94
|
+
if (done) {
|
|
95
|
+
// Process any remaining data in buffer
|
|
96
|
+
if (buffer.trim()) {
|
|
97
|
+
try {
|
|
98
|
+
const json = JSON.parse(buffer);
|
|
99
|
+
yield {
|
|
100
|
+
statusCode: json.status || response.status,
|
|
101
|
+
body: json.data || json
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (_e) {
|
|
105
|
+
// Skip invalid JSON
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
buffer += decoder.decode(value, { stream: true });
|
|
111
|
+
const lines = buffer.split("\n");
|
|
112
|
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
if (line.trim()) {
|
|
115
|
+
try {
|
|
116
|
+
const json = JSON.parse(line);
|
|
117
|
+
yield {
|
|
118
|
+
statusCode: json.status || response.status,
|
|
119
|
+
body: json.data || json
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (_e) {
|
|
123
|
+
// Skip invalid JSON lines
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
reader.releaseLock();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
56
133
|
wrapMultiPartFile(file) {
|
|
57
134
|
if (file instanceof File)
|
|
58
135
|
return file;
|
package/lib/net/client.d.ts
CHANGED
|
@@ -25,10 +25,13 @@ export declare abstract class LaraClient {
|
|
|
25
25
|
get<T>(path: string, params?: Record<string, any>, headers?: Record<string, string>): Promise<T>;
|
|
26
26
|
delete<T>(path: string, params?: Record<string, any>, headers?: Record<string, string>): Promise<T>;
|
|
27
27
|
post<T>(path: string, body?: Record<string, any>, files?: Record<string, MultiPartFile>, headers?: Record<string, string>): Promise<T>;
|
|
28
|
+
postAndGetStream<T>(path: string, body?: Record<string, any>, files?: Record<string, MultiPartFile>, headers?: Record<string, string>): AsyncGenerator<T>;
|
|
28
29
|
put<T>(path: string, body?: Record<string, any>, files?: Record<string, MultiPartFile>, headers?: Record<string, string>): Promise<T>;
|
|
30
|
+
protected requestStream<T>(method: HttpMethod, path: string, body?: Record<string, any>, files?: Record<string, MultiPartFile>, headers?: Record<string, string>): AsyncGenerator<T>;
|
|
29
31
|
protected request<T>(method: HttpMethod, path: string, body?: Record<string, any>, files?: Record<string, MultiPartFile>, headers?: Record<string, string>): Promise<T>;
|
|
30
32
|
private sign;
|
|
31
33
|
protected abstract send(path: string, headers: Record<string, string>, body?: Record<string, any>): Promise<ClientResponse>;
|
|
34
|
+
protected abstract sendAndGetStream(path: string, headers: Record<string, string>, body?: Record<string, any>): AsyncGenerator<ClientResponse>;
|
|
32
35
|
protected abstract wrapMultiPartFile(file: MultiPartFile): any;
|
|
33
36
|
}
|
|
34
37
|
export {};
|
package/lib/net/client.js
CHANGED
|
@@ -49,9 +49,59 @@ class LaraClient {
|
|
|
49
49
|
post(path, body, files, headers) {
|
|
50
50
|
return this.request("POST", path, body, files, headers);
|
|
51
51
|
}
|
|
52
|
+
async *postAndGetStream(path, body, files, headers) {
|
|
53
|
+
for await (const chunk of this.requestStream("POST", path, body, files, headers)) {
|
|
54
|
+
yield chunk;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
52
57
|
put(path, body, files, headers) {
|
|
53
58
|
return this.request("PUT", path, body, files, headers);
|
|
54
59
|
}
|
|
60
|
+
async *requestStream(method, path, body, files, headers) {
|
|
61
|
+
if (!path.startsWith("/"))
|
|
62
|
+
path = `/${path}`;
|
|
63
|
+
const _headers = {
|
|
64
|
+
"X-HTTP-Method-Override": method,
|
|
65
|
+
"X-Lara-Date": new Date().toUTCString(),
|
|
66
|
+
"X-Lara-SDK-Name": "lara-node",
|
|
67
|
+
"X-Lara-SDK-Version": sdk_version_1.version,
|
|
68
|
+
...this.extraHeaders,
|
|
69
|
+
...headers
|
|
70
|
+
};
|
|
71
|
+
if (body) {
|
|
72
|
+
body = Object.fromEntries(Object.entries(body).filter(([_, v]) => v !== undefined && v !== null));
|
|
73
|
+
if (Object.keys(body).length === 0)
|
|
74
|
+
body = undefined;
|
|
75
|
+
if (body) {
|
|
76
|
+
const jsonBody = JSON.stringify(body, undefined, 0);
|
|
77
|
+
_headers["Content-MD5"] = await this.crypto.digest(jsonBody);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let requestBody;
|
|
81
|
+
if (files) {
|
|
82
|
+
// validate files
|
|
83
|
+
for (const [key, file] of Object.entries(files))
|
|
84
|
+
files[key] = this.wrapMultiPartFile(file);
|
|
85
|
+
_headers["Content-Type"] = "multipart/form-data";
|
|
86
|
+
requestBody = Object.assign({}, files, body);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
_headers["Content-Type"] = "application/json";
|
|
90
|
+
if (body)
|
|
91
|
+
requestBody = body;
|
|
92
|
+
}
|
|
93
|
+
const signature = await this.sign(method, path, _headers);
|
|
94
|
+
_headers.Authorization = `Lara ${this.accessKeyId}:${signature}`;
|
|
95
|
+
for await (const chunk of this.sendAndGetStream(path, _headers, requestBody)) {
|
|
96
|
+
if (200 <= chunk.statusCode && chunk.statusCode < 300) {
|
|
97
|
+
yield parseContent(chunk.body.content);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const error = chunk.body.error || {};
|
|
101
|
+
throw new errors_1.LaraApiError(chunk.statusCode, error.type || "UnknownError", error.message || "An unknown error occurred");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
55
105
|
async request(method, path, body, files, headers) {
|
|
56
106
|
if (!path.startsWith("/"))
|
|
57
107
|
path = `/${path}`;
|
package/lib/net/node-client.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export declare class NodeLaraClient extends LaraClient {
|
|
|
6
6
|
private readonly agent;
|
|
7
7
|
constructor(baseUrl: BaseURL, accessKeyId: string, accessKeySecret: string, keepAlive?: boolean);
|
|
8
8
|
protected send(path: string, headers: Record<string, string>, body?: Record<string, any>): Promise<ClientResponse>;
|
|
9
|
+
protected sendAndGetStream(path: string, headers: Record<string, string>, body?: Record<string, any>): AsyncGenerator<ClientResponse>;
|
|
9
10
|
protected wrapMultiPartFile(file: MultiPartFile): Readable;
|
|
10
11
|
}
|
package/lib/net/node-client.js
CHANGED
|
@@ -101,6 +101,137 @@ class NodeLaraClient extends client_1.LaraClient {
|
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
+
async *sendAndGetStream(path, headers, body) {
|
|
105
|
+
let requestBody;
|
|
106
|
+
if (body) {
|
|
107
|
+
if (headers["Content-Type"] === "multipart/form-data") {
|
|
108
|
+
const formBody = new form_data_1.default();
|
|
109
|
+
for (const [key, value] of Object.entries(body)) {
|
|
110
|
+
if (!value)
|
|
111
|
+
continue;
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
for (const v of value)
|
|
114
|
+
formBody.append(key, v);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
formBody.append(key, value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
headers = {
|
|
121
|
+
...headers,
|
|
122
|
+
...formBody.getHeaders()
|
|
123
|
+
};
|
|
124
|
+
requestBody = formBody;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
requestBody = JSON.stringify(body, undefined, 0);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const options = {
|
|
131
|
+
host: this.baseUrl.hostname,
|
|
132
|
+
port: this.baseUrl.port,
|
|
133
|
+
path: path,
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: headers,
|
|
136
|
+
agent: this.agent
|
|
137
|
+
};
|
|
138
|
+
// Create async iterator from the stream
|
|
139
|
+
const chunks = [];
|
|
140
|
+
let resolveChunk = null;
|
|
141
|
+
let streamEnded = false;
|
|
142
|
+
let streamError = null;
|
|
143
|
+
const req = (this.baseUrl.secure ? node_https_1.default : node_http_1.default).request(options, (res) => {
|
|
144
|
+
let buffer = "";
|
|
145
|
+
res.on("data", (chunk) => {
|
|
146
|
+
buffer += chunk.toString();
|
|
147
|
+
const lines = buffer.split("\n");
|
|
148
|
+
// {a}\n{b}\n{c}\n --> [{a}, {b}, {c}, ""]
|
|
149
|
+
// {a}\n{b}\n{c} --> [{a}, {b}, {c}]
|
|
150
|
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
if (line.trim()) {
|
|
153
|
+
try {
|
|
154
|
+
const json = JSON.parse(line);
|
|
155
|
+
chunks.push({
|
|
156
|
+
statusCode: json.status || res.statusCode,
|
|
157
|
+
body: json.data || json
|
|
158
|
+
});
|
|
159
|
+
if (resolveChunk) {
|
|
160
|
+
resolveChunk();
|
|
161
|
+
resolveChunk = null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (_e) {
|
|
165
|
+
// Skip invalid JSON lines
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
res.on("end", () => {
|
|
171
|
+
// Process any remaining data in buffer
|
|
172
|
+
if (buffer.trim()) {
|
|
173
|
+
try {
|
|
174
|
+
const json = JSON.parse(buffer);
|
|
175
|
+
chunks.push({
|
|
176
|
+
statusCode: json.status || res.statusCode,
|
|
177
|
+
body: json.data || json
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (_e) {
|
|
181
|
+
// Skip invalid JSON
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
streamEnded = true;
|
|
185
|
+
if (resolveChunk) {
|
|
186
|
+
resolveChunk();
|
|
187
|
+
resolveChunk = null;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
res.on("error", (err) => {
|
|
191
|
+
req.destroy();
|
|
192
|
+
streamError = err;
|
|
193
|
+
if (resolveChunk) {
|
|
194
|
+
resolveChunk();
|
|
195
|
+
resolveChunk = null;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
req.on("error", (err) => {
|
|
200
|
+
streamError = err;
|
|
201
|
+
if (resolveChunk) {
|
|
202
|
+
resolveChunk();
|
|
203
|
+
resolveChunk = null;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
if (requestBody instanceof form_data_1.default) {
|
|
207
|
+
requestBody.pipe(req);
|
|
208
|
+
}
|
|
209
|
+
else if (requestBody) {
|
|
210
|
+
req.write(requestBody);
|
|
211
|
+
req.end();
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
req.end();
|
|
215
|
+
}
|
|
216
|
+
// Yield chunks as they arrive
|
|
217
|
+
while (true) {
|
|
218
|
+
if (streamError) {
|
|
219
|
+
throw streamError;
|
|
220
|
+
}
|
|
221
|
+
if (chunks.length > 0) {
|
|
222
|
+
yield chunks.shift();
|
|
223
|
+
}
|
|
224
|
+
else if (streamEnded) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
// Wait for next chunk
|
|
229
|
+
await new Promise((resolve) => {
|
|
230
|
+
resolveChunk = resolve;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
104
235
|
wrapMultiPartFile(file) {
|
|
105
236
|
if (typeof file === "string")
|
|
106
237
|
file = node_fs_1.default.createReadStream(file);
|
package/lib/translator.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export type TranslateOptions = {
|
|
|
49
49
|
verbose?: boolean;
|
|
50
50
|
headers?: Record<string, string>;
|
|
51
51
|
style?: TranslationStyle;
|
|
52
|
+
reasoning?: boolean;
|
|
52
53
|
};
|
|
53
54
|
export type TranslationStyle = "faithful" | "fluid" | "creative";
|
|
54
55
|
export interface DetectResult {
|
|
@@ -62,6 +63,6 @@ export declare class Translator {
|
|
|
62
63
|
readonly glossaries: Glossaries;
|
|
63
64
|
constructor(credentials: Credentials, options?: TranslatorOptions);
|
|
64
65
|
getLanguages(): Promise<string[]>;
|
|
65
|
-
translate<T extends string | string[] | TextBlock[]>(text: T, source: string | null, target: string, options?: TranslateOptions): Promise<TextResult<T>>;
|
|
66
|
+
translate<T extends string | string[] | TextBlock[]>(text: T, source: string | null, target: string, options?: TranslateOptions, callback?: (partialResult: TextResult<T>) => void): Promise<TextResult<T>>;
|
|
66
67
|
detect(text: string | string[], hint?: string, passlist?: string[]): Promise<DetectResult>;
|
|
67
68
|
}
|
package/lib/translator.js
CHANGED
|
@@ -18,7 +18,7 @@ class Translator {
|
|
|
18
18
|
async getLanguages() {
|
|
19
19
|
return await this.client.get("/languages");
|
|
20
20
|
}
|
|
21
|
-
async translate(text, source, target, options) {
|
|
21
|
+
async translate(text, source, target, options, callback) {
|
|
22
22
|
const headers = {};
|
|
23
23
|
if (options === null || options === void 0 ? void 0 : options.headers) {
|
|
24
24
|
for (const [name, value] of Object.entries(options.headers)) {
|
|
@@ -28,7 +28,7 @@ class Translator {
|
|
|
28
28
|
if (options === null || options === void 0 ? void 0 : options.noTrace) {
|
|
29
29
|
headers["X-No-Trace"] = "true";
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
const response = this.client.postAndGetStream("/translate", {
|
|
32
32
|
q: text,
|
|
33
33
|
source,
|
|
34
34
|
target,
|
|
@@ -43,8 +43,18 @@ class Translator {
|
|
|
43
43
|
use_cache: options === null || options === void 0 ? void 0 : options.useCache,
|
|
44
44
|
cache_ttl: options === null || options === void 0 ? void 0 : options.cacheTTLSeconds,
|
|
45
45
|
verbose: options === null || options === void 0 ? void 0 : options.verbose,
|
|
46
|
-
style: options === null || options === void 0 ? void 0 : options.style
|
|
46
|
+
style: options === null || options === void 0 ? void 0 : options.style,
|
|
47
|
+
reasoning: options === null || options === void 0 ? void 0 : options.reasoning
|
|
47
48
|
}, undefined, headers);
|
|
49
|
+
let lastResult;
|
|
50
|
+
for await (const partial of response) {
|
|
51
|
+
if ((options === null || options === void 0 ? void 0 : options.reasoning) && callback)
|
|
52
|
+
callback(partial);
|
|
53
|
+
lastResult = partial;
|
|
54
|
+
}
|
|
55
|
+
if (!lastResult)
|
|
56
|
+
throw new Error("No translation result received.");
|
|
57
|
+
return lastResult;
|
|
48
58
|
}
|
|
49
59
|
async detect(text, hint, passlist) {
|
|
50
60
|
return await this.client.post("/detect", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "1.7.
|
|
1
|
+
export declare const version = "1.7.3";
|
package/lib/utils/sdk-version.js
CHANGED