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 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 { model, mode, otomatis, messages: [{ role: 'system', content: systemPrompt(mode) }] };
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 = await chatComplete(s.model, s.messages, tools);
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
- stop();
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 — pastikan model mendukung function calling, mis. /model anthropic/claude-sonnet-4-6.)'));
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
- /** POST /v1/chat/completions (non-stream) with tools — returns the assistant message (for the agent). */
95
- export async function chatComplete(model, messages, tools) {
96
- const { baseUrl, apiKey } = loadConfig();
97
- if (!apiKey)
98
- throw new Error('Belum masuk. Jalankan: kiosapi masuk');
99
- const res = await fetch(`${baseUrl}/v1/chat/completions`, {
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: false,
109
+ stream: true,
112
110
  }),
113
111
  });
114
- if (!res.ok)
115
- throw new Error(humanizeError(res.status));
116
- const body = (await res.json());
117
- const msg = body.choices?.[0]?.message;
118
- if (!msg)
119
- throw new Error('Respons model kosong.');
120
- return { content: msg.content ?? null, tool_calls: msg.tool_calls };
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}" mungkin tak mendukung tool calling — agen bisa tak bekerja. Pilih model 🔧 (lihat: kiosapi model --tools), mis. anthropic/claude-sonnet-4-6.`));
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: { perencana: s.model, pengkode: s.model, peninjau: s.model },
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiosapi",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "CLI Kiosapi.id berbahasa Indonesia — bangun aplikasimu pakai API key Kiosapi (agen + multimodal).",
6
6
  "keywords": [