pi-daddy 0.43.1 → 0.44.0
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/CHANGELOG.md +93 -31
- package/README.md +15 -0
- package/THIRD_PARTY_LICENSES/musl-COPYRIGHT.txt +193 -0
- package/THIRD_PARTY_LICENSES/zig-MIT.txt +21 -0
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -5
- package/dist/cli.js.map +1 -1
- package/dist/executors/activity-session.js +116 -65
- package/dist/executors/activity-session.js.map +1 -1
- package/dist/executors/captured-execution.d.ts +13 -0
- package/dist/executors/captured-execution.d.ts.map +1 -0
- package/dist/executors/captured-execution.js +53 -0
- package/dist/executors/captured-execution.js.map +1 -0
- package/dist/executors/child-final.d.ts +31 -0
- package/dist/executors/child-final.d.ts.map +1 -0
- package/dist/executors/child-final.js +197 -0
- package/dist/executors/child-final.js.map +1 -0
- package/dist/executors/executor.d.ts +2 -1
- package/dist/executors/executor.d.ts.map +1 -1
- package/dist/executors/executor.js +12 -0
- package/dist/executors/executor.js.map +1 -1
- package/dist/executors/owned-worker.d.ts +18 -0
- package/dist/executors/owned-worker.d.ts.map +1 -0
- package/dist/executors/owned-worker.js +295 -0
- package/dist/executors/owned-worker.js.map +1 -0
- package/dist/executors/worker-artifact.d.ts +3 -0
- package/dist/executors/worker-artifact.d.ts.map +1 -0
- package/dist/executors/worker-artifact.js +3 -0
- package/dist/executors/worker-artifact.js.map +1 -0
- package/dist/governance/approval-prompt.d.ts +4 -3
- package/dist/governance/approval-prompt.d.ts.map +1 -1
- package/dist/governance/approval-prompt.js +13 -6
- package/dist/governance/approval-prompt.js.map +1 -1
- package/dist/governance/captured-worker-record.d.ts +9 -0
- package/dist/governance/captured-worker-record.d.ts.map +1 -0
- package/dist/governance/captured-worker-record.js +123 -0
- package/dist/governance/captured-worker-record.js.map +1 -0
- package/dist/governance/lease-record.d.ts +4 -0
- package/dist/governance/lease-record.d.ts.map +1 -1
- package/dist/governance/lease-record.js +19 -2
- package/dist/governance/lease-record.js.map +1 -1
- package/dist/governance/ledger-events.d.ts +5 -5
- package/dist/governance/ledger-events.d.ts.map +1 -1
- package/dist/governance/ledger-v3-validation.js +1 -1
- package/dist/governance/ledger-v3-validation.js.map +1 -1
- package/dist/governance/workspace-acceptance.d.ts +3 -1
- package/dist/governance/workspace-acceptance.d.ts.map +1 -1
- package/dist/governance/workspace-acceptance.js +21 -5
- package/dist/governance/workspace-acceptance.js.map +1 -1
- package/dist/governance/workspace-lease.d.ts.map +1 -1
- package/dist/governance/workspace-lease.js +44 -1
- package/dist/governance/workspace-lease.js.map +1 -1
- package/dist/kernel/authority-text.d.ts +3 -0
- package/dist/kernel/authority-text.d.ts.map +1 -0
- package/dist/kernel/authority-text.js +22 -0
- package/dist/kernel/authority-text.js.map +1 -0
- package/dist/kernel/bounded-read-failures.d.ts +9 -0
- package/dist/kernel/bounded-read-failures.d.ts.map +1 -0
- package/dist/kernel/bounded-read-failures.js +33 -0
- package/dist/kernel/bounded-read-failures.js.map +1 -0
- package/dist/kernel/bounded-read-sync.d.ts +24 -0
- package/dist/kernel/bounded-read-sync.d.ts.map +1 -0
- package/dist/kernel/bounded-read-sync.js +75 -0
- package/dist/kernel/bounded-read-sync.js.map +1 -0
- package/dist/kernel/bounded-read.d.ts +29 -2
- package/dist/kernel/bounded-read.d.ts.map +1 -1
- package/dist/kernel/bounded-read.js +98 -9
- package/dist/kernel/bounded-read.js.map +1 -1
- package/dist/kernel/capacity.d.ts +36 -0
- package/dist/kernel/capacity.d.ts.map +1 -0
- package/dist/kernel/capacity.js +94 -0
- package/dist/kernel/capacity.js.map +1 -0
- package/dist/kernel/captured-worker-contract.d.ts +38 -0
- package/dist/kernel/captured-worker-contract.d.ts.map +1 -0
- package/dist/kernel/captured-worker-contract.js +2 -0
- package/dist/kernel/captured-worker-contract.js.map +1 -0
- package/dist/kernel/catalog.d.ts +12 -3
- package/dist/kernel/catalog.d.ts.map +1 -1
- package/dist/kernel/catalog.js +28 -8
- package/dist/kernel/catalog.js.map +1 -1
- package/dist/kernel/chain.d.ts +1 -0
- package/dist/kernel/chain.d.ts.map +1 -1
- package/dist/kernel/chain.js.map +1 -1
- package/dist/kernel/definitions.d.ts +11 -1
- package/dist/kernel/definitions.d.ts.map +1 -1
- package/dist/kernel/definitions.js +6 -3
- package/dist/kernel/definitions.js.map +1 -1
- package/dist/kernel/fanout.d.ts +5 -25
- package/dist/kernel/fanout.d.ts.map +1 -1
- package/dist/kernel/fanout.js +15 -30
- package/dist/kernel/fanout.js.map +1 -1
- package/dist/kernel/grant-env.d.ts +0 -2
- package/dist/kernel/grant-env.d.ts.map +1 -1
- package/dist/kernel/grant-env.js +0 -1
- package/dist/kernel/grant-env.js.map +1 -1
- package/dist/kernel/model-preflight.d.ts +8 -0
- package/dist/kernel/model-preflight.d.ts.map +1 -1
- package/dist/kernel/model-preflight.js +23 -0
- package/dist/kernel/model-preflight.js.map +1 -1
- package/dist/kernel/selected-definitions.d.ts +15 -0
- package/dist/kernel/selected-definitions.d.ts.map +1 -0
- package/dist/kernel/selected-definitions.js +146 -0
- package/dist/kernel/selected-definitions.js.map +1 -0
- package/dist/kernel/skill-packages.d.ts +4 -2
- package/dist/kernel/skill-packages.d.ts.map +1 -1
- package/dist/kernel/skill-packages.js +34 -41
- package/dist/kernel/skill-packages.js.map +1 -1
- package/dist/kernel/skill-resources.d.ts.map +1 -1
- package/dist/kernel/skill-resources.js +8 -11
- package/dist/kernel/skill-resources.js.map +1 -1
- package/dist/kernel/workspace.d.ts +6 -3
- package/dist/kernel/workspace.d.ts.map +1 -1
- package/dist/kernel/workspace.js +10 -6
- package/dist/kernel/workspace.js.map +1 -1
- package/dist/products/dashboard-cli.js +1 -1
- package/dist/products/dashboard-cli.js.map +1 -1
- package/dist/products/dashboard-render.d.ts +1 -2
- package/dist/products/dashboard-render.d.ts.map +1 -1
- package/dist/products/dashboard-render.js +3 -1
- package/dist/products/dashboard-render.js.map +1 -1
- package/dist/products/dashboard-session-client.d.ts +1 -2
- package/dist/products/dashboard-session-client.d.ts.map +1 -1
- package/dist/products/dashboard-session-client.js.map +1 -1
- package/dist/products/episode-report.d.ts +12 -8
- package/dist/products/episode-report.d.ts.map +1 -1
- package/dist/products/episode-report.js +61 -12
- package/dist/products/episode-report.js.map +1 -1
- package/extensions/approvals.ts +1 -1
- package/extensions/chain-plan.ts +9 -3
- package/extensions/context-staging.ts +30 -35
- package/extensions/dashboard-session-server.ts +1 -2
- package/extensions/definition-describe.ts +44 -0
- package/extensions/definition-runtime.ts +89 -26
- package/extensions/delegate-chain.ts +273 -256
- package/extensions/delegation-activation.ts +1 -1
- package/extensions/delegation.ts +41 -53
- package/extensions/execute-child.ts +167 -134
- package/extensions/fanout-outcome.ts +1 -9
- package/extensions/grants-command.ts +2 -9
- package/extensions/grants.ts +106 -38
- package/extensions/init-command.ts +52 -12
- package/extensions/reload-environment.ts +21 -2
- package/extensions/retired-inputs.ts +7 -0
- package/extensions/run-delegation.ts +212 -265
- package/extensions/session-capacity.ts +53 -0
- package/extensions/session-environment.ts +5 -2
- package/extensions/session-model-prompt.ts +33 -32
- package/extensions/session-report.ts +28 -11
- package/extensions/session.ts +200 -157
- package/extensions/workspace-runtime.ts +2 -1
- package/native/README.md +25 -0
- package/native/linux-x64/worker +0 -0
- package/native/linux-x64/worker.sha256 +1 -0
- package/native/provenance.json +42 -0
- package/native/toolchain.json +18 -0
- package/native/worker.c +209 -0
- package/package.json +15 -7
- package/src/cli.ts +16 -6
- package/src/executors/activity-session.ts +120 -66
- package/src/executors/captured-execution.ts +61 -0
- package/src/executors/child-final.ts +190 -0
- package/src/executors/executor.ts +14 -1
- package/src/executors/owned-worker.ts +322 -0
- package/src/executors/worker-artifact.ts +2 -0
- package/src/governance/approval-prompt.ts +18 -8
- package/src/governance/captured-worker-record.ts +138 -0
- package/src/governance/lease-record.ts +21 -2
- package/src/governance/ledger-events.ts +5 -5
- package/src/governance/ledger-v3-validation.ts +1 -1
- package/src/governance/workspace-acceptance.ts +20 -4
- package/src/governance/workspace-lease.ts +59 -1
- package/src/kernel/authority-text.ts +17 -0
- package/src/kernel/bounded-read-failures.ts +42 -0
- package/src/kernel/bounded-read-sync.ts +87 -0
- package/src/kernel/bounded-read.ts +128 -11
- package/src/kernel/capacity.ts +123 -0
- package/src/kernel/captured-worker-contract.ts +29 -0
- package/src/kernel/catalog.ts +45 -16
- package/src/kernel/chain.ts +1 -0
- package/src/kernel/definitions.ts +11 -2
- package/src/kernel/fanout.ts +17 -32
- package/src/kernel/grant-env.ts +0 -3
- package/src/kernel/model-preflight.ts +29 -0
- package/src/kernel/selected-definitions.ts +156 -0
- package/src/kernel/skill-packages.ts +40 -38
- package/src/kernel/skill-resources.ts +8 -9
- package/src/kernel/workspace.ts +12 -5
- package/src/products/dashboard-cli.ts +1 -1
- package/src/products/dashboard-render.ts +6 -2
- package/src/products/dashboard-session-client.ts +1 -2
- package/src/products/episode-report.ts +80 -17
- package/dist/advisors/advisor.d.ts +0 -90
- package/dist/advisors/advisor.d.ts.map +0 -1
- package/dist/advisors/advisor.js +0 -154
- package/dist/advisors/advisor.js.map +0 -1
- package/dist/advisors/decider.d.ts +0 -88
- package/dist/advisors/decider.d.ts.map +0 -1
- package/dist/advisors/decider.js +0 -29
- package/dist/advisors/decider.js.map +0 -1
- package/dist/advisors/jev.d.ts +0 -41
- package/dist/advisors/jev.d.ts.map +0 -1
- package/dist/advisors/jev.js +0 -139
- package/dist/advisors/jev.js.map +0 -1
- package/dist/advisors/settings.d.ts +0 -49
- package/dist/advisors/settings.d.ts.map +0 -1
- package/dist/advisors/settings.js +0 -99
- package/dist/advisors/settings.js.map +0 -1
- package/dist/governance/episode-cost-gate.d.ts +0 -31
- package/dist/governance/episode-cost-gate.d.ts.map +0 -1
- package/dist/governance/episode-cost-gate.js +0 -112
- package/dist/governance/episode-cost-gate.js.map +0 -1
- package/extensions/advisor-session.ts +0 -67
- package/extensions/effort-advice.ts +0 -112
- package/extensions/pruning-advice.ts +0 -133
- package/src/advisors/advisor.ts +0 -246
- package/src/advisors/decider.ts +0 -74
- package/src/advisors/jev.ts +0 -163
- package/src/advisors/settings.ts +0 -115
- package/src/governance/episode-cost-gate.ts +0 -128
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
* shape, a missing result indistinguishable from an empty one, and the child would have no way to know.
|
|
17
17
|
*/
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
|
-
import {
|
|
19
|
+
import { mkdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
20
|
+
import { BoundedReadCleanupError } from "../src/kernel/bounded-read.ts";
|
|
21
|
+
import { readBoundedTextSync } from "../src/kernel/bounded-read-sync.ts";
|
|
20
22
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
21
23
|
import {
|
|
22
24
|
CONTEXT_RANK,
|
|
@@ -31,6 +33,8 @@ import {
|
|
|
31
33
|
export interface ParentSession {
|
|
32
34
|
getSessionFile(): string | undefined;
|
|
33
35
|
getEntries(): Array<{ id: string; type: string }>;
|
|
36
|
+
/** Public Pi active leaf-to-root ancestry API; unavailable is not an empty conversation. */
|
|
37
|
+
getBranch?(): Array<{ id: string; type: string }>;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export interface StagingInput {
|
|
@@ -86,7 +90,10 @@ export function createHandoffStager(input: StagingInput) {
|
|
|
86
90
|
for (const path of granted.files ?? [])
|
|
87
91
|
sections.push({ ...readSection(input.cwd, path), keepRank: CONTEXT_RANK.file });
|
|
88
92
|
if (granted.mode === "pruned") {
|
|
89
|
-
const
|
|
93
|
+
const branch = parentTurns(input.parentSession);
|
|
94
|
+
const all = branch.turns;
|
|
95
|
+
if (branch.unavailable)
|
|
96
|
+
sections.push({ label: "parent turns — incomplete", body: branch.unavailable, keepRank: CONTEXT_RANK.summary });
|
|
90
97
|
const selection = selectPrunedTurns(all, {
|
|
91
98
|
...(granted.turns !== undefined ? { turns: granted.turns } : {}),
|
|
92
99
|
...(granted.files !== undefined ? { files: granted.files } : {}),
|
|
@@ -113,7 +120,7 @@ export function createHandoffStager(input: StagingInput) {
|
|
|
113
120
|
keepRank: (matched.has(turn.id) ? CONTEXT_RANK.fileMatchedTurn : CONTEXT_RANK.recentTurn) + index,
|
|
114
121
|
});
|
|
115
122
|
});
|
|
116
|
-
if (chosen.length === 0)
|
|
123
|
+
if (chosen.length === 0 && !branch.unavailable)
|
|
117
124
|
sections.push({ label: "parent turns", body: "(no turn of your parent's session matched the selection)" });
|
|
118
125
|
}
|
|
119
126
|
|
|
@@ -193,43 +200,31 @@ function readSection(cwd: string, path: string): ContextSection {
|
|
|
193
200
|
if (within.startsWith("..") || within === "" || isAbsolute(within))
|
|
194
201
|
return refuse("outside this session's working directory");
|
|
195
202
|
try {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
const read = readBoundedTextSync(absolute, {
|
|
204
|
+
maxBytes: MAX_FILE_BYTES,
|
|
205
|
+
prefix: true,
|
|
206
|
+
confinedRoot: root,
|
|
207
|
+
});
|
|
208
|
+
const note = read.truncated ? " (incomplete: bounded file prefix)" : "";
|
|
209
|
+
return { label: `${path}${note}`, body: read.text };
|
|
202
210
|
} catch (error) {
|
|
211
|
+
if (error instanceof BoundedReadCleanupError) throw error;
|
|
203
212
|
return { label: path, body: `(could not be read: ${error instanceof Error ? error.message : String(error)})` };
|
|
204
213
|
}
|
|
205
214
|
}
|
|
206
215
|
|
|
207
|
-
/**
|
|
208
|
-
|
|
209
|
-
*
|
|
210
|
-
* `readFileSync(...).slice(budget)` reads the whole file and then slices by UTF-16 code units, so a large file in
|
|
211
|
-
* the repository stalled the parent and the "(first N of M bytes)" label was wrong for any multi-byte content.
|
|
212
|
-
*/
|
|
213
|
-
function readBounded(path: string, budget: number): string {
|
|
214
|
-
const handle = openSync(path, "r");
|
|
216
|
+
/** Canonical active branch includes shared ancestry and excludes alternate descendants. */
|
|
217
|
+
function parentTurns(session?: ParentSession): { turns: PrunableTurn[]; unavailable?: string } {
|
|
215
218
|
try {
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (!session) return [];
|
|
227
|
-
try {
|
|
228
|
-
return session
|
|
229
|
-
.getEntries()
|
|
230
|
-
.filter((entry) => entry.type === "message")
|
|
231
|
-
.map((entry) => ({ id: entry.id, text: JSON.stringify((entry as { message?: unknown }).message ?? entry) }));
|
|
232
|
-
} catch {
|
|
233
|
-
return [];
|
|
219
|
+
if (!session?.getBranch) throw new Error("active branch API is unavailable");
|
|
220
|
+
const entries = session.getBranch();
|
|
221
|
+
if (!Array.isArray(entries)) throw new Error("active branch did not return entries");
|
|
222
|
+
return {
|
|
223
|
+
turns: entries
|
|
224
|
+
.filter((entry) => entry.type === "message")
|
|
225
|
+
.map((entry) => ({ id: entry.id, text: JSON.stringify((entry as { message?: unknown }).message ?? entry) })),
|
|
226
|
+
};
|
|
227
|
+
} catch (error) {
|
|
228
|
+
return { turns: [], unavailable: `(active branch unavailable; context is incomplete: ${String(error)})` };
|
|
234
229
|
}
|
|
235
230
|
}
|
|
@@ -75,8 +75,7 @@ async function respond(session: GrantsSession, token: string, line: string): Pro
|
|
|
75
75
|
return {
|
|
76
76
|
ok: true,
|
|
77
77
|
rows: sessionModelRows(session, [...session.definitions.keys()].sort()),
|
|
78
|
-
cost:
|
|
79
|
-
ceiling: session.episodeCostGate.ceiling,
|
|
78
|
+
cost: null, // Live complete episode cost is unavailable; retained usage remains in the episode report.
|
|
80
79
|
};
|
|
81
80
|
}
|
|
82
81
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Read-only handshake with the exact immutable definition dispatched by this extension instance. */
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import { digestDefinition } from "../src/kernel/definitions.ts";
|
|
5
|
+
import { maySpawnDefinition } from "../src/kernel/delegate.ts";
|
|
6
|
+
import type { GrantsSession } from "./session.ts";
|
|
7
|
+
import { assertDelegationAuthority } from "./delegation-authority.ts";
|
|
8
|
+
export function assertDefinitionIdentity(
|
|
9
|
+
session: GrantsSession,
|
|
10
|
+
spec: { agent?: string; definitionId?: string },
|
|
11
|
+
): void {
|
|
12
|
+
const definition = spec.agent ? session.definitions.get(spec.agent) : undefined;
|
|
13
|
+
if (spec.definitionId !== undefined && (!definition || definition.definitionId !== spec.definitionId))
|
|
14
|
+
throw Error("definition snapshot changed or is unavailable; call delegate_describe again");
|
|
15
|
+
if (definition?.binding && !spec.definitionId)
|
|
16
|
+
throw Error("Principal delegation requires delegate_describe definitionId");
|
|
17
|
+
}
|
|
18
|
+
export function registerDefinitionDescribe(pi: ExtensionAPI, session: GrantsSession): void {
|
|
19
|
+
pi.registerTool({
|
|
20
|
+
name: "delegate_describe",
|
|
21
|
+
label: "Describe a governed definition",
|
|
22
|
+
description:
|
|
23
|
+
"Read the exact selected definition identity and Principal binding before delegation. Starts no child and grants nothing.",
|
|
24
|
+
parameters: Type.Object({ agent: Type.String() }),
|
|
25
|
+
async execute(_id, params) {
|
|
26
|
+
assertDelegationAuthority(session);
|
|
27
|
+
await session.ensureDefinitions?.();
|
|
28
|
+
if (!maySpawnDefinition(session.ownGrant, params.agent))
|
|
29
|
+
throw Error("definition is outside this session's grant");
|
|
30
|
+
const definition = session.definitions.get(params.agent);
|
|
31
|
+
if (!definition?.definitionId)
|
|
32
|
+
throw Error("selected definition is unavailable; reload after correcting discovery");
|
|
33
|
+
const details = {
|
|
34
|
+
version: 1,
|
|
35
|
+
agent: params.agent,
|
|
36
|
+
sourceHash: definition.sourceHash,
|
|
37
|
+
bodySha256: digestDefinition(definition).sha256,
|
|
38
|
+
binding: definition.binding ?? null,
|
|
39
|
+
definitionId: definition.definitionId,
|
|
40
|
+
};
|
|
41
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details };
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
4
4
|
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
5
|
-
export type ModelSource = "explicit" | "session" | "definition" | "global" | "pi";
|
|
6
|
-
export type ThinkingSource = ModelSource
|
|
5
|
+
export type ModelSource = "explicit" | "session" | "definition" | "authored" | "global" | "pi";
|
|
6
|
+
export type ThinkingSource = ModelSource;
|
|
7
7
|
export interface DefinitionRuntimeChoice {
|
|
8
8
|
model?: string;
|
|
9
9
|
thinking?: ThinkingLevel;
|
|
@@ -58,36 +58,99 @@ export function resolvedModelOf(value: string | undefined): { provider: string;
|
|
|
58
58
|
: null;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/** Parse the complete needed list before evaluating availability. No model/thinking cross product. */
|
|
62
|
+
export function authoredRuntimePreferences(raw: unknown): Required<DefinitionRuntimeChoice>[] {
|
|
63
|
+
if (raw === undefined) return [];
|
|
64
|
+
let value = raw;
|
|
65
|
+
if (typeof value === "string") {
|
|
66
|
+
const text = value.startsWith("'") && value.endsWith("'") ? value.slice(1, -1) : value;
|
|
67
|
+
try {
|
|
68
|
+
value = JSON.parse(text);
|
|
69
|
+
} catch {
|
|
70
|
+
throw new Error("runtime-preferences must be a JSON array of model/thinking pairs");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!Array.isArray(value) || value.length > 32)
|
|
74
|
+
throw new Error("runtime-preferences must contain at most 32 ordered pairs");
|
|
75
|
+
return value.map((row, index) => {
|
|
76
|
+
if (
|
|
77
|
+
!isObject(row) ||
|
|
78
|
+
Object.keys(row).some((key) => !["model", "thinking"].includes(key)) ||
|
|
79
|
+
!qualifiedModel(row.model) ||
|
|
80
|
+
!parseThinkingLevel(row.thinking)
|
|
81
|
+
) {
|
|
82
|
+
throw new Error(`runtime-preferences[${index}] needs an exact provider/model and thinking pair`);
|
|
83
|
+
}
|
|
84
|
+
return { model: row.model as string, thinking: row.thinking as ThinkingLevel };
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function qualifiedModel(value: unknown): value is string {
|
|
89
|
+
return typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9_.-]*\/\S+$/.test(value);
|
|
90
|
+
}
|
|
91
|
+
|
|
61
92
|
export function resolveDefinitionRuntime(input: {
|
|
62
93
|
definition?: string;
|
|
63
94
|
explicit: { model?: string; thinking?: string };
|
|
64
95
|
session: ReadonlyMap<string, DefinitionRuntimeChoice>;
|
|
65
96
|
settings: DefinitionRuntimeSettings;
|
|
66
97
|
piModel?: string;
|
|
67
|
-
|
|
98
|
+
piThinking?: string;
|
|
99
|
+
authoredPreferences?: unknown;
|
|
100
|
+
/** Local, passive evidence only. Undefined means no known reason to reject, not remote health. */
|
|
101
|
+
unavailable?: (choice: DefinitionRuntimeChoice) => string | undefined;
|
|
68
102
|
}): ResolvedDefinitionRuntime {
|
|
69
103
|
const session = input.definition ? input.session.get(input.definition) : undefined;
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
[input.explicit
|
|
73
|
-
[session
|
|
74
|
-
[
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
];
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
const project = input.definition ? input.settings.definitions.get(input.definition) : undefined;
|
|
105
|
+
const fixed = [
|
|
106
|
+
[input.explicit, "explicit"],
|
|
107
|
+
[session, "session"],
|
|
108
|
+
[project, "definition"],
|
|
109
|
+
] as const;
|
|
110
|
+
const model = fixed.find(([choice]) => choice?.model !== undefined);
|
|
111
|
+
const thinking = fixed.find(([choice]) => choice?.thinking !== undefined);
|
|
112
|
+
const fixedModel = model?.[0]?.model;
|
|
113
|
+
const fixedThinking = thinking?.[0]?.thinking;
|
|
114
|
+
if (fixedModel !== undefined && !qualifiedModel(fixedModel))
|
|
115
|
+
throw new Error("explicit runtime model needs provider/model");
|
|
116
|
+
if (fixedThinking !== undefined && !parseThinkingLevel(fixedThinking))
|
|
117
|
+
throw new Error("explicit runtime thinking is invalid");
|
|
118
|
+
const result = (candidate: DefinitionRuntimeChoice, source: ModelSource): ResolvedDefinitionRuntime => ({
|
|
119
|
+
model: fixedModel ?? candidate.model,
|
|
120
|
+
modelSource: model?.[1] ?? source,
|
|
121
|
+
thinking: parseThinkingLevel(fixedThinking ?? candidate.thinking),
|
|
122
|
+
thinkingSource: thinking?.[1] ?? source,
|
|
123
|
+
});
|
|
124
|
+
// Only a complete higher-priority pair can leave malformed authored preferences unused.
|
|
125
|
+
if (fixedModel !== undefined && fixedThinking !== undefined) {
|
|
126
|
+
const selected = result({}, "pi");
|
|
127
|
+
const reason = input.unavailable?.(selected);
|
|
128
|
+
if (reason) throw new Error(reason);
|
|
129
|
+
return selected;
|
|
130
|
+
}
|
|
131
|
+
const preferences = authoredRuntimePreferences(input.authoredPreferences);
|
|
132
|
+
for (const candidate of preferences) {
|
|
133
|
+
if (fixedModel !== undefined && candidate.model !== fixedModel) continue;
|
|
134
|
+
if (fixedThinking !== undefined && candidate.thinking !== fixedThinking) continue;
|
|
135
|
+
const selected = result(candidate, "authored");
|
|
136
|
+
if (!input.unavailable?.(selected)) return selected;
|
|
137
|
+
}
|
|
138
|
+
// Normal fallback fills only missing fields; an overridden row is never described as authored.
|
|
139
|
+
const normal = [
|
|
140
|
+
[input.settings.defaults, "global"],
|
|
141
|
+
[{ model: input.piModel, thinking: parseThinkingLevel(input.piThinking) }, "pi"],
|
|
142
|
+
] as const;
|
|
143
|
+
let refusal: string | undefined;
|
|
144
|
+
for (const [candidate, source] of normal) {
|
|
145
|
+
const selected = result(
|
|
146
|
+
{ model: candidate.model ?? input.piModel, thinking: candidate.thinking ?? parseThinkingLevel(input.piThinking) },
|
|
147
|
+
source,
|
|
148
|
+
);
|
|
149
|
+
// Attribute a field filled from the current session accurately even when another default wins.
|
|
150
|
+
if (!model && candidate.model === undefined) selected.modelSource = "pi";
|
|
151
|
+
if (!thinking && candidate.thinking === undefined) selected.thinkingSource = "pi";
|
|
152
|
+
refusal = input.unavailable?.(selected);
|
|
153
|
+
if (!refusal) return selected;
|
|
154
|
+
}
|
|
155
|
+
throw new Error(refusal ?? "no locally usable configured runtime pair");
|
|
93
156
|
}
|