laohuang 0.9.5 → 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,29 +9268,32 @@ 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,
9265
9281
  estimator: this.#estimator
9266
9282
  });
9267
- if (input.trigger === "manual" && plan.summarizedEntries.length === 0) {
9268
- throw new Error("No messages to compact in current history.");
9283
+ if (plan.summarizedEntries.length === 0) {
9284
+ if (input.trigger === "manual")
9285
+ throw new Error("No messages to compact in current history.");
9286
+ if (input.trigger === "provider_overflow") {
9287
+ throw new ContextBudgetError("No earlier messages to compact after provider overflow.");
9288
+ }
9289
+ throw new NoCompactionAvailableError();
9269
9290
  }
9270
- const summarizedFromSeq = plan.summarizedEntries[0]?.seq ?? 1;
9271
- const summarizedThroughSeq = plan.summarizedEntries.at(-1)?.seq ?? Math.max(0, summarizedFromSeq - 1);
9272
- const built = this.#builder.build(input);
9273
- 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);
9274
9293
  const messagesBefore = built.messages;
9275
9294
  const tokensBefore = this.#estimator.estimateMessages(messagesBefore) + this.#estimator.estimateTools(input.projectTools?.(messagesBefore) ?? input.tools);
9276
- const summary = plan.summarizedEntries.length === 0 ? {
9277
- summary: "No prior conversation needed compaction.",
9278
- inputTokens: 0,
9279
- outputTokens: 0
9280
- } : await this.#summarize({
9281
- serialized: serializeConversation(plan.summarizedEntries),
9295
+ const summary = await this.#summarize({
9296
+ serialized: serializeCompactionInput(activeCompaction?.payload.summary, plan.summarizedEntries),
9282
9297
  maxSummaryTokens: input.policy.maxSummaryTokens
9283
9298
  });
9284
9299
  const payload = {
@@ -9306,10 +9321,13 @@ var ContextGovernor = class {
9306
9321
  throw error;
9307
9322
  }
9308
9323
  }
9309
- const compaction = await this.compact({ ...input, trigger: "provider_overflow" });
9310
- prepared = await this.prepare({ ...input, entries: [...input.entries, compaction.entry] });
9324
+ prepared = await this.recoverAfterOverflow(input);
9311
9325
  return await send(prepared);
9312
9326
  }
9327
+ async recoverAfterOverflow(input) {
9328
+ const compaction = await this.compact({ ...input, trigger: "provider_overflow" });
9329
+ return this.prepare({ ...input, entries: [...input.entries, compaction.entry] });
9330
+ }
9313
9331
  measure(input, built) {
9314
9332
  const tools = input.projectTools?.(built.messages) ?? input.tools;
9315
9333
  return this.#estimator.measure({
@@ -9325,6 +9343,15 @@ var ContextGovernor = class {
9325
9343
  }).totalTokens + (input.reserveTokens ?? 0);
9326
9344
  }
9327
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
+ }
9328
9355
  function calculateModelBudget(input) {
9329
9356
  const hardInputLimit = input.budget.contextWindow - input.budget.maxOutputTokens;
9330
9357
  const safetyTokens = Math.ceil(input.budget.contextWindow * input.policy.safetyRatio);
@@ -9513,8 +9540,22 @@ function parseToolArguments(raw) {
9513
9540
  kind: "protocol"
9514
9541
  });
9515
9542
  }
9543
+ if (!Object.values(parsed).every(isJsonValue)) {
9544
+ throw new ModelError("tool-call arguments contain invalid JSON values", {
9545
+ kind: "protocol"
9546
+ });
9547
+ }
9516
9548
  return parsed;
9517
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
+ }
9518
9559
  function zeroUsage() {
9519
9560
  return {
9520
9561
  input: 0,
@@ -9952,7 +9993,10 @@ var PiModelsStore = class {
9952
9993
  return void 0;
9953
9994
  }
9954
9995
  return {
9955
- 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
+ }),
9956
10000
  ...entry.lastModified === void 0 ? {} : { lastModified: entry.lastModified },
9957
10001
  ...entry.checkedAt === void 0 ? {} : { checkedAt: entry.checkedAt },
9958
10002
  ...entry.etag === void 0 ? {} : { etag: entry.etag }
@@ -9971,12 +10015,15 @@ var PiModelsStore = class {
9971
10015
  }
9972
10016
  };
