co-maintainer 0.5.1-beta.3 → 0.5.1-beta.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 +21 -21
- package/dist/package.json +1 -1
- package/dist/src/services/remote_review.js +15 -8
- package/package.json +1 -1
- package/dist/src/ai/hetzner.d.ts +0 -16
- package/dist/src/ai/hetzner.js +0 -69
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Murat Kirazkaya
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Murat Kirazkaya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/package.json
CHANGED
|
@@ -250,7 +250,12 @@ export function registerRemoteReviewHandler() {
|
|
|
250
250
|
.filter((name) => typeof name === "string" && name));
|
|
251
251
|
const useCodegraph = allowed.size > 0;
|
|
252
252
|
const options = reviewOptionsForRepo(job.repo, useCodegraph, byokKey);
|
|
253
|
-
|
|
253
|
+
// A BYOK client brings a key but not models, so a server with no
|
|
254
|
+
// models cannot run its review either.
|
|
255
|
+
if (!options.aiToken ||
|
|
256
|
+
options.ai === "none" ||
|
|
257
|
+
!options.lowModel ||
|
|
258
|
+
!options.highModel) {
|
|
254
259
|
throw new Error("server AI is not configured for remote review");
|
|
255
260
|
}
|
|
256
261
|
const billed = byokKey !== undefined ? "byok" : "server";
|
|
@@ -319,7 +324,12 @@ export function registerRemoteReviewHandler() {
|
|
|
319
324
|
codegraph: { state: result.codegraphState },
|
|
320
325
|
summary: summaryCounts(findings, blockingMode),
|
|
321
326
|
findings: findings.map((row) => toJsonFinding(row, blockingMode)),
|
|
322
|
-
usage: {
|
|
327
|
+
usage: {
|
|
328
|
+
tokensIn,
|
|
329
|
+
tokensOut,
|
|
330
|
+
...costUsage(costTally),
|
|
331
|
+
billedTo: billed,
|
|
332
|
+
},
|
|
323
333
|
remote: { jobId: job.id, reviewId, clientVersion: VERSION },
|
|
324
334
|
});
|
|
325
335
|
setReviewStatus(reviewId, "done", {
|
|
@@ -377,12 +387,9 @@ export function registerRemoteReviewHandler() {
|
|
|
377
387
|
dropByokKey(job.id);
|
|
378
388
|
if (!completed) {
|
|
379
389
|
// Reached on failure and on the aborted early return above.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
setReviewStatus(reviewId, "failed");
|
|
385
|
-
}
|
|
390
|
+
setReviewStatus(reviewId, signal.aborted ? "aborted" : "failed", {
|
|
391
|
+
billed_to: billedTo,
|
|
392
|
+
});
|
|
386
393
|
deleteRemoteReviewInput(job.id);
|
|
387
394
|
closeRemoteSession(job.id);
|
|
388
395
|
}
|
package/package.json
CHANGED
package/dist/src/ai/hetzner.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { AiProvider, AiRequest, AiResponse } from "../types.ts";
|
|
2
|
-
declare const sleep: (milliseconds: number) => Promise<void>;
|
|
3
|
-
export declare class HetznerProvider implements AiProvider {
|
|
4
|
-
readonly supportsTools = false;
|
|
5
|
-
private readonly endpoint;
|
|
6
|
-
private readonly apiKey;
|
|
7
|
-
private readonly model;
|
|
8
|
-
private readonly sleeper;
|
|
9
|
-
private nextRequest;
|
|
10
|
-
private spacing;
|
|
11
|
-
constructor(apiKey: string, model: string, endpoint?: string, sleeper?: typeof sleep);
|
|
12
|
-
complete(request: AiRequest): Promise<AiResponse>;
|
|
13
|
-
private waitForQuota;
|
|
14
|
-
private jitter;
|
|
15
|
-
}
|
|
16
|
-
export {};
|
package/dist/src/ai/hetzner.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { chatBody, parseChatResponse } from "./provider.js";
|
|
2
|
-
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
3
|
-
export class HetznerProvider {
|
|
4
|
-
supportsTools = false;
|
|
5
|
-
endpoint;
|
|
6
|
-
apiKey;
|
|
7
|
-
model;
|
|
8
|
-
sleeper;
|
|
9
|
-
nextRequest = 0;
|
|
10
|
-
spacing = 8_000;
|
|
11
|
-
constructor(apiKey, model, endpoint = "https://inference.hetzner.com/api/v1/chat/completions", sleeper = sleep) {
|
|
12
|
-
if (!apiKey)
|
|
13
|
-
throw new Error("Hetzner requires HETZNER_API_KEY");
|
|
14
|
-
if (!model)
|
|
15
|
-
throw new Error("Hetzner requires HETZNER_MODEL");
|
|
16
|
-
this.apiKey = apiKey;
|
|
17
|
-
this.model = model;
|
|
18
|
-
this.sleeper = sleeper;
|
|
19
|
-
this.endpoint = endpoint;
|
|
20
|
-
}
|
|
21
|
-
async complete(request) {
|
|
22
|
-
let attempt = 0;
|
|
23
|
-
for (;;) {
|
|
24
|
-
attempt++;
|
|
25
|
-
await this.waitForQuota();
|
|
26
|
-
const response = await fetch(this.endpoint, {
|
|
27
|
-
method: "POST",
|
|
28
|
-
headers: {
|
|
29
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
30
|
-
"Content-Type": "application/json",
|
|
31
|
-
},
|
|
32
|
-
body: JSON.stringify(chatBody(this.model, request)),
|
|
33
|
-
});
|
|
34
|
-
this.nextRequest = Date.now() + this.spacing;
|
|
35
|
-
if (response.ok) {
|
|
36
|
-
this.spacing = Math.max(8_000, this.spacing * 0.9);
|
|
37
|
-
return parseChatResponse((await response.json()), "hetzner", this.model);
|
|
38
|
-
}
|
|
39
|
-
if ([401, 403, 404].includes(response.status)) {
|
|
40
|
-
throw new Error(`Hetzner fatal ${response.status}: ${await response.text()}`);
|
|
41
|
-
}
|
|
42
|
-
if (response.status === 429) {
|
|
43
|
-
this.spacing = Math.min(180_000, this.spacing * 1.5);
|
|
44
|
-
const wait = this.jitter(this.spacing);
|
|
45
|
-
console.log(`[ai] hetzner 429 on attempt ${attempt}; retrying in ${Math.round(wait / 1000)}s`);
|
|
46
|
-
await this.sleeper(wait);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (response.status >= 500 || response.status === 408) {
|
|
50
|
-
const wait = this.jitter(Math.min(180_000, this.spacing * 1.5));
|
|
51
|
-
console.log(`[ai] hetzner ${response.status} on attempt ${attempt}; retrying in ${Math.round(wait / 1000)}s`);
|
|
52
|
-
await this.sleeper(wait);
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
throw new Error(`Hetzner ${response.status}: ${await response.text()}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async waitForQuota() {
|
|
59
|
-
const wait = this.nextRequest - Date.now();
|
|
60
|
-
if (wait > 0) {
|
|
61
|
-
const jittered = this.jitter(wait);
|
|
62
|
-
console.log(`[ai] hetzner quota spacing, waiting ${Math.round(jittered / 1000)}s`);
|
|
63
|
-
await this.sleeper(jittered);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
jitter(milliseconds) {
|
|
67
|
-
return Math.max(250, Math.round(milliseconds * (0.8 + Math.random() * 0.4)));
|
|
68
|
-
}
|
|
69
|
-
}
|