laohuang 0.9.6 → 0.9.7

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/README.md CHANGED
@@ -33,7 +33,7 @@ npm install -g laohuang
33
33
  ```bash
34
34
  laohuang --help
35
35
  laohuang --version
36
- laohuang config set --provider deepseek --model deepseek-v4-flash
36
+ laohuang config set --provider deepseek --model deepseek-flash
37
37
  laohuang doctor
38
38
  laohuang update
39
39
  ```
@@ -42,9 +42,10 @@ Run `laohuang` in a project directory to start an interactive session. The CLI
42
42
  loads local project instructions, uses saved provider profiles, and keeps file
43
43
  and shell tool execution rooted in the current project.
44
44
 
45
- Model providers come from the installed pi-ai API-key catalog. In pi-ai 0.83.0
46
- the product exposes 35 available API-key providers, excluding Amazon Bedrock,
47
- Google Vertex, OAuth-only providers, and OpenAI Codex. Use `/providers` to see
45
+ Model providers come from the installed pi-ai API-key catalog. In pi-ai 1.0.0
46
+ the product exposes 38 available chat API-key providers, excluding Amazon Bedrock,
47
+ Google Vertex, OAuth-only providers, OpenAI Codex, and operation-only catalogs.
48
+ `deepseek-flash` is the built-in DeepSeek V4.1 Flash model. Use `/providers` to see
48
49
  available, configured, and verified status independently.
49
50
 
50
51
  `/login <provider>` stores API-key credentials, `/logout <provider>` removes
package/dist/bin.js CHANGED
@@ -9105,7 +9105,7 @@ function validateToolPairs(selected) {
9105
9105
  }
9106
9106
 
9107
9107
  // ../../packages/session/session-context/dist/summary-prompt.js
9108
- var COMPACTION_SYSTEM_PROMPT = "Summarize the conversation for continuation. Preserve user goals, explicit constraints, completed work, modified files, architectural decisions, tool outcomes, failures, current TODOs, and concrete state needed to continue. Do not include credentials. Do not invent results.";
9108
+ var COMPACTION_SYSTEM_PROMPT = "Summarize the conversation for continuation. If the input contains <previous_summary>, merge and update it with the newer conversation instead of discarding it. Preserve user goals, explicit constraints, completed work, modified files, architectural decisions, tool outcomes, failures, current TODOs, and concrete state needed to continue. Do not include credentials. Do not invent results.";
9109
9109
 
9110
9110
  // ../../packages/session/session-context/dist/compaction.js
9111
9111
  function selectCompactionPlan(input) {
@@ -9225,6 +9225,8 @@ var ContextBudgetError = class extends Error {
9225
9225
  this.name = "ContextBudgetError";
9226
9226
  }
9227
9227
  };
