kiosapi 0.1.3 → 0.1.5
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 +21 -6
- package/dist/agent/team.js +9 -8
- package/dist/api.js +59 -19
- package/dist/commands.js +20 -5
- package/dist/session.js +37 -1
- package/package.json +5 -2
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,18 +100,27 @@ 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) {
|
package/dist/agent/team.js
CHANGED
|
@@ -17,19 +17,20 @@ const ROLES = {
|
|
|
17
17
|
brief: 'Peranmu: PENINJAU. Baca file yang relevan dan tinjau hasil implementasi: sebutkan masalah/risiko & saran perbaikan singkat.',
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
|
-
/** Run one role as a sub-agent and return its final text. */
|
|
21
|
-
async function runRole(
|
|
22
|
-
|
|
23
|
-
const
|
|
20
|
+
/** Run one role as a sub-agent (with its own model) and return its final text. */
|
|
21
|
+
async function runRole(name, task, opts) {
|
|
22
|
+
const role = ROLES[name];
|
|
23
|
+
const model = opts.models[name];
|
|
24
|
+
console.log(`\n${bold(role.title)} ${dim(`(${model})`)}`);
|
|
25
|
+
const s = newSession(model, role.mode, opts.otomatis);
|
|
24
26
|
s.messages.push({ role: 'system', content: role.brief });
|
|
25
27
|
return runTurn(s, task);
|
|
26
28
|
}
|
|
27
29
|
/** Orchestrate the perencana → pengkode → peninjau pipeline for a task. */
|
|
28
30
|
export async function runTeam(task, opts) {
|
|
29
31
|
console.log(bold('👥 Tim agen: perencana → pengkode → peninjau'));
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
await runRole(
|
|
33
|
-
await runRole(ROLES.peninjau, `Tinjau hasil implementasi untuk tugas: ${task}`, opts);
|
|
32
|
+
const plan = await runRole('perencana', task, opts);
|
|
33
|
+
await runRole('pengkode', `Tugas: ${task}\n\nRencana:\n${plan || '(tidak ada rencana eksplisit)'}`, opts);
|
|
34
|
+
await runRole('peninjau', `Tinjau hasil implementasi untuk tugas: ${task}`, opts);
|
|
34
35
|
console.log(green('\n✓ Tim selesai.'));
|
|
35
36
|
}
|
package/dist/api.js
CHANGED
|
@@ -91,33 +91,73 @@ 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) => ({ id: c.id, function: { name: c.name, arguments: c.args } }));
|
|
160
|
+
return { content: content || null, tool_calls: tool_calls.length > 0 ? tool_calls : undefined };
|
|
121
161
|
}
|
|
122
162
|
/** Parse an OpenAI SSE stream, yielding content deltas. */
|
|
123
163
|
async function* consumeSSE(res) {
|
package/dist/commands.js
CHANGED
|
@@ -404,19 +404,34 @@ export async function cmdLihat(args) {
|
|
|
404
404
|
stop();
|
|
405
405
|
process.stdout.write('\n');
|
|
406
406
|
}
|
|
407
|
-
/**
|
|
407
|
+
/**
|
|
408
|
+
* tim — multi-agent pipeline (perencana → pengkode → peninjau). Each role can use a different model
|
|
409
|
+
* via --perencana/--pengkode/--peninjau; roles without an override fall back to -m / the default.
|
|
410
|
+
*/
|
|
408
411
|
export async function cmdTim(args) {
|
|
409
412
|
const { values, positionals } = parseArgs({
|
|
410
413
|
args,
|
|
411
|
-
options: {
|
|
414
|
+
options: {
|
|
415
|
+
model: { type: 'string', short: 'm' },
|
|
416
|
+
otomatis: { type: 'boolean' },
|
|
417
|
+
perencana: { type: 'string' },
|
|
418
|
+
pengkode: { type: 'string' },
|
|
419
|
+
peninjau: { type: 'string' },
|
|
420
|
+
},
|
|
412
421
|
allowPositionals: true,
|
|
413
422
|
});
|
|
414
423
|
const task = positionals.join(' ').trim();
|
|
415
424
|
if (!task)
|
|
416
425
|
throw new Error('Beri tugas. Contoh: kiosapi tim "bikin endpoint /health + tes"');
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
426
|
+
const base = await resolveModel(values.model);
|
|
427
|
+
const models = {
|
|
428
|
+
perencana: values.perencana ?? base,
|
|
429
|
+
pengkode: values.pengkode ?? base,
|
|
430
|
+
peninjau: values.peninjau ?? base,
|
|
431
|
+
};
|
|
432
|
+
for (const m of new Set(Object.values(models)))
|
|
433
|
+
await warnIfNoTools(m);
|
|
434
|
+
await runTeam(task, { models, otomatis: Boolean(values.otomatis) });
|
|
420
435
|
}
|
|
421
436
|
/** saldo — show own balance, bonus tokens, and month-to-date spend. */
|
|
422
437
|
export async function cmdSaldo() {
|
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
|
|
@@ -99,7 +100,42 @@ async function runSlash(line, s) {
|
|
|
99
100
|
if (!task)
|
|
100
101
|
console.log(red('Beri tugas. Contoh: /tim bikin endpoint /health + tes'));
|
|
101
102
|
else
|
|
102
|
-
await runTeam(task, {
|
|
103
|
+
await runTeam(task, {
|
|
104
|
+
models: {
|
|
105
|
+
perencana: s.teamModels.perencana ?? s.model,
|
|
106
|
+
pengkode: s.teamModels.pengkode ?? s.model,
|
|
107
|
+
peninjau: s.teamModels.peninjau ?? s.model,
|
|
108
|
+
},
|
|
109
|
+
otomatis: s.otomatis,
|
|
110
|
+
});
|
|
111
|
+
return false;
|
|
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
|
+
}
|
|
103
139
|
return false;
|
|
104
140
|
}
|
|
105
141
|
case 'gambar':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiosapi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI Kiosapi.id berbahasa Indonesia — bangun aplikasimu pakai API key Kiosapi (agen + multimodal).",
|
|
6
6
|
"keywords": [
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
"bin": {
|
|
29
29
|
"kiosapi": "./dist/index.js"
|
|
30
30
|
},
|
|
31
|
-
"files": [
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
32
35
|
"engines": {
|
|
33
36
|
"node": ">=20"
|
|
34
37
|
},
|