@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.
@@ -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;
@@ -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}`;
@@ -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
  }
@@ -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);
@@ -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
- return await this.client.post("/translate", {
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";
1
+ export declare const version = "1.7.3";
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
- exports.version = "1.7.1";
4
+ exports.version = "1.7.3";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@translated/lara",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "engines": {