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 +57 -13
- package/dist/agent/schemas.js +4 -5
- package/package.json +1 -1
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,
|
|
377
|
-
// starts oriented without
|
|
378
|
-
//
|
|
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'
|
|
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
|
-
|
|
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
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
570
|
+
toolCache.set(sig, stored);
|
|
527
571
|
if (result.done) {
|
|
528
572
|
if (stepModified.size > 0)
|
|
529
573
|
console.log(dim(` ✎ ${[...stepModified].join(' · ')}`));
|
package/dist/agent/schemas.js
CHANGED
|
@@ -22,10 +22,8 @@ const TOOLS = {
|
|
|
22
22
|
properties: {
|
|
23
23
|
path: { type: 'string', description: 'Path folder (default ".")' },
|
|
24
24
|
kedalaman: {
|
|
25
|
-
type: '
|
|
26
|
-
description: 'Kedalaman tree
|
|
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:
|
|
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).
|