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 +5 -4
- package/dist/bin.js +100 -23
- package/dist/bin.js.map +3 -3
- package/package.json +2 -2
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-
|
|
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.
|
|
46
|
-
the product exposes
|
|
47
|
-
Google Vertex, OAuth-only providers,
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
9281
|
-
|
|
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.
|
|
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) =>
|
|
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
|
-
...
|
|
10005
|
-
|
|
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
|
|
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
|
-
|
|
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;
|