pikiloom 0.4.36 → 0.4.37

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.
@@ -773,6 +773,23 @@ function createCodexStreamState(opts) {
773
773
  generatingImages: 0,
774
774
  };
775
775
  }
776
+ // Codex on a ChatGPT-account login rejects non-OpenAI models with a raw upstream JSON blob
777
+ // (e.g. {"detail":"The 'deepseek-v4-pro' model is not supported when using Codex with a
778
+ // ChatGPT account."}). When a third-party model is correctly bound to a Provider profile the
779
+ // injector routes around this; this only fires when no binding exists, so turn the opaque
780
+ // upstream error into an actionable instruction instead of leaking JSON to the chat bubble.
781
+ export function humanizeCodexError(raw) {
782
+ if (!raw)
783
+ return null;
784
+ const match = raw.match(/The '([^']+)' model is not supported when using Codex with a ChatGPT account/i);
785
+ if (match) {
786
+ const model = match[1];
787
+ return `Codex 正在使用 ChatGPT 账号登录,无法运行第三方模型「${model}」。`
788
+ + `请在「智能体配置」页接入该模型的供应商(填入 Base URL 与 API Key),添加对应模型档案并绑定到 Codex,`
789
+ + `或改用 Codex 原生模型(如 gpt-5.5)。`;
790
+ }
791
+ return raw;
792
+ }
776
793
  function codexErrorResult(error, start, sessionId, model, thinkingEffort) {
777
794
  return {
778
795
  ok: false, message: error, thinking: null,
@@ -1119,7 +1136,7 @@ export async function doCodexStream(opts) {
1119
1136
  if (threadResp.error) {
1120
1137
  const errMsg = threadResp.error.message || 'thread/start failed';
1121
1138
  agentWarn(`[codex-rpc] thread error: ${errMsg}`);
1122
- return codexErrorResult(errMsg, start, opts.sessionId, opts.model, opts.thinkingEffort);
1139
+ return codexErrorResult(humanizeCodexError(errMsg) ?? errMsg, start, opts.sessionId, opts.model, opts.thinkingEffort);
1123
1140
  }
1124
1141
  const threadResult = threadResp.result;
1125
1142
  s.sessionId = threadResult.thread?.id ?? s.sessionId;
@@ -1210,7 +1227,7 @@ export async function doCodexStream(opts) {
1210
1227
  unsubscribeRequests();
1211
1228
  const errMsg = turnResp.error.message || 'turn/start failed';
1212
1229
  agentWarn(`[codex-rpc] turn/start error: ${errMsg}`);
1213
- return codexErrorResult(errMsg, start, s.sessionId, s.model, s.thinkingEffort);
1230
+ return codexErrorResult(humanizeCodexError(errMsg) ?? errMsg, start, s.sessionId, s.model, s.thinkingEffort);
1214
1231
  }
1215
1232
  s.turnId = turnResp.result?.turn?.id ?? null;
1216
1233
  publishTurnControl();
@@ -1226,7 +1243,7 @@ export async function doCodexStream(opts) {
1226
1243
  tryEmitCodexImageBlock(s, callId);
1227
1244
  }
1228
1245
  const ok = s.turnStatus === 'completed' && !timedOut && !interrupted;
1229
- const error = s.turnError
1246
+ const error = humanizeCodexError(s.turnError)
1230
1247
  || (interrupted ? 'Interrupted by user.' : null)
1231
1248
  || (timedOut ? `Timed out after ${opts.timeout}s waiting for turn completion.` : null)
1232
1249
  || (!ok ? `Turn ${s.turnStatus || 'unknown'}.` : null);
@@ -7,7 +7,7 @@ export { attachAgentImage, attachInlineImage, materializeImage, rewriteAttachmen
7
7
  export { deliverArtifact, readDeliveredArtifacts, deliveredArtifactBlocks, tailDeliveredBlocks, latestDeliveredTaskId, mimeForArtifact, } from './artifacts.js';
8
8
  export { Q, agentLog, agentWarn, agentError, dedupeStrings, numberOrNull, normalizeStreamPreviewPlan, parseTodoWriteAsPlan, normalizeActivityLine, pushRecentActivity, detectClaudeApiError, isRetryableClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, firstNonEmptyLine, shortValue, normalizeErrorMessage, joinErrorMessages, appendSystemPrompt, mimeForExt, computeContext, buildStreamPreviewMeta, summarizeClaudeToolUse, summarizeClaudeToolResult, previewToolCallInput, previewToolCallResult, roundPercent, toIsoFromEpochSeconds, normalizeUsageStatus, labelFromWindowMinutes, usageWindowFromRateLimit, parseJsonTail, modelFamily, normalizeClaudeModelId, emptyUsage, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, stripClaudeAtMentionImages, isPendingSessionId, emitSessionIdUpdate, sessionListDisplayTitle, } from './utils.js';
9
9
  export { updateSessionMeta, promoteSessionId, recordFork, resolveCanonicalSessionId, getSessionPromotions, listPikiloomSessions, findPikiloomSession, getSessionStoredConfig, ensureManagedSession, findManagedThreadSession, stageSessionFiles, mergeManagedAndNativeSessions, managedRecordToSessionInfo, getSessions, getSessionTail, getSessionMessages, applyTurnWindow, applyTurnFilter, classifySession, deriveUserStatus, exportSession, importSession, deleteAgentSession, isProcessAlive, isRunningSessionStale, reconcileOrphanedRunningSessions, } from './session.js';
10
- export { detectAgentBin, listAgents, resolveDefaultAgent, run, doStream, listModels, resolveAgentModels, getUsage, getAgentBoundModelId, setAgentBoundModelId, } from './stream.js';
10
+ export { detectAgentBin, listAgents, resolveDefaultAgent, run, doStream, recoverProfileIdForModel, listModels, resolveAgentModels, getUsage, getAgentBoundModelId, setAgentBoundModelId, } from './stream.js';
11
11
  export { registerDriver, getDriver, getDriverCapabilities, allDrivers, allDriverIds, hasDriver, shutdownAllDrivers, } from './driver.js';
12
12
  export { getProjectSkillPaths, initializeProjectSkills, listSkills, getGlobalSkillsRoot, collapseSkillPrompt, } from './skills.js';
13
13
  export { readGoal, writeGoal, clearGoal, setGoal, pauseGoal, resumeGoal, completeGoal, accountTurn, bumpContinuationCount, shouldContinueAfterTurn, renderContinuationPrompt, renderBudgetLimitPrompt, sessionGoalPath, DEFAULT_MAX_CONTINUATIONS, } from './goal.js';
@@ -19,6 +19,6 @@ export { getMcpToken, saveMcpToken, deleteMcpToken, hasValidMcpToken, startAutho
19
19
  export { installSkill, removeSkill, checkSkillUpdates, getGlobalSkillsDir, recordSkillInstall, getSkillLedgerEntry, forgetSkillInstall, normalizeSkillSourceKey, } from './skill-installer.js';
20
20
  export { getRecommendedClis, getRecommendedCli, detectCli, getCachedCliStatus, invalidateCliStatus, currentPlatform, getCliCatalog, refreshCliStatus, startCliAuthSession, getAuthSession, cancelAuthSession, applyCliToken, logoutCli, startCliInstallSession, } from './cli/index.js';
21
21
  export { doClaudeStream } from './drivers/claude.js';
22
- export { doCodexStream, buildCodexTurnInput, shutdownCodexServer, getCodexUsageLive } from './drivers/codex.js';
22
+ export { doCodexStream, buildCodexTurnInput, shutdownCodexServer, getCodexUsageLive, humanizeCodexError } from './drivers/codex.js';
23
23
  export { doGeminiStream } from './drivers/gemini.js';
24
24
  export { doHermesStream } from './drivers/hermes.js';
@@ -388,6 +388,35 @@ function finalizeStreamResult(result, workdir, prompt, session, workflowEnabled,
388
388
  saveSessionRecord(workdir, session.record);
389
389
  return { ...result, sessionId: session.sessionId, workspacePath: session.workspacePath };
390
390
  }
391
+ function requestedModelForAgent(opts) {
392
+ switch (opts.agent) {
393
+ case 'claude': return (opts.claudeModel || opts.model || '').trim();
394
+ case 'codex': return (opts.codexModel || opts.model || '').trim();
395
+ case 'gemini': return (opts.geminiModel || opts.model || '').trim();
396
+ case 'hermes': return (opts.hermesModel || opts.model || '').trim();
397
+ }
398
+ return (opts.model || '').trim();
399
+ }
400
+ // When a session carries a third-party model id but its profile binding was lost
401
+ // (stale/deleted profileId, or a model selected without a binding), resolveAgentInjection
402
+ // returns null and the agent silently falls back to its native account — e.g. Codex on a
403
+ // ChatGPT login rejects "deepseek-v4-pro". If exactly one configured profile (for a provider
404
+ // kind this agent accepts) declares that model id, the intent is unambiguous: recover it.
405
+ export function recoverProfileIdForModel(agent, modelId) {
406
+ const model = modelId.trim();
407
+ if (!model)
408
+ return null;
409
+ const accepted = new Set(getAcceptedProviderKinds(agent));
410
+ if (accepted.size === 0)
411
+ return null;
412
+ const matches = listProfiles().filter(profile => {
413
+ if (profile.modelId !== model)
414
+ return false;
415
+ const provider = getProvider(profile.providerId);
416
+ return !!provider && accepted.has(provider.kind);
417
+ });
418
+ return matches.length === 1 ? matches[0].id : null;
419
+ }
391
420
  export async function doStream(opts) {
392
421
  let session;
393
422
  let prepared;
@@ -446,7 +475,18 @@ export async function doStream(opts) {
446
475
  agentWarn(`[mcp] bridge start failed: ${e.message} — proceeding without MCP`);
447
476
  }
448
477
  try {
449
- const injection = await resolveAgentInjection(prepared.agent, prepared.profileId);
478
+ let injection = await resolveAgentInjection(prepared.agent, prepared.profileId);
479
+ if (!injection) {
480
+ const requestedModel = requestedModelForAgent(prepared);
481
+ const recoveredProfileId = recoverProfileIdForModel(prepared.agent, requestedModel);
482
+ if (recoveredProfileId) {
483
+ injection = await resolveAgentInjection(prepared.agent, recoveredProfileId);
484
+ if (injection) {
485
+ prepared.profileId = recoveredProfileId;
486
+ agentLog(`[byok] recovered lost provider binding: agent=${prepared.agent} model=${requestedModel} → profile=${recoveredProfileId}`);
487
+ }
488
+ }
489
+ }
450
490
  if (injection) {
451
491
  prepared.extraEnv = { ...(prepared.extraEnv || {}), ...injection.env };
452
492
  if (injection.modelOverride) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pikiloom",
3
- "version": "0.4.36",
3
+ "version": "0.4.37",
4
4
  "description": "Put the world's smartest AI agents in your pocket. Command local Claude & Gemini via IM. | 让最好用的 IM 变成你电脑上的顶级 Agent 控制台",
5
5
  "type": "module",
6
6
  "bin": {