larkin 0.2.85 → 0.2.87

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.
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.PI_PROVIDER_PRESETS = exports.BUNDLED_PI_VERSION = void 0;
7
7
  exports.validatePiBaseUrl = validatePiBaseUrl;
8
+ exports.listProviderModels = listProviderModels;
8
9
  exports.resolveBuiltinPiProviderSetupSelection = resolveBuiltinPiProviderSetupSelection;
9
10
  exports.validateBuiltinPiProviderSelection = validateBuiltinPiProviderSelection;
10
11
  exports.piAgentDirectory = piAgentDirectory;
@@ -62,6 +63,23 @@ function validatePiBaseUrl(raw) {
62
63
  }
63
64
  return url.toString().replace(/\/$/, "");
64
65
  }
66
+ /**
67
+ * 查询 OpenAI 兼容 provider 的 /models 列表(setup 时校验 --model 是否真实可用)。
68
+ * 失败即抛错:setup 不应该接受一个没有依据的模型名。
69
+ */
70
+ async function listProviderModels(baseUrl, apiKey, fetchImpl = fetch) {
71
+ const url = `${validatePiBaseUrl(baseUrl)}/models`;
72
+ const response = await fetchImpl(url, {
73
+ headers: { Authorization: `Bearer ${apiKey}` },
74
+ signal: AbortSignal.timeout(15_000),
75
+ });
76
+ if (!response.ok)
77
+ throw new Error(`provider /models 查询失败:HTTP ${response.status}`);
78
+ const payload = await response.json();
79
+ if (!payload || !Array.isArray(payload.data))
80
+ throw new Error("provider /models 响应格式非法");
81
+ return payload.data.map((entry) => String(entry.id ?? "")).filter(Boolean);
82
+ }
65
83
  function resolveBuiltinPiProviderSetupSelection(input) {
66
84
  if (input.distribution !== "builtin")
67
85
  throw new Error("provider 凭证只能用于内置 Pi");
@@ -49,6 +49,23 @@ export function validatePiBaseUrl(raw) {
49
49
  }
50
50
  return url.toString().replace(/\/$/, "");
51
51
  }
