@springbrand/agent-runtime 0.1.3-alpha.8 → 0.2.0-alpha.13
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 +4 -3
- package/src/adapter/cloudflare/index.ts +1 -0
- package/src/adapter/cloudflare/sandbox/adapter.ts +4 -0
- package/src/adapter/cloudflare/subagent/definition.ts +60 -5
- package/src/adapter/cloudflare/universal-agent/hooks.ts +23 -1
- package/src/adapter/cloudflare/universal-agent/preparation.ts +34 -0
- package/src/adapter/cloudflare/universal-agent/tools.ts +7 -7
- package/src/adapter/cloudflare/workspace/git-fs.ts +178 -0
- package/src/adapter/cloudflare/workspace/version-control.ts +374 -0
- package/src/db/index.ts +4 -0
- package/src/db/runtime-event-outbox.repo.ts +34 -1
- package/src/db/schema.ts +42 -0
- package/src/db/submission-admission.repo.ts +127 -0
- package/src/db/submission.repo.ts +54 -3
- package/src/index.ts +3 -0
- package/src/kernel/bindings.ts +52 -0
- package/src/kernel/durable-lifecycle.ts +100 -0
- package/src/kernel/public-contracts.ts +11 -0
- package/src/kernel/receipts.ts +1 -0
- package/src/kernel/recoverable-chat-agent.ts +0 -19
- package/src/kernel/subagent-runtime.ts +137 -0
- package/src/kernel/submission-authority.ts +114 -0
- package/src/kernel/submission-lifecycle.ts +17 -5
- package/src/lib/prompt.ts +3 -0
- package/src/pi/runtime-adapter/assembly.ts +2 -1
- package/src/pi/runtime-adapter/execution.ts +8 -1
- package/src/pi/runtime-adapter/index.ts +33 -0
- package/src/pi/runtime-adapter/models.ts +9 -0
- package/src/pi/tool/core-host.ts +5 -1
- package/src/pi/tool/core.ts +1 -10
- package/src/pi/tool/index.ts +1 -0
- package/src/pi/tool/mcp.ts +2 -2
- package/src/pi/tool/subagent.ts +142 -16
- package/src/pi/tool/workspace-revision.ts +64 -0
- package/src/runtime-agent-context.ts +1 -0
- package/src/runtime-assembler.ts +16 -1
- package/src/runtime-definition.ts +12 -0
- package/src/runtime.ts +427 -46
- package/src/tool-registry.ts +2 -0
- package/src/workspace-versioning.ts +46 -0
|
@@ -48,6 +48,11 @@ export interface StoredSubmission {
|
|
|
48
48
|
regenerateMessageId: string | null;
|
|
49
49
|
recoveryErrorCount: number;
|
|
50
50
|
recoveryReason: SubmissionRecoveryReason | null;
|
|
51
|
+
runId: string | null;
|
|
52
|
+
accountId: string | null;
|
|
53
|
+
rateVersion: number | null;
|
|
54
|
+
slotIdentity: string | null;
|
|
55
|
+
retryable?: boolean;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
export interface NewSubmission {
|
|
@@ -62,6 +67,10 @@ export interface NewSubmission {
|
|
|
62
67
|
queuedUiMessageJson?: string | null;
|
|
63
68
|
userMessageId?: string | null;
|
|
64
69
|
regenerateMessageId?: string | null;
|
|
70
|
+
runId?: string | null;
|
|
71
|
+
accountId?: string | null;
|
|
72
|
+
rateVersion?: number | null;
|
|
73
|
+
slotIdentity?: string | null;
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
type SubmissionRow = {
|
|
@@ -83,6 +92,11 @@ type SubmissionRow = {
|
|
|
83
92
|
regenerate_message_id: string | null;
|
|
84
93
|
recovery_error_count: number;
|
|
85
94
|
recovery_reason: string | null;
|
|
95
|
+
run_id: string | null;
|
|
96
|
+
account_id: string | null;
|
|
97
|
+
rate_version: number | null;
|
|
98
|
+
slot_identity: string | null;
|
|
99
|
+
admission_retryable: number | null;
|
|
86
100
|
};
|
|
87
101
|
|
|
88
102
|
// #endregion
|
|
@@ -110,6 +124,13 @@ function mapRow(row: SubmissionRow): StoredSubmission {
|
|
|
110
124
|
regenerateMessageId: row.regenerate_message_id,
|
|
111
125
|
recoveryErrorCount: row.recovery_error_count,
|
|
112
126
|
recoveryReason: row.recovery_reason as SubmissionRecoveryReason | null,
|
|
127
|
+
runId: row.run_id,
|
|
128
|
+
accountId: row.account_id,
|
|
129
|
+
rateVersion: row.rate_version,
|
|
130
|
+
slotIdentity: row.slot_identity,
|
|
131
|
+
retryable: row.admission_retryable === null
|
|
132
|
+
? undefined
|
|
133
|
+
: row.admission_retryable === 1,
|
|
113
134
|
};
|
|
114
135
|
}
|
|
115
136
|
|
|
@@ -130,7 +151,9 @@ export class SubmissionRepository {
|
|
|
130
151
|
error, created_at, completed_at, assembly_revision,
|
|
131
152
|
assembly_descriptor, assistant_message_id, abort_reason,
|
|
132
153
|
queued_input_json, queued_ui_message_json, user_message_id,
|
|
133
|
-
regenerate_message_id, recovery_error_count, recovery_reason
|
|
154
|
+
regenerate_message_id, recovery_error_count, recovery_reason,
|
|
155
|
+
run_id, account_id, rate_version, slot_identity,
|
|
156
|
+
admission_retryable
|
|
134
157
|
FROM pi_submissions
|
|
135
158
|
WHERE submission_id = ${id}
|
|
136
159
|
`[0];
|
|
@@ -230,7 +253,8 @@ export class SubmissionRepository {
|
|
|
230
253
|
submission_id, request_id, idempotency_key, status, accepted,
|
|
231
254
|
created_at, assembly_revision, assembly_descriptor,
|
|
232
255
|
assistant_message_id, queued_input_json, queued_ui_message_json,
|
|
233
|
-
user_message_id, regenerate_message_id
|
|
256
|
+
user_message_id, regenerate_message_id,
|
|
257
|
+
run_id, account_id, rate_version, slot_identity
|
|
234
258
|
) VALUES (
|
|
235
259
|
${s.submissionId},
|
|
236
260
|
${s.requestId},
|
|
@@ -244,7 +268,34 @@ export class SubmissionRepository {
|
|
|
244
268
|
${s.queuedInputJson ?? null},
|
|
245
269
|
${s.queuedUiMessageJson ?? null},
|
|
246
270
|
${s.userMessageId ?? null},
|
|
247
|
-
${s.regenerateMessageId ?? null}
|
|
271
|
+
${s.regenerateMessageId ?? null},
|
|
272
|
+
${s.runId ?? s.submissionId},
|
|
273
|
+
${s.accountId ?? null},
|
|
274
|
+
${s.rateVersion ?? null},
|
|
275
|
+
${s.slotIdentity ?? null}
|
|
276
|
+
)
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
insertRejected(s: {
|
|
281
|
+
submissionId: string;
|
|
282
|
+
requestId: string;
|
|
283
|
+
idempotencyKey: string | null;
|
|
284
|
+
createdAt: number;
|
|
285
|
+
code: string;
|
|
286
|
+
retryable: boolean;
|
|
287
|
+
}): void {
|
|
288
|
+
this.sql`
|
|
289
|
+
INSERT INTO pi_submissions (
|
|
290
|
+
submission_id, request_id, idempotency_key, status, accepted,
|
|
291
|
+
error, created_at, completed_at, assembly_revision,
|
|
292
|
+
assembly_descriptor, assistant_message_id, abort_reason,
|
|
293
|
+
recovery_error_count, recovery_reason, run_id, account_id,
|
|
294
|
+
rate_version, slot_identity, admission_retryable
|
|
295
|
+
) VALUES (
|
|
296
|
+
${s.submissionId}, ${s.requestId}, ${s.idempotencyKey}, 'skipped', 0,
|
|
297
|
+
${s.code}, ${s.createdAt}, ${s.createdAt}, '', '', '', NULL,
|
|
298
|
+
0, NULL, ${s.submissionId}, NULL, NULL, NULL, ${s.retryable ? 1 : 0}
|
|
248
299
|
)
|
|
249
300
|
`;
|
|
250
301
|
}
|
package/src/index.ts
CHANGED
|
@@ -50,6 +50,7 @@ export * from "./kernel/runtime-config";
|
|
|
50
50
|
export type { RuntimeAssemblyView } from "./kernel/runtime-assembly-view";
|
|
51
51
|
export * from "./runtime-agent";
|
|
52
52
|
export * from "./kernel/state";
|
|
53
|
+
export * from "./workspace-versioning";
|
|
53
54
|
export * from "./lib/execution-level";
|
|
54
55
|
export type {
|
|
55
56
|
RuntimeAssemblyInput,
|
|
@@ -119,8 +120,10 @@ export {
|
|
|
119
120
|
} from "./pi/tool";
|
|
120
121
|
export {
|
|
121
122
|
createSandboxTools,
|
|
123
|
+
createWorkspaceRevisionTools,
|
|
122
124
|
createWorkspaceTools,
|
|
123
125
|
sandboxPiToolCandidates,
|
|
126
|
+
workspaceRevisionPiToolCandidate,
|
|
124
127
|
workspacePiToolCandidates,
|
|
125
128
|
} from "./pi/tool";
|
|
126
129
|
export {
|
package/src/kernel/bindings.ts
CHANGED
|
@@ -2,6 +2,23 @@ import type { SkillSource } from "agents/skills";
|
|
|
2
2
|
import type { ScheduleSpec } from "./receipts";
|
|
3
3
|
import type { RuntimeActivityProjection } from "./state";
|
|
4
4
|
import type { ExecutionLevel } from "../lib/execution-level";
|
|
5
|
+
import type {
|
|
6
|
+
RuntimeEventConfirmation,
|
|
7
|
+
RuntimeLifecycleFact,
|
|
8
|
+
RuntimeSubagentUsageEvent,
|
|
9
|
+
RuntimeSubagentUsageMetrics,
|
|
10
|
+
} from "./submission-authority";
|
|
11
|
+
export type {
|
|
12
|
+
RuntimeEventConfirmation,
|
|
13
|
+
RuntimeLifecycleFact,
|
|
14
|
+
RuntimeSubagentLifecyclePort,
|
|
15
|
+
RuntimeSubagentUsageEvent,
|
|
16
|
+
RuntimeSubagentUsageMetrics,
|
|
17
|
+
RuntimeSubmissionAdmissionHook,
|
|
18
|
+
RuntimeSubmissionAdmissionInput,
|
|
19
|
+
RuntimeSubmissionAdmissionResult,
|
|
20
|
+
RuntimeSubmissionAdmissionContext,
|
|
21
|
+
} from "./submission-authority";
|
|
5
22
|
|
|
6
23
|
// #region Workspace 端口
|
|
7
24
|
|
|
@@ -194,6 +211,7 @@ export interface WorkspacePort {
|
|
|
194
211
|
* Workspace;Tool Surface 只把这个已授权 Port 转成 `execute` Tool。
|
|
195
212
|
*/
|
|
196
213
|
export interface RuntimeCodeExecutionPort {
|
|
214
|
+
readonly description: string;
|
|
197
215
|
execute(input: { code: string }): Promise<unknown>;
|
|
198
216
|
}
|
|
199
217
|
|
|
@@ -654,12 +672,16 @@ export type RuntimeSubagentRunResult =
|
|
|
654
672
|
runId: string;
|
|
655
673
|
output: unknown;
|
|
656
674
|
error?: undefined;
|
|
675
|
+
usage?: RuntimeSubagentUsageMetrics;
|
|
676
|
+
childStillRunning?: boolean;
|
|
657
677
|
}
|
|
658
678
|
| {
|
|
659
679
|
status: string;
|
|
660
680
|
runId: string;
|
|
661
681
|
output?: unknown;
|
|
662
682
|
error?: string;
|
|
683
|
+
usage?: RuntimeSubagentUsageMetrics;
|
|
684
|
+
childStillRunning?: boolean;
|
|
663
685
|
};
|
|
664
686
|
|
|
665
687
|
/**
|
|
@@ -671,6 +693,15 @@ export type RuntimeSubagentRunResult =
|
|
|
671
693
|
* 运行记录、后台通知和实际 Agent 路由由 Host 保持,Runtime 只传入类型与结构化输入。
|
|
672
694
|
*/
|
|
673
695
|
export interface RuntimeSubagentPort {
|
|
696
|
+
reserve?(
|
|
697
|
+
agentType: string,
|
|
698
|
+
input: Readonly<Record<string, unknown>>,
|
|
699
|
+
options?: Readonly<{
|
|
700
|
+
parentRunId?: string;
|
|
701
|
+
submissionId?: string;
|
|
702
|
+
toolCallId?: string;
|
|
703
|
+
}>,
|
|
704
|
+
): Promise<{ readonly runId: string }>;
|
|
674
705
|
/**
|
|
675
706
|
* 启动一次 Subagent 运行并返回当前结果。
|
|
676
707
|
*
|
|
@@ -686,6 +717,12 @@ export interface RuntimeSubagentPort {
|
|
|
686
717
|
detached?: boolean;
|
|
687
718
|
maxBudgetMs?: number;
|
|
688
719
|
notifySource?: string;
|
|
720
|
+
subagentRunId?: string;
|
|
721
|
+
parentRunId?: string;
|
|
722
|
+
submissionId?: string;
|
|
723
|
+
accountId?: string;
|
|
724
|
+
rateVersion?: number;
|
|
725
|
+
slotIdentity?: string;
|
|
689
726
|
}>,
|
|
690
727
|
): Promise<RuntimeSubagentRunResult>;
|
|
691
728
|
}
|
|
@@ -706,6 +743,10 @@ export interface RuntimeTurnMessage {
|
|
|
706
743
|
export interface RuntimeModelUsageEvent {
|
|
707
744
|
readonly eventId: string;
|
|
708
745
|
readonly submissionId: string;
|
|
746
|
+
readonly runId?: string;
|
|
747
|
+
readonly accountId?: string;
|
|
748
|
+
readonly rateVersion?: number;
|
|
749
|
+
readonly slotIdentity?: string;
|
|
709
750
|
readonly kind: "assistant" | "compaction";
|
|
710
751
|
readonly api: string;
|
|
711
752
|
readonly provider: string;
|
|
@@ -777,6 +818,14 @@ export interface RuntimeTurnEventsPort {
|
|
|
777
818
|
): Promise<void>;
|
|
778
819
|
/** 投递 Assistant 或 Compaction 的原始 provider usage。 */
|
|
779
820
|
onModelUsage?(event: RuntimeModelUsageEvent): Promise<void>;
|
|
821
|
+
/** Durable SubAgent usage callback. Runtime keeps the event pending until confirmation. */
|
|
822
|
+
onSubagentUsage?(
|
|
823
|
+
event: RuntimeSubagentUsageEvent,
|
|
824
|
+
): Promise<RuntimeEventConfirmation | void>;
|
|
825
|
+
/** Durable lifecycle callback. Runtime keeps the fact pending until confirmation. */
|
|
826
|
+
onLifecycleFact?(
|
|
827
|
+
event: RuntimeLifecycleFact,
|
|
828
|
+
): Promise<RuntimeEventConfirmation | void>;
|
|
780
829
|
/**
|
|
781
830
|
* 投递一次 Tool 开始执行的事件,带上它的参数。
|
|
782
831
|
*
|
|
@@ -863,6 +912,8 @@ export interface RuntimeModelEndpoint {
|
|
|
863
912
|
headers?: Readonly<Record<string, string>>;
|
|
864
913
|
baseURL: string;
|
|
865
914
|
models: readonly string[];
|
|
915
|
+
/** OpenRouter upstream provider pins keyed by model ID; pinned requests never fall back. */
|
|
916
|
+
openRouterProviderPins?: Readonly<Record<string, string>>;
|
|
866
917
|
}
|
|
867
918
|
|
|
868
919
|
/**
|
|
@@ -977,6 +1028,7 @@ export interface RuntimeBindings {
|
|
|
977
1028
|
gateway?: RuntimeGatewayPort;
|
|
978
1029
|
workspace?: WorkspacePort;
|
|
979
1030
|
memory?: RuntimeMemoryPort;
|
|
1031
|
+
subagents?: RuntimeSubagentPort;
|
|
980
1032
|
skills: RuntimeSkillBindings;
|
|
981
1033
|
turnEvents?: RuntimeTurnEventsPort;
|
|
982
1034
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RuntimeEventConfirmation,
|
|
3
|
+
RuntimeLifecycleFact,
|
|
4
|
+
RuntimeSubagentUsageEvent,
|
|
5
|
+
} from "./submission-authority";
|
|
6
|
+
|
|
7
|
+
export interface RuntimeLifecycleSink {
|
|
8
|
+
save(
|
|
9
|
+
event: RuntimeLifecycleFact | RuntimeSubagentUsageEvent,
|
|
10
|
+
): void | Promise<void>;
|
|
11
|
+
has(eventId: string): boolean;
|
|
12
|
+
hasSubagentUsage(submissionId: string, subagentRunId: string): boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Small ordering guard used by Host adapters while they persist child facts.
|
|
17
|
+
* The sink is the Runtime outbox; this class owns no prompt or billing state.
|
|
18
|
+
*/
|
|
19
|
+
export class RuntimeSubagentLifecycleRecorder {
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly input: {
|
|
22
|
+
submissionId: string;
|
|
23
|
+
parentRunId: string;
|
|
24
|
+
accountId?: string;
|
|
25
|
+
rateVersion?: number;
|
|
26
|
+
slotIdentity?: string;
|
|
27
|
+
},
|
|
28
|
+
private readonly sink: RuntimeLifecycleSink,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
async register(subagentRunId: string): Promise<RuntimeEventConfirmation> {
|
|
32
|
+
if (!subagentRunId.trim()) throw new Error("SubAgent run ID is required");
|
|
33
|
+
const eventId = `${subagentRunId}:registered`;
|
|
34
|
+
if (this.sink.has(eventId)) return { confirmed: true };
|
|
35
|
+
if (this.sink.has(`${this.input.submissionId}:terminal`)) {
|
|
36
|
+
throw new Error("SubAgent registration is closed");
|
|
37
|
+
}
|
|
38
|
+
await this.sink.save({
|
|
39
|
+
kind: "subagent-run-registered",
|
|
40
|
+
eventId,
|
|
41
|
+
submissionId: this.input.submissionId,
|
|
42
|
+
subagentRunId,
|
|
43
|
+
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
|
+
});
|
|
48
|
+
return { confirmed: true };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async usage(event: RuntimeSubagentUsageEvent): Promise<RuntimeEventConfirmation> {
|
|
52
|
+
if (
|
|
53
|
+
event.submissionId !== this.input.submissionId ||
|
|
54
|
+
event.parentRunId !== this.input.parentRunId
|
|
55
|
+
) {
|
|
56
|
+
throw new Error("SubAgent usage identity does not match its lifecycle");
|
|
57
|
+
}
|
|
58
|
+
if (!this.sink.has(`${event.subagentRunId}:registered`)) {
|
|
59
|
+
throw new Error("SubAgent usage is outside its lifecycle");
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
this.sink.has(`${event.subagentRunId}:terminal`) &&
|
|
63
|
+
!this.sink.has(event.eventId)
|
|
64
|
+
) {
|
|
65
|
+
throw new Error("SubAgent usage is outside its lifecycle");
|
|
66
|
+
}
|
|
67
|
+
await this.sink.save(event);
|
|
68
|
+
return { confirmed: true };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async terminal(input: {
|
|
72
|
+
subagentRunId: string;
|
|
73
|
+
status: "completed" | "failed" | "aborted" | "skipped";
|
|
74
|
+
zeroUsage?: boolean;
|
|
75
|
+
}): Promise<RuntimeEventConfirmation> {
|
|
76
|
+
if (!this.sink.has(`${input.subagentRunId}:registered`)) {
|
|
77
|
+
throw new Error("SubAgent is not registered");
|
|
78
|
+
}
|
|
79
|
+
const usageSaved = this.sink.hasSubagentUsage(
|
|
80
|
+
this.input.submissionId,
|
|
81
|
+
input.subagentRunId,
|
|
82
|
+
);
|
|
83
|
+
if (!usageSaved && !input.zeroUsage) {
|
|
84
|
+
throw new Error("SubAgent terminal requires durable usage or zero usage");
|
|
85
|
+
}
|
|
86
|
+
await this.sink.save({
|
|
87
|
+
kind: "subagent-terminal",
|
|
88
|
+
eventId: `${input.subagentRunId}:terminal`,
|
|
89
|
+
submissionId: this.input.submissionId,
|
|
90
|
+
subagentRunId: input.subagentRunId,
|
|
91
|
+
parentRunId: this.input.parentRunId,
|
|
92
|
+
status: input.status,
|
|
93
|
+
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
|
+
});
|
|
98
|
+
return { confirmed: true };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -17,3 +17,14 @@ export * from "../pi/message";
|
|
|
17
17
|
export * from "./receipts";
|
|
18
18
|
export * from "./state";
|
|
19
19
|
export * from "../lib/execution-level";
|
|
20
|
+
export type {
|
|
21
|
+
RuntimeEventConfirmation,
|
|
22
|
+
RuntimeLifecycleFact,
|
|
23
|
+
RuntimeSubagentLifecyclePort,
|
|
24
|
+
RuntimeSubagentUsageEvent,
|
|
25
|
+
RuntimeSubagentUsageMetrics,
|
|
26
|
+
RuntimeSubmissionAdmissionHook,
|
|
27
|
+
RuntimeSubmissionAdmissionInput,
|
|
28
|
+
RuntimeSubmissionAdmissionResult,
|
|
29
|
+
RuntimeSubmissionAdmissionContext,
|
|
30
|
+
} from "./submission-authority";
|
package/src/kernel/receipts.ts
CHANGED
|
@@ -453,12 +453,6 @@ export abstract class RecoverableChatAgent<
|
|
|
453
453
|
connection: Connection,
|
|
454
454
|
probeId?: string,
|
|
455
455
|
): Promise<void> {
|
|
456
|
-
console.info("[DEBUG-ws-old-9f3a] resume-request", {
|
|
457
|
-
connectionId: connection.id,
|
|
458
|
-
probeId,
|
|
459
|
-
activeRequestId: this.resumableStream.activeRequestId,
|
|
460
|
-
pending: [...this.pendingResumeConnections],
|
|
461
|
-
});
|
|
462
456
|
return this.resumeHandshake.handleResumeRequest(connection, probeId);
|
|
463
457
|
}
|
|
464
458
|
|
|
@@ -474,12 +468,6 @@ export abstract class RecoverableChatAgent<
|
|
|
474
468
|
connection: Connection,
|
|
475
469
|
requestId: string,
|
|
476
470
|
): Promise<void> {
|
|
477
|
-
console.info("[DEBUG-ws-old-9f3a] resume-ack", {
|
|
478
|
-
connectionId: connection.id,
|
|
479
|
-
requestId,
|
|
480
|
-
activeRequestId: this.resumableStream.activeRequestId,
|
|
481
|
-
pending: [...this.pendingResumeConnections],
|
|
482
|
-
});
|
|
483
471
|
return this.resumeHandshake.handleResumeAck(connection, requestId);
|
|
484
472
|
}
|
|
485
473
|
|
|
@@ -504,13 +492,6 @@ export abstract class RecoverableChatAgent<
|
|
|
504
492
|
readonly continuation?: boolean;
|
|
505
493
|
} = {},
|
|
506
494
|
): void {
|
|
507
|
-
console.info("[DEBUG-ws-old-9f3a] broadcast", {
|
|
508
|
-
requestId,
|
|
509
|
-
done,
|
|
510
|
-
activeRequestId: this.resumableStream.activeRequestId,
|
|
511
|
-
pending: [...this.pendingResumeConnections],
|
|
512
|
-
connections: Array.from(this.getConnections(), ({ id }) => id),
|
|
513
|
-
});
|
|
514
495
|
this.broadcast(
|
|
515
496
|
json({
|
|
516
497
|
type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { RuntimeSubagentUsageMetrics } from "./submission-authority";
|
|
2
|
+
|
|
3
|
+
export interface RuntimeSubagentRunMetadata {
|
|
4
|
+
readonly submissionId: string;
|
|
5
|
+
readonly parentRunId: string;
|
|
6
|
+
readonly accountId?: string;
|
|
7
|
+
readonly rateVersion?: number;
|
|
8
|
+
readonly slotIdentity?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function emptySubagentUsage(): RuntimeSubagentUsageMetrics {
|
|
12
|
+
return {
|
|
13
|
+
input: 0,
|
|
14
|
+
output: 0,
|
|
15
|
+
cacheRead: 0,
|
|
16
|
+
cacheWrite: 0,
|
|
17
|
+
cacheWrite1h: 0,
|
|
18
|
+
reasoning: 0,
|
|
19
|
+
totalTokens: 0,
|
|
20
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isZeroSubagentUsage(
|
|
25
|
+
usage: RuntimeSubagentUsageMetrics,
|
|
26
|
+
): boolean {
|
|
27
|
+
return usage.totalTokens === 0 && usage.cost.total === 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function subagentTerminalStatus(
|
|
31
|
+
status: string,
|
|
32
|
+
): "completed" | "failed" | "aborted" | "skipped" {
|
|
33
|
+
if (status === "completed" || status === "aborted" || status === "skipped") {
|
|
34
|
+
return status;
|
|
35
|
+
}
|
|
36
|
+
return "failed";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function addSubagentUsage(
|
|
40
|
+
total: RuntimeSubagentUsageMetrics,
|
|
41
|
+
usage: RuntimeSubagentUsageMetrics,
|
|
42
|
+
): void {
|
|
43
|
+
const target = total as {
|
|
44
|
+
-readonly [Key in keyof RuntimeSubagentUsageMetrics]: RuntimeSubagentUsageMetrics[Key];
|
|
45
|
+
};
|
|
46
|
+
const cost = total.cost as {
|
|
47
|
+
-readonly [Key in keyof RuntimeSubagentUsageMetrics["cost"]]: number;
|
|
48
|
+
};
|
|
49
|
+
target.input += usage.input;
|
|
50
|
+
target.output += usage.output;
|
|
51
|
+
target.cacheRead += usage.cacheRead;
|
|
52
|
+
target.cacheWrite += usage.cacheWrite;
|
|
53
|
+
target.cacheWrite1h = (target.cacheWrite1h ?? 0) + (usage.cacheWrite1h ?? 0);
|
|
54
|
+
target.reasoning = (target.reasoning ?? 0) + (usage.reasoning ?? 0);
|
|
55
|
+
target.totalTokens += usage.totalTokens;
|
|
56
|
+
cost.input += usage.cost.input;
|
|
57
|
+
cost.output += usage.cost.output;
|
|
58
|
+
cost.cacheRead += usage.cost.cacheRead;
|
|
59
|
+
cost.cacheWrite += usage.cost.cacheWrite;
|
|
60
|
+
cost.total += usage.cost.total;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function encodeSubagentCompletion(
|
|
64
|
+
output: unknown,
|
|
65
|
+
usage: RuntimeSubagentUsageMetrics,
|
|
66
|
+
): string {
|
|
67
|
+
return JSON.stringify({ output, usage });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function decodeSubagentCompletion(value: unknown): {
|
|
71
|
+
output: unknown;
|
|
72
|
+
usage: RuntimeSubagentUsageMetrics;
|
|
73
|
+
} | null {
|
|
74
|
+
if (typeof value !== "string") return null;
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(value) as Record<string, unknown>;
|
|
77
|
+
const usage = parsed.usage as Record<string, unknown> | undefined;
|
|
78
|
+
const cost = usage?.cost as Record<string, unknown> | undefined;
|
|
79
|
+
const tokenFields = [
|
|
80
|
+
usage?.input,
|
|
81
|
+
usage?.output,
|
|
82
|
+
usage?.cacheRead,
|
|
83
|
+
usage?.cacheWrite,
|
|
84
|
+
usage?.totalTokens,
|
|
85
|
+
];
|
|
86
|
+
const costFields = [
|
|
87
|
+
cost?.input,
|
|
88
|
+
cost?.output,
|
|
89
|
+
cost?.cacheRead,
|
|
90
|
+
cost?.cacheWrite,
|
|
91
|
+
cost?.total,
|
|
92
|
+
];
|
|
93
|
+
if (
|
|
94
|
+
tokenFields.some((item) =>
|
|
95
|
+
!Number.isSafeInteger(item) || (item as number) < 0
|
|
96
|
+
) ||
|
|
97
|
+
costFields.some((item) =>
|
|
98
|
+
typeof item !== "number" || !Number.isFinite(item) || item < 0
|
|
99
|
+
) ||
|
|
100
|
+
(usage?.cacheWrite1h !== undefined &&
|
|
101
|
+
(!Number.isSafeInteger(usage.cacheWrite1h) ||
|
|
102
|
+
(usage.cacheWrite1h as number) < 0)) ||
|
|
103
|
+
(usage?.reasoning !== undefined &&
|
|
104
|
+
(!Number.isSafeInteger(usage.reasoning) ||
|
|
105
|
+
(usage.reasoning as number) < 0))
|
|
106
|
+
) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
output: parsed.output,
|
|
111
|
+
usage: usage as unknown as RuntimeSubagentUsageMetrics,
|
|
112
|
+
};
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function runtimeSubagentRunMetadata(
|
|
119
|
+
value: unknown,
|
|
120
|
+
): RuntimeSubagentRunMetadata | null {
|
|
121
|
+
if (!value || typeof value !== "object") return null;
|
|
122
|
+
const input = value as Record<string, unknown>;
|
|
123
|
+
if (
|
|
124
|
+
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))
|
|
133
|
+
) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return input as unknown as RuntimeSubagentRunMetadata;
|
|
137
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export interface RuntimeSubagentUsageMetrics {
|
|
2
|
+
readonly input: number;
|
|
3
|
+
readonly output: number;
|
|
4
|
+
readonly cacheRead: number;
|
|
5
|
+
readonly cacheWrite: number;
|
|
6
|
+
readonly cacheWrite1h?: number;
|
|
7
|
+
readonly reasoning?: number;
|
|
8
|
+
readonly totalTokens: number;
|
|
9
|
+
readonly cost: {
|
|
10
|
+
readonly input: number;
|
|
11
|
+
readonly output: number;
|
|
12
|
+
readonly cacheRead: number;
|
|
13
|
+
readonly cacheWrite: number;
|
|
14
|
+
readonly total: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RuntimeSubagentUsageEvent {
|
|
19
|
+
readonly eventId: string;
|
|
20
|
+
readonly submissionId: string;
|
|
21
|
+
readonly runId: string;
|
|
22
|
+
readonly subagentRunId: string;
|
|
23
|
+
readonly parentRunId: string;
|
|
24
|
+
readonly accountId?: string;
|
|
25
|
+
readonly rateVersion?: number;
|
|
26
|
+
readonly slotIdentity?: string;
|
|
27
|
+
readonly kind: "subagent";
|
|
28
|
+
readonly status: "completed" | "failed" | "aborted" | "skipped";
|
|
29
|
+
readonly usage: RuntimeSubagentUsageMetrics;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RuntimeSubagentLifecyclePort {
|
|
33
|
+
readonly parentRunId: string;
|
|
34
|
+
readonly submissionId: string;
|
|
35
|
+
readonly accountId?: string;
|
|
36
|
+
readonly rateVersion?: number;
|
|
37
|
+
readonly slotIdentity?: string;
|
|
38
|
+
onRegistered(subagentRunId: string): Promise<void> | void;
|
|
39
|
+
onUsage(event: RuntimeSubagentUsageEvent): Promise<void> | void;
|
|
40
|
+
onTerminal(input: {
|
|
41
|
+
readonly subagentRunId: string;
|
|
42
|
+
readonly status: "completed" | "failed" | "aborted" | "skipped";
|
|
43
|
+
readonly zeroUsage?: boolean;
|
|
44
|
+
}): Promise<void> | void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface RuntimeSubmissionAdmissionInput {
|
|
48
|
+
readonly requestId: string;
|
|
49
|
+
readonly idempotencyKey?: string;
|
|
50
|
+
readonly runId: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RuntimeSubmissionAdmissionContext {
|
|
54
|
+
readonly accountId: string;
|
|
55
|
+
readonly rateVersion: number;
|
|
56
|
+
readonly slotIdentity: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type RuntimeSubmissionAdmissionResult =
|
|
60
|
+
| ({ readonly accepted: true } & RuntimeSubmissionAdmissionContext)
|
|
61
|
+
| {
|
|
62
|
+
readonly accepted: false;
|
|
63
|
+
readonly code: string;
|
|
64
|
+
readonly retryable?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type RuntimeSubmissionAdmissionHook = (
|
|
68
|
+
input: RuntimeSubmissionAdmissionInput,
|
|
69
|
+
) => Promise<RuntimeSubmissionAdmissionResult>;
|
|
70
|
+
|
|
71
|
+
export type RuntimeLifecycleFact =
|
|
72
|
+
| ({ readonly kind: "submission-admitted" } & RuntimeSubmissionAdmissionContext & {
|
|
73
|
+
readonly eventId: string;
|
|
74
|
+
readonly submissionId: string;
|
|
75
|
+
readonly runId: string;
|
|
76
|
+
})
|
|
77
|
+
| {
|
|
78
|
+
readonly kind: "submission-terminal";
|
|
79
|
+
readonly eventId: string;
|
|
80
|
+
readonly submissionId: string;
|
|
81
|
+
readonly runId: string;
|
|
82
|
+
readonly status: "completed" | "aborted" | "skipped" | "error";
|
|
83
|
+
readonly childRegistrationClosed: true;
|
|
84
|
+
readonly zeroUsage: boolean;
|
|
85
|
+
readonly accountId?: string;
|
|
86
|
+
readonly rateVersion?: number;
|
|
87
|
+
readonly slotIdentity?: string;
|
|
88
|
+
}
|
|
89
|
+
| {
|
|
90
|
+
readonly kind: "subagent-run-registered";
|
|
91
|
+
readonly eventId: string;
|
|
92
|
+
readonly submissionId: string;
|
|
93
|
+
readonly subagentRunId: string;
|
|
94
|
+
readonly parentRunId: string;
|
|
95
|
+
readonly accountId?: string;
|
|
96
|
+
readonly rateVersion?: number;
|
|
97
|
+
readonly slotIdentity?: string;
|
|
98
|
+
}
|
|
99
|
+
| {
|
|
100
|
+
readonly kind: "subagent-terminal";
|
|
101
|
+
readonly eventId: string;
|
|
102
|
+
readonly submissionId: string;
|
|
103
|
+
readonly subagentRunId: string;
|
|
104
|
+
readonly parentRunId: string;
|
|
105
|
+
readonly status: "completed" | "failed" | "aborted" | "skipped";
|
|
106
|
+
readonly zeroUsage: boolean;
|
|
107
|
+
readonly accountId?: string;
|
|
108
|
+
readonly rateVersion?: number;
|
|
109
|
+
readonly slotIdentity?: string;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export interface RuntimeEventConfirmation {
|
|
113
|
+
readonly confirmed: true;
|
|
114
|
+
}
|