pikiloom 0.4.13 → 0.4.15
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-CKoy_-w4.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-Du91b-sn.js → DirBrowser-DpbuN0OL.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-CV0rbtj2.js → ExtensionsTab-ymr7K8dU.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-BevAFdq9.js → IMAccessTab-CaTtCn3l.js} +1 -1
- package/dashboard/dist/assets/{Modal-DK1MkhKX.js → Modal-DA-9kJxp.js} +1 -1
- package/dashboard/dist/assets/Modals-BkLIRnNK.js +1 -0
- package/dashboard/dist/assets/Select-B0pZtuzF.js +1 -0
- package/dashboard/dist/assets/SessionPanel-CYQtZZNX.js +1 -0
- package/dashboard/dist/assets/{SystemTab-jafqMUsq.js → SystemTab-B9TcGMzc.js} +1 -1
- package/dashboard/dist/assets/codex-C6EwIzap.png +0 -0
- package/dashboard/dist/assets/deepseek-DOQzDJ-4.ico +0 -0
- package/dashboard/dist/assets/hermes-ClPe1RPI.png +0 -0
- package/dashboard/dist/assets/index-BCYshErN.js +3 -0
- package/dashboard/dist/assets/index-C5irxzzD.js +23 -0
- package/dashboard/dist/assets/logo-wordmark-B0Z6VgSZ.png +0 -0
- package/dashboard/dist/assets/logo-wordmark-light-D9FCWeOH.png +0 -0
- package/dashboard/dist/assets/playwright-GP3HuCap.ico +0 -0
- package/dashboard/dist/assets/qwen-DKVAROae.png +0 -0
- package/dashboard/dist/assets/shared-i_XUH0xm.js +1 -0
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/logo.png +0 -0
- package/dist/agent/auto-update.js +99 -4
- package/dist/agent/drivers/claude.js +6 -26
- package/dist/agent/drivers/codex.js +4 -26
- package/dist/agent/drivers/gemini.js +4 -26
- package/dist/agent/drivers/hermes.js +4 -26
- package/dist/agent/index.js +1 -1
- package/dist/agent/mcp/bridge.js +53 -2
- package/dist/agent/session.js +16 -3
- package/dist/agent/stream.js +37 -3
- package/dist/bot/bot.js +18 -5
- package/dist/channels/telegram/bot.js +2 -2
- package/dist/channels/telegram/render.js +47 -1
- package/dist/core/constants.js +8 -0
- package/dist/dashboard/routes/extensions.js +6 -0
- package/dist/dashboard/routes/models.js +9 -1
- package/dist/dashboard/routes/sessions.js +25 -0
- package/dist/dashboard/server.js +8 -0
- package/dist/model/index.js +1 -1
- package/dist/model/injector.js +209 -28
- package/dist/model/responses-bridge.js +407 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/AgentTab-DJ2MSY9m.js +0 -1
- package/dashboard/dist/assets/Modals-UEF0H1UN.js +0 -1
- package/dashboard/dist/assets/Select-YrnugZXH.js +0 -1
- package/dashboard/dist/assets/SessionPanel-DbSdD2Jt.js +0 -1
- package/dashboard/dist/assets/codex-DYadqqp0.png +0 -0
- package/dashboard/dist/assets/deepseek-BeYNZEk0.ico +0 -0
- package/dashboard/dist/assets/hermes-BAarh-tH.png +0 -0
- package/dashboard/dist/assets/index-BnTrNACS.js +0 -23
- package/dashboard/dist/assets/index-SkDflrDp.js +0 -3
- package/dashboard/dist/assets/logo-wordmark-FzeBAUsd.png +0 -0
- package/dashboard/dist/assets/logo-wordmark-light-snSpARTN.png +0 -0
- package/dashboard/dist/assets/playwright-BldPFZgC.ico +0 -0
- package/dashboard/dist/assets/qwen-xykkX0_y.png +0 -0
- package/dashboard/dist/assets/shared-BpcXDkDP.js +0 -1
package/dist/model/injector.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* = adding one entry to AGENT_INJECT_TABLE.
|
|
8
8
|
*/
|
|
9
9
|
import { resolveCredential } from '../core/secrets/index.js';
|
|
10
|
+
import { writeScopedLog } from '../core/logging.js';
|
|
10
11
|
import { getActiveProfile, getProvider } from './store.js';
|
|
11
12
|
import { peekProviderModelInfo, prefetchProviderModels } from './provider-models.js';
|
|
13
|
+
import { ensureResponsesBridge, upstreamToken } from './responses-bridge.js';
|
|
12
14
|
const EMPTY = { env: {}, argvAppend: [], detail: '' };
|
|
13
15
|
// ---------------------------------------------------------------------------
|
|
14
16
|
// Shared host-based provider identification
|
|
@@ -53,7 +55,13 @@ function providerSlug(provider) {
|
|
|
53
55
|
return 'doubao';
|
|
54
56
|
if (host.includes('openrouter'))
|
|
55
57
|
return 'openrouter';
|
|
56
|
-
|
|
58
|
+
// Unknown host: derive a stable slug from the hostname's leading label. (The
|
|
59
|
+
// old `return 'openrouter'` fallback mis-slugged every unrecognised provider —
|
|
60
|
+
// including localhost Ollama — as openrouter.) This never collides with
|
|
61
|
+
// codex's reserved built-in `openai`/`oss`/`ollama` ids, which are routed
|
|
62
|
+
// before we ever reach providerSlug.
|
|
63
|
+
const label = host.replace(/:\d+$/, '').replace(/^(www|api)\./, '').split('.')[0].replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
64
|
+
return label || 'byok';
|
|
57
65
|
}
|
|
58
66
|
/**
|
|
59
67
|
* Canonical env-var name(s) carrying the credential for a provider. Returned
|
|
@@ -152,6 +160,24 @@ function claudeAnthropicBaseURL(provider) {
|
|
|
152
160
|
}
|
|
153
161
|
return raw.replace(/\/v1$/, '');
|
|
154
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* First-party Anthropic = the official API host (`api.anthropic.com` / any
|
|
165
|
+
* `*.anthropic.com`). A Claude route counts as "direct" when it lands here —
|
|
166
|
+
* both the subscription path and an own-key BYOK profile pointed at
|
|
167
|
+
* api.anthropic.com. Everything else (OpenRouter, DeepSeek, domestic series, a
|
|
168
|
+
* self-hosted relay, localhost) is a third-party proxy. Unparseable → treat as
|
|
169
|
+
* proxy (safe default: suppressing attribution is harmless, churning isn't).
|
|
170
|
+
*/
|
|
171
|
+
function isFirstPartyAnthropic(baseURL) {
|
|
172
|
+
let host;
|
|
173
|
+
try {
|
|
174
|
+
host = new URL(baseURL).hostname.toLowerCase();
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return host === 'anthropic.com' || host.endsWith('.anthropic.com');
|
|
180
|
+
}
|
|
155
181
|
/**
|
|
156
182
|
* Claude Code respects `ANTHROPIC_BASE_URL` + `ANTHROPIC_API_KEY` (or
|
|
157
183
|
* `ANTHROPIC_AUTH_TOKEN`) as a BYOK route. The CLI itself is unchanged.
|
|
@@ -170,50 +196,200 @@ const claudeInjector = (provider, profile, apiKey) => {
|
|
|
170
196
|
detail: `Claude BYOK requires Anthropic or OpenAI-compatible (Anthropic-API-shaped) provider; got ${provider.kind}.`,
|
|
171
197
|
};
|
|
172
198
|
}
|
|
199
|
+
const baseURL = claudeAnthropicBaseURL(provider);
|
|
200
|
+
const env = {
|
|
201
|
+
ANTHROPIC_BASE_URL: baseURL,
|
|
202
|
+
ANTHROPIC_API_KEY: apiKey,
|
|
203
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
204
|
+
};
|
|
205
|
+
// Claude Code >= 2.1.36 stamps a per-request `x-anthropic-billing-header`
|
|
206
|
+
// (cc_version / cc_entrypoint / cch=… — the cch token churns every turn).
|
|
207
|
+
// Third-party proxies (OpenRouter, DeepSeek /anthropic, domestic series, any
|
|
208
|
+
// OpenAI-compat or self-hosted Anthropic-shaped front) often key their
|
|
209
|
+
// prefix/KV cache on request headers, so the churn forces a full prompt
|
|
210
|
+
// reprocess every turn — slow and expensive. `0` makes claude omit the header
|
|
211
|
+
// (env-bool: 0/false/no/off). Only on proxy routes: first-party Anthropic
|
|
212
|
+
// (api.anthropic.com — subscription OR own-key direct) is left exactly as
|
|
213
|
+
// shipped; its cache is content/breakpoint based, so attribution is irrelevant
|
|
214
|
+
// there and we don't touch it.
|
|
215
|
+
if (!isFirstPartyAnthropic(baseURL)) {
|
|
216
|
+
env.CLAUDE_CODE_ATTRIBUTION_HEADER = '0';
|
|
217
|
+
}
|
|
173
218
|
return {
|
|
174
|
-
env
|
|
175
|
-
ANTHROPIC_BASE_URL: claudeAnthropicBaseURL(provider),
|
|
176
|
-
ANTHROPIC_API_KEY: apiKey,
|
|
177
|
-
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
178
|
-
},
|
|
219
|
+
env,
|
|
179
220
|
argvAppend: [],
|
|
180
221
|
modelOverride: profile.modelId,
|
|
181
222
|
detail: `Claude BYOK → ${provider.name} / ${profile.modelId}`,
|
|
182
223
|
};
|
|
183
224
|
};
|
|
225
|
+
function providerHostname(provider) {
|
|
226
|
+
try {
|
|
227
|
+
return new URL(provider.baseURL).hostname.toLowerCase();
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return '';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** True for localhost endpoints (Ollama / LM Studio / llama.cpp). */
|
|
234
|
+
function isLocalProvider(provider) {
|
|
235
|
+
const h = providerHostname(provider);
|
|
236
|
+
return h === 'localhost' || h === '127.0.0.1' || h === '0.0.0.0' || h === '::1';
|
|
237
|
+
}
|
|
238
|
+
/** Providers that natively implement the OpenAI Responses API (codex talks to them directly). */
|
|
239
|
+
function isResponsesNativeProvider(provider) {
|
|
240
|
+
return providerHost(provider).includes('openrouter');
|
|
241
|
+
}
|
|
242
|
+
/** codex's built-in local provider id for a localhost endpoint. */
|
|
243
|
+
function codexLocalProvider(provider) {
|
|
244
|
+
let port = '';
|
|
245
|
+
try {
|
|
246
|
+
port = new URL(provider.baseURL).port;
|
|
247
|
+
}
|
|
248
|
+
catch { /* ignore */ }
|
|
249
|
+
if (port === '1234' || /lm\s*studio/i.test(provider.name))
|
|
250
|
+
return 'lmstudio';
|
|
251
|
+
return 'ollama';
|
|
252
|
+
}
|
|
253
|
+
/** Ollama keeps a prewarmed model resident for this long (its `keep_alive`). */
|
|
254
|
+
const PREWARM_KEEP_ALIVE = '30m';
|
|
255
|
+
/**
|
|
256
|
+
* Warm a localhost model backend so the user's first real turn doesn't pay the
|
|
257
|
+
* model cold-load (weights → memory). Fire-and-forget: never blocks the caller,
|
|
258
|
+
* never throws.
|
|
259
|
+
*
|
|
260
|
+
* - Ollama has a native load endpoint — `POST /api/generate {model, keep_alive}`
|
|
261
|
+
* with no prompt loads the weights and returns immediately; `keep_alive`
|
|
262
|
+
* keeps them resident across the seed + real turns of a session.
|
|
263
|
+
* - LM Studio JIT-loads on first request, so we nudge it with a 1-token
|
|
264
|
+
* completion against its OpenAI-compatible endpoint.
|
|
265
|
+
*
|
|
266
|
+
* Called when a local Profile is bound (warm while the user reads / types) and
|
|
267
|
+
* again at spawn (re-assert keep_alive). Measured: a cold gemma3:4b spent ~12s
|
|
268
|
+
* before its first token; prewarmed, generation starts in ~2s.
|
|
269
|
+
*/
|
|
270
|
+
export function prewarmLocalModel(provider, modelId) {
|
|
271
|
+
if (!modelId || !isLocalProvider(provider))
|
|
272
|
+
return;
|
|
273
|
+
let origin;
|
|
274
|
+
try {
|
|
275
|
+
origin = new URL(provider.baseURL).origin;
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const swallow = () => { };
|
|
281
|
+
if (codexLocalProvider(provider) === 'lmstudio') {
|
|
282
|
+
void fetch(`${origin}/v1/chat/completions`, {
|
|
283
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
284
|
+
body: JSON.stringify({ model: modelId, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] }),
|
|
285
|
+
}).then(swallow, swallow);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
void fetch(`${origin}/api/generate`, {
|
|
289
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
290
|
+
body: JSON.stringify({ model: modelId, keep_alive: PREWARM_KEEP_ALIVE }),
|
|
291
|
+
}).then(r => { writeScopedLog('model-prewarm', `ollama load ${modelId} → ${r.status}`); }, e => { writeScopedLog('model-prewarm', `ollama load ${modelId} failed: ${e?.message || e}`, { level: 'warn', stream: 'stderr' }); });
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Decide how codex should reach a provider. Codex 0.140+ speaks ONLY the
|
|
295
|
+
* Responses API, so the route depends on what the provider implements:
|
|
296
|
+
* openai-native genuine OpenAI → built-in `openai` provider
|
|
297
|
+
* local-oss localhost Ollama/LMStudio → built-in `ollama`/`lmstudio` (responses)
|
|
298
|
+
* responses-native OpenRouter, … → custom provider, responses direct
|
|
299
|
+
* bridge chat-only (DeepSeek, Kimi, MiniMax, 豆包, Qwen, Zhipu, …)
|
|
300
|
+
* → local Responses↔Chat bridge
|
|
301
|
+
*/
|
|
302
|
+
function codexRoute(provider) {
|
|
303
|
+
if (provider.kind === 'openai')
|
|
304
|
+
return 'openai-native';
|
|
305
|
+
if (isLocalProvider(provider))
|
|
306
|
+
return 'local-oss';
|
|
307
|
+
if (isResponsesNativeProvider(provider))
|
|
308
|
+
return 'responses-native';
|
|
309
|
+
return 'bridge';
|
|
310
|
+
}
|
|
184
311
|
/**
|
|
185
|
-
* Codex CLI honours `model_providers.<slug>` definitions in `config.toml
|
|
186
|
-
*
|
|
187
|
-
* the
|
|
188
|
-
* one-shot `model_providers.<slug>` via `-c` overrides and bind it via
|
|
189
|
-
* `model_provider="<slug>"`. The credential lives in the env var named by
|
|
190
|
-
* `env_key`, picked host-aware (e.g. `OPENROUTER_API_KEY` for openrouter.ai).
|
|
312
|
+
* Codex CLI honours `model_providers.<slug>` definitions in `config.toml` and
|
|
313
|
+
* binds the active one via `model_provider="<slug>"`. The credential lives in
|
|
314
|
+
* the env var named by `env_key`, picked host-aware (e.g. `DEEPSEEK_API_KEY`).
|
|
191
315
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
316
|
+
* Codex 0.140+ dropped Chat Completions (`wire_api = "chat"` is rejected at
|
|
317
|
+
* config load) — it speaks ONLY the Responses API. So this injector routes per
|
|
318
|
+
* `codexRoute()`: responses-capable providers (OpenAI, OpenRouter, local
|
|
319
|
+
* Ollama/LM Studio) are reached directly with the default `responses` wire;
|
|
320
|
+
* chat-only providers (DeepSeek and the domestic series) are routed through the
|
|
321
|
+
* in-process Responses↔Chat bridge, which codex sees as just another
|
|
322
|
+
* responses-speaking provider on localhost.
|
|
195
323
|
*/
|
|
196
|
-
const codexInjector = (provider, profile, apiKey) => {
|
|
324
|
+
const codexInjector = async (provider, profile, apiKey) => {
|
|
197
325
|
if (provider.kind !== 'openai' && provider.kind !== 'openai-compatible') {
|
|
198
326
|
return {
|
|
199
327
|
...EMPTY,
|
|
200
|
-
detail: `Codex BYOK requires OpenAI-compatible provider; got ${provider.kind}.`,
|
|
328
|
+
detail: `Codex BYOK requires an OpenAI-compatible provider; got ${provider.kind}.`,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const model = profile.modelId;
|
|
332
|
+
const route = codexRoute(provider);
|
|
333
|
+
// Local Ollama / LM Studio: codex's built-in provider already speaks the
|
|
334
|
+
// Responses API to the local server. Just select it — no custom provider, no
|
|
335
|
+
// API key. (Defining `model_providers.<built-in>` is rejected: "Built-in
|
|
336
|
+
// providers cannot be overridden.")
|
|
337
|
+
if (route === 'local-oss') {
|
|
338
|
+
const local = codexLocalProvider(provider);
|
|
339
|
+
prewarmLocalModel(provider, model);
|
|
340
|
+
return {
|
|
341
|
+
env: {}, argvAppend: [],
|
|
342
|
+
codexConfigOverrides: [`model_provider="${local}"`],
|
|
343
|
+
modelOverride: model,
|
|
344
|
+
detail: `Codex local → ${provider.name} / ${model} (built-in ${local}, responses)`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// Genuine OpenAI: use the built-in `openai` provider; inject the key (+ base).
|
|
348
|
+
if (route === 'openai-native') {
|
|
349
|
+
const env = { OPENAI_API_KEY: apiKey };
|
|
350
|
+
if (provider.baseURL)
|
|
351
|
+
env.OPENAI_BASE_URL = provider.baseURL;
|
|
352
|
+
return {
|
|
353
|
+
env, argvAppend: [],
|
|
354
|
+
codexConfigOverrides: ['model_provider="openai"'],
|
|
355
|
+
modelOverride: model,
|
|
356
|
+
detail: `Codex BYOK → OpenAI / ${model}`,
|
|
201
357
|
};
|
|
202
358
|
}
|
|
203
359
|
const slug = providerSlug(provider);
|
|
204
360
|
const envKey = codexEnvKey(provider);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
361
|
+
// Chat-only providers: route through the local Responses↔Chat bridge. Codex
|
|
362
|
+
// forwards `Authorization: Bearer <key>` (from env_key) to the bridge, which
|
|
363
|
+
// relays it to the upstream chat endpoint — the bridge never stores secrets.
|
|
364
|
+
if (route === 'bridge') {
|
|
365
|
+
const port = await ensureResponsesBridge();
|
|
366
|
+
const base = `http://127.0.0.1:${port}/u/${upstreamToken(provider.baseURL)}`;
|
|
367
|
+
return {
|
|
368
|
+
env: { [envKey]: apiKey },
|
|
369
|
+
argvAppend: [],
|
|
370
|
+
codexConfigOverrides: [
|
|
371
|
+
`model_providers.${slug}.name="${tomlEscape(provider.name)}"`,
|
|
372
|
+
`model_providers.${slug}.base_url="${tomlEscape(base)}"`,
|
|
373
|
+
`model_providers.${slug}.env_key="${envKey}"`,
|
|
374
|
+
`model_provider="${slug}"`,
|
|
375
|
+
],
|
|
376
|
+
modelOverride: model,
|
|
377
|
+
detail: `Codex BYOK → ${provider.name} / ${model} via Responses↔Chat bridge (provider=${slug})`,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
// responses-native (OpenRouter, …): point codex straight at the provider's
|
|
381
|
+
// Responses endpoint (wire_api omitted ⇒ codex default `responses`).
|
|
211
382
|
return {
|
|
212
383
|
env: { [envKey]: apiKey },
|
|
213
384
|
argvAppend: [],
|
|
214
|
-
codexConfigOverrides:
|
|
215
|
-
|
|
216
|
-
|
|
385
|
+
codexConfigOverrides: [
|
|
386
|
+
`model_providers.${slug}.name="${tomlEscape(provider.name)}"`,
|
|
387
|
+
`model_providers.${slug}.base_url="${tomlEscape(provider.baseURL)}"`,
|
|
388
|
+
`model_providers.${slug}.env_key="${envKey}"`,
|
|
389
|
+
`model_provider="${slug}"`,
|
|
390
|
+
],
|
|
391
|
+
modelOverride: model,
|
|
392
|
+
detail: `Codex BYOK → ${provider.name} / ${model} (provider=${slug}, native responses)`,
|
|
217
393
|
};
|
|
218
394
|
};
|
|
219
395
|
/** Gemini CLI accepts `GEMINI_API_KEY` but does not allow custom baseURL. */
|
|
@@ -289,12 +465,17 @@ export async function resolveAgentInjection(agentId) {
|
|
|
289
465
|
const injector = AGENT_INJECT_TABLE[agentId];
|
|
290
466
|
if (!injector)
|
|
291
467
|
return null;
|
|
292
|
-
|
|
468
|
+
// Local providers (Ollama / LM Studio / llama.cpp) need no credential — codex
|
|
469
|
+
// reaches them via its built-in localhost provider with no auth. Don't let a
|
|
470
|
+
// missing/placeholder key block an otherwise-valid local binding.
|
|
471
|
+
let apiKey = '';
|
|
293
472
|
try {
|
|
294
473
|
apiKey = await resolveCredential(provider.credential);
|
|
295
474
|
}
|
|
296
475
|
catch (e) {
|
|
297
|
-
|
|
476
|
+
if (!isLocalProvider(provider)) {
|
|
477
|
+
throw new Error(`Failed to resolve credential for ${provider.name}: ${e?.message || e}`);
|
|
478
|
+
}
|
|
298
479
|
}
|
|
299
480
|
const result = await injector(provider, profile, apiKey);
|
|
300
481
|
// Attach the provider display name so renders can surface "via <provider>"
|