52
+ /**
53
+ * 查询 OpenAI 兼容 provider 的 /models 列表(setup 时校验 --model 是否真实可用)。
54
+ * 失败即抛错:setup 不应该接受一个没有依据的模型名。
55
+ */
56
+ export async function listProviderModels(baseUrl, apiKey, fetchImpl = fetch) {
57
+ const url = `${validatePiBaseUrl(baseUrl)}/models`;
58
+ const response = await fetchImpl(url, {
59
+ headers: { Authorization: `Bearer ${apiKey}` },
60
+ signal: AbortSignal.timeout(15_000),
61
+ });
62
+ if (!response.ok)
63
+ throw new Error(`provider /models 查询失败:HTTP ${response.status}`);
64
+ const payload = await response.json();
65
+ if (!payload || !Array.isArray(payload.data))
66
+ throw new Error("provider /models 响应格式非法");
67
+ return payload.data.map((entry) => String(entry.id ?? "")).filter(Boolean);
68
+ }
52
69
  export function resolveBuiltinPiProviderSetupSelection(input) {
53
70
  if (input.distribution !== "builtin")
54
71
  throw new Error("provider 凭证只能用于内置 Pi");
@@ -850,8 +850,16 @@ async function createPiRpcBackend(input, dependencies, spawn) {
850
850
  if (!available?.models?.length)
851
851
  throw new Error("Pi has no authenticated available models. Run the official `pi` login flow or configure provider credentials; Larkin will not create a fallback session.");
852
852
  const effectiveModel = state.model?.provider && state.model.id ? `${state.model.provider}/${state.model.id}` : null;
853
- if (requestedModel && effectiveModel !== requestedModel)
854
- throw new Error(`Pi model fallback refused: requested ${requestedModel}, effective ${effectiveModel || "none"}`);
853
+ // 严格对账(Owner 决策):模型必须来自 pi 的权威可用列表,不做后缀猜测——
854
+ // 配置的模型与 pi 可用列表不符即报错,并在错误里列出可用模型。
855
+ const availableEntries = (available.models ?? []).map((entry) => ({
856
+ provider: String(entry.provider ?? ""), id: String(entry.id ?? ""),
857
+ }));
858
+ const matchesAvailable = (candidate) => availableEntries.some((entry) => entry.id === candidate || (entry.provider ? `${entry.provider}/${entry.id}` === candidate : false));
859
+ if (requestedModel && effectiveModel !== requestedModel && !matchesAvailable(requestedModel)) {
860
+ const availableText = availableEntries.slice(0, 10).map((entry) => entry.provider ? `${entry.provider}/${entry.id}` : entry.id).join(", ");
861
+ throw new Error(`Pi model fallback refused: requested ${requestedModel} is not in Pi's available models${availableText ? ` (${availableText})` : ""}`);
862
+ }
855
863
  if (requestedEffort && state.thinkingLevel !== requestedEffort)
856
864
  throw new Error(`Pi thinking level ${requestedEffort} was not accepted by effective model ${effectiveModel || "unknown"}`);
857
865
  return new PiRpcBackend(client, state);
@@ -14,7 +14,7 @@ import { managedLarkCliEnv } from "../app/agent-lark-cli-workspace.mjs";
14
14
  import { resolveOfficialLarkCli } from "../app/official-lark-cli.mjs";
15
15
  import { collectSetupAgentChoice, recoverUnavailableExternalPi, terminalSetupQuestioner } from "./setup-agent-choice.mjs";
16
16
  import { probeNativeRuntimeReadiness } from "../runtime/runtime-readiness.mjs";
17
- import { configureBuiltinPiProviderModel, stageBuiltinPiProvider, validatePiBaseUrl, PI_PROVIDER_PRESETS } from "../runtime/pi-provider-config.mjs";
17
+ import { configureBuiltinPiProviderModel, stageBuiltinPiProvider, validatePiBaseUrl, listProviderModels, PI_PROVIDER_PRESETS } from "../runtime/pi-provider-config.mjs";
18
18
  import { beginBuiltinPiCredentialTransaction, createOfficialPiCredentialRuntime, createOfficialPiAuthInteraction, createOfficialPiModelRuntime, createOfficialPiLogoutRuntime, createOfficialPiRegistryRuntime, listOfficialPiAuthProviders, logoutOfficialPiProvider, officialPiAuthStatus, runOfficialPiLogin, verifyOfficialPiProviderTurn, } from "../runtime/pi-official-auth.mjs";
19
19
  import { documentCommentSubscriptionCapability, markDocumentCommentSubscriptionVerified, } from "../platform/callback-capability.mjs";
20
20
  // qrcode-terminal does not publish TypeScript declarations.
@@ -610,6 +610,15 @@ export async function main() {
610
610
  model: setupModel ?? presetDef?.defaultModel ?? "",
611
611
  ...(presetDef ? { baseUrl: presetDef.baseUrl } : setupBaseUrl ? { baseUrl: validatePiBaseUrl(setupBaseUrl) } : {}),
612
612
  };
613
+ if (setupModel && raw.baseUrl) {
614
+ // Owner 决策:模型名必须来自 provider 的权威可用列表,输错即报错,不做运行时猜测。
615
+ const availableIds = await listProviderModels(raw.baseUrl, setupApiKey);
616
+ const matched = availableIds.some((id) => id === setupModel || id.endsWith(`/${setupModel}`));
617
+ if (!matched) {
618
+ const preview = availableIds.slice(0, 12).join(", ") + (availableIds.length > 12 ? ", …" : "");
619
+ throw new Error(`未知模型 ${setupModel};provider 可用模型:[${preview || "无"}]`);
620
+ }
621
+ }
613
622
  stageBuiltinPiProvider(CFG_DIR, id, { ...raw, apiKey: setupApiKey });
614
623
  temporaryAgentChoiceFile = path.join(CFG_DIR, `.setup-agent-choice-${process.pid}-${Date.now()}.json`);
615
624
  fs.writeFileSync(temporaryAgentChoiceFile, `${JSON.stringify({ ...raw, runtime: "pi", authCompleted: true, readinessCompleted: true })}\n`, { mode: 0o600, flag: "wx" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkin",
3
- "version": "0.2.85",
3
+ "version": "0.2.87",
4
4
  "private": false,
5
5
  "larkinPackageRole": "npm-published",
6
6
  "files": [