laohuang 0.9.6 → 0.9.8

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/dist/bin.js CHANGED
@@ -7628,21 +7628,10 @@ function parseSessionItem(value, expectedSessionId) {
7628
7628
  if (message["role"] !== "system" || !Array.isArray(message["toolDefinitions"]))
7629
7629
  throw new SessionSchemaError("invalid tool definitions message");
7630
7630
  for (const raw of message["toolDefinitions"]) {
7631
- const definition = objectOf(raw, "loaded tool");
7632
- requireString(definition, "version");
7633
- const spec2 = objectOf(definition["spec"], "tool spec");
7634
- requireString(spec2, "name");
7635
- if (typeof spec2["description"] !== "string" || !Array.isArray(spec2["promptGuidelines"]) || spec2["promptGuidelines"].some((value2) => typeof value2 !== "string"))
7636
- throw new SessionSchemaError("invalid tool spec");
7637
- objectOf(spec2["parameters"], "tool parameters");
7638
- if (spec2["catalog"] !== void 0) {
7639
- const catalog = objectOf(spec2["catalog"], "tool catalog metadata");
7640
- for (const key of ["source", "originalName", "binding"])
7641
- requireString(catalog, key);
7642
- if (catalog["exposure"] !== "direct" && catalog["exposure"] !== "deferred")
7643
- throw new SessionSchemaError("invalid exposure");
7644
- }
7631
+ assertStoredTool(raw, true);
7645
7632
  }
7633
+ if (message["toolsRemoved"] !== void 0)
7634
+ assertToolReferences(message["toolsRemoved"]);
7646
7635
  } else {
7647
7636
  if (message["role"] !== "user")
7648
7637
  throw new SessionSchemaError("invalid catalog message role");
@@ -7654,6 +7643,9 @@ function parseSessionItem(value, expectedSessionId) {
7654
7643
  throw new SessionSchemaError("invalid tool version");
7655
7644
  }
7656
7645
  }
7646
+ if (entryType === "compaction" && entryPayload["toolState"] !== void 0) {
7647
+ assertToolSelectionSnapshot(entryPayload["toolState"]);
7648
+ }
7657
7649
  return input;
7658
7650
  }
7659
7651
  if (kind === "record") {
@@ -7682,6 +7674,54 @@ function parseSessionItem(value, expectedSessionId) {
7682
7674
  }
7683
7675
  throw new SessionSchemaError("invalid session item kind");
7684
7676
  }
7677
+ function assertStoredTool(value, activationOptional) {
7678
+ const definition = objectOf(value, "loaded tool");
7679
+ requireString(definition, "version");
7680
+ const activation = definition["activation"];
7681
+ if ((!activationOptional || activation !== void 0) && activation !== "baseline" && activation !== "search") {
7682
+ throw new SessionSchemaError("invalid tool activation");
7683
+ }
7684
+ const spec2 = objectOf(definition["spec"], "tool spec");
7685
+ requireString(spec2, "name");
7686
+ if (typeof spec2["description"] !== "string" || !Array.isArray(spec2["promptGuidelines"]) || spec2["promptGuidelines"].some((value2) => typeof value2 !== "string")) {
7687
+ throw new SessionSchemaError("invalid tool spec");
7688
+ }
7689
+ objectOf(spec2["parameters"], "tool parameters");
7690
+ if (spec2["catalog"] !== void 0) {
7691
+ const catalog = objectOf(spec2["catalog"], "tool catalog metadata");
7692
+ for (const key of ["source", "originalName", "binding"])
7693
+ requireString(catalog, key);
7694
+ if (catalog["exposure"] !== "direct" && catalog["exposure"] !== "deferred")
7695
+ throw new SessionSchemaError("invalid exposure");
7696
+ }
7697
+ }
7698
+ function assertToolReferences(value) {
7699
+ if (!Array.isArray(value))
7700
+ throw new SessionSchemaError("invalid removed tools");
7701
+ for (const raw of value) {
7702
+ const reference = objectOf(raw, "removed tool");
7703
+ requireString(reference, "name");
7704
+ if (reference["version"] !== void 0)
7705
+ requireString(reference, "version");
7706
+ }
7707
+ }
7708
+ function assertToolSelectionSnapshot(value) {
7709
+ const snapshot = objectOf(value, "tool state");
7710
+ const catalog = snapshot["catalog"];
7711
+ if (catalog !== null) {
7712
+ const state = objectOf(catalog, "tool state catalog");
7713
+ if (state["mode"] !== "full" && state["mode"] !== "deferred")
7714
+ throw new SessionSchemaError("invalid tool mode");
7715
+ for (const version of Object.values(objectOf(state["tools"], "tool versions"))) {
7716
+ if (typeof version !== "string")
7717
+ throw new SessionSchemaError("invalid tool version");
7718
+ }
7719
+ }
7720
+ if (!Array.isArray(snapshot["activeTools"]))
7721
+ throw new SessionSchemaError("invalid active tools");
7722
+ for (const tool of snapshot["activeTools"])
7723
+ assertStoredTool(tool, false);
7724
+ }
7685
7725
  function contextResetBoundary(entries) {
7686
7726
  return entries.reduce((boundary, entry) => entry.entryType === "context_reset" ? Math.max(boundary, entry.payload.resetThroughSeq) : boundary, 0);
7687
7727
  }
@@ -8666,7 +8706,7 @@ var ConversationHistory = class _ConversationHistory {
8666
8706
  payload: input
8667
8707
  });
8668
8708
  }
