kiosapi 0.1.4 → 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/api.js +59 -19
- 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,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/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/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;
|