@chispear/vibekit 0.1.0 → 0.1.1
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/README.md +8 -6
- package/dist/config.js +1 -1
- package/dist/router.js +42 -17
- package/examples/vibekit.config.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
## Instalasi
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install -g vibekit # npm
|
|
19
|
-
pnpm add -g vibekit # pnpm
|
|
20
|
-
npx vibekit ask "halo" # tanpa install, langsung coba
|
|
18
|
+
npm install -g @chispear/vibekit # npm
|
|
19
|
+
pnpm add -g @chispear/vibekit # pnpm
|
|
20
|
+
npx @chispear/vibekit ask "halo" # tanpa install, langsung coba
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
Setelah install global, perintahnya tetap `vibekit` (nama package-nya saja yang scoped).
|
|
24
|
+
|
|
23
25
|
Butuh Node.js >= 18.
|
|
24
26
|
|
|
25
27
|
## Mulai Cepat
|
|
@@ -34,7 +36,7 @@ Perintah ini membuat `~/.vibekit/config.json`:
|
|
|
34
36
|
{
|
|
35
37
|
"providers": [
|
|
36
38
|
{ "nama": "gpt", "baseUrl": "https://api.openai.com/v1", "apiKey": "", "model": "gpt-4o-mini", "anggaranHarian": 500000, "jedaCooldownMenit": 15 },
|
|
37
|
-
{ "nama": "gemini", "baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai/", "apiKey": "", "model": "gemini-
|
|
39
|
+
{ "nama": "gemini", "baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai/", "apiKey": "", "model": "gemini-3.8-flash", "jedaCooldownMenit": 15 },
|
|
38
40
|
{ "nama": "deepseek", "baseUrl": "https://api.deepseek.com/v1", "apiKey": "", "model": "deepseek-chat", "jedaCooldownMenit": 15 }
|
|
39
41
|
],
|
|
40
42
|
"gaya": { "jawaban": "poin-penting", "maksTokenJawaban": 800 }
|
|
@@ -68,7 +70,7 @@ vibekit status
|
|
|
68
70
|
|
|
69
71
|
1. Sebelum memanggil, router memeriksa state lokal (`~/.vibekit/state.json`): penyedia yang masih **cooldown** atau sudah mencapai **anggaran harian** dilewati tanpa dipanggil.
|
|
70
72
|
2. Saat memanggil, permintaan berisi system prompt hemat sesuai `gaya.jawaban` dan `max_tokens` = `gaya.maksTokenJawaban`. Objek `extraBody` per penyedia (bila ada) digabung ke body — berguna untuk kenop khas model, mis. menekan penalaran.
|
|
71
|
-
3. Bila respons menandakan kuota habis — HTTP **429/402** atau pola error `insufficient_quota` / `RESOURCE_EXHAUSTED` / saldo — penyedia ditandai **cooldown sampai akhir hari lokal** dan permintaan yang sama diulangi ke penyedia berikutnya. Rate-limit singkat (429 tanpa pola kuota) → cooldown `jedaCooldownMenit` (bawaan 15 menit).
|
|
73
|
+
3. Bila respons menandakan kuota habis — HTTP **429/402** atau pola error `insufficient_quota` / `RESOURCE_EXHAUSTED` / saldo — penyedia ditandai **cooldown sampai akhir hari lokal** dan permintaan yang sama diulangi ke penyedia berikutnya. Rate-limit singkat (429 tanpa pola kuota) → cooldown `jedaCooldownMenit` (bawaan 15 menit). Khusus HTTP 503 (server penyedia sedang sibuk/penuh sesaat), penyedia yang sama dicoba ulang dulu sampai 2 kali dengan jeda 3 dan 8 detik, dan 503 tidak memicu cooldown.
|
|
72
74
|
4. Error lain (401 kunci salah, 400, jaringan) **tidak** dianggap kuota habis: dicatat apa adanya dan router tetap mencoba penyedia berikutnya.
|
|
73
75
|
5. Token terpakai dicatat dari field `usage` respons (atau estimasi tokenizer bila tidak ada). Bila semua penyedia gagal/habis, Anda menerima ringkasan status tiap penyedia dan jam paling awal untuk mencoba lagi.
|
|
74
76
|
|
|
@@ -91,7 +93,7 @@ Keputusan repo: `dist/` masuk `.gitignore` dan dibangun saat publish (`prepublis
|
|
|
91
93
|
|
|
92
94
|
## Status Nama di npm
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
Package ini terbit di npm sebagai **`@chispear/vibekit`** (v0.1.0, 25 Oktober 2026) karena nama tak-scoped `vibekit` sudah dipakai proyek lain. Nama perintah CLI tetap `vibekit`.
|
|
95
97
|
|
|
96
98
|
## Lisensi
|
|
97
99
|
|
package/dist/config.js
CHANGED
package/dist/router.js
CHANGED
|
@@ -12,6 +12,9 @@ export function klasifikasiError(status, bodyTeks) {
|
|
|
12
12
|
if (status === 429) {
|
|
13
13
|
return { jenis: "rate-limit", ringkas: `terkena rate-limit (HTTP 429) ${potongan}`.trim() };
|
|
14
14
|
}
|
|
15
|
+
if (status === 503) {
|
|
16
|
+
return { jenis: "lainnya", ringkas: `server penyedia sedang sibuk/penuh (HTTP 503) ${potongan}`.trim() };
|
|
17
|
+
}
|
|
15
18
|
return { jenis: "lainnya", ringkas: `HTTP ${status} ${potongan}`.trim() };
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
@@ -20,12 +23,17 @@ export function klasifikasiError(status, bodyTeks) {
|
|
|
20
23
|
* - Kuota habis (429/402/pola kuota) → cooldown sampai akhir hari lokal, lalu permintaan
|
|
21
24
|
* yang sama diulangi ke penyedia berikutnya secara transparan.
|
|
22
25
|
* - Rate-limit singkat → cooldown jedaCooldownMenit (bawaan 15).
|
|
26
|
+
* - HTTP 503 (server penyedia sibuk sesaat) → penyedia yang sama dicoba ulang sampai
|
|
27
|
+
* 2 kali dengan jeda (3 dtk, 8 dtk) sebelum menyerah ke penyedia berikutnya;
|
|
28
|
+
* 503 tidak memicu cooldown.
|
|
23
29
|
* - Error lain (401, 400, jaringan) tidak dianggap kuota habis; dicatat dan lanjut ke berikutnya.
|
|
24
30
|
*/
|
|
25
31
|
export async function tanyaRouter(opsi) {
|
|
26
32
|
const fetchImpl = opsi.fetchImpl ?? fetch;
|
|
27
33
|
const sekarang = opsi.sekarang ?? new Date();
|
|
28
34
|
const timeoutMs = opsi.timeoutMs ?? 90_000;
|
|
35
|
+
const jedaRetry503 = opsi.jedaRetry503Ms ?? [3000, 8000];
|
|
36
|
+
const tunggu = opsi.tungguImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
29
37
|
const state = muatState(opsi.lokasiState);
|
|
30
38
|
const catatan = [];
|
|
31
39
|
const system = { role: "system", content: systemPromptHemat(opsi.config.gaya) };
|
|
@@ -80,32 +88,49 @@ export async function tanyaRouter(opsi) {
|
|
|
80
88
|
...(provider.extraBody ?? {}),
|
|
81
89
|
};
|
|
82
90
|
let respons;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
let teks = "";
|
|
92
|
+
let gagalJaringan = null;
|
|
93
|
+
let percobaan503 = 0;
|
|
94
|
+
for (;;) {
|
|
86
95
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
const kontroler = new AbortController();
|
|
97
|
+
const pengatur = setTimeout(() => kontroler.abort(), timeoutMs);
|
|
98
|
+
try {
|
|
99
|
+
respons = await fetchImpl(url, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
"Content-Type": "application/json",
|
|
103
|
+
Authorization: `Bearer ${apiKey}`,
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify(body),
|
|
106
|
+
signal: kontroler.signal,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
clearTimeout(pengatur);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
gagalJaringan = `gagal jaringan: ${e.message}`;
|
|
115
|
+
break;
|
|
96
116
|
}
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
teks = await respons.text();
|
|
118
|
+
// 503 = server penyedia sedang penuh sesaat: coba ulang penyedia yang sama
|
|
119
|
+
// dengan jeda sebelum menyerah ke penyedia berikutnya.
|
|
120
|
+
if (respons.status === 503 && percobaan503 < jedaRetry503.length) {
|
|
121
|
+
await tunggu(jedaRetry503[percobaan503]);
|
|
122
|
+
percobaan503++;
|
|
123
|
+
continue;
|
|
99
124
|
}
|
|
125
|
+
break;
|
|
100
126
|
}
|
|
101
|
-
|
|
102
|
-
const alasan =
|
|
127
|
+
if (gagalJaringan !== null || !respons) {
|
|
128
|
+
const alasan = gagalJaringan ?? "gagal jaringan";
|
|
103
129
|
entri.alasanTerakhir = alasan;
|
|
104
130
|
simpanState(opsi.lokasiState, state);
|
|
105
131
|
catatan.push({ nama: provider.nama, model: provider.model, status: "gagal", alasan });
|
|
106
132
|
continue;
|
|
107
133
|
}
|
|
108
|
-
const teks = await respons.text();
|
|
109
134
|
if (!respons.ok) {
|
|
110
135
|
const { jenis, ringkas } = klasifikasiError(respons.status, teks);
|
|
111
136
|
entri.alasanTerakhir = ringkas;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"gaya":{"jawaban":"poin-penting","maksTokenJawaban":800},"providers":[{"anggaranHarian":500000,"apiKey":"","baseUrl":"https://api.openai.com/v1","jedaCooldownMenit":15,"model":"gpt-4o-mini","nama":"gpt"},{"apiKey":"env:GEMINI_API_KEY","baseUrl":"https://generativelanguage.googleapis.com/v1beta/openai/","jedaCooldownMenit":15,"model":"gemini-
|
|
1
|
+
{"gaya":{"jawaban":"poin-penting","maksTokenJawaban":800},"providers":[{"anggaranHarian":500000,"apiKey":"","baseUrl":"https://api.openai.com/v1","jedaCooldownMenit":15,"model":"gpt-4o-mini","nama":"gpt"},{"apiKey":"env:GEMINI_API_KEY","baseUrl":"https://generativelanguage.googleapis.com/v1beta/openai/","jedaCooldownMenit":15,"model":"gemini-3.8-flash","nama":"gemini"},{"apiKey":"env:DEEPSEEK_API_KEY","baseUrl":"https://api.deepseek.com/v1","extraBody":{},"jedaCooldownMenit":15,"model":"deepseek-chat","nama":"deepseek"},{"apiKey":"env:OPENROUTER_API_KEY","baseUrl":"https://openrouter.ai/api/v1","model":"openai/gpt-4o-mini","nama":"openrouter"},{"apiKey":"ollama","baseUrl":"http://localhost:11434/v1","model":"llama3.1","nama":"ollama-lokal"}]}
|