@springbrand/agent-runtime 0.2.0-alpha.67 → 0.2.0-alpha.69
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/package.json +3 -3
- package/src/adapter/cloudflare/subagent/runner.ts +23 -22
- package/src/adapter/cloudflare/subagent/tools.ts +1 -1
- package/src/adapter/cloudflare/universal-agent/preparation.ts +0 -7
- package/src/db/schema.ts +0 -12
- package/src/db/submission-admission.repo.ts +5 -20
- package/src/db/submission.repo.ts +5 -21
- package/src/kernel/bindings.ts +8 -7
- package/src/kernel/durable-lifecycle.ts +0 -9
- package/src/kernel/profile.ts +3 -0
- package/src/kernel/public-contracts.ts +0 -1
- package/src/kernel/subagent-runtime.ts +2 -12
- package/src/kernel/submission-authority.ts +4 -24
- package/src/model-usage.ts +52 -0
- package/src/pi/assembly/context.ts +26 -60
- package/src/pi/runtime-adapter/assembly.ts +6 -2
- package/src/pi/runtime-adapter/execution.ts +78 -114
- package/src/pi/runtime-adapter/index.ts +3 -4
- package/src/pi/runtime-adapter/transcript.ts +31 -46
- package/src/pi/session/index.ts +22 -22
- package/src/pi/session/migration.ts +646 -0
- package/src/pi/session/storage.ts +523 -311
- package/src/pi/tool/compiler.ts +2 -2
- package/src/pi/tool/core-host.ts +29 -7
- package/src/pi/tool/mcp.ts +23 -15
- package/src/pi/tool/subagent.ts +0 -9
- package/src/pi/tool/workspace-state-connector.ts +23 -20
- package/src/runtime-agent.ts +11 -0
- package/src/runtime-assembler.ts +13 -2
- package/src/runtime-definition.ts +2 -0
- package/src/runtime.ts +24 -83
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/agent-runtime",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.69",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"@cloudflare/sandbox": "0.12.4",
|
|
22
22
|
"@cloudflare/shell": "0.4.3",
|
|
23
23
|
"@cloudflare/think": "0.17.0",
|
|
24
|
-
"@earendil-works/pi-agent-core": "0.
|
|
25
|
-
"@earendil-works/pi-ai": "0.
|
|
24
|
+
"@earendil-works/pi-agent-core": "0.85.1",
|
|
25
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
26
26
|
"@types/lodash-es": "^4.17.12",
|
|
27
27
|
"cron-schedule": "6.0.0",
|
|
28
28
|
"isomorphic-git": "1.38.6",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
runAgentLoopContinue,
|
|
3
|
+
convertToLlm,
|
|
4
|
+
type AgentMessage,
|
|
3
5
|
type AgentEvent,
|
|
4
6
|
type AgentTool,
|
|
5
7
|
type StreamFn,
|
|
@@ -105,18 +107,15 @@ export async function runCloudflareSubAgent(
|
|
|
105
107
|
// 这里没有审批闸,也装不了:到这一步工具已经被 compilePiTools 编译成 AgentTool,
|
|
106
108
|
// requiredExecutionLevel 只挂在编译前的 PiToolCandidate 上,这里读不到。「子 agent 不许挂需审批的工具」
|
|
107
109
|
// 这条不变量由装配处 createCloudflareSubAgentTools 的启动期断言强制执行。
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
tools: run.tools,
|
|
116
|
-
},
|
|
117
|
-
});
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
const signal = run.signal ? AbortSignal.any([run.signal, controller.signal]) : controller.signal;
|
|
112
|
+
const messages: AgentMessage[] = [{
|
|
113
|
+
role: "user",
|
|
114
|
+
content: taskPrompt(input.data, constrainedStream ? undefined : outputSchema),
|
|
115
|
+
timestamp: Date.now(),
|
|
116
|
+
}];
|
|
118
117
|
let toolFailure: { name: string; error: string } | undefined;
|
|
119
|
-
|
|
118
|
+
const emit = async (event: AgentEvent) => {
|
|
120
119
|
if (event.type === "tool_execution_end" && event.isError) {
|
|
121
120
|
toolFailure ??= {
|
|
122
121
|
name: event.toolName,
|
|
@@ -124,36 +123,38 @@ export async function runCloudflareSubAgent(
|
|
|
124
123
|
};
|
|
125
124
|
}
|
|
126
125
|
await run.onEvent?.(event, signal);
|
|
127
|
-
}
|
|
126
|
+
};
|
|
128
127
|
|
|
129
128
|
let timedOut = false;
|
|
130
|
-
const abort = () => agent.abort();
|
|
131
|
-
run.signal?.addEventListener("abort", abort, { once: true });
|
|
132
129
|
const timeout = setTimeout(() => {
|
|
133
130
|
timedOut = true;
|
|
134
|
-
|
|
131
|
+
controller.abort();
|
|
135
132
|
}, run.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
136
133
|
|
|
134
|
+
let newMessages: AgentMessage[];
|
|
137
135
|
try {
|
|
138
|
-
await
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
newMessages = await runAgentLoopContinue(
|
|
137
|
+
{ systemPrompt: assembleSubagentPrompt(run.type.persona), messages, tools: [...run.tools] },
|
|
138
|
+
{ model: run.model, reasoning: run.type.model === "main" ? "medium" : undefined, toolExecution: "parallel", convertToLlm },
|
|
139
|
+
emit,
|
|
140
|
+
signal,
|
|
141
|
+
constrainedStream ?? run.streamFn,
|
|
142
|
+
);
|
|
142
143
|
} finally {
|
|
143
144
|
clearTimeout(timeout);
|
|
144
|
-
run.signal?.removeEventListener("abort", abort);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
if (timedOut) throw new Error("SubAgent run timed out");
|
|
148
148
|
if (run.signal?.aborted) throw new Error("SubAgent run aborted");
|
|
149
149
|
|
|
150
|
-
const assistant =
|
|
150
|
+
const assistant = newMessages
|
|
151
151
|
.slice()
|
|
152
152
|
.reverse()
|
|
153
153
|
.find(
|
|
154
154
|
(message): message is AssistantMessage => message.role === "assistant",
|
|
155
155
|
);
|
|
156
156
|
if (!assistant) throw new Error("SubAgent produced no structured output");
|
|
157
|
+
if (assistant.stopReason === "deferred") throw new Error("SubAgent deferred responses are not supported");
|
|
157
158
|
if (assistant.stopReason === "aborted") {
|
|
158
159
|
throw new Error("SubAgent run aborted");
|
|
159
160
|
}
|
|
@@ -184,7 +184,7 @@ function fetchCandidate(options: {
|
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
186
|
* SubAgent 链路上没有审批通道:主链的审批闸在 Runtime 的 execution 适配器里
|
|
187
|
-
* (requiresPiToolApproval → park → 等用户裁决),而子
|
|
187
|
+
* (requiresPiToolApproval → park → 等用户裁决),而子 Agent 直接调用 `runAgentLoopContinue`
|
|
188
188
|
* 直接跑,parked 的请求没有任何界面能落地、也没有人能批。
|
|
189
189
|
*
|
|
190
190
|
* 只有 `safe` Tool 可以挂到这条没有审批通道的链路;更高档位必须在装配时失败关闭。
|
|
@@ -229,13 +229,6 @@ export async function prepareSubagents<Env extends Cloudflare.Env>(
|
|
|
229
229
|
inputPreview: {
|
|
230
230
|
submissionId: options.submissionId,
|
|
231
231
|
parentRunId: options.parentRunId,
|
|
232
|
-
...(options.accountId ? { accountId: options.accountId } : {}),
|
|
233
|
-
...(options.rateVersion !== undefined
|
|
234
|
-
? { rateVersion: options.rateVersion }
|
|
235
|
-
: {}),
|
|
236
|
-
...(options.slotIdentity
|
|
237
|
-
? { slotIdentity: options.slotIdentity }
|
|
238
|
-
: {}),
|
|
239
232
|
},
|
|
240
233
|
}
|
|
241
234
|
: {}),
|
package/src/db/schema.ts
CHANGED
|
@@ -35,15 +35,6 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
|
|
|
35
35
|
if (!submissionColumns.has("run_id")) {
|
|
36
36
|
sql`ALTER TABLE pi_submissions ADD COLUMN run_id TEXT`;
|
|
37
37
|
}
|
|
38
|
-
if (!submissionColumns.has("account_id")) {
|
|
39
|
-
sql`ALTER TABLE pi_submissions ADD COLUMN account_id TEXT`;
|
|
40
|
-
}
|
|
41
|
-
if (!submissionColumns.has("rate_version")) {
|
|
42
|
-
sql`ALTER TABLE pi_submissions ADD COLUMN rate_version INTEGER`;
|
|
43
|
-
}
|
|
44
|
-
if (!submissionColumns.has("slot_identity")) {
|
|
45
|
-
sql`ALTER TABLE pi_submissions ADD COLUMN slot_identity TEXT`;
|
|
46
|
-
}
|
|
47
38
|
if (!submissionColumns.has("admission_retryable")) {
|
|
48
39
|
sql`ALTER TABLE pi_submissions ADD COLUMN admission_retryable INTEGER`;
|
|
49
40
|
}
|
|
@@ -146,9 +137,6 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
|
|
|
146
137
|
idempotency_key TEXT UNIQUE,
|
|
147
138
|
run_id TEXT NOT NULL,
|
|
148
139
|
status TEXT NOT NULL CHECK (status IN ('pending', 'accepted', 'rejected')),
|
|
149
|
-
account_id TEXT,
|
|
150
|
-
rate_version INTEGER,
|
|
151
|
-
slot_identity TEXT,
|
|
152
140
|
code TEXT,
|
|
153
141
|
retryable INTEGER,
|
|
154
142
|
attempted_at INTEGER NOT NULL DEFAULT 0
|
|
@@ -5,9 +5,6 @@ export type StoredSubmissionAdmission = {
|
|
|
5
5
|
idempotencyKey: string | null;
|
|
6
6
|
runId: string;
|
|
7
7
|
status: "pending" | "accepted" | "rejected";
|
|
8
|
-
accountId: string | null;
|
|
9
|
-
rateVersion: number | null;
|
|
10
|
-
slotIdentity: string | null;
|
|
11
8
|
code: string | null;
|
|
12
9
|
retryable: boolean | null;
|
|
13
10
|
attemptedAt: number;
|
|
@@ -18,9 +15,6 @@ type Row = {
|
|
|
18
15
|
idempotency_key: string | null;
|
|
19
16
|
run_id: string;
|
|
20
17
|
status: StoredSubmissionAdmission["status"];
|
|
21
|
-
account_id: string | null;
|
|
22
|
-
rate_version: number | null;
|
|
23
|
-
slot_identity: string | null;
|
|
24
18
|
code: string | null;
|
|
25
19
|
retryable: number | null;
|
|
26
20
|
attempted_at: number;
|
|
@@ -31,9 +25,6 @@ const mapRow = (row: Row): StoredSubmissionAdmission => ({
|
|
|
31
25
|
idempotencyKey: row.idempotency_key,
|
|
32
26
|
runId: row.run_id,
|
|
33
27
|
status: row.status,
|
|
34
|
-
accountId: row.account_id,
|
|
35
|
-
rateVersion: row.rate_version,
|
|
36
|
-
slotIdentity: row.slot_identity,
|
|
37
28
|
code: row.code,
|
|
38
29
|
retryable: row.retryable === null ? null : row.retryable === 1,
|
|
39
30
|
attemptedAt: row.attempted_at,
|
|
@@ -44,11 +35,11 @@ export class SubmissionAdmissionRepository {
|
|
|
44
35
|
|
|
45
36
|
find(requestId: string, idempotencyKey?: string): StoredSubmissionAdmission | null {
|
|
46
37
|
const byRequest = this.sql<Row>`SELECT request_id, idempotency_key, run_id, status,
|
|
47
|
-
|
|
38
|
+
code, retryable, attempted_at
|
|
48
39
|
FROM pi_submission_admissions WHERE request_id = ${requestId}`[0];
|
|
49
40
|
if (!idempotencyKey) return byRequest ? mapRow(byRequest) : null;
|
|
50
41
|
const byKey = this.sql<Row>`SELECT request_id, idempotency_key, run_id, status,
|
|
51
|
-
|
|
42
|
+
code, retryable, attempted_at
|
|
52
43
|
FROM pi_submission_admissions WHERE idempotency_key = ${idempotencyKey}`[0];
|
|
53
44
|
if (byRequest && byKey && byRequest.run_id !== byKey.run_id) {
|
|
54
45
|
throw new Error("Conflicting Submission admission identity");
|
|
@@ -66,10 +57,8 @@ export class SubmissionAdmissionRepository {
|
|
|
66
57
|
const existing = this.find(requestId, idempotencyKey ?? undefined);
|
|
67
58
|
if (existing) return existing;
|
|
68
59
|
this.sql`INSERT INTO pi_submission_admissions
|
|
69
|
-
(request_id, idempotency_key, run_id, status,
|
|
70
|
-
|
|
71
|
-
VALUES (${requestId}, ${idempotencyKey}, ${runId}, 'pending', NULL, NULL,
|
|
72
|
-
NULL, NULL, NULL, ${attemptedAt})`;
|
|
60
|
+
(request_id, idempotency_key, run_id, status, code, retryable, attempted_at)
|
|
61
|
+
VALUES (${requestId}, ${idempotencyKey}, ${runId}, 'pending', NULL, NULL, ${attemptedAt})`;
|
|
73
62
|
return this.find(requestId, idempotencyKey ?? undefined)!;
|
|
74
63
|
}
|
|
75
64
|
|
|
@@ -88,13 +77,9 @@ export class SubmissionAdmissionRepository {
|
|
|
88
77
|
saveAccepted(
|
|
89
78
|
requestId: string,
|
|
90
79
|
runId: string,
|
|
91
|
-
accountId: string,
|
|
92
|
-
rateVersion: number,
|
|
93
|
-
slotIdentity: string,
|
|
94
80
|
): void {
|
|
95
81
|
this.sql`UPDATE pi_submission_admissions
|
|
96
|
-
SET run_id = ${runId}, status = 'accepted',
|
|
97
|
-
rate_version = ${rateVersion}, slot_identity = ${slotIdentity}, code = NULL,
|
|
82
|
+
SET run_id = ${runId}, status = 'accepted', code = NULL,
|
|
98
83
|
retryable = NULL
|
|
99
84
|
WHERE request_id = ${requestId} AND run_id = ${runId} AND status = 'pending'`;
|
|
100
85
|
}
|
|
@@ -51,9 +51,6 @@ export interface StoredSubmission {
|
|
|
51
51
|
recoveryErrorCount: number;
|
|
52
52
|
recoveryReason: SubmissionRecoveryReason | null;
|
|
53
53
|
runId: string | null;
|
|
54
|
-
accountId: string | null;
|
|
55
|
-
rateVersion: number | null;
|
|
56
|
-
slotIdentity: string | null;
|
|
57
54
|
retryable?: boolean;
|
|
58
55
|
/** 跨执行片累计的模型回合数,全局回合预算的唯一事实来源。 */
|
|
59
56
|
modelTurns: number;
|
|
@@ -74,9 +71,6 @@ export interface NewSubmission {
|
|
|
74
71
|
userMessageId?: string | null;
|
|
75
72
|
regenerateMessageId?: string | null;
|
|
76
73
|
runId?: string | null;
|
|
77
|
-
accountId?: string | null;
|
|
78
|
-
rateVersion?: number | null;
|
|
79
|
-
slotIdentity?: string | null;
|
|
80
74
|
}
|
|
81
75
|
|
|
82
76
|
type SubmissionRow = {
|
|
@@ -99,9 +93,6 @@ type SubmissionRow = {
|
|
|
99
93
|
recovery_error_count: number;
|
|
100
94
|
recovery_reason: string | null;
|
|
101
95
|
run_id: string | null;
|
|
102
|
-
account_id: string | null;
|
|
103
|
-
rate_version: number | null;
|
|
104
|
-
slot_identity: string | null;
|
|
105
96
|
admission_retryable: number | null;
|
|
106
97
|
model_turns: number;
|
|
107
98
|
continuation_id: string | null;
|
|
@@ -133,9 +124,6 @@ function mapRow(row: SubmissionRow): StoredSubmission {
|
|
|
133
124
|
recoveryErrorCount: row.recovery_error_count,
|
|
134
125
|
recoveryReason: row.recovery_reason as SubmissionRecoveryReason | null,
|
|
135
126
|
runId: row.run_id,
|
|
136
|
-
accountId: row.account_id,
|
|
137
|
-
rateVersion: row.rate_version,
|
|
138
|
-
slotIdentity: row.slot_identity,
|
|
139
127
|
retryable: row.admission_retryable === null
|
|
140
128
|
? undefined
|
|
141
129
|
: row.admission_retryable === 1,
|
|
@@ -162,7 +150,7 @@ export class SubmissionRepository {
|
|
|
162
150
|
assembly_descriptor, assistant_message_id, abort_reason,
|
|
163
151
|
queued_input_json, queued_ui_message_json, user_message_id,
|
|
164
152
|
regenerate_message_id, recovery_error_count, recovery_reason,
|
|
165
|
-
run_id,
|
|
153
|
+
run_id,
|
|
166
154
|
admission_retryable,
|
|
167
155
|
model_turns, continuation_id
|
|
168
156
|
FROM pi_submissions
|
|
@@ -265,7 +253,7 @@ export class SubmissionRepository {
|
|
|
265
253
|
created_at, assembly_revision, assembly_descriptor,
|
|
266
254
|
assistant_message_id, queued_input_json, queued_ui_message_json,
|
|
267
255
|
user_message_id, regenerate_message_id,
|
|
268
|
-
run_id
|
|
256
|
+
run_id
|
|
269
257
|
) VALUES (
|
|
270
258
|
${s.submissionId},
|
|
271
259
|
${s.requestId},
|
|
@@ -280,10 +268,7 @@ export class SubmissionRepository {
|
|
|
280
268
|
${s.queuedUiMessageJson ?? null},
|
|
281
269
|
${s.userMessageId ?? null},
|
|
282
270
|
${s.regenerateMessageId ?? null},
|
|
283
|
-
${s.runId ?? s.submissionId}
|
|
284
|
-
${s.accountId ?? null},
|
|
285
|
-
${s.rateVersion ?? null},
|
|
286
|
-
${s.slotIdentity ?? null}
|
|
271
|
+
${s.runId ?? s.submissionId}
|
|
287
272
|
)
|
|
288
273
|
`;
|
|
289
274
|
}
|
|
@@ -301,12 +286,11 @@ export class SubmissionRepository {
|
|
|
301
286
|
submission_id, request_id, idempotency_key, status, accepted,
|
|
302
287
|
error, created_at, completed_at, assembly_revision,
|
|
303
288
|
assembly_descriptor, assistant_message_id, abort_reason,
|
|
304
|
-
recovery_error_count, recovery_reason, run_id,
|
|
305
|
-
rate_version, slot_identity, admission_retryable
|
|
289
|
+
recovery_error_count, recovery_reason, run_id, admission_retryable
|
|
306
290
|
) VALUES (
|
|
307
291
|
${s.submissionId}, ${s.requestId}, ${s.idempotencyKey}, 'skipped', 0,
|
|
308
292
|
${s.code}, ${s.createdAt}, ${s.createdAt}, '', '', '', NULL,
|
|
309
|
-
0, NULL, ${s.submissionId},
|
|
293
|
+
0, NULL, ${s.submissionId}, ${s.retryable ? 1 : 0}
|
|
310
294
|
)
|
|
311
295
|
`;
|
|
312
296
|
}
|
package/src/kernel/bindings.ts
CHANGED
|
@@ -19,7 +19,6 @@ export type {
|
|
|
19
19
|
RuntimeSubmissionAdmissionHook,
|
|
20
20
|
RuntimeSubmissionAdmissionInput,
|
|
21
21
|
RuntimeSubmissionAdmissionResult,
|
|
22
|
-
RuntimeSubmissionAdmissionContext,
|
|
23
22
|
} from "./submission-authority";
|
|
24
23
|
|
|
25
24
|
// #region Workspace 端口
|
|
@@ -624,6 +623,11 @@ export interface RuntimeGatewayPort {
|
|
|
624
623
|
open(runtimeId: string): Promise<RuntimeGatewaySession>;
|
|
625
624
|
}
|
|
626
625
|
|
|
626
|
+
/** Safe catalog from an external Host; credentials and SDK clients stay there. */
|
|
627
|
+
export type RuntimeMcpHost = import("../pi/tool/mcp").PiMcpHost & {
|
|
628
|
+
readonly servers: readonly import("./profile").RuntimeMcpServer[];
|
|
629
|
+
};
|
|
630
|
+
|
|
627
631
|
/**
|
|
628
632
|
* 描述一条模型可见的定时任务。
|
|
629
633
|
*
|
|
@@ -769,9 +773,6 @@ export interface RuntimeSubagentPort {
|
|
|
769
773
|
subagentRunId?: string;
|
|
770
774
|
parentRunId?: string;
|
|
771
775
|
submissionId?: string;
|
|
772
|
-
accountId?: string;
|
|
773
|
-
rateVersion?: number;
|
|
774
|
-
slotIdentity?: string;
|
|
775
776
|
}>,
|
|
776
777
|
): Promise<RuntimeSubagentRunResult>;
|
|
777
778
|
}
|
|
@@ -793,13 +794,12 @@ export interface RuntimeModelUsageEvent {
|
|
|
793
794
|
readonly eventId: string;
|
|
794
795
|
readonly submissionId: string;
|
|
795
796
|
readonly runId?: string;
|
|
796
|
-
readonly accountId?: string;
|
|
797
|
-
readonly rateVersion?: number;
|
|
798
|
-
readonly slotIdentity?: string;
|
|
799
797
|
readonly kind: "assistant" | "compaction";
|
|
800
798
|
readonly api: string;
|
|
801
799
|
readonly provider: string;
|
|
800
|
+
/** Pinned request model ID, used to resolve the configured provider. */
|
|
802
801
|
readonly model: string;
|
|
802
|
+
/** Uniform provider-reported model, when available; not a validated Rate Card meter. */
|
|
803
803
|
readonly responseModel?: string;
|
|
804
804
|
readonly stopReason?: string;
|
|
805
805
|
readonly usage: {
|
|
@@ -1094,6 +1094,7 @@ export interface RuntimeBindings {
|
|
|
1094
1094
|
codeExecution?: RuntimeCodeExecutionFactory;
|
|
1095
1095
|
/** Secret-capability binding; implementations must keep Runtime Grant in closure state. */
|
|
1096
1096
|
gateway?: RuntimeGatewayPort;
|
|
1097
|
+
mcp?: RuntimeMcpHost;
|
|
1097
1098
|
workspace?: WorkspacePort;
|
|
1098
1099
|
memory?: RuntimeMemoryPort;
|
|
1099
1100
|
subagents?: RuntimeSubagentPort;
|
|
@@ -21,9 +21,6 @@ export class RuntimeSubagentLifecycleRecorder {
|
|
|
21
21
|
private readonly input: {
|
|
22
22
|
submissionId: string;
|
|
23
23
|
parentRunId: string;
|
|
24
|
-
accountId?: string;
|
|
25
|
-
rateVersion?: number;
|
|
26
|
-
slotIdentity?: string;
|
|
27
24
|
},
|
|
28
25
|
private readonly sink: RuntimeLifecycleSink,
|
|
29
26
|
) {}
|
|
@@ -41,9 +38,6 @@ export class RuntimeSubagentLifecycleRecorder {
|
|
|
41
38
|
submissionId: this.input.submissionId,
|
|
42
39
|
subagentRunId,
|
|
43
40
|
parentRunId: this.input.parentRunId,
|
|
44
|
-
...(this.input.accountId ? { accountId: this.input.accountId } : {}),
|
|
45
|
-
...(this.input.rateVersion !== undefined ? { rateVersion: this.input.rateVersion } : {}),
|
|
46
|
-
...(this.input.slotIdentity ? { slotIdentity: this.input.slotIdentity } : {}),
|
|
47
41
|
});
|
|
48
42
|
return { confirmed: true };
|
|
49
43
|
}
|
|
@@ -91,9 +85,6 @@ export class RuntimeSubagentLifecycleRecorder {
|
|
|
91
85
|
parentRunId: this.input.parentRunId,
|
|
92
86
|
status: input.status,
|
|
93
87
|
zeroUsage: input.zeroUsage ?? !usageSaved,
|
|
94
|
-
...(this.input.accountId ? { accountId: this.input.accountId } : {}),
|
|
95
|
-
...(this.input.rateVersion !== undefined ? { rateVersion: this.input.rateVersion } : {}),
|
|
96
|
-
...(this.input.slotIdentity ? { slotIdentity: this.input.slotIdentity } : {}),
|
|
97
88
|
});
|
|
98
89
|
return { confirmed: true };
|
|
99
90
|
}
|
package/src/kernel/profile.ts
CHANGED
|
@@ -3,9 +3,6 @@ import type { RuntimeSubagentUsageMetrics } from "./submission-authority";
|
|
|
3
3
|
export interface RuntimeSubagentRunMetadata {
|
|
4
4
|
readonly submissionId: string;
|
|
5
5
|
readonly parentRunId: string;
|
|
6
|
-
readonly accountId?: string;
|
|
7
|
-
readonly rateVersion?: number;
|
|
8
|
-
readonly slotIdentity?: string;
|
|
9
6
|
}
|
|
10
7
|
|
|
11
8
|
export function emptySubagentUsage(): RuntimeSubagentUsageMetrics {
|
|
@@ -122,16 +119,9 @@ export function runtimeSubagentRunMetadata(
|
|
|
122
119
|
const input = value as Record<string, unknown>;
|
|
123
120
|
if (
|
|
124
121
|
typeof input.submissionId !== "string" || !input.submissionId.trim() ||
|
|
125
|
-
typeof input.parentRunId !== "string" || !input.parentRunId.trim()
|
|
126
|
-
(input.accountId !== undefined &&
|
|
127
|
-
(typeof input.accountId !== "string" || !input.accountId.trim())) ||
|
|
128
|
-
(input.slotIdentity !== undefined &&
|
|
129
|
-
(typeof input.slotIdentity !== "string" || !input.slotIdentity.trim())) ||
|
|
130
|
-
(input.rateVersion !== undefined &&
|
|
131
|
-
(!Number.isSafeInteger(input.rateVersion) ||
|
|
132
|
-
(input.rateVersion as number) <= 0))
|
|
122
|
+
typeof input.parentRunId !== "string" || !input.parentRunId.trim()
|
|
133
123
|
) {
|
|
134
124
|
return null;
|
|
135
125
|
}
|
|
136
|
-
return input
|
|
126
|
+
return { submissionId: input.submissionId, parentRunId: input.parentRunId };
|
|
137
127
|
}
|
|
@@ -21,9 +21,6 @@ export interface RuntimeSubagentUsageEvent {
|
|
|
21
21
|
readonly runId: string;
|
|
22
22
|
readonly subagentRunId: string;
|
|
23
23
|
readonly parentRunId: string;
|
|
24
|
-
readonly accountId?: string;
|
|
25
|
-
readonly rateVersion?: number;
|
|
26
|
-
readonly slotIdentity?: string;
|
|
27
24
|
readonly kind: "subagent";
|
|
28
25
|
readonly status: "completed" | "failed" | "aborted" | "skipped";
|
|
29
26
|
readonly usage: RuntimeSubagentUsageMetrics;
|
|
@@ -32,9 +29,6 @@ export interface RuntimeSubagentUsageEvent {
|
|
|
32
29
|
export interface RuntimeSubagentLifecyclePort {
|
|
33
30
|
readonly parentRunId: string;
|
|
34
31
|
readonly submissionId: string;
|
|
35
|
-
readonly accountId?: string;
|
|
36
|
-
readonly rateVersion?: number;
|
|
37
|
-
readonly slotIdentity?: string;
|
|
38
32
|
onRegistered(subagentRunId: string): Promise<void> | void;
|
|
39
33
|
onUsage(event: RuntimeSubagentUsageEvent): Promise<void> | void;
|
|
40
34
|
onTerminal(input: {
|
|
@@ -50,14 +44,8 @@ export interface RuntimeSubmissionAdmissionInput {
|
|
|
50
44
|
readonly runId: string;
|
|
51
45
|
}
|
|
52
46
|
|
|
53
|
-
export interface RuntimeSubmissionAdmissionContext {
|
|
54
|
-
readonly accountId: string;
|
|
55
|
-
readonly rateVersion: number;
|
|
56
|
-
readonly slotIdentity: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
47
|
export type RuntimeSubmissionAdmissionResult =
|
|
60
|
-
|
|
|
48
|
+
| { readonly accepted: true }
|
|
61
49
|
| {
|
|
62
50
|
readonly accepted: false;
|
|
63
51
|
readonly code: string;
|
|
@@ -69,11 +57,12 @@ export type RuntimeSubmissionAdmissionHook = (
|
|
|
69
57
|
) => Promise<RuntimeSubmissionAdmissionResult>;
|
|
70
58
|
|
|
71
59
|
export type RuntimeLifecycleFact =
|
|
72
|
-
|
|
|
60
|
+
| {
|
|
61
|
+
readonly kind: "submission-admitted";
|
|
73
62
|
readonly eventId: string;
|
|
74
63
|
readonly submissionId: string;
|
|
75
64
|
readonly runId: string;
|
|
76
|
-
}
|
|
65
|
+
}
|
|
77
66
|
| {
|
|
78
67
|
readonly kind: "submission-terminal";
|
|
79
68
|
readonly eventId: string;
|
|
@@ -82,9 +71,6 @@ export type RuntimeLifecycleFact =
|
|
|
82
71
|
readonly status: "completed" | "aborted" | "skipped" | "error";
|
|
83
72
|
readonly childRegistrationClosed: true;
|
|
84
73
|
readonly zeroUsage: boolean;
|
|
85
|
-
readonly accountId?: string;
|
|
86
|
-
readonly rateVersion?: number;
|
|
87
|
-
readonly slotIdentity?: string;
|
|
88
74
|
}
|
|
89
75
|
| {
|
|
90
76
|
readonly kind: "subagent-run-registered";
|
|
@@ -92,9 +78,6 @@ export type RuntimeLifecycleFact =
|
|
|
92
78
|
readonly submissionId: string;
|
|
93
79
|
readonly subagentRunId: string;
|
|
94
80
|
readonly parentRunId: string;
|
|
95
|
-
readonly accountId?: string;
|
|
96
|
-
readonly rateVersion?: number;
|
|
97
|
-
readonly slotIdentity?: string;
|
|
98
81
|
}
|
|
99
82
|
| {
|
|
100
83
|
readonly kind: "subagent-terminal";
|
|
@@ -104,9 +87,6 @@ export type RuntimeLifecycleFact =
|
|
|
104
87
|
readonly parentRunId: string;
|
|
105
88
|
readonly status: "completed" | "failed" | "aborted" | "skipped";
|
|
106
89
|
readonly zeroUsage: boolean;
|
|
107
|
-
readonly accountId?: string;
|
|
108
|
-
readonly rateVersion?: number;
|
|
109
|
-
readonly slotIdentity?: string;
|
|
110
90
|
};
|
|
111
91
|
|
|
112
92
|
export interface RuntimeEventConfirmation {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Api, AssistantMessage, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { RuntimeModelUsageEvent } from "./kernel/bindings";
|
|
3
|
+
|
|
4
|
+
type ModelUsage = Pick<RuntimeModelUsageEvent,
|
|
5
|
+
"api" | "provider" | "model" | "responseModel" | "stopReason" | "usage"
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
function reportedModel(response: AssistantMessage): string | undefined {
|
|
9
|
+
const model = response.responseModel ?? response.model;
|
|
10
|
+
return model && model !== "unknown" ? model : undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Build usage facts without choosing a billing meter or changing Pi messages. */
|
|
14
|
+
export function summarizeModelUsage(
|
|
15
|
+
requested: Pick<Model<Api>, "id" | "api" | "provider">,
|
|
16
|
+
responses: readonly [AssistantMessage, ...AssistantMessage[]],
|
|
17
|
+
): ModelUsage {
|
|
18
|
+
const [first, ...remaining] = responses;
|
|
19
|
+
const total = structuredClone(first.usage);
|
|
20
|
+
for (const response of remaining) {
|
|
21
|
+
const usage = response.usage;
|
|
22
|
+
total.input += usage.input;
|
|
23
|
+
total.output += usage.output;
|
|
24
|
+
total.cacheRead += usage.cacheRead;
|
|
25
|
+
total.cacheWrite += usage.cacheWrite;
|
|
26
|
+
if (usage.cacheWrite1h !== undefined) {
|
|
27
|
+
total.cacheWrite1h = (total.cacheWrite1h ?? 0) + usage.cacheWrite1h;
|
|
28
|
+
}
|
|
29
|
+
if (usage.reasoning !== undefined) {
|
|
30
|
+
total.reasoning = (total.reasoning ?? 0) + usage.reasoning;
|
|
31
|
+
}
|
|
32
|
+
total.totalTokens += usage.totalTokens;
|
|
33
|
+
total.cost.input += usage.cost.input;
|
|
34
|
+
total.cost.output += usage.cost.output;
|
|
35
|
+
total.cost.cacheRead += usage.cost.cacheRead;
|
|
36
|
+
total.cost.cacheWrite += usage.cost.cacheWrite;
|
|
37
|
+
total.cost.total += usage.cost.total;
|
|
38
|
+
}
|
|
39
|
+
const responseModel = reportedModel(first);
|
|
40
|
+
return {
|
|
41
|
+
api: requested.api,
|
|
42
|
+
provider: requested.provider,
|
|
43
|
+
model: requested.id,
|
|
44
|
+
// An aggregate must not attribute different responses to one reported model.
|
|
45
|
+
...(responseModel && responseModel !== requested.id &&
|
|
46
|
+
remaining.every(response => reportedModel(response) === responseModel)
|
|
47
|
+
? { responseModel } : {}),
|
|
48
|
+
...(remaining.every(response => response.stopReason === first.stopReason)
|
|
49
|
+
? { stopReason: first.stopReason } : {}),
|
|
50
|
+
usage: total,
|
|
51
|
+
};
|
|
52
|
+
}
|