9228
+ var NoCompactionAvailableError = class extends Error {
9229
+ };
9228
9230
  var ContextGovernor = class {
9229
9231
  #builder;
9230
9232
  #estimator;
@@ -9245,7 +9247,17 @@ var ContextGovernor = class {
9245
9247
  throw new ContextBudgetError("context exceeds hard input limit");
9246
9248
  return { built, messages: built.messages, compacted: false, tokens };
9247
9249
  }
9248
- const result = await this.compact({ ...input, trigger: "automatic" });
9250
+ let result;
9251
+ try {
9252
+ result = await this.compact({ ...input, trigger: "automatic" });
9253
+ } catch (error) {
9254
+ if (!(error instanceof NoCompactionAvailableError))
9255
+ throw error;
9256
+ if (tokens > calculated.hardInputLimit) {
9257
+ throw new ContextBudgetError("context exceeds hard input limit with no earlier messages to compact");
9258
+ }
9259
+ return { built, messages: built.messages, compacted: false, tokens };
9260
+ }
9249
9261
  const entries = [...input.entries, result.entry];
9250
9262
  built = this.#builder.build({ ...input, entries });
9251
9263
  tokens = this.measure({ ...input, entries }, built);
@@ -9256,9 +9268,13 @@ var ContextGovernor = class {
9256
9268
  }
9257
9269
  async compact(input) {
9258
9270
  const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
9271
+ const built = this.#builder.build(input);
9272
+ const activeCompaction = input.entries.find((entry) => entry.id === built.activeCompactionId && entry.entryType === "compaction");
9273
+ const activeSourceEntryIds = new Set(built.sourceEntryIds);
9274
+ const projectedEntries = activeCompaction === void 0 ? input.entries : input.entries.filter((entry) => entry.id !== activeCompaction.id && activeSourceEntryIds.has(entry.id));
9259
9275
  const plan = selectCompactionPlan({
9260
9276
  protectedFromSeq: input.protectedFromSeq,
9261
- entries: input.entries,
9277
+ entries: projectedEntries,
9262
9278
  // A search preflight reserves the pending call/result/definition unit.
9263
9279
  // Keep the latest completed unit and summarize older units to make room.
9264
9280
  retainTokens: input.reserveTokens === void 0 ? calculated.retainTokens : 1,
@@ -9270,19 +9286,14 @@ var ContextGovernor = class {
9270
9286
  if (input.trigger === "provider_overflow") {
9271
9287
  throw new ContextBudgetError("No earlier messages to compact after provider overflow.");
9272
9288
  }
9289
+ throw new NoCompactionAvailableError();
9273
9290
  }
9274
- const summarizedFromSeq = plan.summarizedEntries[0]?.seq ?? 1;
9275
- const summarizedThroughSeq = plan.summarizedEntries.at(-1)?.seq ?? Math.max(0, summarizedFromSeq - 1);
9276
- const built = this.#builder.build(input);
9277
- const activeCompaction = input.entries.find((entry) => entry.id === built.activeCompactionId);
9291
+ const summarizedFromSeq = activeCompaction?.payload.summarizedFromSeq ?? plan.summarizedEntries[0]?.seq ?? 1;
9292
+ const summarizedThroughSeq = plan.summarizedEntries.at(-1)?.seq ?? activeCompaction?.payload.summarizedThroughSeq ?? Math.max(0, summarizedFromSeq - 1);
9278
9293
  const messagesBefore = built.messages;
9279
9294
  const tokensBefore = this.#estimator.estimateMessages(messagesBefore) + this.#estimator.estimateTools(input.projectTools?.(messagesBefore) ?? input.tools);
9280
- const summary = plan.summarizedEntries.length === 0 ? {
9281
- summary: "No prior conversation needed compaction.",
9282
- inputTokens: 0,
9283
- outputTokens: 0
9284
- } : await this.#summarize({
9285
- serialized: serializeConversation(plan.summarizedEntries),
9295
+ const summary = await this.#summarize({
9296
+ serialized: serializeCompactionInput(activeCompaction?.payload.summary, plan.summarizedEntries),
9286
9297
  maxSummaryTokens: input.policy.maxSummaryTokens
9287
9298
  });
9288
9299
  const payload = {
@@ -9332,6 +9343,15 @@ var ContextGovernor = class {
9332
9343
  }).totalTokens + (input.reserveTokens ?? 0);
9333
9344
  }
9334
9345
  };
9346
+ function serializeCompactionInput(previousSummary, entries) {
9347
+ const conversation = serializeConversation(entries);
9348
+ if (previousSummary === void 0)
9349
+ return conversation;
9350
+ return `<previous_summary>
9351
+ ${previousSummary}
9352
+ </previous_summary>
9353
+ ${conversation}`;
9354
+ }
9335
9355
  function calculateModelBudget(input) {
9336
9356
  const hardInputLimit = input.budget.contextWindow - input.budget.maxOutputTokens;
9337
9357
  const safetyTokens = Math.ceil(input.budget.contextWindow * input.policy.safetyRatio);
@@ -9520,8 +9540,22 @@ function parseToolArguments(raw) {
9520
9540
  kind: "protocol"
9521
9541
  });
9522
9542
  }
9543
+ if (!Object.values(parsed).every(isJsonValue)) {
9544
+ throw new ModelError("tool-call arguments contain invalid JSON values", {
9545
+ kind: "protocol"
9546
+ });
9547
+ }
9523
9548
  return parsed;
9524
9549
  }
