kiosapi 0.1.4 → 0.1.6
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 +22 -7
- package/dist/api.js +63 -19
- package/dist/commands.js +1 -1
- package/dist/session.js +34 -1
- package/package.json +1 -1
package/dist/agent/run.js
CHANGED
|
@@ -78,7 +78,13 @@ async function runTool(call, otomatis) {
|
|
|
78
78
|
}
|
|
79
79
|
/** Start a fresh session (system prompt seeded from the mode). */
|
|
80
80
|
export function newSession(model, mode, otomatis) {
|
|
81
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
model,
|
|
83
|
+
mode,
|
|
84
|
+
otomatis,
|
|
85
|
+
messages: [{ role: 'system', content: systemPrompt(mode) }],
|
|
86
|
+
teamModels: {},
|
|
87
|
+
};
|
|
82
88
|
}
|
|
83
89
|
/** Reset the conversation, keeping the current mode/model/otomatis. */
|
|
84
90
|
export function resetSession(s) {
|
|
@@ -94,22 +100,31 @@ export async function runTurn(s, userText) {
|
|
|
94
100
|
let lastText = '';
|
|
95
101
|
for (let step = 0; step < MAX_STEPS; step++) {
|
|
96
102
|
const stop = thinking();
|
|
103
|
+
let streamed = false;
|
|
97
104
|
let reply;
|
|
98
105
|
try {
|
|
99
|
-
reply
|
|
106
|
+
// Stream the reply live: stop the spinner on the first token and print as it arrives.
|
|
107
|
+
reply = await chatComplete(s.model, s.messages, tools, (delta) => {
|
|
108
|
+
if (!streamed) {
|
|
109
|
+
stop();
|
|
110
|
+
streamed = true;
|
|
111
|
+
}
|
|
112
|
+
process.stdout.write(delta);
|
|
113
|
+
});
|
|
100
114
|
}
|
|
101
115
|
finally {
|
|
102
|
-
|
|
116
|
+
if (!streamed)
|
|
117
|
+
stop();
|
|
103
118
|
}
|
|
119
|
+
if (streamed)
|
|
120
|
+
process.stdout.write('\n');
|
|
104
121
|
s.messages.push({ role: 'assistant', content: reply.content, tool_calls: reply.tool_calls });
|
|
105
|
-
if (reply.content)
|
|
106
|
-
console.log(reply.content);
|
|
122
|
+
if (reply.content)
|
|
107
123
|
lastText = reply.content;
|
|
108
|
-
}
|
|
109
124
|
const calls = reply.tool_calls ?? [];
|
|
110
125
|
if (calls.length === 0) {
|
|
111
126
|
if (step === 0) {
|
|
112
|
-
console.log(dim('(Model tidak memakai tool —
|
|
127
|
+
console.log(dim('(Model tidak memakai tool — pilih model ber-🔧 untuk agen, mis. /model deepseek/deepseek-v4-flash.)'));
|
|
113
128
|
}
|
|
114
129
|
return lastText; // final text answer
|
|
115
130
|
}
|
package/dist/api.js
CHANGED
|
@@ -91,33 +91,77 @@ export async function createTopup(amount) {
|
|
|
91
91
|
const res = await authedFetch('/v1/topup', { method: 'POST', body: JSON.stringify({ amount }) });
|
|
92
92
|
return res.json();
|
|
93
93
|
}
|
|
94
|
-
/**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
/**
|
|
95
|
+
* One agent step: POST /v1/chat/completions with tools, STREAMED and reassembled into a single
|
|
96
|
+
* assistant reply (content + tool_calls). Streaming keeps bytes flowing so a long "thinking" pause
|
|
97
|
+
* doesn't hit the Worker's sync-request timeout (which surfaced as HTTP 502). An optional onText
|
|
98
|
+
* callback receives content deltas live.
|
|
99
|
+
*/
|
|
100
|
+
export async function chatComplete(model, messages, tools, onText) {
|
|
101
|
+
const res = await authedFetch('/v1/chat/completions', {
|
|
100
102
|
method: 'POST',
|
|
101
|
-
headers: {
|
|
102
|
-
Authorization: `Bearer ${apiKey}`,
|
|
103
|
-
'Content-Type': 'application/json',
|
|
104
|
-
'X-Title': 'kiosapi-cli',
|
|
105
|
-
},
|
|
103
|
+
headers: { 'X-Title': 'kiosapi-cli' },
|
|
106
104
|
body: JSON.stringify({
|
|
107
105
|
model,
|
|
108
106
|
messages,
|
|
109
107
|
tools,
|
|
110
108
|
tool_choice: tools && tools.length > 0 ? 'auto' : undefined,
|
|
111
|
-
stream:
|
|
109
|
+
stream: true,
|
|
112
110
|
}),
|
|
113
111
|
});
|
|
114
|
-
if (!res.
|
|
115
|
-
throw new Error(
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
if (!res.body)
|
|
113
|
+
throw new Error('Respons kosong dari server.');
|
|
114
|
+
const reader = res.body.getReader();
|
|
115
|
+
const decoder = new TextDecoder();
|
|
116
|
+
let buffer = '';
|
|
117
|
+
let content = '';
|
|
118
|
+
const calls = new Map();
|
|
119
|
+
while (true) {
|
|
120
|
+
const { done, value } = await reader.read();
|
|
121
|
+
if (done)
|
|
122
|
+
break;
|
|
123
|
+
buffer += decoder.decode(value, { stream: true });
|
|
124
|
+
const lines = buffer.split('\n');
|
|
125
|
+
buffer = lines.pop() ?? '';
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (!trimmed.startsWith('data:'))
|
|
129
|
+
continue;
|
|
130
|
+
const payload = trimmed.slice(5).trim();
|
|
131
|
+
if (payload === '[DONE]')
|
|
132
|
+
continue;
|
|
133
|
+
try {
|
|
134
|
+
const delta = JSON.parse(payload).choices?.[0]?.delta;
|
|
135
|
+
if (!delta)
|
|
136
|
+
continue;
|
|
137
|
+
if (delta.content) {
|
|
138
|
+
content += delta.content;
|
|
139
|
+
onText?.(delta.content);
|
|
140
|
+
}
|
|
141
|
+
for (const tc of delta.tool_calls ?? []) {
|
|
142
|
+
const cur = calls.get(tc.index) ?? { id: '', name: '', args: '' };
|
|
143
|
+
if (tc.id)
|
|
144
|
+
cur.id = tc.id;
|
|
145
|
+
if (tc.function?.name)
|
|
146
|
+
cur.name = tc.function.name;
|
|
147
|
+
if (tc.function?.arguments)
|
|
148
|
+
cur.args += tc.function.arguments;
|
|
149
|
+
calls.set(tc.index, cur);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// ignore keep-alive / non-JSON lines
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const tool_calls = [...calls.values()]
|
|
158
|
+
.filter((c) => c.name)
|
|
159
|
+
.map((c) => ({
|
|
160
|
+
id: c.id,
|
|
161
|
+
type: 'function',
|
|
162
|
+
function: { name: c.name, arguments: c.args },
|
|
163
|
+
}));
|
|
164
|
+
return { content: content || null, tool_calls: tool_calls.length > 0 ? tool_calls : undefined };
|
|
121
165
|
}
|
|
122
166
|
/** Parse an OpenAI SSE stream, yielding content deltas. */
|
|
123
167
|
async function* consumeSSE(res) {
|
package/dist/commands.js
CHANGED
|
@@ -136,7 +136,7 @@ export async function cmdModel(args) {
|
|
|
136
136
|
/** Warn (best-effort) if the chosen model likely can't drive the agent's tools. */
|
|
137
137
|
export async function warnIfNoTools(model) {
|
|
138
138
|
if (!(await modelSupportsTools(model))) {
|
|
139
|
-
console.error(yellow(`⚠ Model "${model}"
|
|
139
|
+
console.error(yellow(`⚠ Model "${model}" tak mendukung tool calling via Kiosapi — agen tak bekerja. Pilih model 🔧 (lihat: kiosapi model --tools), mis. deepseek/deepseek-v4-flash.`));
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
/** tanya — one-shot streaming question (also reads piped stdin as context). */
|
package/dist/session.js
CHANGED
|
@@ -19,6 +19,7 @@ ${dim('Ketik tugasmu langsung. Perintah meta diawali "/". /bantuan untuk daftar,
|
|
|
19
19
|
function slashHelp() {
|
|
20
20
|
console.log(`${bold('Perintah sesi:')}
|
|
21
21
|
/tim <tugas> Multi-agen (perencana→pengkode→peninjau)
|
|
22
|
+
/peran [peran] [model] Atur model per peran tim (atau lihat/reset)
|
|
22
23
|
/gambar <prompt> Buat gambar → file
|
|
23
24
|
/video <prompt> Buat video → file
|
|
24
25
|
/lihat <file> <pertanyaan> Tanya model vision tentang gambar
|
|
@@ -100,11 +101,43 @@ async function runSlash(line, s) {
|
|
|
100
101
|
console.log(red('Beri tugas. Contoh: /tim bikin endpoint /health + tes'));
|
|
101
102
|
else
|
|
102
103
|
await runTeam(task, {
|
|
103
|
-
models: {
|
|
104
|
+
models: {
|
|
105
|
+
perencana: s.teamModels.perencana ?? s.model,
|
|
106
|
+
pengkode: s.teamModels.pengkode ?? s.model,
|
|
107
|
+
peninjau: s.teamModels.peninjau ?? s.model,
|
|
108
|
+
},
|
|
104
109
|
otomatis: s.otomatis,
|
|
105
110
|
});
|
|
106
111
|
return false;
|
|
107
112
|
}
|
|
113
|
+
case 'peran': {
|
|
114
|
+
const [role, ...mrest] = rest;
|
|
115
|
+
const names = ['perencana', 'pengkode', 'peninjau'];
|
|
116
|
+
if (!role) {
|
|
117
|
+
console.log(dim('Model per peran tim:'));
|
|
118
|
+
for (const r of names)
|
|
119
|
+
console.log(` ${r}: ${s.teamModels[r] ?? `${s.model} (default)`}`);
|
|
120
|
+
console.log(dim('Set: /peran <peran> <model> · reset: /peran reset'));
|
|
121
|
+
}
|
|
122
|
+
else if (role === 'reset') {
|
|
123
|
+
s.teamModels = {};
|
|
124
|
+
console.log(dim('Model per peran direset (pakai model sesi).'));
|
|
125
|
+
}
|
|
126
|
+
else if (names.includes(role)) {
|
|
127
|
+
const m = mrest.join(' ').trim();
|
|
128
|
+
if (m) {
|
|
129
|
+
s.teamModels[role] = m;
|
|
130
|
+
console.log(dim(`${role} → ${m}`));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.log(dim(`${role}: ${s.teamModels[role] ?? `${s.model} (default)`}`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.log(red(`Peran tak dikenal: ${role} (perencana|pengkode|peninjau)`));
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
108
141
|
case 'gambar':
|
|
109
142
|
await cmdGambar(rest);
|
|
110
143
|
return false;
|