@pi-unipi/background-tasks 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -0
- package/extensions/anthropic-attribution.ts +1 -0
- package/extensions/delegate-child.ts +1 -0
- package/extensions/fusion-child.ts +1 -0
- package/package.json +40 -0
- package/src/__tests__/anthropic-attribution.test.ts +195 -0
- package/src/__tests__/config.test.ts +137 -0
- package/src/__tests__/core.test.ts +493 -0
- package/src/__tests__/delegate-artifacts.test.ts +528 -0
- package/src/__tests__/delegate-budget.test.ts +456 -0
- package/src/__tests__/delegate-launch.test.ts +676 -0
- package/src/__tests__/delegate-result-package.test.ts +350 -0
- package/src/__tests__/delegate-seed.test.ts +392 -0
- package/src/__tests__/durable-fs.test.ts +559 -0
- package/src/__tests__/extension-api.test.ts +579 -0
- package/src/__tests__/fusion-artifacts.test.ts +1039 -0
- package/src/__tests__/fusion-budget.test.ts +1356 -0
- package/src/__tests__/fusion-claude-cache.test.ts +320 -0
- package/src/__tests__/fusion-config.test.ts +335 -0
- package/src/__tests__/fusion-context-prompts.test.ts +670 -0
- package/src/__tests__/fusion-evaluation.test.ts +315 -0
- package/src/__tests__/fusion-extraction-equivalence.test.ts +58 -0
- package/src/__tests__/fusion-golden-bytes.test.ts +35 -0
- package/src/__tests__/fusion-high-cardinality.test.ts +192 -0
- package/src/__tests__/fusion-model-selector.test.ts +205 -0
- package/src/__tests__/fusion-orchestrator.test.ts +1194 -0
- package/src/__tests__/fusion-rpc.test.ts +369 -0
- package/src/__tests__/fusion-sdk.test.ts +1226 -0
- package/src/__tests__/fusion-v5-core.test.ts +219 -0
- package/src/__tests__/fusion-validate-orchestrator.test.ts +240 -0
- package/src/__tests__/fusion-web-fetch.test.ts +485 -0
- package/src/__tests__/fusion-workflows.test.ts +59 -0
- package/src/__tests__/helpers/delegate-deterministic-seed.ts +109 -0
- package/src/__tests__/helpers/delegate-seed-subprocess.ts +10 -0
- package/src/__tests__/helpers/fusion-canonical-subprocess.ts +21 -0
- package/src/__tests__/helpers/fusion-canonical.ts +140 -0
- package/src/__tests__/helpers/fusion-fake-pi.ts +279 -0
- package/src/__tests__/helpers/fusion-golden-corpus.ts +500 -0
- package/src/__tests__/helpers/fusion-high-cardinality.ts +140 -0
- package/src/__tests__/helpers/normalize.ts +22 -0
- package/src/__tests__/helpers/pi-hook-contract-evidence.json +18 -0
- package/src/__tests__/pi-launch.test.ts +202 -0
- package/src/__tests__/registry.test.ts +1580 -0
- package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +130 -0
- package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +631 -0
- package/src/__tests__/scripted-provider/delegate-guard-provider.ts +403 -0
- package/src/__tests__/scripted-provider/follow-up.test.ts +448 -0
- package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +132 -0
- package/src/__tests__/scripted-provider/fusion-reason.test.ts +310 -0
- package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +163 -0
- package/src/__tests__/scripted-provider/hook-contract-provider.ts +179 -0
- package/src/__tests__/scripted-provider/hook-probe-a.ts +3 -0
- package/src/__tests__/scripted-provider/hook-probe-b.ts +3 -0
- package/src/__tests__/scripted-provider/hook-probe-extension.ts +126 -0
- package/src/__tests__/scripted-provider/output-recovery-provider.ts +153 -0
- package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +18 -0
- package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +477 -0
- package/src/__tests__/scripted-provider/runtime-guard-probe.ts +28 -0
- package/src/__tests__/scripted-provider/runtime-guard-provider.ts +49 -0
- package/src/__tests__/scripted-provider/scripted-provider-extension.ts +408 -0
- package/src/__tests__/task-manager.test.ts +479 -0
- package/src/__tests__/windows-taskkill.test.ts +161 -0
- package/src/anthropic-attribution-path.ts +21 -0
- package/src/anthropic-attribution.ts +1983 -0
- package/src/attested-pi-run.ts +612 -0
- package/src/child-process.ts +55 -0
- package/src/common.ts +8 -0
- package/src/config.ts +292 -0
- package/src/context-parent-snapshot.ts +142 -0
- package/src/context-token-budget.ts +903 -0
- package/src/context-visible-conversation-v2.ts +551 -0
- package/src/delegate/artifacts.ts +487 -0
- package/src/delegate/budget.ts +415 -0
- package/src/delegate/hook-contract-evidence.json +18 -0
- package/src/delegate/hook-contract.ts +154 -0
- package/src/delegate/launch.ts +497 -0
- package/src/delegate/result-package.ts +459 -0
- package/src/delegate/runner.ts +449 -0
- package/src/delegate/seed.ts +423 -0
- package/src/delegate/types.ts +323 -0
- package/src/delegate-child-extension.ts +978 -0
- package/src/delegate-extension.ts +806 -0
- package/src/durable-fs.ts +386 -0
- package/src/extension-api.ts +548 -0
- package/src/fixtures/delegate-context-incident.json +17 -0
- package/src/fixtures/fusion-golden-bytes.json +310 -0
- package/src/fixtures/fusion-validate-golden-bytes.json +282 -0
- package/src/fusion/artifacts.ts +967 -0
- package/src/fusion/budget.ts +1162 -0
- package/src/fusion/child-protocol.ts +305 -0
- package/src/fusion/claude-cache.ts +207 -0
- package/src/fusion/clean-context.ts +91 -0
- package/src/fusion/config.ts +449 -0
- package/src/fusion/context.ts +265 -0
- package/src/fusion/evaluation.ts +800 -0
- package/src/fusion/orchestrator.ts +1288 -0
- package/src/fusion/output-contract.ts +34 -0
- package/src/fusion/pi-child.ts +2373 -0
- package/src/fusion/prompts.ts +345 -0
- package/src/fusion/result-package.ts +959 -0
- package/src/fusion/source-policy.ts +257 -0
- package/src/fusion/types.ts +1139 -0
- package/src/fusion/web-fetch.ts +1060 -0
- package/src/fusion/workflows.ts +184 -0
- package/src/fusion-child-extension.ts +1052 -0
- package/src/fusion-extension.ts +1293 -0
- package/src/index.ts +295 -0
- package/src/pi-launch.ts +225 -0
- package/src/registry.ts +2424 -0
- package/src/settings-overlay.ts +208 -0
- package/src/task-manager.ts +774 -0
- package/src/tools.ts +530 -0
- package/src/turndown.d.ts +15 -0
- package/src/types.ts +963 -0
- package/src/ui/fusion-model-selector.ts +322 -0
- package/src/windows-taskkill.ts +250 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
3
|
+
TOKEN_BUDGET_FAMILY_CALIBRATIONS,
|
|
4
|
+
TOKEN_BUDGET_RATE_SCALE,
|
|
5
|
+
estimateInputTokens,
|
|
6
|
+
knownTextSegment,
|
|
7
|
+
resolveTokenBudgetFamily,
|
|
8
|
+
utf8ByteClassBreakdown,
|
|
9
|
+
allowedInputTokens,
|
|
10
|
+
isUsableContextWindow,
|
|
11
|
+
type EstimateInputTokensResult,
|
|
12
|
+
type TokenBudgetByteClassBreakdown,
|
|
13
|
+
type TokenBudgetFamily,
|
|
14
|
+
type TokenBudgetFamilyCalibration,
|
|
15
|
+
type TokenBudgetRateSource,
|
|
16
|
+
} from '../context-token-budget.js';
|
|
17
|
+
import {
|
|
18
|
+
DELEGATE_BUDGET_PLAN_SCHEMA_VERSION,
|
|
19
|
+
DelegateError,
|
|
20
|
+
type DelegateLimits,
|
|
21
|
+
type DelegatePinnedRoute,
|
|
22
|
+
} from './types.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Delegate budgeting.
|
|
26
|
+
*
|
|
27
|
+
* A delegate child is a multi-turn, tool-using agent, so its budget has two
|
|
28
|
+
* distinct phases rather than Fusion's single-shot stage forecast:
|
|
29
|
+
*
|
|
30
|
+
* 1. Launch admission checks the frozen seed, framing, and child system prompt
|
|
31
|
+
* with the same backed family calibration used by Fusion for large prompts.
|
|
32
|
+
* 2. A separate provable 1 B/token forecast sizes the transcript-growth runway
|
|
33
|
+
* used for explicit tool-result spilling.
|
|
34
|
+
* 3. Runtime measurements are advisory. Package-owned growth is controlled
|
|
35
|
+
* before transcript entry; Pi and the provider own live context handling.
|
|
36
|
+
*
|
|
37
|
+
* Nothing here clips, substitutes, or silently reduces content. Tool bytes that
|
|
38
|
+
* do not fit the retained-growth runway are preserved as hashed spill artifacts.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** Output tokens reserved so the child can always finish an answer. */
|
|
42
|
+
export const DELEGATE_RESERVED_OUTPUT_TOKENS = 16_384;
|
|
43
|
+
/** Provider/tool-schema framing the package does not directly control. */
|
|
44
|
+
export const DELEGATE_FRAMING_RESERVE_TOKENS = 8_192;
|
|
45
|
+
export const DELEGATE_SAFETY_RESERVE_TOKENS = 4_096;
|
|
46
|
+
/** Below this, a route cannot hold a useful seed plus real investigation. */
|
|
47
|
+
export const DELEGATE_MIN_USABLE_INPUT_TOKENS = 8_192;
|
|
48
|
+
export const DELEGATE_MIN_CONTEXT_WINDOW_TOKENS =
|
|
49
|
+
DELEGATE_MIN_USABLE_INPUT_TOKENS +
|
|
50
|
+
DELEGATE_RESERVED_OUTPUT_TOKENS +
|
|
51
|
+
DELEGATE_FRAMING_RESERVE_TOKENS +
|
|
52
|
+
DELEGATE_SAFETY_RESERVE_TOKENS;
|
|
53
|
+
|
|
54
|
+
export const DELEGATE_DEFAULT_MAX_TURNS = 24;
|
|
55
|
+
export const DELEGATE_DEFAULT_MAX_TOOL_CALLS = 120;
|
|
56
|
+
export const DELEGATE_DEFAULT_TIMEOUT_SECONDS = 1200;
|
|
57
|
+
export const DELEGATE_MAX_TOOL_RESULT_BYTES = 64 * 1024;
|
|
58
|
+
export const DELEGATE_MAX_TOTAL_TOOL_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
59
|
+
export const DELEGATE_MAX_ANSWER_BYTES = 4 * 1024 * 1024;
|
|
60
|
+
/** Input runway held back for a final no-tool answer after investigation. */
|
|
61
|
+
export const DELEGATE_FINALIZATION_INPUT_RESERVE_TOKENS = 32 * 1024;
|
|
62
|
+
/** Remaining retained-growth runway at which the child disables tools. */
|
|
63
|
+
export const DELEGATE_FINALIZATION_TRIGGER_TOKENS = 8 * 1024;
|
|
64
|
+
/** Answers at or under this serialize inline; larger ones degrade explicitly. */
|
|
65
|
+
export const DELEGATE_INLINE_ANSWER_BYTES = 48 * 1024;
|
|
66
|
+
|
|
67
|
+
export const DELEGATE_BUDGET_POLICY_ID = 'delegate-budget-policy-v3';
|
|
68
|
+
|
|
69
|
+
export interface DelegateBudgetPolicyDescriptor {
|
|
70
|
+
id: typeof DELEGATE_BUDGET_POLICY_ID;
|
|
71
|
+
calibration_version: string;
|
|
72
|
+
calibration_table: Readonly<Record<TokenBudgetFamily, TokenBudgetFamilyCalibration>>;
|
|
73
|
+
reserved_output_tokens: number;
|
|
74
|
+
framing_reserve_tokens: number;
|
|
75
|
+
safety_reserve_tokens: number;
|
|
76
|
+
min_usable_input_tokens: number;
|
|
77
|
+
inline_answer_bytes: number;
|
|
78
|
+
finalization_input_reserve_tokens: number;
|
|
79
|
+
finalization_trigger_tokens: number;
|
|
80
|
+
launch_estimator_scope: 'calibrated_large_prompt';
|
|
81
|
+
retained_growth_estimator_scope: 'provable_1_byte_per_token';
|
|
82
|
+
live_provider_context_owner: 'pi_and_provider';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const DELEGATE_BUDGET_POLICY: DelegateBudgetPolicyDescriptor = {
|
|
86
|
+
id: DELEGATE_BUDGET_POLICY_ID,
|
|
87
|
+
calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
88
|
+
calibration_table: TOKEN_BUDGET_FAMILY_CALIBRATIONS,
|
|
89
|
+
reserved_output_tokens: DELEGATE_RESERVED_OUTPUT_TOKENS,
|
|
90
|
+
framing_reserve_tokens: DELEGATE_FRAMING_RESERVE_TOKENS,
|
|
91
|
+
safety_reserve_tokens: DELEGATE_SAFETY_RESERVE_TOKENS,
|
|
92
|
+
min_usable_input_tokens: DELEGATE_MIN_USABLE_INPUT_TOKENS,
|
|
93
|
+
inline_answer_bytes: DELEGATE_INLINE_ANSWER_BYTES,
|
|
94
|
+
finalization_input_reserve_tokens: DELEGATE_FINALIZATION_INPUT_RESERVE_TOKENS,
|
|
95
|
+
finalization_trigger_tokens: DELEGATE_FINALIZATION_TRIGGER_TOKENS,
|
|
96
|
+
launch_estimator_scope: 'calibrated_large_prompt',
|
|
97
|
+
retained_growth_estimator_scope: 'provable_1_byte_per_token',
|
|
98
|
+
live_provider_context_owner: 'pi_and_provider',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export interface DelegateAdmissionPlanV1 {
|
|
102
|
+
schema_version: typeof DELEGATE_BUDGET_PLAN_SCHEMA_VERSION;
|
|
103
|
+
policy: DelegateBudgetPolicyDescriptor;
|
|
104
|
+
route: {
|
|
105
|
+
provider: string;
|
|
106
|
+
model: string;
|
|
107
|
+
qualified_id: string;
|
|
108
|
+
context_window_tokens: number;
|
|
109
|
+
allowed_input_tokens: number;
|
|
110
|
+
family: TokenBudgetFamily;
|
|
111
|
+
backed: boolean;
|
|
112
|
+
rate_source: TokenBudgetRateSource;
|
|
113
|
+
byte_capacity_utf8_bytes: number;
|
|
114
|
+
};
|
|
115
|
+
child_prompt_utf8_bytes: number;
|
|
116
|
+
child_prompt_multibyte_utf8_bytes: number;
|
|
117
|
+
system_prompt_utf8_bytes: number;
|
|
118
|
+
system_prompt_multibyte_utf8_bytes: number;
|
|
119
|
+
launch_utf8_bytes: number;
|
|
120
|
+
launch_input_tokens_upper_bound: number;
|
|
121
|
+
conservative_launch_input_tokens: number;
|
|
122
|
+
conservative_launch_fits: boolean;
|
|
123
|
+
signed_headroom_tokens: number;
|
|
124
|
+
utilization_basis_points: number;
|
|
125
|
+
retained_growth_budget_tokens: number;
|
|
126
|
+
finalization_input_reserve_tokens: number;
|
|
127
|
+
byte_class_breakdown: TokenBudgetByteClassBreakdown;
|
|
128
|
+
dominant_byte_class: EstimateInputTokensResult['rateSource']['dominant_byte_class'];
|
|
129
|
+
estimate: EstimateInputTokensResult;
|
|
130
|
+
conservative_estimate: EstimateInputTokensResult;
|
|
131
|
+
fits: boolean;
|
|
132
|
+
limits: DelegateLimits;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function utilizationBasisPoints(tokens: number, allowed: number): number {
|
|
136
|
+
if (!Number.isSafeInteger(tokens) || tokens < 0) {
|
|
137
|
+
throw new TypeError('tokens must be a non-negative safe integer');
|
|
138
|
+
}
|
|
139
|
+
if (!Number.isSafeInteger(allowed) || allowed <= 0) {
|
|
140
|
+
throw new TypeError('allowed must be a positive safe integer');
|
|
141
|
+
}
|
|
142
|
+
return Math.floor(((tokens * 10_000) + allowed - 1) / allowed);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function routeFamily(route: DelegatePinnedRoute): ReturnType<typeof resolveTokenBudgetFamily> {
|
|
146
|
+
return resolveTokenBudgetFamily({ provider: route.provider, model: route.model });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Usable input tokens for a pinned route.
|
|
151
|
+
*
|
|
152
|
+
* A route with an unknown, non-integral, or non-positive context window is a
|
|
153
|
+
* loud `route_capacity_unknown` refusal. The delegate never assumes a default
|
|
154
|
+
* window, because assuming one is how oversized prompts reach a provider.
|
|
155
|
+
*/
|
|
156
|
+
export function delegateAllowedInputTokens(route: DelegatePinnedRoute): number {
|
|
157
|
+
if (!isUsableContextWindow(route.context_window_tokens)) {
|
|
158
|
+
throw new DelegateError(
|
|
159
|
+
`bg_delegate route ${route.qualified_id} reports no usable context-window capacity`,
|
|
160
|
+
{
|
|
161
|
+
code: 'route_capacity_unknown',
|
|
162
|
+
childCreated: false,
|
|
163
|
+
remediation: [
|
|
164
|
+
'Pin an explicit route whose model catalogue entry declares a context window.',
|
|
165
|
+
'No child was created and no capacity was assumed.',
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const allowed = allowedInputTokens(route.context_window_tokens, {
|
|
171
|
+
reservedOutputTokens: DELEGATE_RESERVED_OUTPUT_TOKENS,
|
|
172
|
+
framingReserveTokens: DELEGATE_FRAMING_RESERVE_TOKENS,
|
|
173
|
+
safetyReserveTokens: DELEGATE_SAFETY_RESERVE_TOKENS,
|
|
174
|
+
});
|
|
175
|
+
if (allowed < DELEGATE_MIN_USABLE_INPUT_TOKENS) {
|
|
176
|
+
throw new DelegateError(
|
|
177
|
+
`bg_delegate route ${route.qualified_id} has a ${String(route.context_window_tokens)}-token context window, but a delegate child requires at least ${String(DELEGATE_MIN_CONTEXT_WINDOW_TOKENS)} tokens: ${String(DELEGATE_RESERVED_OUTPUT_TOKENS)} output + ${String(DELEGATE_FRAMING_RESERVE_TOKENS)} framing + ${String(DELEGATE_SAFETY_RESERVE_TOKENS)} safety + ${String(DELEGATE_MIN_USABLE_INPUT_TOKENS)} usable input`,
|
|
178
|
+
{
|
|
179
|
+
code: 'route_capacity_unknown',
|
|
180
|
+
childCreated: false,
|
|
181
|
+
remediation: ['Pin a larger-context route for this delegate.'],
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return allowed;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface DelegateAdmissionInput {
|
|
189
|
+
route: DelegatePinnedRoute;
|
|
190
|
+
childPrompt: string;
|
|
191
|
+
childSystemPrompt: string;
|
|
192
|
+
limits: DelegateLimits;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Deterministic launch-admission forecast. Pure; creates nothing. */
|
|
196
|
+
export function planDelegateAdmission(input: DelegateAdmissionInput): DelegateAdmissionPlanV1 {
|
|
197
|
+
const allowed = delegateAllowedInputTokens(input.route);
|
|
198
|
+
const family = routeFamily(input.route);
|
|
199
|
+
const childPrompt = utf8ByteClassBreakdown(input.childPrompt);
|
|
200
|
+
const system = utf8ByteClassBreakdown(input.childSystemPrompt);
|
|
201
|
+
const segments = [knownTextSegment(input.childPrompt), knownTextSegment(input.childSystemPrompt)];
|
|
202
|
+
const estimate = estimateInputTokens({
|
|
203
|
+
family: family.family,
|
|
204
|
+
calibrationBacked: family.backed,
|
|
205
|
+
familyResolution: family.resolution,
|
|
206
|
+
allowedInputTokens: allowed,
|
|
207
|
+
scope: 'delegate_launch',
|
|
208
|
+
segments,
|
|
209
|
+
});
|
|
210
|
+
const conservativeEstimate = estimateInputTokens({
|
|
211
|
+
family: family.family,
|
|
212
|
+
calibrationBacked: family.backed,
|
|
213
|
+
familyResolution: family.resolution,
|
|
214
|
+
allowedInputTokens: allowed,
|
|
215
|
+
scope: 'conservative',
|
|
216
|
+
segments,
|
|
217
|
+
});
|
|
218
|
+
// `conservativeEstimate.tokens` intentionally uses the shared estimator's
|
|
219
|
+
// calibrated multibyte diagnostic rate. The counter-forecast published as
|
|
220
|
+
// "provable" must instead use that estimator's explicit 1 B/token ceiling
|
|
221
|
+
// for multibyte bytes as well as normal/dense bytes.
|
|
222
|
+
const provableConservativeLaunchTokens =
|
|
223
|
+
conservativeEstimate.advisory.input_tokens_if_multibyte_used_provable_ceiling;
|
|
224
|
+
const byteCapacity = Math.floor(
|
|
225
|
+
(allowed * estimate.rateSource.effective_rate_bytes_per_token_x100) / TOKEN_BUDGET_RATE_SCALE,
|
|
226
|
+
);
|
|
227
|
+
const launchBytes = childPrompt.bytes + system.bytes;
|
|
228
|
+
const retainedGrowthBudget = Math.max(
|
|
229
|
+
0,
|
|
230
|
+
allowed - estimate.tokens - DELEGATE_FINALIZATION_INPUT_RESERVE_TOKENS,
|
|
231
|
+
);
|
|
232
|
+
return {
|
|
233
|
+
schema_version: DELEGATE_BUDGET_PLAN_SCHEMA_VERSION,
|
|
234
|
+
policy: DELEGATE_BUDGET_POLICY,
|
|
235
|
+
route: {
|
|
236
|
+
provider: input.route.provider,
|
|
237
|
+
model: input.route.model,
|
|
238
|
+
qualified_id: input.route.qualified_id,
|
|
239
|
+
context_window_tokens: input.route.context_window_tokens,
|
|
240
|
+
allowed_input_tokens: allowed,
|
|
241
|
+
family: family.family,
|
|
242
|
+
backed: estimate.rateSource.backed,
|
|
243
|
+
rate_source: estimate.rateSource,
|
|
244
|
+
byte_capacity_utf8_bytes: byteCapacity,
|
|
245
|
+
},
|
|
246
|
+
child_prompt_utf8_bytes: childPrompt.bytes,
|
|
247
|
+
child_prompt_multibyte_utf8_bytes: childPrompt.multibyteBytes,
|
|
248
|
+
system_prompt_utf8_bytes: system.bytes,
|
|
249
|
+
system_prompt_multibyte_utf8_bytes: system.multibyteBytes,
|
|
250
|
+
launch_utf8_bytes: launchBytes,
|
|
251
|
+
launch_input_tokens_upper_bound: estimate.tokens,
|
|
252
|
+
conservative_launch_input_tokens: provableConservativeLaunchTokens,
|
|
253
|
+
conservative_launch_fits: provableConservativeLaunchTokens <= allowed,
|
|
254
|
+
signed_headroom_tokens: allowed - estimate.tokens,
|
|
255
|
+
utilization_basis_points: utilizationBasisPoints(estimate.tokens, allowed),
|
|
256
|
+
retained_growth_budget_tokens: retainedGrowthBudget,
|
|
257
|
+
finalization_input_reserve_tokens: DELEGATE_FINALIZATION_INPUT_RESERVE_TOKENS,
|
|
258
|
+
byte_class_breakdown: estimate.byte_class_breakdown,
|
|
259
|
+
dominant_byte_class: estimate.rateSource.dominant_byte_class,
|
|
260
|
+
estimate,
|
|
261
|
+
conservative_estimate: conservativeEstimate,
|
|
262
|
+
fits: estimate.tokens <= allowed,
|
|
263
|
+
limits: input.limits,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function rateWarningText(rateSource: TokenBudgetRateSource, qualifiedId: string): string {
|
|
268
|
+
if (rateSource.warning === null) return '';
|
|
269
|
+
return ` Estimator warning for ${qualifiedId}: ${rateSource.warning}.`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function requiredByteReduction(plan: DelegateAdmissionPlanV1): number {
|
|
273
|
+
const variableTokens =
|
|
274
|
+
plan.route.allowed_input_tokens - plan.route.rate_source.affine_f_tokens;
|
|
275
|
+
const maximumBytes = variableTokens <= 0
|
|
276
|
+
? 0
|
|
277
|
+
: Math.floor(
|
|
278
|
+
(variableTokens * plan.route.rate_source.effective_rate_bytes_per_token_x100) /
|
|
279
|
+
TOKEN_BUDGET_RATE_SCALE,
|
|
280
|
+
);
|
|
281
|
+
return Math.max(0, plan.launch_utf8_bytes - maximumBytes);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Enforce the admission plan.
|
|
286
|
+
*
|
|
287
|
+
* Called before the child process, the child session, and the artifact
|
|
288
|
+
* directory exist, so a refusal leaves zero children and zero artifacts.
|
|
289
|
+
*/
|
|
290
|
+
export function assertDelegateAdmission(plan: DelegateAdmissionPlanV1): void {
|
|
291
|
+
if (plan.fits) return;
|
|
292
|
+
const overage = plan.launch_input_tokens_upper_bound - plan.route.allowed_input_tokens;
|
|
293
|
+
throw new DelegateError(
|
|
294
|
+
`bg_delegate child prompt does not fit the pinned route before launch. Route ${plan.route.qualified_id} allows ${String(plan.route.allowed_input_tokens)} input tokens; the exact child prompt plus child system prompt measure ${String(plan.launch_utf8_bytes)} UTF-8 bytes (<= ${String(plan.launch_input_tokens_upper_bound)} input tokens), over by ${String(overage)} tokens. Estimator family ${plan.route.family}, source ${plan.route.rate_source.source}, backed=${String(plan.route.rate_source.backed)}, dominant_byte_class=${plan.dominant_byte_class}, rate ${String(plan.route.rate_source.effective_rate_bytes_per_token_x100)}/100 B/tok + ${String(plan.route.rate_source.affine_f_tokens)} tokens.${rateWarningText(plan.route.rate_source, plan.route.qualified_id)} Required reduction is at least ${String(requiredByteReduction(plan))} UTF-8 bytes. No child process, child session, or artifact was created. Nothing was clipped, dropped, or substituted.`,
|
|
295
|
+
{
|
|
296
|
+
code: 'seed_budget_exceeded',
|
|
297
|
+
childCreated: false,
|
|
298
|
+
budget: {
|
|
299
|
+
measurement_kind: 'launch_admission',
|
|
300
|
+
measured_utf8_bytes: plan.launch_utf8_bytes,
|
|
301
|
+
measured_input_tokens_upper_bound: plan.launch_input_tokens_upper_bound,
|
|
302
|
+
allowed_input_tokens: plan.route.allowed_input_tokens,
|
|
303
|
+
rate_source: plan.route.rate_source,
|
|
304
|
+
backed: plan.route.rate_source.backed,
|
|
305
|
+
dominant_byte_class: plan.dominant_byte_class,
|
|
306
|
+
byte_class_breakdown: plan.byte_class_breakdown,
|
|
307
|
+
},
|
|
308
|
+
remediation: [
|
|
309
|
+
'Pin a larger-context route with the route argument.',
|
|
310
|
+
'Delegate earlier in the session, or start a fresh conversation, so less history is projected.',
|
|
311
|
+
'Restate only the required findings as visible conversation text; omitted tool payloads are not what is large here.',
|
|
312
|
+
],
|
|
313
|
+
},
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export interface DelegateRuntimeMeasurement {
|
|
318
|
+
/** Complete retained input for the next model call, in UTF-8 bytes. */
|
|
319
|
+
retainedInputBytes: number;
|
|
320
|
+
retainedInputMultibyteBytes: number;
|
|
321
|
+
retainedInputDenseBytes: number;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export interface DelegateGovernorVerdict {
|
|
325
|
+
withinBudget: boolean;
|
|
326
|
+
measuredTokens: number;
|
|
327
|
+
allowedTokens: number;
|
|
328
|
+
overageTokens: number;
|
|
329
|
+
byteClassBreakdown: TokenBudgetByteClassBreakdown;
|
|
330
|
+
dominantByteClass: EstimateInputTokensResult['rateSource']['dominant_byte_class'];
|
|
331
|
+
backed: boolean;
|
|
332
|
+
rateSource: TokenBudgetRateSource;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Advisory runtime measurement for one prospective model call.
|
|
337
|
+
*
|
|
338
|
+
* This deliberately uses the calibrated large-prompt policy and never decides
|
|
339
|
+
* whether transport may occur. Fusion's BUG-185 proved that a package-local
|
|
340
|
+
* estimator must not reject a live provider payload by subtracting hypothetical
|
|
341
|
+
* output. The delegate child uses this result for evidence and graceful
|
|
342
|
+
* finalization while proactive spilling controls package-owned growth.
|
|
343
|
+
*/
|
|
344
|
+
export function evaluateDelegateRuntimeBudget(
|
|
345
|
+
measurement: DelegateRuntimeMeasurement,
|
|
346
|
+
allowedTokens: number,
|
|
347
|
+
route: { provider: string; model: string },
|
|
348
|
+
): DelegateGovernorVerdict {
|
|
349
|
+
const family = resolveTokenBudgetFamily(route);
|
|
350
|
+
const estimate = estimateInputTokens({
|
|
351
|
+
family: family.family,
|
|
352
|
+
calibrationBacked: family.backed,
|
|
353
|
+
familyResolution: family.resolution,
|
|
354
|
+
allowedInputTokens: allowedTokens,
|
|
355
|
+
scope: 'delegate_launch',
|
|
356
|
+
segments: [
|
|
357
|
+
{
|
|
358
|
+
kind: 'known_text',
|
|
359
|
+
bytes: measurement.retainedInputBytes,
|
|
360
|
+
multibyteBytes: measurement.retainedInputMultibyteBytes,
|
|
361
|
+
denseBytes: measurement.retainedInputDenseBytes,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
withinBudget: estimate.tokens <= allowedTokens,
|
|
367
|
+
measuredTokens: estimate.tokens,
|
|
368
|
+
allowedTokens,
|
|
369
|
+
overageTokens: Math.max(0, estimate.tokens - allowedTokens),
|
|
370
|
+
byteClassBreakdown: estimate.byte_class_breakdown,
|
|
371
|
+
dominantByteClass: estimate.rateSource.dominant_byte_class,
|
|
372
|
+
backed: estimate.rateSource.backed,
|
|
373
|
+
rateSource: estimate.rateSource,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export interface DelegateLimitOverrides {
|
|
378
|
+
maxTurns?: number | undefined;
|
|
379
|
+
maxToolCalls?: number | undefined;
|
|
380
|
+
timeoutSeconds?: number | undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function positiveInteger(value: number | undefined, fallback: number, label: string): number {
|
|
384
|
+
if (value === undefined) return fallback;
|
|
385
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
386
|
+
throw new DelegateError(`bg_delegate ${label} must be a positive integer`, {
|
|
387
|
+
code: 'invalid_arguments',
|
|
388
|
+
childCreated: false,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return value;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function resolveDelegateLimits(
|
|
395
|
+
route: DelegatePinnedRoute,
|
|
396
|
+
overrides: DelegateLimitOverrides = {},
|
|
397
|
+
): DelegateLimits {
|
|
398
|
+
return {
|
|
399
|
+
max_turns: positiveInteger(overrides.maxTurns, DELEGATE_DEFAULT_MAX_TURNS, 'maxTurns'),
|
|
400
|
+
max_tool_calls: positiveInteger(
|
|
401
|
+
overrides.maxToolCalls,
|
|
402
|
+
DELEGATE_DEFAULT_MAX_TOOL_CALLS,
|
|
403
|
+
'maxToolCalls',
|
|
404
|
+
),
|
|
405
|
+
timeout_seconds: positiveInteger(
|
|
406
|
+
overrides.timeoutSeconds,
|
|
407
|
+
DELEGATE_DEFAULT_TIMEOUT_SECONDS,
|
|
408
|
+
'timeoutSeconds',
|
|
409
|
+
),
|
|
410
|
+
max_tool_result_bytes: DELEGATE_MAX_TOOL_RESULT_BYTES,
|
|
411
|
+
max_total_tool_output_bytes: DELEGATE_MAX_TOTAL_TOOL_OUTPUT_BYTES,
|
|
412
|
+
max_answer_bytes: DELEGATE_MAX_ANSWER_BYTES,
|
|
413
|
+
allowed_input_tokens: delegateAllowedInputTokens(route),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "pi-background-tasks.delegate-hook-contract.v1",
|
|
3
|
+
"contract_id": "context-measure-abort-v1+tool-result-spill-v1",
|
|
4
|
+
"guarantees": {
|
|
5
|
+
"context_fires_before_every_model_call": true,
|
|
6
|
+
"context_result_messages_reach_provider": true,
|
|
7
|
+
"context_abort_blocks_provider_call": true,
|
|
8
|
+
"context_abort_skips_stream_invocation": false,
|
|
9
|
+
"context_abort_terminates_run": true,
|
|
10
|
+
"context_throw_blocks_provider_call": false,
|
|
11
|
+
"context_throw_isolated_to_throwing_handler": true,
|
|
12
|
+
"tool_result_fires_before_transcript_entry": true,
|
|
13
|
+
"tool_result_replacement_reaches_provider": true,
|
|
14
|
+
"tool_result_replacement_preserves_identity": true,
|
|
15
|
+
"tool_result_chains_in_load_order": true,
|
|
16
|
+
"handlers_run_in_extension_load_order": true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi extension-hook contract required by the `bg_delegate` child-side guard.
|
|
3
|
+
*
|
|
4
|
+
* The guard installed inside a delegate child depends on runtime behaviour of
|
|
5
|
+
* Pi's `context` and `tool_result` hooks. That behaviour is proven by the
|
|
6
|
+
* `tests/scripted-provider/pi-hook-contract.test.ts` characterisation gate,
|
|
7
|
+
* which drives a real Pi agent loop and writes the observed guarantees to
|
|
8
|
+
* `tests/scripted-provider/pi-hook-contract-evidence.json`.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here infers behaviour from type declarations. A guarantee is either
|
|
11
|
+
* observed by that gate or the delegate launch refuses to spawn a child.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const DELEGATE_HOOK_CONTRACT_SCHEMA_VERSION =
|
|
15
|
+
'pi-background-tasks.delegate-hook-contract.v1' as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Identifier for the exact guard mechanism the child installs.
|
|
19
|
+
*
|
|
20
|
+
* `context-measure-abort-v1`: measure the outgoing message set inside the
|
|
21
|
+
* `context` hook and, when it would exceed the pinned route window, call
|
|
22
|
+
* `ctx.abort()` so the request is never issued, then report a typed failure over
|
|
23
|
+
* the child result channel.
|
|
24
|
+
*
|
|
25
|
+
* Empirically established across the supported Pi lines by the characterisation
|
|
26
|
+
* and exact-version compatibility gates: Pi 0.81.1-0.83.0 invoke the provider
|
|
27
|
+
* entry point with an already-aborted `AbortSignal`, while Pi 0.84.0 propagates
|
|
28
|
+
* that signal through auth resolution and skips the entry point. No network
|
|
29
|
+
* request is issued in either mode, and the run terminates. Throwing from a
|
|
30
|
+
* `context` handler is NOT a barrier at all: Pi catches the exception, reports
|
|
31
|
+
* it as an extension error, and continues dispatch. The guard therefore uses
|
|
32
|
+
* abort, never a throw, and additionally suppresses the oversized content itself
|
|
33
|
+
* so a non-conforming provider cannot transmit it.
|
|
34
|
+
*
|
|
35
|
+
* `tool-result-spill-v1`: replace an oversized `tool_result` payload with an
|
|
36
|
+
* explicit hash-accounted receipt before it enters the transcript.
|
|
37
|
+
*/
|
|
38
|
+
export const DELEGATE_HOOK_CONTRACT_ID = 'context-measure-abort-v1+tool-result-spill-v1' as const;
|
|
39
|
+
|
|
40
|
+
export const DELEGATE_HOOK_GUARANTEE_NAMES = [
|
|
41
|
+
'context_fires_before_every_model_call',
|
|
42
|
+
'context_result_messages_reach_provider',
|
|
43
|
+
'context_abort_blocks_provider_call',
|
|
44
|
+
'context_abort_skips_stream_invocation',
|
|
45
|
+
'context_abort_terminates_run',
|
|
46
|
+
'context_throw_blocks_provider_call',
|
|
47
|
+
'context_throw_isolated_to_throwing_handler',
|
|
48
|
+
'tool_result_fires_before_transcript_entry',
|
|
49
|
+
'tool_result_replacement_reaches_provider',
|
|
50
|
+
'tool_result_replacement_preserves_identity',
|
|
51
|
+
'tool_result_chains_in_load_order',
|
|
52
|
+
'handlers_run_in_extension_load_order',
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
export type DelegateHookGuaranteeName = (typeof DELEGATE_HOOK_GUARANTEE_NAMES)[number];
|
|
56
|
+
|
|
57
|
+
export type DelegateHookGuarantees = Readonly<Record<DelegateHookGuaranteeName, boolean>>;
|
|
58
|
+
|
|
59
|
+
export interface DelegateHookContractEvidence {
|
|
60
|
+
schema_version: typeof DELEGATE_HOOK_CONTRACT_SCHEMA_VERSION;
|
|
61
|
+
contract_id: typeof DELEGATE_HOOK_CONTRACT_ID;
|
|
62
|
+
guarantees: DelegateHookGuarantees;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Guarantees the child guard actually depends on.
|
|
67
|
+
*
|
|
68
|
+
* `context_throw_blocks_provider_call` and
|
|
69
|
+
* `context_abort_skips_stream_invocation` are deliberately absent. Throws do
|
|
70
|
+
* not block dispatch, and skipping the stream entry point is not shared by every
|
|
71
|
+
* supported Pi line. The guard needs neither behavior: it aborts the run AND
|
|
72
|
+
* removes the oversized content from the outgoing message set, so the request
|
|
73
|
+
* cannot be issued and could not carry the content even if it were.
|
|
74
|
+
*/
|
|
75
|
+
export const DELEGATE_REQUIRED_HOOK_GUARANTEES: readonly DelegateHookGuaranteeName[] = [
|
|
76
|
+
'context_fires_before_every_model_call',
|
|
77
|
+
'context_result_messages_reach_provider',
|
|
78
|
+
'context_abort_blocks_provider_call',
|
|
79
|
+
'context_abort_terminates_run',
|
|
80
|
+
'context_throw_isolated_to_throwing_handler',
|
|
81
|
+
'tool_result_fires_before_transcript_entry',
|
|
82
|
+
'tool_result_replacement_reaches_provider',
|
|
83
|
+
'tool_result_replacement_preserves_identity',
|
|
84
|
+
'tool_result_chains_in_load_order',
|
|
85
|
+
'handlers_run_in_extension_load_order',
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
export interface DelegateHookContractVerdict {
|
|
89
|
+
supported: boolean;
|
|
90
|
+
missing: readonly DelegateHookGuaranteeName[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function evaluateDelegateHookContract(
|
|
94
|
+
evidence: DelegateHookContractEvidence,
|
|
95
|
+
): DelegateHookContractVerdict {
|
|
96
|
+
const missing = DELEGATE_REQUIRED_HOOK_GUARANTEES.filter(
|
|
97
|
+
(guarantee) => evidence.guarantees[guarantee] !== true,
|
|
98
|
+
);
|
|
99
|
+
return { supported: missing.length === 0, missing };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
|
|
103
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Strict parse. A malformed or partial evidence file is a loud failure, never a default-allow. */
|
|
107
|
+
export function parseDelegateHookContractEvidence(value: unknown): DelegateHookContractEvidence {
|
|
108
|
+
if (!isRecord(value)) throw new Error('delegate hook-contract evidence must be an object');
|
|
109
|
+
if (value['schema_version'] !== DELEGATE_HOOK_CONTRACT_SCHEMA_VERSION) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`delegate hook-contract evidence schema_version must be ${DELEGATE_HOOK_CONTRACT_SCHEMA_VERSION}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (value['contract_id'] !== DELEGATE_HOOK_CONTRACT_ID) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`delegate hook-contract evidence contract_id must be ${DELEGATE_HOOK_CONTRACT_ID}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const raw = value['guarantees'];
|
|
120
|
+
if (!isRecord(raw)) throw new Error('delegate hook-contract evidence guarantees must be an object');
|
|
121
|
+
const keys = Object.keys(raw).sort();
|
|
122
|
+
const expected = [...DELEGATE_HOOK_GUARANTEE_NAMES].sort();
|
|
123
|
+
if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`delegate hook-contract evidence guarantees keys mismatch: expected ${expected.join(', ')}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const flag = (name: DelegateHookGuaranteeName): boolean => {
|
|
129
|
+
const observed = raw[name];
|
|
130
|
+
if (typeof observed !== 'boolean') {
|
|
131
|
+
throw new Error(`delegate hook-contract evidence guarantee ${name} must be a boolean`);
|
|
132
|
+
}
|
|
133
|
+
return observed;
|
|
134
|
+
};
|
|
135
|
+
const guarantees: DelegateHookGuarantees = {
|
|
136
|
+
context_fires_before_every_model_call: flag('context_fires_before_every_model_call'),
|
|
137
|
+
context_result_messages_reach_provider: flag('context_result_messages_reach_provider'),
|
|
138
|
+
context_abort_blocks_provider_call: flag('context_abort_blocks_provider_call'),
|
|
139
|
+
context_abort_skips_stream_invocation: flag('context_abort_skips_stream_invocation'),
|
|
140
|
+
context_abort_terminates_run: flag('context_abort_terminates_run'),
|
|
141
|
+
context_throw_blocks_provider_call: flag('context_throw_blocks_provider_call'),
|
|
142
|
+
context_throw_isolated_to_throwing_handler: flag('context_throw_isolated_to_throwing_handler'),
|
|
143
|
+
tool_result_fires_before_transcript_entry: flag('tool_result_fires_before_transcript_entry'),
|
|
144
|
+
tool_result_replacement_reaches_provider: flag('tool_result_replacement_reaches_provider'),
|
|
145
|
+
tool_result_replacement_preserves_identity: flag('tool_result_replacement_preserves_identity'),
|
|
146
|
+
tool_result_chains_in_load_order: flag('tool_result_chains_in_load_order'),
|
|
147
|
+
handlers_run_in_extension_load_order: flag('handlers_run_in_extension_load_order'),
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
schema_version: DELEGATE_HOOK_CONTRACT_SCHEMA_VERSION,
|
|
151
|
+
contract_id: DELEGATE_HOOK_CONTRACT_ID,
|
|
152
|
+
guarantees,
|
|
153
|
+
};
|
|
154
|
+
}
|