@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 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-2.5-flash", "jedaCooldownMenit": 15 },
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
- Nama paket **`vibekit` di npm sudah terpakai** oleh proyek lain (per Oktober 2026). Sebelum publish, nama perlu diganti — mis. scoped package `@username/vibekit` atau nama turunan — keputusan ada pada pemilik repo. Nama perintah CLI tetap `vibekit`.
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
@@ -34,7 +34,7 @@ export const TEMPLATE_CONFIG = {
34
34
  nama: "gemini",
35
35
  baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai/",
36
36
  apiKey: "",
37
- model: "gemini-2.5-flash",
37
+ model: "gemini-3.8-flash",
38
38
  jedaCooldownMenit: 15,
39
39
  },
40
40
  {
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
- try {
84
- const kontroler = new AbortController();
85
- const pengatur = setTimeout(() => kontroler.abort(), timeoutMs);
91
+ let teks = "";
92
+ let gagalJaringan = null;
93
+ let percobaan503 = 0;
94
+ for (;;) {
86
95
  try {
87
- respons = await fetchImpl(url, {
88
- method: "POST",
89
- headers: {
90
- "Content-Type": "application/json",
91
- Authorization: `Bearer ${apiKey}`,
92
- },
93
- body: JSON.stringify(body),
94
- signal: kontroler.signal,
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
- finally {
98
- clearTimeout(pengatur);
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
- catch (e) {
102
- const alasan = `gagal jaringan: ${e.message}`;
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-2.5-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"}]}
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"}]}
package/package.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "test": "npm run build && node --test test/*.test.mjs"
29
29
  },
30
30
  "type": "module",
31
- "version": "0.1.0",
31
+ "version": "0.1.1",
32
32
  "publishConfig": {
33
33
  "access": "public"
34
34
  }