8669
- appendToolDefinitions(input) {
8709
+ appendToolState(input) {
8670
8710
  return this.append({ entryType: "tool_definitions", payload: input });
8671
8711
  }
8672
8712
  appendToolCatalog(input) {
@@ -9054,7 +9094,9 @@ function conversationTail(entries, activeCompaction) {
9054
9094
  return entries.filter((entry) => entry.seq >= boundary && entry.entryType !== "system_context" && entry.entryType !== "project_instructions" && entry.entryType !== "compaction");
9055
9095
  }
9056
9096
  function modelMessageForEntry(entry, currentProvider, currentModel) {
9057
- if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9097
+ if (entry.entryType === "tool_definitions")
9098
+ return null;
9099
+ if (entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9058
9100
  return entry.payload.message;
9059
9101
  if (entry.entryType === "user_message" || entry.entryType === "reminder") {
9060
9102
  return entry.payload.message;
@@ -9105,7 +9147,7 @@ function validateToolPairs(selected) {
9105
9147
  }
9106
9148
 
9107
9149
  // ../../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.";
9150
+ 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
9151
 
9110
9152
  // ../../packages/session/session-context/dist/compaction.js
9111
9153
  function selectCompactionPlan(input) {
@@ -9151,8 +9193,6 @@ function serializeConversation(entries) {
9151
9193
  }
9152
9194
  } else if (entry.entryType === "tool_result") {
9153
9195
  lines2.push(`[tool-result ${entry.payload.message.toolCallId} ${entry.payload.message.toolName}] ${entry.payload.message.content}`);
9154
- } else if (entry.entryType === "tool_definitions") {
9155
- lines2.push(`[loaded tool definitions] ${(entry.payload.message.toolDefinitions ?? []).map((tool) => tool.spec.name).join(", ")}`);
9156
9196
  } else if (entry.entryType === "user_message" || entry.entryType === "skill_context" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
9157
9197
  lines2.push(`[${entry.payload.message.role}] ${entry.payload.message.content}`);
9158
9198
  }
@@ -9167,12 +9207,9 @@ function serializeConversation(entries) {
9167
9207
  }
9168
9208
  function semanticUnits(entries) {
9169
9209
  const units = [];
9170
- const groupedDefinitions = /* @__PURE__ */ new Set();
9171
9210
  for (let index = 0; index < entries.length; index += 1) {
9172
9211
  const entry = entries[index];
9173
- if (groupedDefinitions.has(entry.id))
9174
- continue;
9175
- if (entry.entryType === "context_reset" || entry.entryType === "image_offload" || entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
9212
+ if (entry.entryType === "context_reset" || entry.entryType === "image_offload" || entry.entryType === "tool_definitions" || entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
9176
9213
  continue;
9177
9214
  }
9178
9215
  if (entry.entryType === "assistant_message") {
@@ -9186,12 +9223,6 @@ function semanticUnits(entries) {
9186
9223
  }
9187
9224
  grouped.push(entries[resultIndex]);
9188
9225
  }
9189
- const lastResultIndex = entries.findIndex((candidate) => candidate.id === grouped.at(-1).id);
9190
- const definitions = entries[lastResultIndex + 1];
9191
- if (definitions?.entryType === "tool_definitions") {
9192
- grouped.push(definitions);
9193
- groupedDefinitions.add(definitions.id);
9194
- }
9195
9226
  units.push({ entries: grouped, messages: grouped.map(messageForEntry) });
9196
9227
  continue;
9197
9228
  }
@@ -9204,7 +9235,7 @@ function semanticUnits(entries) {
9204
9235
  return units;
9205
9236
  }
9206
9237
  function messageForEntry(entry) {
9207
- if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9238
+ if (entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9208
9239
  return entry.payload.message;
9209
9240
  if (entry.entryType === "assistant_message") {
9210
9241
  return entry.payload.message;
@@ -9225,6 +9256,8 @@ var ContextBudgetError = class extends Error {
9225
9256
  this.name = "ContextBudgetError";
9226
9257
  }
9227
9258
  };
9259
+ var NoCompactionAvailableError = class extends Error {
9260
+ };
9228
9261
  var ContextGovernor = class {
9229
9262
  #builder;
9230
9263
  #estimator;
@@ -9245,7 +9278,17 @@ var ContextGovernor = class {
9245
9278
  throw new ContextBudgetError("context exceeds hard input limit");
9246
9279
  return { built, messages: built.messages, compacted: false, tokens };
9247
9280
  }
9248
- const result = await this.compact({ ...input, trigger: "automatic" });
9281
+ let result;
9282
+ try {
9283
+ result = await this.compact({ ...input, trigger: "automatic" });
9284
+ } catch (error) {
9285
+ if (!(error instanceof NoCompactionAvailableError))
9286
+ throw error;
9287
+ if (tokens > calculated.hardInputLimit) {
9288
+ throw new ContextBudgetError("context exceeds hard input limit with no earlier messages to compact");
9289
+ }
9290
+ return { built, messages: built.messages, compacted: false, tokens };
9291
+ }
9249
9292
  const entries = [...input.entries, result.entry];
9250
9293
  built = this.#builder.build({ ...input, entries });
9251
9294
  tokens = this.measure({ ...input, entries }, built);
@@ -9256,12 +9299,14 @@ var ContextGovernor = class {
9256
9299
  }
9257
9300
  async compact(input) {
9258
9301
  const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
9302
+ const built = this.#builder.build(input);
9303
+ const activeCompaction = input.entries.find((entry) => entry.id === built.activeCompactionId && entry.entryType === "compaction");
9304
+ const activeSourceEntryIds = new Set(built.sourceEntryIds);
9305
+ const projectedEntries = activeCompaction === void 0 ? input.entries : input.entries.filter((entry) => entry.id !== activeCompaction.id && activeSourceEntryIds.has(entry.id));
9259
9306
  const plan = selectCompactionPlan({
9260
9307
  protectedFromSeq: input.protectedFromSeq,
9261
- entries: input.entries,
9262
- // A search preflight reserves the pending call/result/definition unit.
9263
- // Keep the latest completed unit and summarize older units to make room.
9264
- retainTokens: input.reserveTokens === void 0 ? calculated.retainTokens : 1,
9308
+ entries: projectedEntries,
9309
+ retainTokens: calculated.retainTokens,
9265
9310
  estimator: this.#estimator
9266
9311
  });
9267
9312
  if (plan.summarizedEntries.length === 0) {
@@ -9270,19 +9315,14 @@ var ContextGovernor = class {
9270
9315
  if (input.trigger === "provider_overflow") {
9271
9316
  throw new ContextBudgetError("No earlier messages to compact after provider overflow.");
9272
9317
  }
9318
+ throw new NoCompactionAvailableError();
9273
9319
  }
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);
9320
+ const summarizedFromSeq = activeCompaction?.payload.summarizedFromSeq ?? plan.summarizedEntries[0]?.seq ?? 1;
9321
+ const summarizedThroughSeq = plan.summarizedEntries.at(-1)?.seq ?? activeCompaction?.payload.summarizedThroughSeq ?? Math.max(0, summarizedFromSeq - 1);
9278
9322
  const messagesBefore = built.messages;
9279
9323
  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),
9324
+ const summary = await this.#summarize({
9325
+ serialized: serializeCompactionInput(activeCompaction?.payload.summary, plan.summarizedEntries),
9286
9326
  maxSummaryTokens: input.policy.maxSummaryTokens
9287
9327
  });
9288
9328
  const payload = {
@@ -9297,7 +9337,8 @@ var ContextGovernor = class {
9297
9337
  summaryOutputTokens: summary.outputTokens,
9298
9338
  provider: input.currentProvider,
9299
9339
  model: input.currentModel,
9300
- trigger: input.trigger
9340
+ trigger: input.trigger,
9341
+ ...input.toolState === void 0 ? {} : { toolState: input.toolState }
9301
9342
  };
9302
9343
  return { entry: this.#appendCompaction(payload) };
9303
9344
  }
@@ -9329,9 +9370,18 @@ var ContextGovernor = class {
9329
9370
  systemFingerprint: fingerprintContextPart(built.messages.find((message) => message.role === "system") ?? null),
9330
9371
  projectInstructionsFingerprint: fingerprintContextPart(built.messages.filter((message, index) => index > 0 && message.role === "user")),
9331
9372
  toolsFingerprint: fingerprintContextPart(tools)
9332
- }).totalTokens + (input.reserveTokens ?? 0);
9373
+ }).totalTokens;
9333
9374
  }
9334
9375
  };
9376
+ function serializeCompactionInput(previousSummary, entries) {
9377
+ const conversation = serializeConversation(entries);
9378
+ if (previousSummary === void 0)
9379
+ return conversation;
9380
+ return `<previous_summary>
9381
+ ${previousSummary}
9382
+ </previous_summary>
9383
+ ${conversation}`;
9384
+ }
9335
9385
  function calculateModelBudget(input) {
9336
9386
  const hardInputLimit = input.budget.contextWindow - input.budget.maxOutputTokens;
9337
9387
  const safetyTokens = Math.ceil(input.budget.contextWindow * input.policy.safetyRatio);
@@ -9340,6 +9390,52 @@ function calculateModelBudget(input) {
9340
9390
  return { hardInputLimit, safetyTokens, autoTrigger, retainTokens };
9341
9391
  }
9342
9392
 
9393
+ // ../../packages/session/session-context/dist/tool-state.js
9394
+ function projectToolSelectionState(entries) {
9395
+ const sorted = [...entries].sort((left, right) => left.seq - right.seq);
9396
+ const latestCompaction2 = [...sorted].reverse().find((entry) => entry.entryType === "compaction");
9397
+ const checkpoint = latestCompaction2?.entryType === "compaction" && latestCompaction2.payload.toolState !== void 0 ? latestCompaction2 : void 0;
9398
+ let catalog = checkpoint?.entryType === "compaction" ? copyCatalog(checkpoint.payload.toolState.catalog) : null;
9399
+ const active = /* @__PURE__ */ new Map();
9400
+ if (checkpoint?.entryType === "compaction") {
9401
+ for (const tool of checkpoint.payload.toolState.activeTools) {
9402
+ active.set(tool.spec.name, copyActiveTool(tool));
9403
+ }
9404
+ }
9405
+ const afterSeq = checkpoint?.seq ?? 0;
9406
+ for (const entry of sorted) {
9407
+ if (entry.seq <= afterSeq)
9408
+ continue;
9409
+ if (entry.entryType === "tool_catalog") {
9410
+ catalog = copyCatalog(entry.payload.message.toolCatalog ?? null);
9411
+ continue;
9412
+ }
9413
+ if (entry.entryType !== "tool_definitions")
9414
+ continue;
9415
+ for (const removed of entry.payload.message.toolsRemoved ?? []) {
9416
+ const current = active.get(removed.name);
9417
+ if (current !== void 0 && (removed.version === void 0 || current.version === removed.version)) {
9418
+ active.delete(removed.name);
9419
+ }
9420
+ }
9421
+ for (const tool of entry.payload.message.toolDefinitions ?? []) {
9422
+ const activeTool = normalizeActiveTool(tool);
9423
+ active.set(activeTool.spec.name, activeTool);
9424
+ }
9425
+ }
9426
+ return { catalog, activeTools: [...active.values()] };
9427
+ }
9428
+ function normalizeActiveTool(tool) {
9429
+ const activation = "activation" in tool && (tool.activation === "baseline" || tool.activation === "search") ? tool.activation : "search";
9430
+ return { version: tool.version, spec: tool.spec, activation };
9431
+ }
9432
+ function copyCatalog(catalog) {
9433
+ return catalog === null ? null : { mode: catalog.mode, tools: { ...catalog.tools } };
9434
+ }
9435
+ function copyActiveTool(tool) {
9436
+ return { version: tool.version, spec: tool.spec, activation: tool.activation };
9437
+ }
9438
+
9343
9439
  // ../../packages/llm/llm-pi-ai/dist/adapter.js
9344
9440
  import { clampThinkingLevel, ModelsError } from "@earendil-works/pi-ai";
9345
9441
  import { builtinModels } from "@earendil-works/pi-ai/providers/all";
@@ -9520,8 +9616,22 @@ function parseToolArguments(raw) {
9520
9616
  kind: "protocol"
9521
9617
  });
9522
9618
  }
9619
+ if (!Object.values(parsed).every(isJsonValue)) {
9620
+ throw new ModelError("tool-call arguments contain invalid JSON values", {
9621
+ kind: "protocol"
9622
+ });
9623
+ }
9523
9624
  return parsed;
9524
9625
  }
9626
+ function isJsonValue(value) {
9627
+ if (value === null || typeof value === "string" || typeof value === "boolean")
9628
+ return true;
9629
+ if (typeof value === "number")
9630
+ return Number.isFinite(value);
9631
+ if (Array.isArray(value))
9632
+ return value.every(isJsonValue);
9633
+ return isRecord(value) && Object.values(value).every(isJsonValue);
9634
+ }
9525
9635
  function zeroUsage() {
9526
9636
  return {
9527
9637
  input: 0,
@@ -9959,7 +10069,10 @@ var PiModelsStore = class {
9959
10069
  return void 0;
9960
10070
  }
9961
10071
  return {
9962
- models: entry.models.map((model2) => restoreModel(providerId, model2)),
10072
+ models: entry.models.flatMap((model2) => {
10073
+ const restored = restoreModel(providerId, model2);
10074
+ return restored === void 0 ? [] : [restored];
10075
+ }),
9963
10076
  ...entry.lastModified === void 0 ? {} : { lastModified: entry.lastModified },
9964
10077
  ...entry.checkedAt === void 0 ? {} : { checkedAt: entry.checkedAt },
9965
10078
  ...entry.etag === void 0 ? {} : { etag: entry.etag }
@@ -9978,12 +10091,15 @@ var PiModelsStore = class {
9978
10091
  }
9979
10092
  };
9980
10093
  function restoreModel(providerId, model2) {
10094
+ const type = model2["type"] ?? "chat";
10095
+ if (type !== "chat" && type !== "image" && type !== "classifier")
10096
+ return void 0;
9981
10097
  const id = readString(model2, "id", providerId);
9982
10098
  const name = readString(model2, "name", providerId);
9983
10099
  const api2 = readString(model2, "api", providerId);
9984
10100
  const provider2 = readString(model2, "provider", providerId);
9985
10101
  const input = model2["input"];
9986
- if (!Array.isArray(input) || !input.every((item) => typeof item === "string")) {
10102
+ if (!Array.isArray(input) || !input.every((item) => item === "text" || item === "image")) {
9987
10103
  throw new ModelError(`stored model for '${providerId}' has invalid input`, {
9988
10104
  kind: "protocol"
9989
10105
  });
@@ -9994,6 +10110,27 @@ function restoreModel(providerId, model2) {
9994
10110
  kind: "protocol"
9995
10111
  });
9996
10112
  }
10113
+ const common = {
10114
+ ...model2,
10115
+ id,
10116
+ name,
10117
+ api: api2,
10118
+ provider: provider2,
10119
+ input,
10120
+ cost: cost2,
10121
+ baseUrl: readString(model2, "baseUrl", providerId)
10122
+ };
10123
+ if (type === "image") {
10124
+ const output = model2["output"];
10125
+ if (!Array.isArray(output) || !output.includes("image") || !output.every((item) => item === "text" || item === "image")) {
10126
+ throw new ModelError(`stored model for '${providerId}' has invalid output`, { kind: "protocol" });
10127
+ }
10128
+ return { ...common, type, api: api2, output };
10129
+ }
10130
+ const contextWindow = readPositiveNumber(model2, "contextWindow", providerId);
10131
+ if (type === "classifier") {
10132
+ return { ...common, type, api: api2, contextWindow };
10133
+ }
9997
10134
  const reasoning = model2["reasoning"];
9998
10135
  if (typeof reasoning !== "boolean") {
9999
10136
  throw new ModelError(`stored model for '${providerId}' has invalid reasoning`, {
@@ -10001,15 +10138,13 @@ function restoreModel(providerId, model2) {
10001
10138
  });
10002
10139
  }
10003
10140
  return {
10004
- ...model2,
10005
- id,
10006
- name,
10141
+ ...common,
10142
+ type,
10007
10143
  api: api2,
10008
- provider: provider2,
10009
10144
  reasoning,
10010
10145
  input,
10011
10146
  cost: cost2,
10012
- contextWindow: readPositiveNumber(model2, "contextWindow", providerId),
10147
+ contextWindow,
10013
10148
  maxTokens: readPositiveNumber(model2, "maxTokens", providerId)
10014
10149
  };
10015
10150
  }
@@ -10043,11 +10178,11 @@ function isCost(value) {
10043
10178
  }
10044
10179
 
10045
10180
  // ../../packages/llm/llm-pi-ai/dist/platform.js
10046
- import { defaultProviderAuthContext, getSupportedThinkingLevels } from "@earendil-works/pi-ai";
10181
+ import { defaultProviderAuthContext, getSupportedThinkingLevels, isModelType as isModelType2 } from "@earendil-works/pi-ai";
10047
10182
  import { builtinModels as builtinModels2 } from "@earendil-works/pi-ai/providers/all";
10048
10183
 
10049
10184
  // ../../packages/llm/llm-pi-ai/dist/custom-models.js
10050
- import { createProvider, envApiKeyAuth } from "@earendil-works/pi-ai";
10185
+ import { createProvider, envApiKeyAuth, isModelType } from "@earendil-works/pi-ai";
10051
10186
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
10052
10187
  import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
10053
10188
  import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
@@ -17873,6 +18008,8 @@ var completionsCompat = partial(typebox_exports.Object({
17873
18008
  supportsOpenAIGrammarTools: typebox_exports.Boolean(),
17874
18009
  supportsStrictMode: typebox_exports.Boolean(),
17875
18010
  cacheControlFormat: typebox_exports.Literal("anthropic"),
18011
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
18012
+ supportsMidConvoToolAdditions: typebox_exports.Boolean(),
17876
18013
  sendSessionAffinityHeaders: typebox_exports.Boolean(),
17877
18014
  deferredToolsMode: typebox_exports.Literal("kimi"),
17878
18015
  sessionAffinityFormat: affinity,
@@ -17882,6 +18019,7 @@ var completionsCompat = partial(typebox_exports.Object({
17882
18019
  var responsesCompat = partial(typebox_exports.Object({
17883
18020
  supportsDeveloperRole: typebox_exports.Boolean(),
17884
18021
  sessionAffinityFormat: affinity,
18022
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
17885
18023
  supportsLongCacheRetention: typebox_exports.Boolean(),
17886
18024
  supportsStrictMode: typebox_exports.Boolean(),
17887
18025
  supportsOpenAIGrammarTools: typebox_exports.Boolean(),
@@ -17901,6 +18039,9 @@ var anthropicCompat = partial(typebox_exports.Object({
17901
18039
  supportsStrictTools: typebox_exports.Boolean(),
17902
18040
  supportsMidConvoEffort: typebox_exports.Boolean(),
17903
18041
  supportsToolReferences: typebox_exports.Boolean(),
18042
+ supportsMidConvoSystemMessages: typebox_exports.Boolean(),
18043
+ supportsMidConvoToolChanges: typebox_exports.Boolean(),
18044
+ sessionAffinityFormat: typebox_exports.Literal("openrouter"),
17904
18045
  allowedFallbackModels: typebox_exports.Array(typebox_exports.Object({ provider: text, model: text, cost }, { additionalProperties: false }))
17905
18046
  }, { additionalProperties: false }));
17906
18047
  var api = typebox_exports.Union([
@@ -18073,6 +18214,10 @@ function customProvider(id, config, base) {
18073
18214
  baseUrl: config.baseUrl ?? base?.baseUrl,
18074
18215
  headers: { ...base?.headers, ...config.headers },
18075
18216
  getModels,
18217
+ getAllModels: () => [
18218
+ ...getModels(),
18219
+ ...(base?.getAllModels?.() ?? []).filter((model2) => !isModelType(model2, "chat"))
18220
+ ],
18076
18221
  // Keep provider-owned refresh/cache logic; getModels reapplies user overrides afterwards.
18077
18222
  ...base?.filterModels === void 0 ? {} : {
18078
18223
  filterModels: (models, credential) => {
@@ -18080,6 +18225,12 @@ function customProvider(id, config, base) {
18080
18225
  return models.filter((model2) => available.has(model2.id) || additions.has(model2.id));
18081
18226
  }
18082
18227
  },
18228
+ ...base?.filterAllModels === void 0 ? {} : {
18229
+ filterAllModels: (models, credential) => {
18230
+ const available = new Set(base.filterAllModels(models, credential).map((model2) => `${model2.type ?? "chat"}:${model2.id}`));
18231
+ return models.filter((model2) => available.has(`${model2.type ?? "chat"}:${model2.id}`) || isModelType(model2, "chat") && additions.has(model2.id));
18232
+ }
18233
+ },
18083
18234
  stream: (model2, context, options) => dispatch(model2).stream(model2, context, options),
18084
18235
  streamSimple: (model2, context, options) => dispatch(model2).streamSimple(model2, context, options)
18085
18236
  };
@@ -18190,7 +18341,9 @@ var PiAiPlatform = class {
18190
18341
  }
18191
18342
  };
18192
18343
  function isEligibleProvider(provider2, excludedProviderIds) {
18193
- return provider2.auth.apiKey?.login !== void 0 && !excludedProviderIds.has(provider2.id);
18344
+ const allModels = provider2.getAllModels?.() ?? provider2.getModels();
18345
+ return provider2.auth.apiKey?.login !== void 0 && !excludedProviderIds.has(provider2.id) && // Empty dynamic catalogs remain eligible; operation-only catalogs do not.
18346
+ (allModels.length === 0 || allModels.some((model2) => isModelType2(model2, "chat")));
18194
18347
  }
18195
18348
  var PiAiCatalog = class {
18196
18349
  models;
@@ -18381,6 +18534,33 @@ function errorMessage3(error) {
18381
18534
  return error instanceof Error ? error.message : String(error);
18382
18535
  }
18383
18536
 
18537
+ // ../../packages/llm/llm-pi-ai/dist/tool-validation.js
18538
+ import { validateToolArguments } from "@earendil-works/pi-ai";
18539
+ var validatePiToolArguments = (spec2, call, args) => {
18540
+ const tool = {
18541
+ name: spec2.name,
18542
+ description: spec2.description,
18543
+ parameters: spec2.parameters
18544
+ };
18545
+ const toolCall = {
18546
+ type: "toolCall",
18547
+ id: call.id,
18548
+ name: call.name,
18549
+ arguments: args
18550
+ };
18551
+ let validated;
18552
+ try {
18553
+ validated = validateToolArguments(tool, toolCall);
18554
+ } catch (error) {
18555
+ const message = error instanceof Error ? error.message : String(error);
18556
+ throw new Error(message.split("\n\nReceived arguments:\n")[0]);
18557
+ }
18558
+ if (typeof validated !== "object" || validated === null || Array.isArray(validated)) {
18559
+ throw new Error("Validated tool arguments must be a JSON object");
18560
+ }
18561
+ return validated;
18562
+ };
18563
+
18384
18564
  // ../../packages/storage/local-config/dist/config.js
18385
18565
  import { randomUUID as randomUUID3 } from "node:crypto";
18386
18566
  import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
@@ -28760,8 +28940,13 @@ var StdTerminalDriver = class {
28760
28940
  var ToolRuntime = class {
28761
28941
  tools;
28762
28942
  createExecutionContext;
28763
- constructor(tools, options = {}) {
28943
+ validateArguments;
28944
+ constructor(tools, options) {
28945
+ if (typeof options?.validateToolArguments !== "function") {
28946
+ throw new Error("Tool argument validator is required");
28947
+ }
28764
28948
  this.tools = tools;
28949
+ this.validateArguments = options.validateToolArguments;
28765
28950
  this.createExecutionContext = options.createExecutionContext ?? ((_, cancelToken) => ({
28766
28951
  signal: cancelToken?.signal,
28767
28952
  isCancelled: () => isCancelled(cancelToken),
@@ -28772,6 +28957,7 @@ var ToolRuntime = class {
28772
28957
  }
28773
28958
  async execute(request) {
28774
28959
  const registry = request.registry ?? this.tools;
28960
+ const definitions = new Map(registry.definitions.map((tool) => [tool.name, tool]));
28775
28961
  const results = new Array(request.toolCalls.length).fill(void 0);
28776
28962
  const prepared = [];
28777
28963
  for (let offset = 0; offset < request.toolCalls.length; offset += 1) {
@@ -28779,7 +28965,7 @@ var ToolRuntime = class {
28779
28965
  if (toolCall === void 0) {
28780
28966
  continue;
28781
28967
  }
28782
- let args;
28968
+ let args = { _raw: toolCall.arguments };
28783
28969
  let result;
28784
28970
  try {
28785
28971
  const decoded = JSON.parse(toolCall.arguments);
@@ -28787,8 +28973,11 @@ var ToolRuntime = class {
28787
28973
  throw new Error("Tool arguments must be a JSON object");
28788
28974
  }
28789
28975
  args = decoded;
28976
+ const tool = definitions.get(toolCall.name);
28977
+ if (tool === void 0)
28978
+ throw new Error(`Unknown tool: ${toolCall.name}`);
28979
+ args = this.validateArguments(tool, toolCall, args);
28790
28980
  } catch (error) {
28791
- args = { _raw: toolCall.arguments };
28792
28981
  result = { ok: false, error: errorMessage7(error) };
28793
28982
  }
28794
28983
  const event = {
@@ -28961,23 +29150,92 @@ var TOOL_SEARCH_THRESHOLD = 20;
28961
29150
  var ToolSelection = class {
28962
29151
  indexKey = "";
28963
29152
  index;
28964
- prepare(registry, records, canLoad = () => true, retainedRecords) {
29153
+ catalog = null;
29154
+ active = /* @__PURE__ */ new Map();
29155
+ apply(update) {
29156
+ if (update.catalog !== void 0)
29157
+ this.catalog = copyCatalog2(update.catalog);
29158
+ for (const removed of update.toolsRemoved) {
29159
+ const current = this.active.get(removed.name);
29160
+ if (current !== void 0 && (removed.version === void 0 || current.version === removed.version)) {
29161
+ this.active.delete(removed.name);
29162
+ }
29163
+ }
29164
+ for (const tool of update.toolsAdded)
29165
+ this.active.set(tool.spec.name, copyActiveTool2(tool));
29166
+ }
29167
+ restore(snapshot) {
29168
+ this.catalog = snapshot.catalog === null ? null : copyCatalog2(snapshot.catalog);
29169
+ this.active.clear();
29170
+ for (const tool of snapshot.activeTools)
29171
+ this.active.set(tool.spec.name, copyActiveTool2(tool));
29172
+ }
29173
+ snapshot() {
29174
+ return {
29175
+ catalog: this.catalog === null ? null : copyCatalog2(this.catalog),
29176
+ activeTools: [...this.active.values()].map(copyActiveTool2)
29177
+ };
29178
+ }
29179
+ prepare(registry) {
28965
29180
  const all = registry.definitions.filter((spec2) => spec2.name !== SEARCH_TOOL_NAME);
28966
29181
  const deferred = all.filter((spec2) => spec2.catalog?.exposure === "deferred");
28967
29182
  const mode = all.length >= TOOL_SEARCH_THRESHOLD ? "deferred" : "full";
28968
- const versions = new Map(deferred.map((spec2) => [spec2.name, toolVersion(spec2)]));
28969
- const catalog = { mode, tools: mode === "deferred" ? Object.fromEntries(versions) : {} };
28970
- let previous;
28971
- const loaded = loadedVersions(records, versions);
28972
- for (const record of records) {
28973
- if (record.toolCatalog) {
28974
- previous = record.toolCatalog;
29183
+ const versions = new Map(all.map((spec2) => [spec2.name, toolVersion(spec2)]));
29184
+ const catalog = {
29185
+ mode,
29186
+ tools: mode === "deferred" ? Object.fromEntries(deferred.map((spec2) => [spec2.name, versions.get(spec2.name)])) : {}
29187
+ };
29188
+ const current = new Map(all.map((spec2) => [spec2.name, spec2]));
29189
+ const desired = /* @__PURE__ */ new Map();
29190
+ const toolsAdded = [];
29191
+ const toolsRemoved = [];
29192
+ const missing = [];
29193
+ for (const [name, active] of this.active) {
29194
+ const spec2 = current.get(name);
29195
+ if (spec2 === void 0) {
29196
+ toolsRemoved.push({ name, version: active.version });
29197
+ missing.push(name);
29198
+ continue;
29199
+ }
29200
+ const baseline = mode === "full" || spec2.catalog?.exposure !== "deferred";
29201
+ const version = versions.get(name);
29202
+ if (version !== active.version) {
29203
+ toolsRemoved.push({ name, version: active.version });
29204
+ if (baseline || active.activation === "search") {
29205
+ const updated = {
29206
+ spec: spec2,
29207
+ version,
29208
+ activation: spec2.catalog?.exposure === "deferred" && active.activation === "search" ? "search" : "baseline"
29209
+ };
29210
+ desired.set(name, updated);
29211
+ toolsAdded.push(updated);
29212
+ }
29213
+ continue;
29214
+ }
29215
+ if (baseline || active.activation === "search") {
29216
+ desired.set(name, { spec: spec2, version, activation: active.activation });
29217
+ } else {
29218
+ toolsRemoved.push({ name, version: active.version });
28975
29219
  }
28976
29220
  }
28977
- const changed = canonicalToolJson(previous ?? { mode: "full", tools: {} }) !== canonicalToolJson(catalog);
29221
+ for (const spec2 of all) {
29222
+ const baseline = mode === "full" || spec2.catalog?.exposure !== "deferred";
29223
+ if (!baseline || desired.has(spec2.name))
29224
+ continue;
29225
+ const added = { spec: spec2, version: versions.get(spec2.name), activation: "baseline" };
29226
+ desired.set(spec2.name, added);
29227
+ toolsAdded.push(added);
29228
+ }
29229
+ const catalogChanged = canonicalToolJson(this.catalog ?? { mode: "full", tools: {} }) !== canonicalToolJson(catalog);
29230
+ const update = {
29231
+ ...catalogChanged ? { catalog } : {},
29232
+ toolsAdded,
29233
+ toolsRemoved
29234
+ };
29235
+ const loaded = new Map([...desired].map(([name, tool]) => [name, tool.version]));
28978
29236
  let announcement;
28979
- if (changed) {
28980
- const before = previous?.mode === "deferred" && mode === "deferred" ? previous.tools : {};
29237
+ if (catalogChanged) {
29238
+ const before = this.catalog?.mode === "deferred" && mode === "deferred" ? this.catalog.tools : {};
28981
29239
  const added = Object.keys(catalog.tools).filter((name) => before[name] !== catalog.tools[name]);
28982
29240
  const removed = Object.keys(before).filter((name) => before[name] !== catalog.tools[name]);
28983
29241
  const sections = ["<system-reminder>", mode === "deferred" ? "Use tool_search to search and load tool definitions before calling deferred tools." : "All currently available tool definitions are now directly available."];
@@ -28996,8 +29254,10 @@ var ToolSelection = class {
28996
29254
  }
28997
29255
  }
28998
29256
  const pending = /* @__PURE__ */ new Map();
28999
- let resultBytes = 0;
29000
- const definitions = mode === "full" ? all : [...all.filter((spec2) => spec2.catalog?.exposure !== "deferred" || loaded.has(spec2.name)), SEARCH_TOOL_SPEC];
29257
+ const definitions = [
29258
+ ...all.filter((spec2) => desired.has(spec2.name)),
29259
+ ...mode === "deferred" ? [SEARCH_TOOL_SPEC] : []
29260
+ ];
29001
29261
  const visible = new Set(definitions.map((spec2) => spec2.name));
29002
29262
  const index = this.index;
29003
29263
  const view = {
@@ -29021,55 +29281,29 @@ var ToolSelection = class {
29021
29281
  name: name2,
29022
29282
  description: spec2.description.length > 1024 ? `${spec2.description.slice(0, 1024)}...[truncated]` : spec2.description,
29023
29283
  source: spec2.catalog?.source,
29024
- status: "not_loaded_budget"
29284
+ status: "loaded"
29025
29285
  };
29026
29286
  });
29027
- resultBytes += Buffer.byteLength(JSON.stringify({ ok: true, matches }), "utf8");
29028
- const previouslyPending = new Set(pending.keys());
29029
- for (let pass = 0; pass < (retainedRecords ? 2 : 1); pass++) {
29030
- const retained = retainedRecords ? loadedVersions(retainedRecords(), versions) : loaded;
29031
- for (const match of matches) {
29032
- const { name: name2 } = match;
29033
- if (pending.has(name2)) {
29034
- match.status = previouslyPending.has(name2) ? "already_loaded" : "loaded";
29035
- continue;
29036
- }
29037
- if (retained.has(name2)) {
29038
- match.status = "already_loaded";
29039
- continue;
29040
- }
29041
- if (pass > 0 && match.status === "not_loaded_budget")
29042
- continue;
29043
- const spec2 = deferred.find((tool2) => tool2.name === name2);
29044
- const tool = { spec: spec2, version: versions.get(name2) };
29045
- if (await canLoad([...pending.values(), tool], resultBytes)) {
29046
- pending.set(name2, tool);
29047
- match.status = "loaded";
29048
- } else
29049
- match.status = "not_loaded_budget";
29287
+ for (const match of matches) {
29288
+ const { name: name2 } = match;
29289
+ if (pending.has(name2) || loaded.has(name2)) {
29290
+ match.status = "already_loaded";
29291
+ continue;
29050
29292
  }
29293
+ const spec2 = deferred.find((tool) => tool.name === name2);
29294
+ pending.set(name2, { spec: spec2, version: versions.get(name2), activation: "search" });
29051
29295
  }
29052
29296
  return { ok: true, matches, ...matches.length ? {} : { content: "No matching tools. Try an exact name, source, or different keywords." } };
29053
29297
  }
29054
29298
  };
29055
- return { view, announcement, mode, pending: () => [...pending.values()] };
29299
+ return { view, announcement, mode, update, missing, pending: () => [...pending.values()] };
29056
29300
  }
29057
29301
  };
29058
- function loadedVersions(records, versions) {
29059
- const loaded = /* @__PURE__ */ new Map();
29060
- for (const record of records) {
29061
- if (record.toolCatalog?.mode === "deferred") {
29062
- for (const [name, version] of loaded)
29063
- if (record.toolCatalog.tools[name] !== version)
29064
- loaded.delete(name);
29065
- }
29066
- for (const tool of record.toolDefinitions ?? [])
29067
- loaded.set(tool.spec.name, tool.version);
29068
- }
29069
- for (const [name, version] of loaded)
29070
- if (versions.get(name) !== version)
29071
- loaded.delete(name);
29072
- return loaded;
29302
+ function copyCatalog2(catalog) {
29303
+ return { mode: catalog.mode, tools: { ...catalog.tools } };
29304
+ }
29305
+ function copyActiveTool2(tool) {
29306
+ return { version: tool.version, spec: tool.spec, activation: tool.activation };
29073
29307
  }
29074
29308
 
29075
29309
  // ../../packages/core/tools/dist/index.js
@@ -32506,44 +32740,22 @@ var AgentStepRunner = class {
32506
32740
  this.options.onRequestId(requestId);
32507
32741
  const registry = this.options.getTools();
32508
32742
  const selection = this.options.selection ?? new ToolSelection();
32509
- let remainingTokens = Number.POSITIVE_INFINITY;
32510
- let preflightCompacted = false;
32511
- const select = (messages) => selection.prepare(registry, messages.map((message) => message.role === "system" || message.role === "user" ? message : {}), async (pending2, resultBytes) => {
32512
- const snapshot = history.snapshot();
32513
- const assistant = snapshot.at(-1);
32514
- const reserve = Math.ceil((resultBytes + Buffer.byteLength(JSON.stringify(assistant ?? {}), "utf8")) / 4);
32515
- const cost2 = reserve + pending2.reduce((sum, { spec: { name, description, parameters } }) => sum + Math.ceil(Buffer.byteLength(JSON.stringify({ name, description, parameters }), "utf8") / 4) + 8, 0);
32516
- if (cost2 <= remainingTokens)
32517
- return true;
32518
- if (preflightCompacted || !this.options.contextGovernor)
32519
- return false;
32520
- preflightCompacted = true;
32521
- if (assistant?.role !== "assistant")
32522
- return false;
32523
- try {
32524
- const projectTools = (retained) => selection.prepare(registry, [...retained.map((message) => message.role === "system" || message.role === "user" ? message : {}), { toolDefinitions: pending2 }]).view.definitions;
32525
- const compacted = await this.options.contextGovernor.prepare({
32526
- messages: snapshot.slice(0, -1),
32527
- tools: projectTools(snapshot),
32528
- projectTools,
32529
- provider: this.options.provider,
32530
- model: this.options.model,
32531
- pendingToolCall: true,
32532
- reserveTokens: reserve + 1024
32533
- });
32534
- history.retain([...compacted.messages, assistant]);
32535
- remainingTokens = compacted.hardInputLimit === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, compacted.hardInputLimit - (compacted.contextTokens ?? 0)) + cost2;
32536
- return cost2 <= remainingTokens;
32537
- } catch (error) {
32538
- if (error instanceof Error && error.name === "ContextBudgetError")
32539
- return false;
32540
- throw error;
32541
- }
32542
- }, () => history.snapshot().map((message) => message.role === "system" || message.role === "user" ? message : {}));
32743
+ const select = () => selection.prepare(registry);
32744
+ const synchronizeSelection = () => {
32745
+ const planned = select();
32746
+ if (planned.update.toolsAdded.length > 0 || planned.update.toolsRemoved.length > 0) {
32747
+ history.commitToolState(planned.update);
32748
+ }
32749
+ if (planned.announcement) {
32750
+ history.commitToolCatalog({ role: "user", ...planned.announcement });
32751
+ }
32752
+ selection.apply(planned.update);
32753
+ if (planned.missing.length > 0)
32754
+ this.options.onToolStateWarning?.(planned.missing);
32755
+ return select();
32756
+ };
32543
32757
  const prepareRequest = async () => {
32544
- let selected2 = select(history.snapshot());
32545
- if (selected2.announcement)
32546
- history.commitToolContext({ role: "user", ...selected2.announcement });
32758
+ let selected2 = synchronizeSelection();
32547
32759
  let prepared2 = { messages: history.snapshot() };
32548
32760
  for (let attempt = 0; attempt < 3; attempt++) {
32549
32761
  prepared2 = await this.options.contextGovernor?.prepare({
@@ -32551,23 +32763,23 @@ var AgentStepRunner = class {
32551
32763
  tools: selected2.view.definitions,
32552
32764
  provider: this.options.provider,
32553
32765
  model: this.options.model,
32554
- projectTools: (messages) => select(messages).view.definitions
32766
+ projectTools: () => select().view.definitions
32555
32767
  }) ?? { messages: history.snapshot() };
32556
32768
  history.retain(prepared2.messages);
32557
- selected2 = select(prepared2.messages);
32769
+ selected2 = select();
32558
32770
  const resources = await this.options.resources?.prepareContext(prepared2.messages, cancelToken?.signal) ?? [];
32559
32771
  if (!selected2.announcement && resources.length === 0)
32560
32772
  break;
32561
32773
  if (attempt === 2)
32562
32774
  throw this.options.createError("Resource catalog cannot fit the retained context.");
32563
- if (selected2.announcement)
32564
- history.commitToolContext({ role: "user", ...selected2.announcement });
32775
+ if (selected2.announcement || selected2.update.toolsAdded.length > 0 || selected2.update.toolsRemoved.length > 0) {
32776
+ selected2 = synchronizeSelection();
32777
+ }
32565
32778
  for (const message of resources) {
32566
32779
  history.commitSkillContext(message);
32567
32780
  this.options.resources?.committed([message]);
32568
32781
  }
32569
32782
  }
32570
- remainingTokens = prepared2.hardInputLimit === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, prepared2.hardInputLimit - (prepared2.contextTokens ?? 0) - 1024);
32571
32783
  return { selected: selected2, prepared: prepared2 };
32572
32784
  };
32573
32785
  let { selected, prepared } = await prepareRequest();
@@ -32639,12 +32851,10 @@ var AgentStepRunner = class {
32639
32851
  tools: definitions,
32640
32852
  provider: this.options.provider,
32641
32853
  model: this.options.model,
32642
- projectTools: (messages) => select(messages).view.definitions
32854
+ projectTools: () => select().view.definitions
32643
32855
  });
32644
32856
  this.raiseIfCancelled();
32645
32857
  history.retain(recovered.messages);
32646
- remainingTokens = Number.POSITIVE_INFINITY;
32647
- preflightCompacted = false;
32648
32858
  ({ selected, prepared } = await prepareRequest());
32649
32859
  this.raiseIfCancelled();
32650
32860
  tools = selected.view;
@@ -32728,13 +32938,16 @@ Authentication failed for ${this.options.provider}. Run /login ${this.options.pr
32728
32938
  onToolResult: (event) => this.completeToolEvent(event, modelRound)
32729
32939
  })).results;
32730
32940
  const repeated = repeatToolPolicy.record(toolCalls, toolResults);
32941
+ const current = new Map(this.options.getTools().definitions.map((spec2) => [spec2.name, toolVersion(spec2)]));
32942
+ const pending = selected.pending().filter((tool) => current.get(tool.spec.name) === tool.version);
32943
+ if (pending.length) {
32944
+ const update = { toolsAdded: pending, toolsRemoved: [] };
32945
+ history.commitToolState(update);
32946
+ selection.apply(update);
32947
+ }
32731
32948
  history.commitToolResults(toolCalls, toolResults);
32732
32949
  this.options.resources?.committed(history.snapshot().slice(-toolResults.length));
32733
32950
  this.raiseIfCancelled();
32734
- const current = new Map(this.options.getTools().definitions.map((spec2) => [spec2.name, toolVersion(spec2)]));
32735
- const pending = selected.pending().filter((tool) => current.get(tool.spec.name) === tool.version);
32736
- if (pending.length)
32737
- history.commitToolContext({ role: "system", content: "", toolDefinitions: pending });
32738
32951
  const touchedPaths = [];
32739
32952
  for (const toolResult of toolResults) {
32740
32953
  const touched = touchedPathOf(toolResult);
@@ -32971,12 +33184,20 @@ var HistoryCommitter = class {
32971
33184
  this.conversationHistory?.appendSkillContext?.({ message });
32972
33185
  this.messages.push(message);
32973
33186
  }
32974
- commitToolContext(message) {
33187
+ commitToolState(update) {
32975
33188
  this.raiseIfCancelled();
32976
- if (message.role === "system")
32977
- this.conversationHistory?.appendToolDefinitions?.({ message });
32978
- else
32979
- this.conversationHistory?.appendToolCatalog?.({ message });
33189
+ this.conversationHistory?.appendToolState?.({
33190
+ message: {
33191
+ role: "system",
33192
+ content: "",
33193
+ toolDefinitions: update.toolsAdded,
33194
+ toolsRemoved: update.toolsRemoved
33195
+ }
33196
+ });
33197
+ }
33198
+ commitToolCatalog(message) {
33199
+ this.raiseIfCancelled();
33200
+ this.conversationHistory?.appendToolCatalog?.({ message });
32980
33201
  this.messages.push(message);
32981
33202
  }
32982
33203
  /** Adopt the governor's retained context without rewriting the append-only journal. */
@@ -33045,7 +33266,7 @@ var CodingAgent = class {
33045
33266
  modelRuntime;
33046
33267
  reasoningEffort;
33047
33268
  toolRuntime;
33048
- selection = new ToolSelection();
33269
+ selection;
33049
33270
  tools;
33050
33271
  repeatToolReminderThresholds;
33051
33272
  toolExecution;
@@ -33066,9 +33287,11 @@ var CodingAgent = class {
33066
33287
  contextGovernor;
33067
33288
  prepareTools;
33068
33289
  resources;
33290
+ onToolStateWarning;
33069
33291
  constructor(options) {
33070
33292
  this.repeatToolReminderThresholds = normalizeReminderThresholds(options.repeatToolReminderThresholds ?? [3, 5, 8]);
33071
33293
  this.model = options.model;
33294
+ this.selection = options.toolSelection ?? new ToolSelection();
33072
33295
  this.tools = options.tools;
33073
33296
  this.onToolEvent = options.onToolEvent ?? null;
33074
33297
  this.onAgentEvent = options.onAgentEvent ?? null;
@@ -33082,9 +33305,11 @@ var CodingAgent = class {
33082
33305
  this.contextGovernor = options.contextGovernor ?? null;
33083
33306
  this.prepareTools = options.prepareTools;
33084
33307
  this.resources = options.resources;
33308
+ this.onToolStateWarning = options.onToolStateWarning;
33085
33309
  this.modelRuntime = new ModelRuntime(this.adapter);
33086
33310
  this.toolExecution = options.toolExecution ?? "parallel";
33087
33311
  this.toolRuntime = new ToolRuntime(this.tools, {
33312
+ validateToolArguments: options.validateToolArguments,
33088
33313
  createExecutionContext: (toolCallId, cancelToken) => makeToolContext(this.activeContext, toolCallId, cancelToken)
33089
33314
  });
33090
33315
  this.instructionRoot = options.projectRoot == null ? null : realpathOrSelf(options.projectRoot);
@@ -33167,6 +33392,7 @@ var CodingAgent = class {
33167
33392
  onToolEvent: (name, args, result) => {
33168
33393
  this.onToolEvent?.(name, args, result);
33169
33394
  },
33395
+ onToolStateWarning: this.onToolStateWarning,
33170
33396
  createError: (message, cause) => new AgentError(message, { cause }),
33171
33397
  createCancelled: (message, cause) => new AgentCancelled(message, { cause })
33172
33398
  });
@@ -33477,7 +33703,7 @@ function createSessionRuntime(options) {
33477
33703
  let releaseAttachments;
33478
33704
  const activeConversationHistory = {
33479
33705
  appendSkillContext: (input) => activeHistory(options.sessionController).appendSkillContext(input),
33480
- appendToolDefinitions: (input) => activeHistory(options.sessionController).appendToolDefinitions(input),
33706
+ appendToolState: (input) => activeHistory(options.sessionController).appendToolState(input),
33481
33707
  appendToolCatalog: (input) => activeHistory(options.sessionController).appendToolCatalog(input),
33482
33708
  appendUser: (input) => activeHistory(options.sessionController).appendUser(input),
33483
33709
  appendAssistant: (input) => activeHistory(options.sessionController).appendAssistant(input),
@@ -33508,6 +33734,7 @@ function createSessionRuntime(options) {
33508
33734
  };
33509
33735
  }
33510
33736
  });
33737
+ const toolSelection = new ToolSelection();
33511
33738
  const agent = new CodingAgent({
33512
33739
  resources: {
33513
33740
  prepareInput: async (input, signal, inputs) => {
@@ -33535,7 +33762,10 @@ function createSessionRuntime(options) {
33535
33762
  modelAdapter: adapter,
33536
33763
  model: route.model,
33537
33764
  tools: options.tools,
33765
+ validateToolArguments: validatePiToolArguments,
33538
33766
  prepareTools: options.prepareTools,
33767
+ toolSelection,
33768
+ onToolStateWarning: (names) => options.presentation?.toolStateWarning?.(names),
33539
33769
  cliName: "laohuang",
33540
33770
  cliVersion: options.version,
33541
33771
  provider: route.provider,
@@ -33544,21 +33774,20 @@ function createSessionRuntime(options) {
33544
33774
  startupCwd: options.startupCwd,
33545
33775
  conversationHistory: activeConversationHistory,
33546
33776
  contextGovernor: {
33547
- prepare: async ({ tools, projectTools: projectTools2, reserveTokens, pendingToolCall }) => {
33777
+ prepare: async ({ tools, projectTools: projectTools2 }) => {
33548
33778
  const history = options.sessionController.history;
33549
33779
  if (history === null) {
33550
33780
  return { messages: [], contextTokens: 0, contextWindow: 0, hardInputLimit: 0 };
33551
33781
  }
33552
33782
  const entries = history.entries();
33553
- const pendingAssistant = pendingToolCall ? [...entries].reverse().find((entry) => entry.entryType === "assistant_message") : void 0;
33554
33783
  const prepared = await createGovernor(history).prepare({
33555
33784
  protectedFromSeq: attachmentTurnStart,
33556
- entries: entries.filter((entry) => entry !== pendingAssistant),
33785
+ entries,
33557
33786
  currentProvider: route.provider,
33558
33787
  currentModel: route.model,
33559
33788
  tools,
33560
33789
  projectTools: projectTools2,
33561
- reserveTokens,
33790
+ toolState: toolSelection.snapshot(),
33562
33791
  budget: modelBudget(selectedModel),
33563
33792
  policy: defaultContextPolicy()
33564
33793
  });
@@ -33578,6 +33807,7 @@ function createSessionRuntime(options) {
33578
33807
  currentModel: route.model,
33579
33808
  tools,
33580
33809
  projectTools: projectTools2,
33810
+ toolState: toolSelection.snapshot(),
33581
33811
  budget: modelBudget(selectedModel),
33582
33812
  policy: defaultContextPolicy()
33583
33813
  });
@@ -33603,10 +33833,7 @@ function createSessionRuntime(options) {
33603
33833
  eventBus: session.eventBus,
33604
33834
  journal: () => options.sessionController.currentJournal
33605
33835
  });
33606
- const projectTools = (messages) => new ToolSelection().prepare(
33607
- options.tools,
33608
- messages.map((message) => message.role === "system" || message.role === "user" ? message : {})
33609
- ).view.definitions;
33836
+ const projectTools = (_messages) => toolSelection.prepare(options.tools).view.definitions;
33610
33837
  const refreshContextUsage = () => {
33611
33838
  const notify = options.presentation?.contextUsageChanged;
33612
33839
  if (notify === void 0) {
@@ -33632,6 +33859,7 @@ function createSessionRuntime(options) {
33632
33859
  options.skills?.selectSession(sessionId);
33633
33860
  options.presentation?.sessionChanged?.(sessionId);
33634
33861
  let entries = history.entries();
33862
+ toolSelection.restore(projectToolSelectionState(entries));
33635
33863
  if (entries.length === 0) {
33636
33864
  const system = agent.messages.find(
33637
33865
  (message) => message.role === "system"
@@ -33661,6 +33889,7 @@ function createSessionRuntime(options) {
33661
33889
  currentModel: route.model,
33662
33890
  tools: options.tools.definitions,
33663
33891
  projectTools,
33892
+ toolState: toolSelection.snapshot(),
33664
33893
  budget: modelBudget(selectedModel),
33665
33894
  policy: defaultContextPolicy(),
33666
33895
  trigger: "manual"
@@ -40082,17 +40311,22 @@ Submit an empty line to retry, or enter a replacement.`);
40082
40311
  projectRoot: instructionRoot,
40083
40312
  startupCwd: process.cwd(),
40084
40313
  version: VERSION,
40085
- presentation: terminalUi === null ? void 0 : {
40086
- sessionChanged: (sessionId) => terminalUi?.setSessionId(sessionId),
40087
- historyChanged: (entries) => {
40088
- const transcript = projectTranscript(entries);
40089
- if (transcript.length > 0) {
40090
- terminalUi?.replaceTranscript(transcript);
40091
- } else if (runtimeInitialized) {
40092
- resetEmptySessionTranscript(terminalUi);
40093
- }
40094
- },
40095
- contextUsageChanged: ({ contextTokens, contextWindow }) => terminalUi?.setContextUsage(contextTokens, contextWindow)
40314
+ presentation: {
40315
+ toolStateWarning: (names) => runtimeForCleanup?.publishNotice(
40316
+ `Previously active tools are no longer available and were removed: ${names.join(", ")}`
40317
+ ),
40318
+ ...terminalUi === null ? {} : {
40319
+ sessionChanged: (sessionId) => terminalUi?.setSessionId(sessionId),
40320
+ historyChanged: (entries) => {
40321
+ const transcript = projectTranscript(entries);
40322
+ if (transcript.length > 0) {
40323
+ terminalUi?.replaceTranscript(transcript);
40324
+ } else if (runtimeInitialized) {
40325
+ resetEmptySessionTranscript(terminalUi);
40326
+ }
40327
+ },
40328
+ contextUsageChanged: ({ contextTokens, contextWindow }) => terminalUi?.setContextUsage(contextTokens, contextWindow)
40329
+ }
40096
40330
  },
40097
40331
  commandDispatcher: (command) => commandDispatcher?.(command) ?? { status: "not_found", command }
40098
40332
  });