9973
10017
  function restoreModel(providerId, model2) {
10018
+ const type = model2["type"] ?? "chat";
10019
+ if (type !== "chat" && type !== "image" && type !== "classifier")
10020
+ return void 0;
9974
10021
  const id = readString(model2, "id", providerId);
9975
10022
  const name = readString(model2, "name", providerId);
9976
10023
  const api2 = readString(model2, "api", providerId);
9977
10024
  const provider2 = readString(model2, "provider", providerId);
9978
10025
  const input = model2["input"];
9979
- if (!Array.isArray(input) || !input.every((item) => typeof item === "string")) {
10026
+ if (!Array.isArray(input) || !input.every((item) => item === "text" || item === "image")) {
9980
10027
  throw new ModelError(`stored model for '${providerId}' has invalid input`, {
9981
10028
  kind: "protocol"
9982
10029
  });
@@ -9987,6 +10034,27 @@ function restoreModel(providerId, model2) {
9987
10034
  kind: "protocol"
9988
10035
  });
9989
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
+ }
9990
10058
  const reasoning = model2["reasoning"];
9991
10059
  if (typeof reasoning !== "boolean") {
9992
10060
  throw new ModelError(`stored model for '${providerId}' has invalid reasoning`, {
@@ -9994,15 +10062,13 @@ function restoreModel(providerId, model2) {
9994
10062
  });
9995
10063
  }
9996
10064
  return {
9997
- ...model2,
9998
- id,
9999
- name,
10065
+ ...common,
10066
+ type,
10000
10067
  api: api2,
10001
- provider: provider2,
10002
10068
  reasoning,
10003
10069
  input,
10004
10070
  cost: cost2,
10005
- contextWindow: readPositiveNumber(model2, "contextWindow", providerId),
10071
+ contextWindow,
10006
10072
  maxTokens: readPositiveNumber(model2, "maxTokens", providerId)
10007
10073
  };
10008
10074
  }
@@ -10036,11 +10102,11 @@ function isCost(value) {
10036
10102
  }
10037
10103
 
10038
10104
  // ../../packages/llm/llm-pi-ai/dist/platform.js
10039
- import { defaultProviderAuthContext, getSupportedThinkingLevels } from "@earendil-works/pi-ai";
10105
+ import { defaultProviderAuthContext, getSupportedThinkingLevels, isModelType as isModelType2 } from "@earendil-works/pi-ai";
10040
10106
  import { builtinModels as builtinModels2 } from "@earendil-works/pi-ai/providers/all";
10041
10107
 
10042
10108
  // ../../packages/llm/llm-pi-ai/dist/custom-models.js
10043
- import { createProvider, envApiKeyAuth } from "@earendil-works/pi-ai";
10109
+ import { createProvider, envApiKeyAuth, isModelType } from "@earendil-works/pi-ai";
10044
10110
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
10045
10111
  import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
10046
10112
  import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
@@ -17866,6 +17932,8 @@ var completionsCompat = partial(typebox_exports.Object({
17866
17932
  supportsOpenAIGrammarTools: typebox_exports.Boolean(),
17867
17933
  supportsStrictMode: typebox_exports.Boolean(),
17868
17934
  cacheControlFormat: typebox_exports.Literal("anthropic"),
17935
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
17936
+ supportsMidConvoToolAdditions: typebox_exports.Boolean(),
17869
17937
  sendSessionAffinityHeaders: typebox_exports.Boolean(),
17870
17938
  deferredToolsMode: typebox_exports.Literal("kimi"),
17871
17939
  sessionAffinityFormat: affinity,
@@ -17875,6 +17943,7 @@ var completionsCompat = partial(typebox_exports.Object({
17875
17943
  var responsesCompat = partial(typebox_exports.Object({
17876
17944
  supportsDeveloperRole: typebox_exports.Boolean(),
17877
17945
  sessionAffinityFormat: affinity,
17946
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
17878
17947
  supportsLongCacheRetention: typebox_exports.Boolean(),
17879
17948
  supportsStrictMode: typebox_exports.Boolean(),
17880
17949
  supportsOpenAIGrammarTools: typebox_exports.Boolean(),
@@ -17894,6 +17963,9 @@ var anthropicCompat = partial(typebox_exports.Object({
17894
17963
  supportsStrictTools: typebox_exports.Boolean(),
17895
17964
  supportsMidConvoEffort: typebox_exports.Boolean(),
17896
17965
  supportsToolReferences: typebox_exports.Boolean(),
17966
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
17967
+ supportsMidConvoToolChanges: typebox_exports.Boolean(),
17968
+ sessionAffinityFormat: typebox_exports.Literal("openrouter"),
17897
17969
  allowedFallbackModels: typebox_exports.Array(typebox_exports.Object({ provider: text, model: text, cost }, { additionalProperties: false }))
17898
17970
  }, { additionalProperties: false }));
17899
17971
  var api = typebox_exports.Union([
@@ -18066,6 +18138,10 @@ function customProvider(id, config, base) {
18066
18138
  baseUrl: config.baseUrl ?? base?.baseUrl,
18067
18139
  headers: { ...base?.headers, ...config.headers },
18068
18140
  getModels,
18141
+ getAllModels: () => [
18142
+ ...getModels(),
18143
+ ...(base?.getAllModels?.() ?? []).filter((model2) => !isModelType(model2, "chat"))
18144
+ ],
18069
18145
  // Keep provider-owned refresh/cache logic; getModels reapplies user overrides afterwards.
18070
18146
  ...base?.filterModels === void 0 ? {} : {
18071
18147
  filterModels: (models, credential) => {
@@ -18073,6 +18149,12 @@ function customProvider(id, config, base) {
18073
18149
  return models.filter((model2) => available.has(model2.id) || additions.has(model2.id));
18074
18150
  }
18075
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
+ },
18076
18158
  stream: (model2, context, options) => dispatch(model2).stream(model2, context, options),
18077
18159
  streamSimple: (model2, context, options) => dispatch(model2).streamSimple(model2, context, options)
18078
18160
  };
@@ -18183,7 +18265,9 @@ var PiAiPlatform = class {
18183
18265
  }
18184
18266
  };
18185
18267
  function isEligibleProvider(provider2, excludedProviderIds) {
18186
- 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")));
18187
18271
  }
18188
18272
  var PiAiCatalog = class {
18189
18273
  models;
@@ -32495,7 +32579,7 @@ var AgentStepRunner = class {
32495
32579
  }
32496
32580
  modelRound += 1;
32497
32581
  modelRequests += 1;
32498
- const requestId = modelRound === 1 && this.options.requestId !== null ? this.options.requestId : randomUUID10();
32582
+ let requestId = modelRound === 1 && this.options.requestId !== null ? this.options.requestId : randomUUID10();
32499
32583
  this.options.onRequestId(requestId);
32500
32584
  const registry = this.options.getTools();
32501
32585
  const selection = this.options.selection ?? new ToolSelection();
@@ -32533,96 +32617,137 @@ var AgentStepRunner = class {
32533
32617
  throw error;
32534
32618
  }
32535
32619
  }, () => history.snapshot().map((message) => message.role === "system" || message.role === "user" ? message : {}));
32536
- let selected = select(history.snapshot());
32537
- if (selected.announcement)
32538
- history.commitToolContext({ role: "user", ...selected.announcement });
32539
- let prepared = { messages: history.snapshot() };
32540
- for (let attempt = 0; attempt < 3; attempt++) {
32541
- prepared = await this.options.contextGovernor?.prepare({
32542
- messages: history.snapshot(),
32543
- tools: selected.view.definitions,
32544
- provider: this.options.provider,
32545
- model: this.options.model,
32546
- projectTools: (messages) => select(messages).view.definitions
32547
- }) ?? { messages: history.snapshot() };
32548
- history.retain(prepared.messages);
32549
- selected = select(prepared.messages);
32550
- const resources = await this.options.resources?.prepareContext(prepared.messages, cancelToken?.signal) ?? [];
32551
- if (!selected.announcement && resources.length === 0)
32552
- break;
32553
- if (attempt === 2)
32554
- throw this.options.createError("Resource catalog cannot fit the retained context.");
32555
- if (selected.announcement)
32556
- history.commitToolContext({ role: "user", ...selected.announcement });
32557
- for (const message of resources) {
32558
- history.commitSkillContext(message);
32559
- this.options.resources?.committed([message]);
32560
- }
32561
- }
32562
- remainingTokens = prepared.hardInputLimit === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, prepared.hardInputLimit - (prepared.contextTokens ?? 0) - 1024);
32563
- const tools = selected.view;
32564
- const definitions = tools.definitions;
32565
- const requestMessages = [...prepared.messages];
32566
- this.emit("model_request", {
32567
- round: modelRound,
32568
- request_id: requestId,
32569
- message_count: requestMessages.length,
32570
- tool_rounds: toolRounds,
32571
- model_requests: modelRequests,
32572
- total_tokens: totalTokens,
32573
- ...prepared.contextTokens === void 0 ? {} : { context_tokens: prepared.contextTokens },
32574
- ...prepared.contextWindow === void 0 ? {} : { context_window: prepared.contextWindow }
32575
- });
32576
- let result;
32577
- try {
32578
- const modelRequestOpened = context?.modelRequestOpened;
32579
- result = await this.options.modelRuntime.complete({
32580
- provider: this.options.provider,
32581
- model: this.options.model,
32582
- ...this.options.baseUrl === null ? {} : { baseUrl: this.options.baseUrl },
32583
- messages: requestMessages,
32584
- tools: definitions,
32585
- reasoningEffort: this.options.getReasoningEffort(),
32586
- requestId,
32587
- cancelToken,
32588
- isRequestActive: this.options.isRequestActive,
32589
- onDelta: (kind, payload) => {
32590
- this.emit(kind, { round: modelRound, request_id: requestId, ...payload });
32591
- },
32592
- onRequestOpened: () => {
32593
- if (modelRequestOpened?.call(context) === false)
32594
- return false;
32595
- this.options.resources?.committed(pendingResourceInputs);
32596
- pendingResourceInputs = [];
32597
- return true;
32620
+ const prepareRequest = async () => {
32621
+ let selected2 = select(history.snapshot());
32622
+ if (selected2.announcement)
32623
+ history.commitToolContext({ role: "user", ...selected2.announcement });
32624
+ let prepared2 = { messages: history.snapshot() };
32625
+ for (let attempt = 0; attempt < 3; attempt++) {
32626
+ prepared2 = await this.options.contextGovernor?.prepare({
32627
+ messages: history.snapshot(),
32628
+ tools: selected2.view.definitions,
32629
+ provider: this.options.provider,
32630
+ model: this.options.model,
32631
+ projectTools: (messages) => select(messages).view.definitions
32632
+ }) ?? { messages: history.snapshot() };
32633
+ history.retain(prepared2.messages);
32634
+ selected2 = select(prepared2.messages);
32635
+ const resources = await this.options.resources?.prepareContext(prepared2.messages, cancelToken?.signal) ?? [];
32636
+ if (!selected2.announcement && resources.length === 0)
32637
+ break;
32638
+ if (attempt === 2)
32639
+ throw this.options.createError("Resource catalog cannot fit the retained context.");
32640
+ if (selected2.announcement)
32641
+ history.commitToolContext({ role: "user", ...selected2.announcement });
32642
+ for (const message of resources) {
32643
+ history.commitSkillContext(message);
32644
+ this.options.resources?.committed([message]);
32598
32645
  }
32646
+ }
32647
+ remainingTokens = prepared2.hardInputLimit === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, prepared2.hardInputLimit - (prepared2.contextTokens ?? 0) - 1024);
32648
+ return { selected: selected2, prepared: prepared2 };
32649
+ };
32650
+ let { selected, prepared } = await prepareRequest();
32651
+ let tools = selected.view;
32652
+ let definitions = tools.definitions;
32653
+ let requestMessages = [...prepared.messages];
32654
+ let result;
32655
+ for (let overflowAttempt = 0; ; overflowAttempt++) {
32656
+ const attemptRequestId = requestId;
32657
+ const attemptRound = modelRound;
32658
+ this.emit("model_request", {
32659
+ round: attemptRound,
32660
+ request_id: attemptRequestId,
32661
+ message_count: requestMessages.length,
32662
+ tool_rounds: toolRounds,
32663
+ model_requests: modelRequests,
32664
+ total_tokens: totalTokens,
32665
+ ...prepared.contextTokens === void 0 ? {} : { context_tokens: prepared.contextTokens },
32666
+ ...prepared.contextWindow === void 0 ? {} : { context_window: prepared.contextWindow }
32599
32667
  });
32600
- } catch (error) {
32601
- if (error instanceof ModelStreamCancelled) {
32602
- this.emit("model_response_aborted", {
32603
- round: modelRound,
32604
- request_id: requestId,
32605
- reason: errorMessage10(error)
32668
+ try {
32669
+ const modelRequestOpened = context?.modelRequestOpened;
32670
+ result = await this.options.modelRuntime.complete({
32671
+ provider: this.options.provider,
32672
+ model: this.options.model,
32673
+ ...this.options.baseUrl === null ? {} : { baseUrl: this.options.baseUrl },
32674
+ messages: requestMessages,
32675
+ tools: definitions,
32676
+ reasoningEffort: this.options.getReasoningEffort(),
32677
+ requestId: attemptRequestId,
32678
+ cancelToken,
32679
+ isRequestActive: this.options.isRequestActive,
32680
+ onDelta: (kind, payload) => {
32681
+ this.emit(kind, { round: attemptRound, request_id: attemptRequestId, ...payload });
32682
+ },
32683
+ onRequestOpened: () => {
32684
+ if (modelRequestOpened?.call(context) === false)
32685
+ return false;
32686
+ this.options.resources?.committed(pendingResourceInputs);
32687
+ pendingResourceInputs = [];
32688
+ return true;
32689
+ }
32606
32690
  });
32607
- throw this.options.createCancelled(errorMessage10(error), error);
32608
- }
32609
- const errorPayload = {
32610
- round: modelRound,
32611
- request_id: requestId,
32612
- error: errorMessage10(error)
32613
- };
32614
- if (error instanceof ModelStreamError && error.hadDelta) {
32615
- this.emit("model_response_aborted", errorPayload);
32616
- this.options.emitLegacy("model_error", errorPayload);
32617
- } else {
32618
- this.emit("model_error", errorPayload);
32619
- }
32620
- let message = `Model request failed: ${errorMessage10(error)}`;
32621
- if (this.options.provider && modelErrorKind(error) === "authentication") {
32622
- message += `
32691
+ break;
32692
+ } catch (error) {
32693
+ if (error instanceof ModelStreamCancelled) {
32694
+ this.emit("model_response_aborted", {
32695
+ round: attemptRound,
32696
+ request_id: attemptRequestId,
32697
+ reason: errorMessage10(error)
32698
+ });
32699
+ throw this.options.createCancelled(errorMessage10(error), error);
32700
+ }
32701
+ const errorPayload = {
32702
+ round: attemptRound,
32703
+ request_id: attemptRequestId,
32704
+ error: errorMessage10(error)
32705
+ };
32706
+ if (error instanceof ModelStreamError && error.hadDelta) {
32707
+ this.emit("model_response_aborted", errorPayload);
32708
+ this.options.emitLegacy("model_error", errorPayload);
32709
+ } else {
32710
+ this.emit("model_error", errorPayload);
32711
+ }
32712
+ if (overflowAttempt === 0 && error instanceof ModelError && error.kind === "context_overflow" && !error.hadDelta && this.options.contextGovernor?.recoverAfterOverflow !== void 0) {
32713
+ this.raiseIfCancelled();
32714
+ const recovered = await this.options.contextGovernor.recoverAfterOverflow({
32715
+ messages: history.snapshot(),
32716
+ tools: definitions,
32717
+ provider: this.options.provider,
32718
+ model: this.options.model,
32719
+ projectTools: (messages) => select(messages).view.definitions
32720
+ });
32721
+ this.raiseIfCancelled();
32722
+ history.retain(recovered.messages);
32723
+ remainingTokens = Number.POSITIVE_INFINITY;
32724
+ preflightCompacted = false;
32725
+ ({ selected, prepared } = await prepareRequest());
32726
+ this.raiseIfCancelled();
32727
+ tools = selected.view;
32728
+ definitions = tools.definitions;
32729
+ requestMessages = [...prepared.messages];
32730
+ this.emit("model_retry_scheduled", {
32731
+ round: attemptRound,
32732
+ request_id: attemptRequestId,
32733
+ attempt: 2,
32734
+ max_attempts: 2,
32735
+ delay_ms: 0,
32736
+ error_kind: "context_overflow"
32737
+ });
32738
+ modelRound += 1;
32739
+ modelRequests += 1;
32740
+ requestId = randomUUID10();
32741
+ this.options.onRequestId(requestId);
32742
+ continue;
32743
+ }
32744
+ let message = `Model request failed: ${errorMessage10(error)}`;
32745
+ if (this.options.provider && modelErrorKind(error) === "authentication") {
32746
+ message += `
32623
32747
  Authentication failed for ${this.options.provider}. Run /login ${this.options.provider} to update your API key.`;
32748
+ }
32749
+ throw this.options.createError(message, error);
32624
32750
  }
32625
- throw this.options.createError(message, error);
32626
32751
  }
32627
32752
  const toolCalls = toolCallsOf(result.message);
32628
32753
  let requestTokens = usageTotalTokens(result.usage);
@@ -33520,6 +33645,25 @@ function createSessionRuntime(options) {
33520
33645
  contextWindow: selectedModel.contextWindow,
33521
33646
  hardInputLimit: selectedModel.contextWindow - selectedModel.maxTokens
33522
33647
  };
33648
+ },
33649
+ recoverAfterOverflow: async ({ tools, projectTools: projectTools2 }) => {
33650
+ const history = activeHistory(options.sessionController);
33651
+ const prepared = await createGovernor(history).recoverAfterOverflow({
33652
+ protectedFromSeq: attachmentTurnStart,
33653
+ entries: history.entries(),
33654
+ currentProvider: route.provider,
33655
+ currentModel: route.model,
33656
+ tools,
33657
+ projectTools: projectTools2,
33658
+ budget: modelBudget(selectedModel),
33659
+ policy: defaultContextPolicy()
33660
+ });
33661
+ return {
33662
+ messages: prepared.messages,
33663
+ contextTokens: prepared.tokens,
33664
+ contextWindow: selectedModel.contextWindow,
33665
+ hardInputLimit: selectedModel.contextWindow - selectedModel.maxTokens
33666
+ };
33523
33667
  }
33524
33668
  }
33525
33669
  });
@@ -38703,64 +38847,7 @@ async function handleCommandResult(presenter, suggestCommand, result) {
38703
38847
  return true;
38704
38848
  }
38705
38849
  async function runSessionRepl(session, options) {
38706
- if (options.runUi !== void 0 || typeof options.ui.run === "function") {
38707
- return runPersistentSessionRepl(session, options);
38708
- }
38709
- return runClassicSessionRepl(session, options);
38710
- }
38711
- async function runClassicSessionRepl(session, options) {
38712
- const { commandHandler, presenter, suggestCommand, ui } = options;
38713
- ui.startEventRenderer?.();
38714
- ui.showWelcome?.();
38715
- let cleanShutdown = false;
38716
- try {
38717
- for (; ; ) {
38718
- let userInput;
38719
- try {
38720
- const prompt = ui.prompt;
38721
- if (typeof prompt !== "function") {
38722
- throw new Error("Classic session UI requires a prompt function.");
38723
- }
38724
- userInput = await prompt.call(ui);
38725
- if (!/^\s*\/skill:/.test(userInput)) userInput = userInput.trim();
38726
- } catch (error) {
38727
- if (isEofError(error)) {
38728
- break;
38729
- }
38730
- if (isInterruptedError(error)) {
38731
- presentNotice(presenter, "Interrupted.", "warning");
38732
- continue;
38733
- }
38734
- throw error;
38735
- }
38736
- const keepGoing = await handleSessionInput(
38737
- session,
38738
- commandHandler,
38739
- presenter,
38740
- suggestCommand,
38741
- userInput
38742
- );
38743
- if (!keepGoing) {
38744
- break;
38745
- }
38746
- }
38747
- } finally {
38748
- cleanShutdown = await session.close({ wait: true, timeoutMs: 1e4 });
38749
- if (cleanShutdown) {
38750
- await session.eventBus.flush();
38751
- ui.flushEventRenderer?.();
38752
- }
38753
- ui.stopEventRenderer?.();
38754
- }
38755
- if (cleanShutdown) {
38756
- ui.showGoodbye?.();
38757
- } else {
38758
- presenter.notice({
38759
- text: "Task worker did not stop before the shutdown timeout.",
38760
- tone: "error"
38761
- });
38762
- }
38763
- return cleanShutdown;
38850
+ return runPersistentSessionRepl(session, options);
38764
38851
  }
38765
38852
  function startCoordinator(session, commandHandler, presenter, suggestCommand) {
38766
38853
  const queue = [];
@@ -39767,6 +39854,7 @@ async function main(argv, options = {}) {
39767
39854
  let attachmentRuntime;
39768
39855
  try {
39769
39856
  modelPlatform = await createPiAiPlatform({
39857
+ // 附件运行时稍后才创建,通过回调取得届时可用的存储实例。
39770
39858
  attachments: { store: () => attachmentRuntime?.store },
39771
39859
  credentials,
39772
39860
  modelCatalogStore,