pikiloom 0.4.41 → 0.4.42
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/dashboard/dist/assets/AgentTab-B9z4W2mh.js +1 -0
- package/dashboard/dist/assets/{ConnectionModal-CAlACYKM.js → ConnectionModal-2bL7sFKk.js} +1 -1
- package/dashboard/dist/assets/{DirBrowser-D3KGIof1.js → DirBrowser-CA4k8E_A.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-C_69Y7CR.js → ExtensionsTab-CRjTmFRl.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-B5_6IS4X.js → IMAccessTab-DDb7rsHe.js} +1 -1
- package/dashboard/dist/assets/{Modal-BjZifpga.js → Modal-DDiBG4kb.js} +1 -1
- package/dashboard/dist/assets/{Modals-B_V24pNA.js → Modals-CsUQjpxn.js} +1 -1
- package/dashboard/dist/assets/{Select-D7xW38wq.js → Select-CG_7h_Tz.js} +1 -1
- package/dashboard/dist/assets/SessionPanel-B5964GUj.js +1 -0
- package/dashboard/dist/assets/{SystemTab-i5nmYweA.js → SystemTab-D7r1PsC9.js} +1 -1
- package/dashboard/dist/assets/index-BbplxgnQ.js +3 -0
- package/dashboard/dist/assets/index-CULTTvtY.js +23 -0
- package/dashboard/dist/assets/index-DwmXPtDd.css +1 -0
- package/dashboard/dist/assets/{shared-BKwEgcmy.js → shared-Bv8WvQSo.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/accounts.js +181 -0
- package/dist/agent/drivers/claude.js +108 -3
- package/dist/agent/kernel-bridge.js +45 -1
- package/dist/agent/session.js +6 -2
- package/dist/agent/stream.js +26 -0
- package/dist/bot/bot.js +45 -10
- package/dist/bot/command-ui.js +69 -4
- package/dist/cli/kernel-app.js +4 -2
- package/dist/dashboard/routes/accounts.js +113 -0
- package/dist/dashboard/server.js +2 -0
- package/dist/model/responses-bridge.js +37 -0
- package/package.json +1 -1
- package/packages/kernel/dist/accounts.d.ts +6 -0
- package/packages/kernel/dist/accounts.js +29 -0
- package/packages/kernel/dist/drivers/acp.d.ts +24 -0
- package/packages/kernel/dist/drivers/acp.js +477 -0
- package/packages/kernel/dist/drivers/codex.js +20 -12
- package/packages/kernel/dist/drivers/hermes.d.ts +3 -12
- package/packages/kernel/dist/drivers/hermes.js +8 -191
- package/packages/kernel/dist/drivers/index.d.ts +1 -0
- package/packages/kernel/dist/drivers/index.js +1 -0
- package/packages/kernel/dist/index.d.ts +2 -0
- package/packages/kernel/dist/index.js +3 -0
- package/dashboard/dist/assets/AgentTab-B4ZC9QFL.js +0 -1
- package/dashboard/dist/assets/SessionPanel-BbrfUDLg.js +0 -1
- package/dashboard/dist/assets/index-A_dL4aEz.js +0 -23
- package/dashboard/dist/assets/index-Bthwt6K_.css +0 -1
- package/dashboard/dist/assets/index-D18pCeqv.js +0 -3
package/dist/bot/command-ui.js
CHANGED
|
@@ -2,6 +2,7 @@ import { normalizeAgent } from './bot.js';
|
|
|
2
2
|
import { getDriverCapabilities } from '../agent/driver.js';
|
|
3
3
|
import { getSessionStatusForChat } from './session-status.js';
|
|
4
4
|
import { getAgentsListData, getModelsListData, getSessionsPageData, getSkillsListData, summarizeSessionRun, modelMatchesSelection, resolveSkillPrompt, } from './commands.js';
|
|
5
|
+
import { accountAgentSupported, listAccounts, getAccount, getActiveAccountId, setActiveAccount, warmAccountUsages, accountUsageSummary, } from '../agent/accounts.js';
|
|
5
6
|
function chunkRows(items, columns) {
|
|
6
7
|
const rows = [];
|
|
7
8
|
const size = Math.max(1, columns);
|
|
@@ -28,6 +29,8 @@ export function encodeCommandAction(action) {
|
|
|
28
29
|
return `sess:${action.sessionId}`;
|
|
29
30
|
case 'agent.switch':
|
|
30
31
|
return `ag:${action.agent}`;
|
|
32
|
+
case 'agent.account.set':
|
|
33
|
+
return `agacc:${action.agent}:${action.accountId ?? ''}`;
|
|
31
34
|
case 'model.switch':
|
|
32
35
|
return `mod:${action.modelId}`;
|
|
33
36
|
case 'effort.set':
|
|
@@ -63,6 +66,18 @@ export function decodeCommandAction(data) {
|
|
|
63
66
|
return null;
|
|
64
67
|
return { kind: 'session.switch', sessionId };
|
|
65
68
|
}
|
|
69
|
+
if (data.startsWith('agacc:')) {
|
|
70
|
+
const rest = data.slice(6);
|
|
71
|
+
const sep = rest.indexOf(':');
|
|
72
|
+
if (sep < 0)
|
|
73
|
+
return null;
|
|
74
|
+
try {
|
|
75
|
+
return { kind: 'agent.account.set', agent: normalizeAgent(rest.slice(0, sep)), accountId: rest.slice(sep + 1) || null };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
66
81
|
if (data.startsWith('ag:')) {
|
|
67
82
|
try {
|
|
68
83
|
return { kind: 'agent.switch', agent: normalizeAgent(data.slice(3)) };
|
|
@@ -194,15 +209,43 @@ export function buildAgentsCommandView(bot, chatId) {
|
|
|
194
209
|
};
|
|
195
210
|
});
|
|
196
211
|
const current = installed.find(a => a.isCurrent);
|
|
212
|
+
// For the current agent, if it has local accounts (claude), surface them so the user can
|
|
213
|
+
// switch the active account right here — labelled with 👤 and each account's live usage.
|
|
214
|
+
const accountRows = [];
|
|
215
|
+
const accountItems = [];
|
|
216
|
+
const curAgent = data.currentAgent;
|
|
217
|
+
if (accountAgentSupported(curAgent)) {
|
|
218
|
+
const accs = listAccounts(curAgent);
|
|
219
|
+
if (accs.length) {
|
|
220
|
+
warmAccountUsages(curAgent);
|
|
221
|
+
const activeAccId = getActiveAccountId(curAgent);
|
|
222
|
+
const entry = (label, accountId, summary) => {
|
|
223
|
+
const isActive = accountId === activeAccId;
|
|
224
|
+
accountRows.push([{
|
|
225
|
+
label: summary ? `👤 ${label} · ${summary}` : `👤 ${label}`,
|
|
226
|
+
action: { kind: 'agent.account.set', agent: curAgent, accountId },
|
|
227
|
+
state: isActive ? 'current' : 'default',
|
|
228
|
+
primary: isActive,
|
|
229
|
+
}]);
|
|
230
|
+
accountItems.push({ label: `👤 ${label}`, detail: summary, state: isActive ? 'current' : 'default' });
|
|
231
|
+
};
|
|
232
|
+
for (const acc of accs)
|
|
233
|
+
entry(acc.label, acc.id, accountUsageSummary(curAgent, acc.id));
|
|
234
|
+
entry('Default login', null, null);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const helperText = actions.length
|
|
238
|
+
? (accountRows.length ? 'Tap an agent to switch · 👤 = account' : 'Tap an agent to switch.')
|
|
239
|
+
: undefined;
|
|
197
240
|
return {
|
|
198
241
|
kind: 'agents',
|
|
199
242
|
title: 'Agents',
|
|
200
243
|
detail: current ? current.label : undefined,
|
|
201
244
|
metaLines: current ? [`Current: ${current.label}`] : [],
|
|
202
|
-
items,
|
|
245
|
+
items: [...items, ...accountItems],
|
|
203
246
|
emptyText: actions.length ? undefined : 'No installed agents.',
|
|
204
|
-
helperText
|
|
205
|
-
rows: actions.map(action => [action]),
|
|
247
|
+
helperText,
|
|
248
|
+
rows: [...actions.map(action => [action]), ...accountRows],
|
|
206
249
|
};
|
|
207
250
|
}
|
|
208
251
|
const modelsDrafts = new Map();
|
|
@@ -434,9 +477,18 @@ export async function executeCommandAction(bot, chatId, action, opts = {}) {
|
|
|
434
477
|
}
|
|
435
478
|
case 'agent.switch': {
|
|
436
479
|
const chat = bot.chat(chatId);
|
|
437
|
-
|
|
480
|
+
const hasAccounts = accountAgentSupported(action.agent) && listAccounts(action.agent).length > 0;
|
|
481
|
+
if (chat.agent === action.agent) {
|
|
482
|
+
// Already current: reopen the menu (now showing accounts) if it has any, else no-op.
|
|
483
|
+
if (hasAccounts)
|
|
484
|
+
return { kind: 'view', view: buildAgentsCommandView(bot, chatId), callbackText: '' };
|
|
438
485
|
return { kind: 'noop', message: `Already using ${action.agent}` };
|
|
486
|
+
}
|
|
439
487
|
bot.switchAgentForChat(chatId, action.agent);
|
|
488
|
+
// For an account-capable agent, drop the user straight into its account picker.
|
|
489
|
+
if (hasAccounts) {
|
|
490
|
+
return { kind: 'view', view: buildAgentsCommandView(bot, chatId), callbackText: `Switched to ${action.agent}` };
|
|
491
|
+
}
|
|
440
492
|
const resumed = bot.selectedSession(chatId);
|
|
441
493
|
return {
|
|
442
494
|
kind: 'notice',
|
|
@@ -453,6 +505,19 @@ export async function executeCommandAction(bot, chatId, action, opts = {}) {
|
|
|
453
505
|
: null,
|
|
454
506
|
};
|
|
455
507
|
}
|
|
508
|
+
case 'agent.account.set': {
|
|
509
|
+
if (!accountAgentSupported(action.agent))
|
|
510
|
+
return { kind: 'noop', message: `${action.agent} has no accounts` };
|
|
511
|
+
try {
|
|
512
|
+
setActiveAccount(action.agent, action.accountId);
|
|
513
|
+
}
|
|
514
|
+
catch (e) {
|
|
515
|
+
return { kind: 'noop', message: e?.message || 'Switch failed' };
|
|
516
|
+
}
|
|
517
|
+
warmAccountUsages(action.agent);
|
|
518
|
+
const label = action.accountId ? (getAccount(action.agent, action.accountId)?.label ?? action.accountId) : 'Default login';
|
|
519
|
+
return { kind: 'view', view: buildAgentsCommandView(bot, chatId), callbackText: `Account → ${label}` };
|
|
520
|
+
}
|
|
456
521
|
case 'model.switch': {
|
|
457
522
|
const chat = bot.chat(chatId);
|
|
458
523
|
const currentModel = bot.modelForAgent(chat.agent);
|
package/dist/cli/kernel-app.js
CHANGED
|
@@ -19,13 +19,14 @@ const DEMO_MODELS = {
|
|
|
19
19
|
claude: [{ id: 'claude-opus-4-8', label: 'Opus 4.8', providerName: 'anthropic', contextWindow: 200000 }],
|
|
20
20
|
codex: [{ id: 'gpt-5.5-codex', label: 'GPT-5.5 Codex', providerName: 'openai', contextWindow: 400000 }],
|
|
21
21
|
gemini: [{ id: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro', providerName: 'google', contextWindow: 1000000 }],
|
|
22
|
+
opencode: [{ id: 'claude-sonnet-4-6', label: 'OpenCode · Sonnet 4.6 (ACP)', providerName: 'opencode', contextWindow: 200000 }],
|
|
22
23
|
echo: [{ id: 'echo-1', label: 'Echo (hermetic)', providerName: 'local', contextWindow: 8192 }],
|
|
23
24
|
};
|
|
24
25
|
export async function runKernelApp(argv) {
|
|
25
26
|
const i = argv.indexOf('--dashboard-port');
|
|
26
27
|
const port = (i >= 0 && parseInt(argv[i + 1], 10)) || 3940;
|
|
27
28
|
const k = await loadKernel();
|
|
28
|
-
const { createLoom, ClaudeDriver, CodexDriver, GeminiDriver, HermesDriver, EchoDriver, WebSurface } = k;
|
|
29
|
+
const { createLoom, ClaudeDriver, CodexDriver, GeminiDriver, HermesDriver, AcpDriver, EchoDriver, WebSurface } = k;
|
|
29
30
|
// Load providers/profiles so the active BYOK/豆包 bindings are visible to the resolver.
|
|
30
31
|
try {
|
|
31
32
|
applyUserConfig(loadUserConfig(), undefined, { overwrite: true, clearMissing: true });
|
|
@@ -100,7 +101,8 @@ export async function runKernelApp(argv) {
|
|
|
100
101
|
const surfaces = [web];
|
|
101
102
|
const loom = createLoom({
|
|
102
103
|
appNamespace: 'pikiloom-kernel',
|
|
103
|
-
|
|
104
|
+
// OpenCode (and any other ACP CLI) plugs in via the generic AcpDriver — same registry as the natives.
|
|
105
|
+
drivers: [new EchoDriver(), new ClaudeDriver(), new CodexDriver(), new GeminiDriver(), new HermesDriver(), new AcpDriver({ id: 'opencode', command: 'opencode', args: ['acp'] })],
|
|
104
106
|
defaultAgent: 'claude',
|
|
105
107
|
surfaces,
|
|
106
108
|
catalog,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { allDriverIds } from '../../agent/index.js';
|
|
3
|
+
import { accountAgentSupported, listAccounts, getAccount, addAccount, updateAccount, removeAccount, getActiveAccountId, setActiveAccount, probeAccountUsage, MAX_ACCOUNTS_PER_AGENT, } from '../../agent/accounts.js';
|
|
4
|
+
const app = new Hono();
|
|
5
|
+
function agentError(agent) {
|
|
6
|
+
if (!allDriverIds().includes(agent))
|
|
7
|
+
return { status: 400, msg: `Unknown agent: ${agent}` };
|
|
8
|
+
if (!accountAgentSupported(agent))
|
|
9
|
+
return { status: 400, msg: `Agent ${agent} does not support multiple accounts` };
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
// Never returns the token — only the display name, usage, and active flag.
|
|
13
|
+
async function publicAccount(agent, rec, activeId) {
|
|
14
|
+
return {
|
|
15
|
+
id: rec.id,
|
|
16
|
+
label: rec.label,
|
|
17
|
+
createdAt: rec.createdAt,
|
|
18
|
+
lastUsedAt: rec.lastUsedAt ?? null,
|
|
19
|
+
active: rec.id === activeId,
|
|
20
|
+
usage: await probeAccountUsage(agent, rec.id),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
app.get('/api/agents/:agent/accounts', async (c) => {
|
|
24
|
+
const agent = c.req.param('agent');
|
|
25
|
+
if (!allDriverIds().includes(agent))
|
|
26
|
+
return c.json({ ok: false, error: `Unknown agent: ${agent}` }, 400);
|
|
27
|
+
if (!accountAgentSupported(agent)) {
|
|
28
|
+
return c.json({ ok: true, agent, supported: false, accounts: [], activeAccountId: null, max: MAX_ACCOUNTS_PER_AGENT });
|
|
29
|
+
}
|
|
30
|
+
const activeId = getActiveAccountId(agent);
|
|
31
|
+
const accounts = await Promise.all(listAccounts(agent).map(r => publicAccount(agent, r, activeId)));
|
|
32
|
+
return c.json({ ok: true, agent, supported: true, accounts, activeAccountId: activeId, max: MAX_ACCOUNTS_PER_AGENT });
|
|
33
|
+
});
|
|
34
|
+
app.post('/api/agents/:agent/accounts', async (c) => {
|
|
35
|
+
const agent = c.req.param('agent');
|
|
36
|
+
const err = agentError(agent);
|
|
37
|
+
if (err)
|
|
38
|
+
return c.json({ ok: false, error: err.msg }, err.status);
|
|
39
|
+
let body = {};
|
|
40
|
+
try {
|
|
41
|
+
body = await c.req.json();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
body = {};
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const rec = await addAccount(agent, String(body.label || ''), String(body.token || ''));
|
|
48
|
+
return c.json({ ok: true, account: await publicAccount(agent, rec, getActiveAccountId(agent)) });
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return c.json({ ok: false, error: e?.message || String(e) }, 400);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
app.patch('/api/agents/:agent/accounts/:id', async (c) => {
|
|
55
|
+
const agent = c.req.param('agent');
|
|
56
|
+
const id = c.req.param('id');
|
|
57
|
+
const err = agentError(agent);
|
|
58
|
+
if (err)
|
|
59
|
+
return c.json({ ok: false, error: err.msg }, err.status);
|
|
60
|
+
if (!getAccount(agent, id))
|
|
61
|
+
return c.json({ ok: false, error: 'Account not found' }, 404);
|
|
62
|
+
let body = {};
|
|
63
|
+
try {
|
|
64
|
+
body = await c.req.json();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
body = {};
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const rec = await updateAccount(agent, id, {
|
|
71
|
+
label: typeof body.label === 'string' ? body.label : undefined,
|
|
72
|
+
token: typeof body.token === 'string' && body.token.trim() ? body.token : undefined,
|
|
73
|
+
});
|
|
74
|
+
return c.json({ ok: true, account: await publicAccount(agent, rec, getActiveAccountId(agent)) });
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
return c.json({ ok: false, error: e?.message || String(e) }, 400);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
app.delete('/api/agents/:agent/accounts/:id', async (c) => {
|
|
81
|
+
const agent = c.req.param('agent');
|
|
82
|
+
const id = c.req.param('id');
|
|
83
|
+
const err = agentError(agent);
|
|
84
|
+
if (err)
|
|
85
|
+
return c.json({ ok: false, error: err.msg }, err.status);
|
|
86
|
+
if (!(await removeAccount(agent, id)))
|
|
87
|
+
return c.json({ ok: false, error: 'Account not found' }, 404);
|
|
88
|
+
return c.json({ ok: true });
|
|
89
|
+
});
|
|
90
|
+
app.post('/api/agents/:agent/active-account', async (c) => {
|
|
91
|
+
const agent = c.req.param('agent');
|
|
92
|
+
const err = agentError(agent);
|
|
93
|
+
if (err)
|
|
94
|
+
return c.json({ ok: false, error: err.msg }, err.status);
|
|
95
|
+
let body = {};
|
|
96
|
+
try {
|
|
97
|
+
body = await c.req.json();
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
body = {};
|
|
101
|
+
}
|
|
102
|
+
const accountId = body.accountId === null ? null : (typeof body.accountId === 'string' ? body.accountId : undefined);
|
|
103
|
+
if (accountId === undefined)
|
|
104
|
+
return c.json({ ok: false, error: 'accountId (string|null) is required' }, 400);
|
|
105
|
+
try {
|
|
106
|
+
setActiveAccount(agent, accountId);
|
|
107
|
+
return c.json({ ok: true, agent, activeAccountId: accountId });
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return c.json({ ok: false, error: e?.message || String(e) }, 400);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
export default app;
|
package/dist/dashboard/server.js
CHANGED
|
@@ -13,6 +13,7 @@ import extensionRoutes from './routes/extensions.js';
|
|
|
13
13
|
import cliRoutes from './routes/cli.js';
|
|
14
14
|
import modelsRoutes from './routes/models.js';
|
|
15
15
|
import localModelsRoutes from './routes/local-models.js';
|
|
16
|
+
import accountsRoutes from './routes/accounts.js';
|
|
16
17
|
import { runtime } from './runtime.js';
|
|
17
18
|
import { registerProcessRuntime } from '../core/process-control.js';
|
|
18
19
|
import { mountPikichannel } from '../pikichannel/server.js';
|
|
@@ -30,6 +31,7 @@ export async function startDashboard(opts = {}) {
|
|
|
30
31
|
app.route('/', cliRoutes);
|
|
31
32
|
app.route('/', modelsRoutes);
|
|
32
33
|
app.route('/', localModelsRoutes);
|
|
34
|
+
app.route('/', accountsRoutes);
|
|
33
35
|
let pikichannel = null;
|
|
34
36
|
try {
|
|
35
37
|
pikichannel = await mountPikichannel(app);
|
|
@@ -137,7 +137,33 @@ async function handleResponses(req, res, upstreamBase, body) {
|
|
|
137
137
|
const tools = new Map();
|
|
138
138
|
let usage = null;
|
|
139
139
|
const finalItems = [];
|
|
140
|
+
// Reasoning ("thinking") from chat-only providers arrives as delta.reasoning_content
|
|
141
|
+
// (DeepSeek / 豆包) or delta.reasoning (GLM / OpenRouter). Translate it to the Responses
|
|
142
|
+
// reasoning-summary stream so codex surfaces item/reasoning/summaryTextDelta live; without
|
|
143
|
+
// this the entire thinking phase is silent and the answer looks like it appears all at once.
|
|
144
|
+
let reasoningOpen = false, reasoningDone = false, reasoningId = '', reasoningIndex = 0, reasoningText = '';
|
|
145
|
+
const openReasoning = () => {
|
|
146
|
+
if (reasoningOpen || reasoningDone)
|
|
147
|
+
return;
|
|
148
|
+
reasoningOpen = true;
|
|
149
|
+
reasoningId = genId('rs');
|
|
150
|
+
reasoningIndex = nextIndex++;
|
|
151
|
+
emit('response.output_item.added', { output_index: reasoningIndex, item: { id: reasoningId, type: 'reasoning', summary: [] } });
|
|
152
|
+
emit('response.reasoning_summary_part.added', { item_id: reasoningId, output_index: reasoningIndex, summary_index: 0, part: { type: 'summary_text', text: '' } });
|
|
153
|
+
};
|
|
154
|
+
const closeReasoning = () => {
|
|
155
|
+
if (!reasoningOpen)
|
|
156
|
+
return;
|
|
157
|
+
reasoningOpen = false;
|
|
158
|
+
reasoningDone = true;
|
|
159
|
+
emit('response.reasoning_summary_text.done', { item_id: reasoningId, output_index: reasoningIndex, summary_index: 0, text: reasoningText });
|
|
160
|
+
emit('response.reasoning_summary_part.done', { item_id: reasoningId, output_index: reasoningIndex, summary_index: 0, part: { type: 'summary_text', text: reasoningText } });
|
|
161
|
+
const item = { id: reasoningId, type: 'reasoning', summary: reasoningText ? [{ type: 'summary_text', text: reasoningText }] : [] };
|
|
162
|
+
emit('response.output_item.done', { output_index: reasoningIndex, item });
|
|
163
|
+
finalItems.push(item);
|
|
164
|
+
};
|
|
140
165
|
const openMsg = () => {
|
|
166
|
+
closeReasoning(); // a reasoning item must finish before the message item opens (ordered output)
|
|
141
167
|
if (msgOpen)
|
|
142
168
|
return;
|
|
143
169
|
msgOpen = true;
|
|
@@ -178,12 +204,22 @@ async function handleResponses(req, res, upstreamBase, body) {
|
|
|
178
204
|
if (!choice)
|
|
179
205
|
continue;
|
|
180
206
|
const delta = choice.delta || {};
|
|
207
|
+
const reasoningChunk = typeof delta.reasoning_content === 'string' ? delta.reasoning_content
|
|
208
|
+
: typeof delta.reasoning === 'string' ? delta.reasoning : '';
|
|
209
|
+
if (reasoningChunk && !msgOpen && tools.size === 0) {
|
|
210
|
+
openReasoning();
|
|
211
|
+
if (reasoningOpen) {
|
|
212
|
+
reasoningText += reasoningChunk;
|
|
213
|
+
emit('response.reasoning_summary_text.delta', { item_id: reasoningId, output_index: reasoningIndex, summary_index: 0, delta: reasoningChunk });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
181
216
|
if (typeof delta.content === 'string' && delta.content) {
|
|
182
217
|
openMsg();
|
|
183
218
|
msgText += delta.content;
|
|
184
219
|
emit('response.output_text.delta', { item_id: msgId, output_index: msgIndex, content_index: 0, delta: delta.content });
|
|
185
220
|
}
|
|
186
221
|
if (Array.isArray(delta.tool_calls)) {
|
|
222
|
+
closeReasoning(); // tool calls follow the thinking phase; close the reasoning item first
|
|
187
223
|
for (const tc of delta.tool_calls) {
|
|
188
224
|
const idx = typeof tc.index === 'number' ? tc.index : 0;
|
|
189
225
|
let st = tools.get(idx);
|
|
@@ -213,6 +249,7 @@ async function handleResponses(req, res, upstreamBase, body) {
|
|
|
213
249
|
catch (e) {
|
|
214
250
|
warn(`stream read error: ${e?.message || e}`);
|
|
215
251
|
}
|
|
252
|
+
closeReasoning(); // reasoning-only turn (no content/tools followed): finish the reasoning item
|
|
216
253
|
if (msgOpen) {
|
|
217
254
|
emit('response.output_text.done', { item_id: msgId, output_index: msgIndex, content_index: 0, text: msgText });
|
|
218
255
|
const item = { id: msgId, type: 'message', role: 'assistant', content: [{ type: 'output_text', text: msgText }] };
|
package/package.json
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Whether this agent's local accounts are switched by injecting a token. */
|
|
2
|
+
export declare function accountTokenSupported(agent: string): boolean;
|
|
3
|
+
/** The env-var name that overrides this agent's subscription identity, or null. */
|
|
4
|
+
export declare function accountTokenEnvVar(agent: string): string | null;
|
|
5
|
+
/** Env to merge at spawn so the agent runs under the given account token. */
|
|
6
|
+
export declare function accountTokenEnv(agent: string, token: string): Record<string, string>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Multi-account capability (token form): switch between several local subscription accounts
|
|
2
|
+
// of a coding-agent CLI by injecting a long-lived auth token at spawn time. The CLI keeps
|
|
3
|
+
// using its normal single config home (~/.claude etc.) — only the *identity* changes.
|
|
4
|
+
//
|
|
5
|
+
// claude / claude-tui -> CLAUDE_CODE_OAUTH_TOKEN=<token from `claude setup-token`>
|
|
6
|
+
//
|
|
7
|
+
// This replaces the earlier per-account config-directory approach: on macOS claude stores
|
|
8
|
+
// its credential in one shared OS-keychain item (not per CLAUDE_CONFIG_DIR), so the token is
|
|
9
|
+
// what actually isolates an account. codex has no equivalent token, so it is not supported
|
|
10
|
+
// here yet (single account only).
|
|
11
|
+
//
|
|
12
|
+
// Dependency-free (no imports) so any @pikiloom/kernel consumer inherits account switching.
|
|
13
|
+
const ACCOUNT_TOKEN_ENV = {
|
|
14
|
+
claude: 'CLAUDE_CODE_OAUTH_TOKEN',
|
|
15
|
+
'claude-tui': 'CLAUDE_CODE_OAUTH_TOKEN',
|
|
16
|
+
};
|
|
17
|
+
/** Whether this agent's local accounts are switched by injecting a token. */
|
|
18
|
+
export function accountTokenSupported(agent) {
|
|
19
|
+
return Object.prototype.hasOwnProperty.call(ACCOUNT_TOKEN_ENV, agent);
|
|
20
|
+
}
|
|
21
|
+
/** The env-var name that overrides this agent's subscription identity, or null. */
|
|
22
|
+
export function accountTokenEnvVar(agent) {
|
|
23
|
+
return ACCOUNT_TOKEN_ENV[agent] ?? null;
|
|
24
|
+
}
|
|
25
|
+
/** Env to merge at spawn so the agent runs under the given account token. */
|
|
26
|
+
export function accountTokenEnv(agent, token) {
|
|
27
|
+
const key = accountTokenEnvVar(agent);
|
|
28
|
+
return key && token ? { [key]: token } : {};
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, DriverEvent, McpServerSpec } from '../contracts/driver.js';
|
|
2
|
+
export interface AcpDriverConfig {
|
|
3
|
+
id: string;
|
|
4
|
+
command: string;
|
|
5
|
+
args?: string[];
|
|
6
|
+
env?: Record<string, string>;
|
|
7
|
+
fsAccess?: boolean;
|
|
8
|
+
permissionFallback?: 'allow' | 'reject' | 'cancel';
|
|
9
|
+
protocolVersion?: number;
|
|
10
|
+
promptTimeoutMs?: number;
|
|
11
|
+
capabilities?: AgentDriver['capabilities'];
|
|
12
|
+
}
|
|
13
|
+
export declare function toAcpMcpServers(servers?: McpServerSpec[]): any[];
|
|
14
|
+
export declare function buildAcpPromptBlocks(prompt: string, attachments: string[]): any[];
|
|
15
|
+
export declare function applyAcpUpdate(update: any, s: any, tools: Set<string>, emit: (e: DriverEvent) => void): void;
|
|
16
|
+
export declare class AcpDriver implements AgentDriver {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly capabilities: NonNullable<AgentDriver['capabilities']>;
|
|
19
|
+
protected readonly cfg: Required<Omit<AcpDriverConfig, 'capabilities' | 'env'>> & Pick<AcpDriverConfig, 'env' | 'capabilities'>;
|
|
20
|
+
constructor(config: AcpDriverConfig);
|
|
21
|
+
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
22
|
+
private resolvePermission;
|
|
23
|
+
private startupError;
|
|
24
|
+
}
|