9550
+ function isJsonValue(value) {
9551
+ if (value === null || typeof value === "string" || typeof value === "boolean")
9552
+ return true;
9553
+ if (typeof value === "number")
9554
+ return Number.isFinite(value);
9555
+ if (Array.isArray(value))
9556
+ return value.every(isJsonValue);
9557
+ return isRecord(value) && Object.values(value).every(isJsonValue);
9558
+ }
9525
9559
  function zeroUsage() {
9526
9560
  return {
9527
9561
  input: 0,
@@ -9959,7 +9993,10 @@ var PiModelsStore = class {
9959
9993
  return void 0;
9960
9994
  }
9961
9995
  return {
9962
- models: entry.models.map((model2) => restoreModel(providerId, model2)),
9996
+ models: entry.models.flatMap((model2) => {
9997
+ const restored = restoreModel(providerId, model2);
9998
+ return restored === void 0 ? [] : [restored];
9999
+ }),
9963
10000
  ...entry.lastModified === void 0 ? {} : { lastModified: entry.lastModified },
9964
10001
  ...entry.checkedAt === void 0 ? {} : { checkedAt: entry.checkedAt },
9965
10002
  ...entry.etag === void 0 ? {} : { etag: entry.etag }
@@ -9978,12 +10015,15 @@ var PiModelsStore = class {
9978
10015
  }
9979
10016
  };
9980
10017
  function restoreModel(providerId, model2) {
10018
+ const type = model2["type"] ?? "chat";
10019
+ if (type !== "chat" && type !== "image" && type !== "classifier")
10020
+ return void 0;
9981
10021
  const id = readString(model2, "id", providerId);
9982
10022
  const name = readString(model2, "name", providerId);
9983
10023
  const api2 = readString(model2, "api", providerId);
9984
10024
  const provider2 = readString(model2, "provider", providerId);
9985
10025
  const input = model2["input"];
9986
- if (!Array.isArray(input) || !input.every((item) => typeof item === "string")) {
10026
+ if (!Array.isArray(input) || !input.every((item) => item === "text" || item === "image")) {
9987
10027
  throw new ModelError(`stored model for '${providerId}' has invalid input`, {
9988
10028
  kind: "protocol"
9989
10029
  });
@@ -9994,6 +10034,27 @@ function restoreModel(providerId, model2) {
9994
10034
  kind: "protocol"
9995
10035
  });
9996
10036
  }
10037
+ const common = {
10038
+ ...model2,
10039
+ id,
10040
+ name,
10041
+ api: api2,
10042
+ provider: provider2,
10043
+ input,
10044
+ cost: cost2,
10045
+ baseUrl: readString(model2, "baseUrl", providerId)
10046
+ };
10047
+ if (type === "image") {
10048
+ const output = model2["output"];
10049
+ if (!Array.isArray(output) || !output.includes("image") || !output.every((item) => item === "text" || item === "image")) {
10050
+ throw new ModelError(`stored model for '${providerId}' has invalid output`, { kind: "protocol" });
10051
+ }
10052
+ return { ...common, type, api: api2, output };
10053
+ }
10054
+ const contextWindow = readPositiveNumber(model2, "contextWindow", providerId);
10055
+ if (type === "classifier") {
10056
+ return { ...common, type, api: api2, contextWindow };
10057
+ }
9997
10058
  const reasoning = model2["reasoning"];
9998
10059
  if (typeof reasoning !== "boolean") {
9999
10060
  throw new ModelError(`stored model for '${providerId}' has invalid reasoning`, {
@@ -10001,15 +10062,13 @@ function restoreModel(providerId, model2) {
10001
10062
  });
10002
10063
  }
10003
10064
  return {
10004
- ...model2,
10005
- id,
10006
- name,
10065
+ ...common,
10066
+ type,
10007
10067
  api: api2,
10008
- provider: provider2,
10009
10068
  reasoning,
10010
10069
  input,
10011
10070
  cost: cost2,
10012
- contextWindow: readPositiveNumber(model2, "contextWindow", providerId),
10071
+ contextWindow,
10013
10072
  maxTokens: readPositiveNumber(model2, "maxTokens", providerId)
10014
10073
  };
10015
10074
  }
@@ -10043,11 +10102,11 @@ function isCost(value) {
10043
10102
  }
10044
10103
 
10045
10104
  // ../../packages/llm/llm-pi-ai/dist/platform.js
10046
- import { defaultProviderAuthContext, getSupportedThinkingLevels } from "@earendil-works/pi-ai";
10105
+ import { defaultProviderAuthContext, getSupportedThinkingLevels, isModelType as isModelType2 } from "@earendil-works/pi-ai";
10047
10106
  import { builtinModels as builtinModels2 } from "@earendil-works/pi-ai/providers/all";
10048
10107
 
10049
10108
  // ../../packages/llm/llm-pi-ai/dist/custom-models.js
10050
- import { createProvider, envApiKeyAuth } from "@earendil-works/pi-ai";
10109
+ import { createProvider, envApiKeyAuth, isModelType } from "@earendil-works/pi-ai";
10051
10110
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
10052
10111
  import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
10053
10112
  import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
@@ -17873,6 +17932,8 @@ var completionsCompat = partial(typebox_exports.Object({
17873
17932
  supportsOpenAIGrammarTools: typebox_exports.Boolean(),
17874
17933
  supportsStrictMode: typebox_exports.Boolean(),
17875
17934
  cacheControlFormat: typebox_exports.Literal("anthropic"),
17935
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
17936
+ supportsMidConvoToolAdditions: typebox_exports.Boolean(),
17876
17937
  sendSessionAffinityHeaders: typebox_exports.Boolean(),
17877
17938
  deferredToolsMode: typebox_exports.Literal("kimi"),
17878
17939
  sessionAffinityFormat: affinity,
@@ -17882,6 +17943,7 @@ var completionsCompat = partial(typebox_exports.Object({
17882
17943
  var responsesCompat = partial(typebox_exports.Object({
17883
17944
  supportsDeveloperRole: typebox_exports.Boolean(),
17884
17945
  sessionAffinityFormat: affinity,
17946
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
17885
17947
  supportsLongCacheRetention: typebox_exports.Boolean(),
17886
17948
  supportsStrictMode: typebox_exports.Boolean(),
17887
17949
  supportsOpenAIGrammarTools: typebox_exports.Boolean(),
@@ -17901,6 +17963,9 @@ var anthropicCompat = partial(typebox_exports.Object({
17901
17963
  supportsStrictTools: typebox_exports.Boolean(),
17902
17964
  supportsMidConvoEffort: typebox_exports.Boolean(),
17903
17965
  supportsToolReferences: typebox_exports.Boolean(),
17966
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
17967
+ supportsMidConvoToolChanges: typebox_exports.Boolean(),
17968
+ sessionAffinityFormat: typebox_exports.Literal("openrouter"),
17904
17969
  allowedFallbackModels: typebox_exports.Array(typebox_exports.Object({ provider: text, model: text, cost }, { additionalProperties: false }))
17905
17970
  }, { additionalProperties: false }));
17906
17971
  var api = typebox_exports.Union([
@@ -18073,6 +18138,10 @@ function customProvider(id, config, base) {
18073
18138
  baseUrl: config.baseUrl ?? base?.baseUrl,
18074
18139
  headers: { ...base?.headers, ...config.headers },
18075
18140
  getModels,
18141
+ getAllModels: () => [
18142
+ ...getModels(),
18143
+ ...(base?.getAllModels?.() ?? []).filter((model2) => !isModelType(model2, "chat"))
18144
+ ],
18076
18145
  // Keep provider-owned refresh/cache logic; getModels reapplies user overrides afterwards.
18077
18146
  ...base?.filterModels === void 0 ? {} : {
18078
18147
  filterModels: (models, credential) => {
@@ -18080,6 +18149,12 @@ function customProvider(id, config, base) {
18080
18149
  return models.filter((model2) => available.has(model2.id) || additions.has(model2.id));
18081
18150
  }
18082
18151
  },
18152
+ ...base?.filterAllModels === void 0 ? {} : {
18153
+ filterAllModels: (models, credential) => {
18154
+ const available = new Set(base.filterAllModels(models, credential).map((model2) => `${model2.type ?? "chat"}:${model2.id}`));
18155
+ return models.filter((model2) => available.has(`${model2.type ?? "chat"}:${model2.id}`) || isModelType(model2, "chat") && additions.has(model2.id));
18156
+ }
18157
+ },
18083
18158
  stream: (model2, context, options) => dispatch(model2).stream(model2, context, options),
18084
18159
  streamSimple: (model2, context, options) => dispatch(model2).streamSimple(model2, context, options)
18085
18160
  };
@@ -18190,7 +18265,9 @@ var PiAiPlatform = class {
18190
18265
  }
18191
18266
  };
18192
18267
  function isEligibleProvider(provider2, excludedProviderIds) {
18193
- return provider2.auth.apiKey?.login !== void 0 && !excludedProviderIds.has(provider2.id);
18268
+ const allModels = provider2.getAllModels?.() ?? provider2.getModels();
18269
+ return provider2.auth.apiKey?.login !== void 0 && !excludedProviderIds.has(provider2.id) && // Empty dynamic catalogs remain eligible; operation-only catalogs do not.
18270
+ (allModels.length === 0 || allModels.some((model2) => isModelType2(model2, "chat")));
18194
18271
  }
18195
18272
  var PiAiCatalog = class {
18196
18273
  models;