kiosapi 0.1.11 → 0.1.13
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 +27 -8
- package/package.json +1 -1
package/dist/agent/run.js
CHANGED
|
@@ -20,6 +20,8 @@ const MAX_CONTEXT_MESSAGES = 40;
|
|
|
20
20
|
*
|
|
21
21
|
* The kept slice always:
|
|
22
22
|
* - starts with the original system prompt
|
|
23
|
+
* - removes any assistant messages with null content and no tool_calls (invalid for providers;
|
|
24
|
+
* they appear when a stream ends prematurely or the model produces reasoning-only output)
|
|
23
25
|
* - contains at most MAX_CONTEXT_MESSAGES non-system messages
|
|
24
26
|
* - begins on a `user` turn boundary (never mid tool-call sequence, which providers reject)
|
|
25
27
|
* - prepends a system note so the model knows early context was dropped
|
|
@@ -31,19 +33,29 @@ function trimContext(messages) {
|
|
|
31
33
|
if (messages.length === 0)
|
|
32
34
|
return messages;
|
|
33
35
|
const [system, ...rest] = messages;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
// Sanitize: assistant messages with no content AND no tool_calls are invalid for providers
|
|
37
|
+
// (they arise from truncated streams or reasoning-only model steps). Remove them so they
|
|
38
|
+
// cannot corrupt subsequent API calls.
|
|
39
|
+
const clean = rest.filter((m) => {
|
|
40
|
+
if (m.role !== 'assistant')
|
|
41
|
+
return true;
|
|
42
|
+
return m.content !== null || (m.tool_calls?.length ?? 0) > 0;
|
|
43
|
+
});
|
|
44
|
+
if (clean.length <= MAX_CONTEXT_MESSAGES) {
|
|
45
|
+
// Return original if nothing changed (avoids allocation on the common path)
|
|
46
|
+
return clean.length === rest.length ? messages : [system, ...clean];
|
|
47
|
+
}
|
|
36
48
|
// Take the tail we want to keep, then advance past any leading tool/assistant messages so
|
|
37
49
|
// the slice always starts on a complete user turn. Orphaned tool results (whose paired
|
|
38
50
|
// assistant message was trimmed) cause provider errors on the next API call.
|
|
39
|
-
let tail =
|
|
51
|
+
let tail = clean.slice(-MAX_CONTEXT_MESSAGES);
|
|
40
52
|
let skip = 0;
|
|
41
53
|
while (skip < tail.length && tail[skip]?.role !== 'user')
|
|
42
54
|
skip++;
|
|
43
55
|
tail = tail.slice(skip);
|
|
44
|
-
const dropped =
|
|
56
|
+
const dropped = clean.length - tail.length;
|
|
45
57
|
if (dropped <= 0)
|
|
46
|
-
return
|
|
58
|
+
return [system, ...clean];
|
|
47
59
|
const note = {
|
|
48
60
|
role: 'system',
|
|
49
61
|
content: `[Kiosapi: ${dropped} pesan awal dihapus dari konteks untuk menghemat token. Lanjutkan dari konteks terkini di bawah.]`,
|
|
@@ -401,13 +413,20 @@ export async function runTurn(s, userText) {
|
|
|
401
413
|
totalIn += reply.usage.promptTokens;
|
|
402
414
|
totalOut += reply.usage.completionTokens;
|
|
403
415
|
}
|
|
404
|
-
|
|
416
|
+
const calls = reply.tool_calls ?? [];
|
|
417
|
+
// Only push the assistant message when it carries meaningful content. An empty response
|
|
418
|
+
// (null content + no tool_calls) can appear from a truncated stream or a reasoning-only
|
|
419
|
+
// step; pushing it corrupts the history and causes providers to reject subsequent calls.
|
|
420
|
+
if (reply.content !== null || calls.length > 0) {
|
|
421
|
+
s.messages.push({ role: 'assistant', content: reply.content, tool_calls: reply.tool_calls });
|
|
422
|
+
}
|
|
405
423
|
if (reply.content)
|
|
406
424
|
lastText = reply.content;
|
|
407
|
-
const calls = reply.tool_calls ?? [];
|
|
408
425
|
if (calls.length === 0) {
|
|
409
426
|
if (step === 0) {
|
|
410
|
-
console.log(dim(
|
|
427
|
+
console.log(dim(reply.content
|
|
428
|
+
? '(Model tidak memakai tool — pilih model ber-🔧 untuk agen, mis. /model deepseek/deepseek-v4-flash.)'
|
|
429
|
+
: '(Model tidak merespons — coba ulangi atau ganti model.)'));
|
|
411
430
|
}
|
|
412
431
|
showUsage();
|
|
413
432
|
s.totalTokens += totalIn + totalOut;
|