comisai 1.0.25 → 1.0.27
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/node_modules/@comis/agent/dist/bootstrap/sections/tool-descriptions.js +130 -10
- package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.d.ts +1 -1
- package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.js +9 -2
- package/node_modules/@comis/agent/dist/bridge/bridge-metrics.d.ts +8 -0
- package/node_modules/@comis/agent/dist/bridge/bridge-metrics.js +2 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.d.ts +29 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +242 -2
- package/node_modules/@comis/agent/dist/bridge/thinking-block-hash-invariant.d.ts +210 -0
- package/node_modules/@comis/agent/dist/bridge/thinking-block-hash-invariant.js +566 -0
- package/node_modules/@comis/agent/dist/context-engine/context-engine.js +8 -6
- package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.d.ts +51 -30
- package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.js +109 -36
- package/node_modules/@comis/agent/dist/executor/executor-context-engine-setup.js +5 -1
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +22 -20
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +2 -0
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +111 -15
- package/node_modules/@comis/agent/dist/executor/executor-response-filter.d.ts +20 -17
- package/node_modules/@comis/agent/dist/executor/executor-response-filter.js +132 -52
- package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +16 -3
- package/node_modules/@comis/agent/dist/executor/model-retry.d.ts +14 -0
- package/node_modules/@comis/agent/dist/executor/model-retry.js +72 -1
- package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +3 -0
- package/node_modules/@comis/agent/dist/executor/pi-executor.js +68 -9
- package/node_modules/@comis/agent/dist/executor/post-batch-continuation.d.ts +82 -0
- package/node_modules/@comis/agent/dist/executor/post-batch-continuation.js +200 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.js +1 -9
- package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +37 -2
- package/node_modules/@comis/agent/dist/executor/tool-deferral.js +45 -3
- package/node_modules/@comis/agent/dist/executor/tool-parallelism.js +0 -1
- package/node_modules/@comis/agent/dist/executor/types.d.ts +11 -2
- package/node_modules/@comis/agent/dist/index.d.ts +3 -1
- package/node_modules/@comis/agent/dist/index.js +2 -0
- package/node_modules/@comis/agent/dist/model/last-known-model.d.ts +36 -0
- package/node_modules/@comis/agent/dist/model/last-known-model.js +49 -0
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.d.ts +16 -4
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.js +65 -21
- package/node_modules/@comis/agent/dist/planner/types.d.ts +0 -2
- package/node_modules/@comis/agent/dist/session/comis-session-manager.d.ts +10 -0
- package/node_modules/@comis/agent/dist/session/comis-session-manager.js +5 -0
- package/node_modules/@comis/agent/dist/spawn/pi-mono-adapters.js +7 -0
- package/node_modules/@comis/agent/package.json +1 -1
- package/node_modules/@comis/channels/package.json +1 -1
- package/node_modules/@comis/cli/dist/client/rpc-client.js +6 -1
- package/node_modules/@comis/cli/dist/commands/doctor.js +5 -3
- package/node_modules/@comis/cli/dist/commands/health.js +5 -2
- package/node_modules/@comis/cli/dist/wizard/json-output.js +7 -3
- package/node_modules/@comis/cli/dist/wizard/steps/11-daemon-start.js +130 -0
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/dist/config/immutable-keys.d.ts +2 -2
- package/node_modules/@comis/core/dist/config/immutable-keys.js +8 -3
- package/node_modules/@comis/core/dist/config/managed-sections.d.ts +43 -4
- package/node_modules/@comis/core/dist/config/managed-sections.js +100 -6
- package/node_modules/@comis/core/dist/config/schema-agent.d.ts +39 -0
- package/node_modules/@comis/core/dist/config/schema-agent.js +14 -0
- package/node_modules/@comis/core/dist/config/schema.d.ts +4 -0
- package/node_modules/@comis/core/dist/config/schema.js +14 -0
- package/node_modules/@comis/core/dist/domain/execution-graph.d.ts +1 -1
- package/node_modules/@comis/core/dist/event-bus/events-agent.d.ts +17 -2
- package/node_modules/@comis/core/dist/exports/config.d.ts +2 -2
- package/node_modules/@comis/core/dist/exports/config.js +1 -1
- package/node_modules/@comis/core/package.json +1 -1
- package/node_modules/@comis/daemon/dist/daemon.d.ts +22 -0
- package/node_modules/@comis/daemon/dist/daemon.js +42 -0
- package/node_modules/@comis/daemon/dist/rpc/agent-handlers.d.ts +5 -2
- package/node_modules/@comis/daemon/dist/rpc/agent-handlers.js +80 -1
- package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.d.ts +67 -0
- package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.js +139 -0
- package/node_modules/@comis/daemon/dist/rpc/model-handlers.d.ts +3 -0
- package/node_modules/@comis/daemon/dist/rpc/model-handlers.js +29 -5
- package/node_modules/@comis/daemon/dist/rpc/probe-provider-auth.d.ts +30 -0
- package/node_modules/@comis/daemon/dist/rpc/probe-provider-auth.js +59 -0
- package/node_modules/@comis/daemon/dist/rpc/provider-handlers.d.ts +37 -0
- package/node_modules/@comis/daemon/dist/rpc/provider-handlers.js +330 -0
- package/node_modules/@comis/daemon/dist/rpc/rpc-dispatch.js +18 -1
- package/node_modules/@comis/daemon/dist/setup-docker-restart-warn.d.ts +4 -0
- package/node_modules/@comis/daemon/dist/setup-docker-restart-warn.js +30 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.d.ts +3 -1
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.js +28 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-cross-session.js +1 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-tools.js +7 -4
- package/node_modules/@comis/daemon/package.json +1 -1
- package/node_modules/@comis/gateway/package.json +1 -1
- package/node_modules/@comis/infra/dist/index.d.ts +1 -0
- package/node_modules/@comis/infra/dist/index.js +2 -0
- package/node_modules/@comis/infra/dist/runtime/is-docker.d.ts +1 -0
- package/node_modules/@comis/infra/dist/runtime/is-docker.js +25 -0
- package/node_modules/@comis/infra/package.json +1 -1
- package/node_modules/@comis/memory/package.json +1 -1
- package/node_modules/@comis/scheduler/package.json +1 -1
- package/node_modules/@comis/shared/package.json +1 -1
- package/node_modules/@comis/skills/dist/bridge/tool-metadata-registry.js +1 -3
- package/node_modules/@comis/skills/dist/builtin/platform/admin-manage-factory.js +24 -1
- package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.d.ts +53 -7
- package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.js +218 -24
- package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.d.ts +4 -1
- package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.js +16 -1
- package/node_modules/@comis/skills/dist/builtin/platform/index.d.ts +1 -1
- package/node_modules/@comis/skills/dist/builtin/platform/index.js +1 -1
- package/node_modules/@comis/skills/dist/builtin/platform/providers-manage-tool.d.ts +56 -0
- package/node_modules/@comis/skills/dist/builtin/platform/providers-manage-tool.js +203 -0
- package/node_modules/@comis/skills/dist/index.d.ts +1 -1
- package/node_modules/@comis/skills/dist/index.js +2 -2
- package/node_modules/@comis/skills/dist/policy/tool-policy.js +0 -1
- package/node_modules/@comis/skills/package.json +1 -1
- package/node_modules/@comis/web/dist/assets/{agent-detail-ru-AhppM.js → agent-detail-DqL6Artv.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{agent-editor-hjwRuFVp.js → agent-editor-CNM_h94Y.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{agent-list-6Uotjatr.js → agent-list-Dbh-xD_F.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{billing-view-CxysXH0p.js → billing-view-C1DmtyzK.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{channel-detail-BBCKtmne.js → channel-detail-CtCH22N1.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{channel-list-FkfeOLBQ.js → channel-list-C7xXn-60.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{chat-console-BumBaIgO.js → chat-console-C51pjFwk.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{config-editor-C9BSwHGy.js → config-editor-BLArYRB7.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{context-dag-browser-BHm00mJD.js → context-dag-browser-fuyMinNI.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{context-engine-BENY3pWE.js → context-engine-Bngf2bH0.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{delivery-view-BCnkPsAp.js → delivery-view-C80hucxX.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{diagnostics-view-C_jQFG2H.js → diagnostics-view-Cl4VbHZ6.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{ic-chat-message-FdQcZsSQ.js → ic-chat-message-ByFUoMm6.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{ic-connection-dot-BgYiK2N4.js → ic-connection-dot-C4nDHgY2.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{ic-tool-call-DMPHsLyx.js → ic-tool-call-Bh5kq-yY.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{index-FLPhHz8p.js → index-BBkuC-EU.js} +2 -2
- package/node_modules/@comis/web/dist/assets/{mcp-management-5jyScQis.js → mcp-management-DB-phOo7.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{media-config-J9oT9PPs.js → media-config-CRqZ1ZUH.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{media-test-DGTCtM8-.js → media-test-C9vE20Oy.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{memory-inspector-D5Re9ptG.js → memory-inspector-CeqfnxMZ.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{message-center-cRLK6ZmG.js → message-center-Daup7Mof.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{models-D5vu07MR.js → models-DLYnEU8E.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{observe-view-CalNNEmd.js → observe-view-BTSt_PO5.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-builder-DUYDGwZf.js → pipeline-builder-DknfzyLt.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-history-BAO8brOe.js → pipeline-history-JnHZdeU_.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-history-detail-DectIoQt.js → pipeline-history-detail-Dg4knsEb.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-list-BHlaBKww.js → pipeline-list-AEnibjsp.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-monitor-BhtpNEHf.js → pipeline-monitor-DG7RbIOO.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{scheduler-VafN_8xi.js → scheduler-uL1fYKAT.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{security-QQXMRTlo.js → security-C3DywRLH.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{session-detail-BpZ_8Yih.js → session-detail-BtqCNWXV.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{session-list-DfCm8Cec.js → session-list-CJXWa2XT.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{setup-wizard-C-z477CG.js → setup-wizard-ywn7oJvu.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{skills-BCOGPf6s.js → skills-DX0KYnWD.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{subagents-l-auUraL.js → subagents-B8p5YJEB.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{workspace-manager-DlvBixiq.js → workspace-manager-CgzNIrw1.js} +1 -1
- package/node_modules/@comis/web/dist/index.html +1 -1
- package/node_modules/@comis/web/package.json +1 -1
- package/package.json +14 -14
- package/node_modules/@comis/skills/dist/builtin/platform/agents-list-tool.d.ts +0 -19
- package/node_modules/@comis/skills/dist/builtin/platform/agents-list-tool.js +0 -39
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-batch continuation handler (L4 silent-termination recovery).
|
|
3
|
+
*
|
|
4
|
+
* When the LLM emits an empty final assistant turn (zero text + zero thinking +
|
|
5
|
+
* zero tool calls) following a successful tool batch within the same execution
|
|
6
|
+
* window, this handler fires a directive `session.followUp()` with multi-shot
|
|
7
|
+
* retry. Replaces the legacy SEP one-shot `generateCompletenessNudge` (whose
|
|
8
|
+
* enforcement role is now superseded; SEP plan extraction + step counting
|
|
9
|
+
* remain intact for observability).
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { type Result } from "@comis/shared";
|
|
14
|
+
import type { ComisLogger } from "@comis/infra";
|
|
15
|
+
/** Configuration for the post-batch continuation handler. */
|
|
16
|
+
export interface ContinuationConfig {
|
|
17
|
+
/** Master toggle. When false, handler returns
|
|
18
|
+
* `{recovered: false, outcome: "disabled"}` without calling followUp. */
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
/** Maximum directive followUp attempts before falling through to L3
|
|
21
|
+
* synthesis. Range 0..5; 0 is treated as disabled. */
|
|
22
|
+
maxRetries: number;
|
|
23
|
+
}
|
|
24
|
+
/** Outcome returned by the handler. */
|
|
25
|
+
export interface ContinuationOutcome {
|
|
26
|
+
recovered: boolean;
|
|
27
|
+
/** Recovered visible text from the followed-up assistant turn (only set
|
|
28
|
+
* when `recovered === true`). */
|
|
29
|
+
response?: string;
|
|
30
|
+
/** Number of followUp attempts actually made (0 when handler did not fire). */
|
|
31
|
+
attempts: number;
|
|
32
|
+
/** Terminal outcome:
|
|
33
|
+
* - `recovered` — followUp produced visible text on some attempt
|
|
34
|
+
* - `still_empty` — followUp ran but produced no visible text
|
|
35
|
+
* (single-attempt diagnostic; not a terminal flag)
|
|
36
|
+
* - `max_attempts_exhausted` — all `maxRetries` attempts produced empty
|
|
37
|
+
* - `disabled` — config.enabled = false OR maxRetries = 0
|
|
38
|
+
* - `no_match` — empty-after-tool-batch pattern not detected */
|
|
39
|
+
outcome: "recovered" | "still_empty" | "max_attempts_exhausted" | "disabled" | "no_match";
|
|
40
|
+
priorToolCallCount: number;
|
|
41
|
+
priorToolNames: string[];
|
|
42
|
+
}
|
|
43
|
+
/** Error variant — only ever returned when `session.followUp` rejects. */
|
|
44
|
+
export type ContinuationError = {
|
|
45
|
+
kind: "followup_error";
|
|
46
|
+
cause: unknown;
|
|
47
|
+
};
|
|
48
|
+
/** Dependencies passed in by the executor wire-in site. */
|
|
49
|
+
export interface RunPostBatchContinuationDeps {
|
|
50
|
+
/** Live session — invoked via `followUp(text)` to issue the directive. */
|
|
51
|
+
session: {
|
|
52
|
+
followUp(text: string): Promise<unknown>;
|
|
53
|
+
messages?: unknown[];
|
|
54
|
+
};
|
|
55
|
+
/** Session messages — passed explicitly per the canonical
|
|
56
|
+
* `(session as any).messages ?? []` pattern at executor-prompt-runner.ts:797. */
|
|
57
|
+
messages: unknown[];
|
|
58
|
+
config: ContinuationConfig;
|
|
59
|
+
logger: ComisLogger;
|
|
60
|
+
agentId?: string;
|
|
61
|
+
/** Read visible text from the latest assistant turn (post-followUp). */
|
|
62
|
+
getVisibleAssistantText: (session: any) => string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Run the post-batch continuation handler. Returns a `Result` so callers can
|
|
66
|
+
* distinguish a clean outcome (any `ContinuationOutcome.outcome` value) from
|
|
67
|
+
* a true error (followUp rejected).
|
|
68
|
+
*
|
|
69
|
+
* Detection (pure inspection, no throw):
|
|
70
|
+
* 1. Walk `messages` to find the most recent user-role index (lower bound).
|
|
71
|
+
* 2. The last message must be assistant with NO visible text, NO thinking
|
|
72
|
+
* blocks, and NO tool_use/toolCall blocks.
|
|
73
|
+
* 3. Within `[lowerBound, messages.length)`, count assistant turns whose
|
|
74
|
+
* content includes tool_use/toolCall blocks; collect tool names where
|
|
75
|
+
* `block.name` is a string.
|
|
76
|
+
* 4. Fire when (2) AND (≥1 tool call from step 3); else `no_match`.
|
|
77
|
+
*
|
|
78
|
+
* `session.followUp` errors are caught and propagated as
|
|
79
|
+
* `Result<_, ContinuationError>` per AGENTS.md §2.1 + the
|
|
80
|
+
* `executor-prompt-runner.ts:931` precedent.
|
|
81
|
+
*/
|
|
82
|
+
export declare function runPostBatchContinuation(deps: RunPostBatchContinuationDeps): Promise<Result<ContinuationOutcome, ContinuationError>>;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Post-batch continuation handler (L4 silent-termination recovery).
|
|
4
|
+
*
|
|
5
|
+
* When the LLM emits an empty final assistant turn (zero text + zero thinking +
|
|
6
|
+
* zero tool calls) following a successful tool batch within the same execution
|
|
7
|
+
* window, this handler fires a directive `session.followUp()` with multi-shot
|
|
8
|
+
* retry. Replaces the legacy SEP one-shot `generateCompletenessNudge` (whose
|
|
9
|
+
* enforcement role is now superseded; SEP plan extraction + step counting
|
|
10
|
+
* remain intact for observability).
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { fromPromise, ok, err } from "@comis/shared";
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Implementation
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const MODULE = "agent.executor.post-batch-continuation";
|
|
19
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
20
|
+
function isToolCallBlock(block) {
|
|
21
|
+
return block?.type === "toolCall" || block?.type === "tool_use";
|
|
22
|
+
}
|
|
23
|
+
function isThinkingBlock(block) {
|
|
24
|
+
return block?.type === "thinking";
|
|
25
|
+
}
|
|
26
|
+
function hasVisibleTextBlock(content) {
|
|
27
|
+
if (!Array.isArray(content))
|
|
28
|
+
return false;
|
|
29
|
+
for (const block of content) {
|
|
30
|
+
if (block?.type === "text" &&
|
|
31
|
+
typeof block.text === "string" &&
|
|
32
|
+
block.text.trim().length > 0) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function hasThinkingBlock(content) {
|
|
39
|
+
if (!Array.isArray(content))
|
|
40
|
+
return false;
|
|
41
|
+
return content.some(isThinkingBlock);
|
|
42
|
+
}
|
|
43
|
+
function hasToolCallBlock(content) {
|
|
44
|
+
if (!Array.isArray(content))
|
|
45
|
+
return false;
|
|
46
|
+
return content.some(isToolCallBlock);
|
|
47
|
+
}
|
|
48
|
+
function findLastUserIndex(messages) {
|
|
49
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
50
|
+
if (messages[i]?.role === "user")
|
|
51
|
+
return i; // eslint-disable-line security/detect-object-injection
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
function buildDirective(priorToolCallCount, priorToolNames) {
|
|
56
|
+
const toolList = priorToolNames.join(", ");
|
|
57
|
+
return (`[comis: post-batch continuation — your last turn was empty after ${priorToolCallCount} successful tool calls]\n` +
|
|
58
|
+
`You completed ${priorToolCallCount} tool calls (toolNames: [${toolList}]). Your previous turn produced no text, thinking, or new tool calls. The conversation is incomplete.\n\n` +
|
|
59
|
+
`You MUST either:\n` +
|
|
60
|
+
` (a) Provide a brief summary of what you accomplished AND continue with the next step from your plan, OR\n` +
|
|
61
|
+
` (b) Explicitly state "task complete" with reasoning for stopping (e.g., "All N agents created and ROLE.md customized — the user can now use them").\n\n` +
|
|
62
|
+
`Do NOT emit empty turns. If you have nothing else to do, say so explicitly.`);
|
|
63
|
+
}
|
|
64
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
65
|
+
/**
|
|
66
|
+
* Run the post-batch continuation handler. Returns a `Result` so callers can
|
|
67
|
+
* distinguish a clean outcome (any `ContinuationOutcome.outcome` value) from
|
|
68
|
+
* a true error (followUp rejected).
|
|
69
|
+
*
|
|
70
|
+
* Detection (pure inspection, no throw):
|
|
71
|
+
* 1. Walk `messages` to find the most recent user-role index (lower bound).
|
|
72
|
+
* 2. The last message must be assistant with NO visible text, NO thinking
|
|
73
|
+
* blocks, and NO tool_use/toolCall blocks.
|
|
74
|
+
* 3. Within `[lowerBound, messages.length)`, count assistant turns whose
|
|
75
|
+
* content includes tool_use/toolCall blocks; collect tool names where
|
|
76
|
+
* `block.name` is a string.
|
|
77
|
+
* 4. Fire when (2) AND (≥1 tool call from step 3); else `no_match`.
|
|
78
|
+
*
|
|
79
|
+
* `session.followUp` errors are caught and propagated as
|
|
80
|
+
* `Result<_, ContinuationError>` per AGENTS.md §2.1 + the
|
|
81
|
+
* `executor-prompt-runner.ts:931` precedent.
|
|
82
|
+
*/
|
|
83
|
+
export async function runPostBatchContinuation(deps) {
|
|
84
|
+
const { session, messages, config, logger, agentId, getVisibleAssistantText } = deps;
|
|
85
|
+
// Step 1: disable check.
|
|
86
|
+
if (!config.enabled || config.maxRetries === 0) {
|
|
87
|
+
logger.info({ module: MODULE, agentId, decision: "skip", reason: "disabled" }, "Post-batch continuation skipped");
|
|
88
|
+
return ok({
|
|
89
|
+
recovered: false,
|
|
90
|
+
attempts: 0,
|
|
91
|
+
outcome: "disabled",
|
|
92
|
+
priorToolCallCount: 0,
|
|
93
|
+
priorToolNames: [],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// Step 2: detection — last message must be empty assistant turn.
|
|
97
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
98
|
+
const msgs = messages;
|
|
99
|
+
if (!Array.isArray(msgs) || msgs.length === 0) {
|
|
100
|
+
logger.info({ module: MODULE, agentId, decision: "skip", reason: "non_empty_final" }, "Post-batch continuation skipped");
|
|
101
|
+
return ok({
|
|
102
|
+
recovered: false,
|
|
103
|
+
attempts: 0,
|
|
104
|
+
outcome: "no_match",
|
|
105
|
+
priorToolCallCount: 0,
|
|
106
|
+
priorToolNames: [],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const last = msgs[msgs.length - 1];
|
|
110
|
+
const lastIsAssistant = last?.role === "assistant";
|
|
111
|
+
const lastIsEmpty = lastIsAssistant &&
|
|
112
|
+
!hasVisibleTextBlock(last.content) &&
|
|
113
|
+
!hasThinkingBlock(last.content) &&
|
|
114
|
+
!hasToolCallBlock(last.content);
|
|
115
|
+
if (!lastIsEmpty) {
|
|
116
|
+
logger.info({ module: MODULE, agentId, decision: "skip", reason: "non_empty_final" }, "Post-batch continuation skipped");
|
|
117
|
+
return ok({
|
|
118
|
+
recovered: false,
|
|
119
|
+
attempts: 0,
|
|
120
|
+
outcome: "no_match",
|
|
121
|
+
priorToolCallCount: 0,
|
|
122
|
+
priorToolNames: [],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Step 3: collect tool calls within the current execution window.
|
|
126
|
+
const lowerBound = findLastUserIndex(msgs);
|
|
127
|
+
let priorToolCallCount = 0;
|
|
128
|
+
const priorToolNamesSet = new Set();
|
|
129
|
+
for (let i = lowerBound; i < msgs.length; i++) {
|
|
130
|
+
const m = msgs[i]; // eslint-disable-line security/detect-object-injection
|
|
131
|
+
if (m?.role !== "assistant" || !Array.isArray(m.content))
|
|
132
|
+
continue;
|
|
133
|
+
for (const block of m.content) {
|
|
134
|
+
if (isToolCallBlock(block)) {
|
|
135
|
+
priorToolCallCount++;
|
|
136
|
+
if (typeof block?.name === "string")
|
|
137
|
+
priorToolNamesSet.add(block.name);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
142
|
+
const priorToolNames = [...priorToolNamesSet];
|
|
143
|
+
if (priorToolCallCount === 0) {
|
|
144
|
+
logger.info({ module: MODULE, agentId, decision: "skip", reason: "no_tool_calls" }, "Post-batch continuation skipped");
|
|
145
|
+
return ok({
|
|
146
|
+
recovered: false,
|
|
147
|
+
attempts: 0,
|
|
148
|
+
outcome: "no_match",
|
|
149
|
+
priorToolCallCount: 0,
|
|
150
|
+
priorToolNames: [],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Step 4: decision-log fire.
|
|
154
|
+
logger.info({
|
|
155
|
+
module: MODULE,
|
|
156
|
+
agentId,
|
|
157
|
+
decision: "fire",
|
|
158
|
+
reason: "empty_after_tool_batch",
|
|
159
|
+
priorToolCallCount,
|
|
160
|
+
priorToolNames,
|
|
161
|
+
maxAttempts: config.maxRetries,
|
|
162
|
+
}, "Post-batch continuation firing");
|
|
163
|
+
// Step 5: directive multi-shot retry loop.
|
|
164
|
+
const directive = buildDirective(priorToolCallCount, priorToolNames);
|
|
165
|
+
for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
|
|
166
|
+
const followUpResult = await fromPromise(session.followUp(directive));
|
|
167
|
+
if (!followUpResult.ok) {
|
|
168
|
+
return err({ kind: "followup_error", cause: followUpResult.error });
|
|
169
|
+
}
|
|
170
|
+
const text = getVisibleAssistantText(session);
|
|
171
|
+
const outcomeForLog = text && text.length > 0 ? "recovered" : "still_empty";
|
|
172
|
+
logger.info({
|
|
173
|
+
module: MODULE,
|
|
174
|
+
agentId,
|
|
175
|
+
attempt,
|
|
176
|
+
maxAttempts: config.maxRetries,
|
|
177
|
+
priorToolCallCount,
|
|
178
|
+
priorToolNames,
|
|
179
|
+
outcome: outcomeForLog,
|
|
180
|
+
}, "Post-batch continuation attempt");
|
|
181
|
+
if (text && text.length > 0) {
|
|
182
|
+
return ok({
|
|
183
|
+
recovered: true,
|
|
184
|
+
response: text,
|
|
185
|
+
attempts: attempt,
|
|
186
|
+
outcome: "recovered",
|
|
187
|
+
priorToolCallCount,
|
|
188
|
+
priorToolNames,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Step 6: max retries exhausted.
|
|
193
|
+
return ok({
|
|
194
|
+
recovered: false,
|
|
195
|
+
attempts: config.maxRetries,
|
|
196
|
+
outcome: "max_attempts_exhausted",
|
|
197
|
+
priorToolCallCount,
|
|
198
|
+
priorToolNames,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -17,6 +17,7 @@ import { createAccumulativeLatch } from "../session-latch.js";
|
|
|
17
17
|
import { MIN_CACHEABLE_TOKENS, DEFAULT_MIN_CACHEABLE_TOKENS, CHARS_PER_TOKEN_RATIO, CHARS_PER_TOKEN_RATIO_STRUCTURED, CACHE_LOOKBACK_WINDOW } from "../../context-engine/index.js";
|
|
18
18
|
import { estimateContextChars } from "../../safety/token-estimator.js";
|
|
19
19
|
import { computeHash, djb2 } from "../cache-break-detection.js";
|
|
20
|
+
import { supportsToolSearch } from "../tool-deferral.js";
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Tool schema caches extracted to tool-schema-cache.ts (leaf module).
|
|
22
23
|
// Re-exported here for backward compatibility with existing consumers.
|
|
@@ -208,15 +209,6 @@ export function addCacheControlToLastBlock(message, retention) {
|
|
|
208
209
|
// Edge case: no cacheable block found -- place on last block as fallback
|
|
209
210
|
content[content.length - 1].cache_control = cacheControl;
|
|
210
211
|
}
|
|
211
|
-
/** DEFER-TOOL: Check if a model supports Anthropic's tool search (defer_loading).
|
|
212
|
-
* Supported: Sonnet 4.x+, Opus 4.x+. NOT supported: Haiku. */
|
|
213
|
-
function supportsToolSearch(modelId) {
|
|
214
|
-
const lower = modelId.toLowerCase();
|
|
215
|
-
if (lower.includes("haiku"))
|
|
216
|
-
return false;
|
|
217
|
-
// Sonnet 4.x and Opus 4.x support tool search
|
|
218
|
-
return lower.includes("sonnet") || lower.includes("opus");
|
|
219
|
-
}
|
|
220
212
|
/**
|
|
221
213
|
* Place exactly 1 cache_control marker on the second-to-last user message.
|
|
222
214
|
* The SDK already places one on the last user message, so we target second-to-last
|
|
@@ -100,6 +100,20 @@ export declare function resolveModelTier(contextWindow: number): ModelTier;
|
|
|
100
100
|
* Small models benefit from deterministic tool selection (0.0).
|
|
101
101
|
*/
|
|
102
102
|
export declare function resolveToolCallingTemperature(modelTier: ModelTier): number;
|
|
103
|
+
/**
|
|
104
|
+
* Anthropic models that support server-side tool_search_tool_regex
|
|
105
|
+
* (defer_loading). Sonnet 4.x+, Opus 4.x+; NOT Haiku.
|
|
106
|
+
*
|
|
107
|
+
* When this returns true, request-body-injector strips client-side
|
|
108
|
+
* `discover_tools` from the API payload and appends `tool_search_tool_regex`
|
|
109
|
+
* instead -- so any model-facing teaching string about `discover_tools`
|
|
110
|
+
* contradicts the actual tool list and must be suppressed (260428-oyc).
|
|
111
|
+
*
|
|
112
|
+
* Lowercase-normalize so provider-prefixed model ids
|
|
113
|
+
* (`anthropic/claude-sonnet-4`, `bedrock/anthropic.claude-opus-4`) resolve
|
|
114
|
+
* correctly.
|
|
115
|
+
*/
|
|
116
|
+
export declare function supportsToolSearch(modelId: string): boolean;
|
|
103
117
|
/**
|
|
104
118
|
* Extract recently-used tool names from session history messages.
|
|
105
119
|
* Looks at the most recent N assistant messages for tool_use blocks.
|
|
@@ -119,12 +133,33 @@ export declare function resolveToolDescription(tool: ToolDefinition): string;
|
|
|
119
133
|
/**
|
|
120
134
|
* Build a `<deferred-tools>` XML block for dynamic preamble injection.
|
|
121
135
|
* Lists deferred tool names and descriptions so the LLM knows what's
|
|
122
|
-
* available behind
|
|
136
|
+
* available behind a discovery mechanism.
|
|
137
|
+
*
|
|
138
|
+
* The third line (the instruction line) is conditional on `useToolSearch`:
|
|
139
|
+
*
|
|
140
|
+
* - `useToolSearch=false` (default, every non-Anthropic provider + Haiku):
|
|
141
|
+
* teaches the model to call the client-side `discover_tools` tool, which
|
|
142
|
+
* IS present in those payloads.
|
|
143
|
+
*
|
|
144
|
+
* - `useToolSearch=true` (Anthropic Sonnet/Opus 4.x): the API payload no
|
|
145
|
+
* longer contains a client-side `discover_tools` tool -- the
|
|
146
|
+
* request-body-injector replaces it with the server-side
|
|
147
|
+
* `tool_search_tool_regex` and marks deferred tools `defer_loading: true`,
|
|
148
|
+
* meaning Anthropic auto-loads them on first direct invocation. The
|
|
149
|
+
* teaching string therefore points at direct invocation + tool-search by
|
|
150
|
+
* regex, never at `discover_tools`. Without this conditional, the model
|
|
151
|
+
* reads its own preamble ("call discover_tools") against a tool list that
|
|
152
|
+
* doesn't contain that tool and gives up (260428-oyc production repro).
|
|
123
153
|
*
|
|
124
154
|
* @param entries - Deferred tool entries (remaining after discovery re-inclusion)
|
|
155
|
+
* @param options - Optional flags. `useToolSearch=true` switches the third
|
|
156
|
+
* line to the tool-search-aware variant. Defaults to false (backward-
|
|
157
|
+
* compatible with the discover_tools teaching).
|
|
125
158
|
* @returns XML block string, or empty string when no entries
|
|
126
159
|
*/
|
|
127
|
-
export declare function buildDeferredToolsContext(entries: DeferredToolEntry[]
|
|
160
|
+
export declare function buildDeferredToolsContext(entries: DeferredToolEntry[], options?: {
|
|
161
|
+
useToolSearch?: boolean;
|
|
162
|
+
}): string;
|
|
128
163
|
/**
|
|
129
164
|
* Apply unified tool deferral: rule-based, budget-based, small-model,
|
|
130
165
|
* lifecycle merge, and operator overrides.
|
|
@@ -95,6 +95,25 @@ export function resolveModelTier(contextWindow) {
|
|
|
95
95
|
export function resolveToolCallingTemperature(modelTier) {
|
|
96
96
|
return modelTier === "small" ? 0.0 : 0.1;
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Anthropic models that support server-side tool_search_tool_regex
|
|
100
|
+
* (defer_loading). Sonnet 4.x+, Opus 4.x+; NOT Haiku.
|
|
101
|
+
*
|
|
102
|
+
* When this returns true, request-body-injector strips client-side
|
|
103
|
+
* `discover_tools` from the API payload and appends `tool_search_tool_regex`
|
|
104
|
+
* instead -- so any model-facing teaching string about `discover_tools`
|
|
105
|
+
* contradicts the actual tool list and must be suppressed (260428-oyc).
|
|
106
|
+
*
|
|
107
|
+
* Lowercase-normalize so provider-prefixed model ids
|
|
108
|
+
* (`anthropic/claude-sonnet-4`, `bedrock/anthropic.claude-opus-4`) resolve
|
|
109
|
+
* correctly.
|
|
110
|
+
*/
|
|
111
|
+
export function supportsToolSearch(modelId) {
|
|
112
|
+
const lower = modelId.toLowerCase();
|
|
113
|
+
if (lower.includes("haiku"))
|
|
114
|
+
return false;
|
|
115
|
+
return lower.includes("sonnet") || lower.includes("opus");
|
|
116
|
+
}
|
|
98
117
|
// ---------------------------------------------------------------------------
|
|
99
118
|
// Recently-used tool extraction
|
|
100
119
|
// ---------------------------------------------------------------------------
|
|
@@ -148,14 +167,34 @@ export function resolveToolDescription(tool) {
|
|
|
148
167
|
/**
|
|
149
168
|
* Build a `<deferred-tools>` XML block for dynamic preamble injection.
|
|
150
169
|
* Lists deferred tool names and descriptions so the LLM knows what's
|
|
151
|
-
* available behind
|
|
170
|
+
* available behind a discovery mechanism.
|
|
171
|
+
*
|
|
172
|
+
* The third line (the instruction line) is conditional on `useToolSearch`:
|
|
173
|
+
*
|
|
174
|
+
* - `useToolSearch=false` (default, every non-Anthropic provider + Haiku):
|
|
175
|
+
* teaches the model to call the client-side `discover_tools` tool, which
|
|
176
|
+
* IS present in those payloads.
|
|
177
|
+
*
|
|
178
|
+
* - `useToolSearch=true` (Anthropic Sonnet/Opus 4.x): the API payload no
|
|
179
|
+
* longer contains a client-side `discover_tools` tool -- the
|
|
180
|
+
* request-body-injector replaces it with the server-side
|
|
181
|
+
* `tool_search_tool_regex` and marks deferred tools `defer_loading: true`,
|
|
182
|
+
* meaning Anthropic auto-loads them on first direct invocation. The
|
|
183
|
+
* teaching string therefore points at direct invocation + tool-search by
|
|
184
|
+
* regex, never at `discover_tools`. Without this conditional, the model
|
|
185
|
+
* reads its own preamble ("call discover_tools") against a tool list that
|
|
186
|
+
* doesn't contain that tool and gives up (260428-oyc production repro).
|
|
152
187
|
*
|
|
153
188
|
* @param entries - Deferred tool entries (remaining after discovery re-inclusion)
|
|
189
|
+
* @param options - Optional flags. `useToolSearch=true` switches the third
|
|
190
|
+
* line to the tool-search-aware variant. Defaults to false (backward-
|
|
191
|
+
* compatible with the discover_tools teaching).
|
|
154
192
|
* @returns XML block string, or empty string when no entries
|
|
155
193
|
*/
|
|
156
|
-
export function buildDeferredToolsContext(entries) {
|
|
194
|
+
export function buildDeferredToolsContext(entries, options) {
|
|
157
195
|
if (entries.length === 0)
|
|
158
196
|
return "";
|
|
197
|
+
const useToolSearch = options?.useToolSearch === true;
|
|
159
198
|
// Separate MCP tools (group by server) from non-MCP tools (individual listing)
|
|
160
199
|
const mcpByServer = new Map();
|
|
161
200
|
const nonMcpEntries = [];
|
|
@@ -181,10 +220,13 @@ export function buildDeferredToolsContext(entries) {
|
|
|
181
220
|
const shortNames = tools.map(t => t.name.startsWith(prefix) ? t.name.slice(prefix.length) : t.name);
|
|
182
221
|
lines.push(`[${server}] (${tools.length} tools): ${shortNames.join(", ")}`);
|
|
183
222
|
}
|
|
223
|
+
const instruction = useToolSearch
|
|
224
|
+
? "These tools auto-load on first invocation -- call them directly by name with the right arguments. To preview a tool's schema before calling, use tool_search_tool_regex with a regex matching the tool name (e.g., tool_search_tool_regex(pattern: \"agents_manage\"))."
|
|
225
|
+
: "Call discover_tools to search by keyword or server name (e.g., discover_tools(\"yfinance\")).";
|
|
184
226
|
return [
|
|
185
227
|
"<deferred-tools>",
|
|
186
228
|
"The following tools are available but not loaded.",
|
|
187
|
-
|
|
229
|
+
instruction,
|
|
188
230
|
"",
|
|
189
231
|
...lines,
|
|
190
232
|
"</deferred-tools>",
|
|
@@ -59,14 +59,23 @@ export interface ExecutionResult {
|
|
|
59
59
|
/** Stop reason from tracker (budget_reached | diminishing_returns | max_continuations | under_budget). */
|
|
60
60
|
stopReason: string;
|
|
61
61
|
};
|
|
62
|
-
/** Silent Execution Planner metrics (undefined if SEP inactive).
|
|
62
|
+
/** Silent Execution Planner metrics (undefined if SEP inactive).
|
|
63
|
+
* SEP is observability-only post-L4: plan extraction + step counting
|
|
64
|
+
* remain; the legacy enforcement nudge was replaced by the post-batch
|
|
65
|
+
* continuation handler. */
|
|
63
66
|
plannerMetrics?: {
|
|
64
67
|
stepsPlanned: number;
|
|
65
68
|
stepsCompleted: number;
|
|
66
69
|
stepsSkipped: number;
|
|
67
|
-
nudgeTriggered: boolean;
|
|
68
70
|
planExtractionTurn: number;
|
|
69
71
|
};
|
|
72
|
+
/** Post-batch continuation handler outcome (undefined when handler did
|
|
73
|
+
* not run, e.g., guardrail failed before reaching it). */
|
|
74
|
+
continuationMetrics?: {
|
|
75
|
+
fired: boolean;
|
|
76
|
+
attempts: number;
|
|
77
|
+
outcome: "recovered" | "still_empty" | "max_attempts_exhausted" | "disabled" | "no_match";
|
|
78
|
+
};
|
|
70
79
|
}
|
|
71
80
|
/** Optional overrides for per-execution behavior (e.g., sub-agent isolation). */
|
|
72
81
|
export interface ExecutionOverrides {
|
|
@@ -52,6 +52,8 @@ export { createOAuthTokenManager } from "./model/oauth-token-manager.js";
|
|
|
52
52
|
export type { OAuthTokenManager, OAuthTokenManagerDeps, OAuthError } from "./model/oauth-token-manager.js";
|
|
53
53
|
export { createAuthUsageTracker } from "./model/auth-usage-tracker.js";
|
|
54
54
|
export type { AuthUsageTracker, ProfileStats, ProfileUsageInput } from "./model/auth-usage-tracker.js";
|
|
55
|
+
export { createLastKnownModelTracker } from "./model/last-known-model.js";
|
|
56
|
+
export type { LastKnownModelTracker, LastKnownModelEntry } from "./model/last-known-model.js";
|
|
55
57
|
export { createMessageRouter, resolveAgent } from "./routing/message-router.js";
|
|
56
58
|
export type { MessageRouter } from "./routing/message-router.js";
|
|
57
59
|
export { createSessionLifecycle } from "./session/session-lifecycle.js";
|
|
@@ -136,7 +138,7 @@ export type { PiEventBridgeDeps, PiEventBridgeResult } from "./bridge/pi-event-b
|
|
|
136
138
|
export { createAuthStorageAdapter, DEFAULT_PROVIDER_KEYS } from "./model/auth-storage-adapter.js";
|
|
137
139
|
export type { AuthStorageAdapterOptions } from "./model/auth-storage-adapter.js";
|
|
138
140
|
export { createModelRegistryAdapter, registerCustomProviders, resolveInitialModel } from "./model/model-registry-adapter.js";
|
|
139
|
-
export type { CustomProviderRegistration, CustomProviderLogger } from "./model/model-registry-adapter.js";
|
|
141
|
+
export type { CustomProviderRegistration, CustomProviderLogger, RegisterCustomProvidersResult } from "./model/model-registry-adapter.js";
|
|
140
142
|
export { sessionKeyToPath, pathToSessionKey } from "./session/session-key-mapper.js";
|
|
141
143
|
export { detectBrokenFollowThrough, FOLLOW_THROUGH_PATTERNS } from "./safety/response-safety-checks.js";
|
|
142
144
|
export type { FollowThroughResult } from "./safety/response-safety-checks.js";
|
|
@@ -44,6 +44,8 @@ export { createModelScanner } from "./model/model-scanner.js";
|
|
|
44
44
|
export { createOAuthTokenManager } from "./model/oauth-token-manager.js";
|
|
45
45
|
// Auth usage tracker (from 62-05)
|
|
46
46
|
export { createAuthUsageTracker } from "./model/auth-usage-tracker.js";
|
|
47
|
+
// Last-known-working model tracker (auth-failure fallback)
|
|
48
|
+
export { createLastKnownModelTracker } from "./model/last-known-model.js";
|
|
47
49
|
// Routing
|
|
48
50
|
export { createMessageRouter, resolveAgent } from "./routing/message-router.js";
|
|
49
51
|
// Session lifecycle (renamed from session-manager.ts)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last-known-working model tracker.
|
|
3
|
+
*
|
|
4
|
+
* Tracks the most recent successfully-used model per agent across the daemon.
|
|
5
|
+
* When all configured fallbacks fail with auth errors (401/403), the retry
|
|
6
|
+
* pipeline can query this tracker for a model that recently worked -- either
|
|
7
|
+
* for the same agent or any other agent on the daemon.
|
|
8
|
+
*
|
|
9
|
+
* Follows the closure-over-mutable-state factory pattern (no classes),
|
|
10
|
+
* matching createProviderHealthMonitor.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
/** A record of a model that successfully completed a prompt. */
|
|
15
|
+
export interface LastKnownModelEntry {
|
|
16
|
+
provider: string;
|
|
17
|
+
model: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
}
|
|
20
|
+
/** Tracker interface for last-known-working model queries. */
|
|
21
|
+
export interface LastKnownModelTracker {
|
|
22
|
+
/** Record a successful model completion for an agent. */
|
|
23
|
+
recordSuccess(agentId: string, provider: string, model: string): void;
|
|
24
|
+
/** Get the last-known-working model for a specific agent. */
|
|
25
|
+
getLastKnown(agentId: string): LastKnownModelEntry | undefined;
|
|
26
|
+
/** Get any successful model from ANY agent (daemon-wide).
|
|
27
|
+
* Optionally exclude a specific provider (useful when that provider is failing). */
|
|
28
|
+
getAnyKnown(excludeProvider?: string): LastKnownModelEntry | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a last-known-working model tracker.
|
|
32
|
+
*
|
|
33
|
+
* Uses closure over mutable state (no classes) following the
|
|
34
|
+
* provider-health-monitor pattern. All operations are synchronous.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createLastKnownModelTracker(): LastKnownModelTracker;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Last-known-working model tracker.
|
|
4
|
+
*
|
|
5
|
+
* Tracks the most recent successfully-used model per agent across the daemon.
|
|
6
|
+
* When all configured fallbacks fail with auth errors (401/403), the retry
|
|
7
|
+
* pipeline can query this tracker for a model that recently worked -- either
|
|
8
|
+
* for the same agent or any other agent on the daemon.
|
|
9
|
+
*
|
|
10
|
+
* Follows the closure-over-mutable-state factory pattern (no classes),
|
|
11
|
+
* matching createProviderHealthMonitor.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Factory
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Create a last-known-working model tracker.
|
|
20
|
+
*
|
|
21
|
+
* Uses closure over mutable state (no classes) following the
|
|
22
|
+
* provider-health-monitor pattern. All operations are synchronous.
|
|
23
|
+
*/
|
|
24
|
+
export function createLastKnownModelTracker() {
|
|
25
|
+
const entries = new Map();
|
|
26
|
+
return {
|
|
27
|
+
recordSuccess(agentId, provider, model) {
|
|
28
|
+
entries.set(agentId, {
|
|
29
|
+
provider,
|
|
30
|
+
model,
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
getLastKnown(agentId) {
|
|
35
|
+
return entries.get(agentId);
|
|
36
|
+
},
|
|
37
|
+
getAnyKnown(excludeProvider) {
|
|
38
|
+
let best;
|
|
39
|
+
for (const entry of entries.values()) {
|
|
40
|
+
if (excludeProvider && entry.provider === excludeProvider)
|
|
41
|
+
continue;
|
|
42
|
+
if (!best || entry.timestamp > best.timestamp) {
|
|
43
|
+
best = entry;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return best;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -58,6 +58,18 @@ export interface CustomProviderLogger {
|
|
|
58
58
|
warn(obj: Record<string, unknown>, msg: string): void;
|
|
59
59
|
debug(obj: Record<string, unknown>, msg: string): void;
|
|
60
60
|
}
|
|
61
|
+
/** Result of custom provider registration. */
|
|
62
|
+
export interface RegisterCustomProvidersResult {
|
|
63
|
+
/** Number of provider entries successfully registered. */
|
|
64
|
+
registered: number;
|
|
65
|
+
/**
|
|
66
|
+
* Comis provider name → built-in pi SDK provider name.
|
|
67
|
+
* Populated when a YAML entry's `type` matches a built-in provider but the
|
|
68
|
+
* entry's key (comis name) differs. Lets model resolution fall back to the
|
|
69
|
+
* built-in catalog: `registry.find("gemini", id)` fails → try `registry.find("google", id)`.
|
|
70
|
+
*/
|
|
71
|
+
providerAliases: Map<string, string>;
|
|
72
|
+
}
|
|
61
73
|
/**
|
|
62
74
|
* Register YAML `providers.entries.*` with pi-coding-agent's ModelRegistry.
|
|
63
75
|
*
|
|
@@ -69,13 +81,13 @@ export interface CustomProviderLogger {
|
|
|
69
81
|
* Per-entry behavior:
|
|
70
82
|
* - Skipped if `enabled === false`.
|
|
71
83
|
* - Skipped if no models declared and no `baseUrl` override.
|
|
84
|
+
* - Models that already exist in the built-in pi SDK catalog for the
|
|
85
|
+
* entry's `type` are filtered out (no redundant registration).
|
|
72
86
|
* - On `registerProvider` error (missing baseUrl, missing apiKey, etc.),
|
|
73
87
|
* a WARN is logged and the loop continues -- one bad entry must not
|
|
74
88
|
* prevent the daemon from starting.
|
|
75
|
-
*
|
|
76
|
-
* @returns Number of entries successfully registered.
|
|
77
89
|
*/
|
|
78
|
-
export declare function registerCustomProviders(registry: ModelRegistry, entries: Record<string, CustomProviderRegistration>, secretManager: SecretManager, logger: CustomProviderLogger):
|
|
90
|
+
export declare function registerCustomProviders(registry: ModelRegistry, entries: Record<string, CustomProviderRegistration>, secretManager: SecretManager, logger: CustomProviderLogger): RegisterCustomProvidersResult;
|
|
79
91
|
/**
|
|
80
92
|
* Resolve the initial model for an agent session.
|
|
81
93
|
*
|
|
@@ -90,4 +102,4 @@ export declare function registerCustomProviders(registry: ModelRegistry, entries
|
|
|
90
102
|
export declare function resolveInitialModel(registry: ModelRegistry, config: {
|
|
91
103
|
provider: string;
|
|
92
104
|
model: string;
|
|
93
|
-
}, allowlist?: ModelAllowlist): Promise<InitialModelResult>;
|
|
105
|
+
}, allowlist?: ModelAllowlist, providerAliases?: Map<string, string>): Promise<InitialModelResult>;
|