kiosapi 0.1.16 → 0.1.18

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/dist/agent/run.js CHANGED
@@ -89,8 +89,27 @@ export function loadCheckpoint() {
89
89
  return null;
90
90
  try {
91
91
  const data = JSON.parse(readFileSync(CHECKPOINT_PATH, 'utf8'));
92
+ // Sanitize messages from older checkpoint formats:
93
+ // - Strip extra system messages (index > 0): pre-v0.1.17 auto-inject pushed a second
94
+ // {role:'system'} that some providers reject when they appear mid-conversation.
95
+ // - Null out content when tool_calls are present: strict OpenAI-compat providers (DeepSeek
96
+ // etc.) require content:null on assistant tool-call turns; old checkpoints stored the
97
+ // prefacing text alongside tool_calls which caused HTTP 400 on the next API call.
98
+ const [sys0, ...rest] = data.messages ?? [];
99
+ const sanitized = [
100
+ ...(sys0 ? [sys0] : []),
101
+ ...rest
102
+ .filter((m) => m.role !== 'system')
103
+ .map((m) => {
104
+ if (m.role === 'assistant' && m.tool_calls?.length) {
105
+ return { ...m, content: null };
106
+ }
107
+ return m;
108
+ }),
109
+ ];
92
110
  return {
93
111
  ...data,
112
+ messages: sanitized,
94
113
  teamModels: data.teamModels ?? {},
95
114
  totalTokens: data.totalTokens ?? 0,
96
115
  };
@@ -373,30 +392,41 @@ export function undoLastTurn(s) {
373
392
  * agent's final text (last answer or the `selesai` summary) — useful for chaining agents in a team.
374
393
  */
375
394
  export async function runTurn(s, userText) {
376
- // On the very first turn of a fresh session, auto-inject project metadata so the model
377
- // starts oriented without needing to call daftar_file(".") for basic orientation.
378
- // Injected: root directory tree + key manifest files (package.json, README, etc.).
395
+ // On the very first turn of a fresh session, append a compact project snapshot to the
396
+ // existing system message so the model starts oriented without calling daftar_file(".").
397
+ // DESIGN CONSTRAINTS:
398
+ // - APPEND, never push a second system message (some providers reject role:'system' at idx>0)
399
+ // - Keep total injected content small: free-tier models (Workers AI llama etc.) have 8K token
400
+ // context windows; a fat system message plus a few tool results fills it up fast, causing the
401
+ // upstream to return HTTP 400. Depth-2 tree (~1-2KB) is enough for high-level orientation.
379
402
  if (s.messages.filter((m) => m.role === 'user').length === 0) {
380
403
  const snippets = [];
404
+ // Depth 2 (not 3): shows top-level dirs + their immediate contents — enough to orient without
405
+ // producing 300 lines that consume half a small model's context window.
381
406
  try {
382
- snippets.push(`<root-directory>\n${daftarFile('.')}\n</root-directory>`);
407
+ snippets.push(`<root-directory>\n${daftarFile('.', 2)}\n</root-directory>`);
383
408
  }
384
409
  catch { /* ignore */ }
385
- for (const f of ['package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod', 'README.md']) {
410
+ for (const f of ['package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod']) {
386
411
  const abs = join(process.cwd(), f);
387
412
  if (existsSync(abs)) {
388
413
  try {
389
414
  const content = readFileSync(abs, 'utf8');
390
- snippets.push(`<file path="${f}">\n${content.slice(0, 4000)}\n</file>`);
415
+ // 1500 chars: enough for name/scripts/deps, not the full lockfile prose
416
+ snippets.push(`<file path="${f}">\n${content.slice(0, 1_500)}\n</file>`);
391
417
  }
392
418
  catch { /* unreadable — skip */ }
393
419
  }
394
420
  }
395
421
  if (snippets.length > 0) {
396
- s.messages.push({
397
- role: 'system',
398
- content: `## Konteks Proyek (auto-injected jangan panggil daftar_file(".") lagi)\n${snippets.join('\n\n')}`,
399
- });
422
+ const sysMsg = s.messages[0];
423
+ if (sysMsg?.role === 'system') {
424
+ // Hard cap: keep total injected context under 5KB so the system message stays manageable.
425
+ const joined = snippets.join('\n\n');
426
+ const capped = joined.length > 5_000 ? `${joined.slice(0, 5_000)}\n…` : joined;
427
+ sysMsg.content +=
428
+ `\n\n## Konteks Proyek\n${capped}`;
429
+ }
400
430
  }
401
431
  }
402
432
  s.messages.push({ role: 'user', content: userText });
@@ -452,7 +482,13 @@ export async function runTurn(s, userText) {
452
482
  // (null content + no tool_calls) can appear from a truncated stream or a reasoning-only
453
483
  // step; pushing it corrupts the history and causes providers to reject subsequent calls.
454
484
  if (reply.content !== null || calls.length > 0) {
455
- s.messages.push({ role: 'assistant', content: reply.content, tool_calls: reply.tool_calls });
485
+ // When tool_calls are present, set content to null regardless of what text the model
486
+ // prefaced the call with. The text was already streamed to the user via onText; keeping
487
+ // it in history causes strict OpenAI-compat providers (DeepSeek, Workers AI, etc.) to
488
+ // return HTTP 400 on the next call because they require content: null when tool_calls
489
+ // is non-empty. Anthropic also works correctly with null here.
490
+ const storedContent = calls.length > 0 ? null : reply.content;
491
+ s.messages.push({ role: 'assistant', content: storedContent, tool_calls: reply.tool_calls });
456
492
  }
457
493
  if (reply.content)
458
494
  lastText = reply.content;
@@ -519,11 +555,19 @@ export async function runTurn(s, userText) {
519
555
  continue;
520
556
  }
521
557
  const result = await runTool(call, s.otomatis, s.model);
522
- s.messages.push({ role: 'tool', content: result.output, tool_call_id: call.id });
558
+ // Cap what goes into the conversation history: full output can be 300 lines of directory
559
+ // listing or 100 KB of file content, which quickly overflows small-context-window models
560
+ // (Workers AI llama has ~8K token limit). The full result is already returned by runTool;
561
+ // truncating only the *stored* copy keeps the API payload manageable without losing info.
562
+ const MAX_STORED_RESULT = 5_000;
563
+ const stored = result.output.length > MAX_STORED_RESULT
564
+ ? `${result.output.slice(0, MAX_STORED_RESULT)}\n…[dipotong — gunakan path/range spesifik jika perlu lebih]`
565
+ : result.output;
566
+ s.messages.push({ role: 'tool', content: stored, tool_call_id: call.id });
523
567
  if (result.modifiedPath)
524
568
  stepModified.add(result.modifiedPath);
525
569
  if (READ_ONLY_TOOLS.has(call.function.name))
526
- toolCache.set(sig, result.output);
570
+ toolCache.set(sig, stored);
527
571
  if (result.done) {
528
572
  if (stepModified.size > 0)
529
573
  console.log(dim(` ✎ ${[...stepModified].join(' · ')}`));
@@ -22,10 +22,8 @@ const TOOLS = {
22
22
  properties: {
23
23
  path: { type: 'string', description: 'Path folder (default ".")' },
24
24
  kedalaman: {
25
- type: 'integer',
26
- description: 'Kedalaman tree (1–5). Default: 3 untuk ".", 1 untuk subfolder.',
27
- minimum: 1,
28
- maximum: 5,
25
+ type: 'number',
26
+ description: 'Kedalaman tree 1–5. Default: 3 untuk ".", 1 untuk subfolder.',
29
27
  },
30
28
  },
31
29
  },
@@ -200,7 +198,8 @@ Direktori kerja: ${process.cwd()}
200
198
  OS: ${osName} · ${shellNote}
201
199
  Aturan:
202
200
  - Bekerja langkah demi langkah: pakai tool untuk membaca sebelum mengubah.
203
- - Strategi eksplorasi: daftar_file(".") sudah tersedia di konteks awal — identifikasi file yang relevan LANGSUNG, lalu baca dengan baca_file. Jangan ulangi daftar_file pada path yang sama.
201
+ - Strategi eksplorasi: struktur root tersedia di konteks awal — identifikasi subfolder relevan LANGSUNG, lalu baca dengan baca_file. Jangan ulangi daftar_file pada path yang sama.
202
+ - Kedalaman daftar_file: gunakan kedalaman=2 (default) untuk subfolder besar. Kedalaman=3 hanya jika kamu sudah tahu folder itu kecil. JANGAN gunakan kedalaman=4+ kecuali diminta eksplisit.
204
203
  - JANGAN memanggil tool APAPUN dengan argumen identik lebih dari 1× dalam satu sesi. Jika tool mengembalikan peringatan cache "⚠", langsung ganti ke path atau argumen BERBEDA.
205
204
  - Path selalu relatif ke direktori kerja; akses ke luar ditolak.
206
205
  - Gunakan hapus_file/pindah_file untuk menghapus/memindahkan file (lebih aman dari jalankan del/rm).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiosapi",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "description": "CLI Kiosapi.id berbahasa Indonesia — bangun aplikasimu pakai API key Kiosapi (agen + multimodal).",
6
6
  "keywords": [