@springbrand/agent-runtime 0.3.0-alpha.1 → 0.3.0-alpha.3
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 +5 -5
- package/src/adapter/cloudflare/agent.ts +55 -2
- package/src/adapter/cloudflare/define-agent.ts +6 -6
- package/src/context/assembly.ts +1 -1
- package/src/lib/token-estimate.ts +8 -0
- package/src/session/cloudflare/migration.ts +29 -0
- package/src/submission/repository.ts +1 -1
- package/src/tools/base.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/agent-runtime",
|
|
3
|
-
"version": "0.3.0-alpha.
|
|
3
|
+
"version": "0.3.0-alpha.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"./models": "./src/lib/model-catalog.ts"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@cloudflare/codemode": "0.5.
|
|
20
|
+
"@cloudflare/codemode": "0.5.2",
|
|
21
21
|
"@cloudflare/sandbox": "0.12.4",
|
|
22
22
|
"@cloudflare/shell": "0.4.3",
|
|
23
|
-
"@cloudflare/think": "0.
|
|
23
|
+
"@cloudflare/think": "0.18.0",
|
|
24
24
|
"@earendil-works/pi-agent-core": "0.85.1",
|
|
25
25
|
"@earendil-works/pi-ai": "0.85.1",
|
|
26
26
|
"@types/lodash-es": "^4.17.12",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"zod": "^4.4.3"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"agents": "^0.
|
|
33
|
+
"agents": "^0.23.0",
|
|
34
34
|
"ai": "^7.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"agents": "0.
|
|
37
|
+
"agents": "0.23.0",
|
|
38
38
|
"ai": "7.0.56"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -668,11 +668,13 @@ export abstract class AgentRuntimeKernel<
|
|
|
668
668
|
return submission;
|
|
669
669
|
}
|
|
670
670
|
if (submission.abortReason && submission.status === "pending") {
|
|
671
|
-
|
|
671
|
+
const terminal = await this.submissions.finish(
|
|
672
672
|
submission,
|
|
673
673
|
"aborted",
|
|
674
674
|
submission.abortReason,
|
|
675
675
|
);
|
|
676
|
+
await this.projectTerminal(terminal);
|
|
677
|
+
return terminal;
|
|
676
678
|
}
|
|
677
679
|
if (mode === "recovery") {
|
|
678
680
|
this.telemetry.capture("turnRecovering", {
|
|
@@ -683,7 +685,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
683
685
|
await this.broadcastApprovals();
|
|
684
686
|
}
|
|
685
687
|
try {
|
|
686
|
-
|
|
688
|
+
const outcome = await this.executeHarnessSubmission(submission, mode);
|
|
689
|
+
if (isTerminalSubmissionStatus(outcome.status)) {
|
|
690
|
+
await this.projectTerminal(outcome);
|
|
691
|
+
}
|
|
692
|
+
return outcome;
|
|
687
693
|
} catch (error) {
|
|
688
694
|
const failed = await this.submissions.finish(
|
|
689
695
|
submission,
|
|
@@ -697,10 +703,57 @@ export abstract class AgentRuntimeKernel<
|
|
|
697
703
|
: undefined,
|
|
698
704
|
mode !== "fresh",
|
|
699
705
|
);
|
|
706
|
+
await this.projectTerminal(failed);
|
|
700
707
|
return failed;
|
|
701
708
|
}
|
|
702
709
|
}
|
|
703
710
|
|
|
711
|
+
/** Project one authoritative Submission terminal after Harness and client settlement. */
|
|
712
|
+
private async projectTerminal(submission: StoredSubmission): Promise<void> {
|
|
713
|
+
if (!isTerminalSubmissionStatus(submission.status)) {
|
|
714
|
+
throw new Error(`Cannot project unfinished Submission: ${submission.submissionId}`);
|
|
715
|
+
}
|
|
716
|
+
const events = this.activeRuntime?.snapshot.bindings.turnEvents;
|
|
717
|
+
if (!events) return;
|
|
718
|
+
const messages = await this.getMessages();
|
|
719
|
+
try {
|
|
720
|
+
await events.onResponse(
|
|
721
|
+
messages.flatMap((message) =>
|
|
722
|
+
message.role === "user" || message.role === "assistant"
|
|
723
|
+
? [{
|
|
724
|
+
role: message.role,
|
|
725
|
+
text: message.parts
|
|
726
|
+
.filter(
|
|
727
|
+
(
|
|
728
|
+
part,
|
|
729
|
+
): part is Extract<UIMessage["parts"][number], { type: "text" }> =>
|
|
730
|
+
part.type === "text",
|
|
731
|
+
)
|
|
732
|
+
.map((part) => part.text)
|
|
733
|
+
.join(""),
|
|
734
|
+
}]
|
|
735
|
+
: []
|
|
736
|
+
),
|
|
737
|
+
);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
console.warn("[runtime-turn-projection:degraded]", json({
|
|
740
|
+
error: errorText(error),
|
|
741
|
+
}));
|
|
742
|
+
}
|
|
743
|
+
try {
|
|
744
|
+
await events.onSubmissionTerminal?.({
|
|
745
|
+
submissionId: submission.submissionId,
|
|
746
|
+
status: submission.status,
|
|
747
|
+
...(submission.error ? { error: submission.error } : {}),
|
|
748
|
+
resultMessageId: submission.assistantMessageId,
|
|
749
|
+
});
|
|
750
|
+
} catch (error) {
|
|
751
|
+
console.warn("[runtime-terminal-projection:degraded]", json({
|
|
752
|
+
error: errorText(error),
|
|
753
|
+
}));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
704
757
|
/** Run or recover the Submission through Pi's single durable AgentHarness. */
|
|
705
758
|
private async executeHarnessSubmission(
|
|
706
759
|
submission: StoredSubmission,
|
|
@@ -114,7 +114,7 @@ interface RuntimeAgentDefinitionBase<
|
|
|
114
114
|
/** Prepare the optional telemetry consumer for this concrete Agent facet. */
|
|
115
115
|
readonly telemetry?: (
|
|
116
116
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
117
|
-
) => AgentTelemetryBinding | undefined
|
|
117
|
+
) => AgentTelemetryBinding | undefined | Promise<AgentTelemetryBinding | undefined>;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
export interface ReadonlyRuntimeAgentDefinition<
|
|
@@ -406,14 +406,14 @@ export function defineRuntimeAgent<
|
|
|
406
406
|
| MutableRuntimeAgentDefinition<Env, Config, Command, Change>
|
|
407
407
|
| ReadonlyRuntimeAgentDefinition<Env, Config>,
|
|
408
408
|
): RuntimeAgentClass<Env, Command, Change> {
|
|
409
|
-
const resolveDefinitionTelemetry = (
|
|
409
|
+
const resolveDefinitionTelemetry = async (
|
|
410
410
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
411
|
-
): AgentTelemetryBinding | undefined => {
|
|
411
|
+
): Promise<AgentTelemetryBinding | undefined> => {
|
|
412
412
|
const telemetry = definition.telemetry;
|
|
413
413
|
if (!telemetry) return undefined;
|
|
414
|
-
return (telemetry as (
|
|
414
|
+
return await (telemetry as (
|
|
415
415
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
416
|
-
) => AgentTelemetryBinding | undefined)(context);
|
|
416
|
+
) => AgentTelemetryBinding | undefined | Promise<AgentTelemetryBinding | undefined>)(context);
|
|
417
417
|
};
|
|
418
418
|
|
|
419
419
|
const GeneratedRuntimeAgent = {
|
|
@@ -635,7 +635,7 @@ export function defineRuntimeAgent<
|
|
|
635
635
|
let resources = loaded.resources;
|
|
636
636
|
let commitGuard = loaded.commitGuard;
|
|
637
637
|
if (!this.telemetryResolved) {
|
|
638
|
-
this.resolvedTelemetry = resolveDefinitionTelemetry(context);
|
|
638
|
+
this.resolvedTelemetry = await resolveDefinitionTelemetry(context);
|
|
639
639
|
this.telemetryResolved = true;
|
|
640
640
|
}
|
|
641
641
|
const telemetry = this.resolvedTelemetry;
|
package/src/context/assembly.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { estimateStringTokens } from "
|
|
1
|
+
import { estimateStringTokens } from "../lib/token-estimate";
|
|
2
2
|
import type { RuntimeMemoryPort } from "../config/ports";
|
|
3
3
|
import type { RuntimeDegradation } from "../config/degradation";
|
|
4
4
|
import type { RuntimeMemoryProfile } from "../config/profile";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Conservative token estimate for prompt budgeting, not provider billing. */
|
|
2
|
+
export function estimateStringTokens(text: string): number {
|
|
3
|
+
if (!text) return 0;
|
|
4
|
+
return Math.ceil(Math.max(
|
|
5
|
+
text.length / 4,
|
|
6
|
+
text.split(/\s+/u).filter(Boolean).length * 1.3,
|
|
7
|
+
));
|
|
8
|
+
}
|
|
@@ -310,6 +310,34 @@ function hasToolExecution(value: ToolExecution | undefined): boolean {
|
|
|
310
310
|
typeof value.args === "object" && !Array.isArray(value.args));
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
/** A terminal Submission cannot resume, so its stale lane pointer is not active work. */
|
|
314
|
+
function clearTerminalOperationPointers(sql: SqlStorage): void {
|
|
315
|
+
if (!hasTable(sql, "pi_submissions")) return;
|
|
316
|
+
const lanes = sql.exec<{ key: string; body: string }>(
|
|
317
|
+
"SELECT key, body FROM pi_values WHERE namespace = ?",
|
|
318
|
+
laneState("").namespace,
|
|
319
|
+
).toArray();
|
|
320
|
+
for (const lane of lanes) {
|
|
321
|
+
const state = JSON.parse(lane.body) as LaneState;
|
|
322
|
+
if (state.currentOperationId === null) continue;
|
|
323
|
+
const terminal = sql.exec(
|
|
324
|
+
`SELECT 1 FROM pi_submissions
|
|
325
|
+
WHERE submission_id = ?
|
|
326
|
+
AND status IN ('completed', 'failed', 'aborted', 'skipped')
|
|
327
|
+
LIMIT 1`,
|
|
328
|
+
state.currentOperationId,
|
|
329
|
+
).toArray().length === 1;
|
|
330
|
+
if (!terminal) continue;
|
|
331
|
+
sql.exec(
|
|
332
|
+
`UPDATE pi_values
|
|
333
|
+
SET body = json_set(body, '$.currentOperationId', json('null'))
|
|
334
|
+
WHERE namespace = ? AND key = ?`,
|
|
335
|
+
laneState("").namespace,
|
|
336
|
+
lane.key,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
313
341
|
/** Old active tool outcomes cannot be reconstructed from error text or approval facts. */
|
|
314
342
|
function assertActiveToolProvenance(sql: SqlStorage): void {
|
|
315
343
|
const read = <T>(address: Value<T>): T | undefined => {
|
|
@@ -379,6 +407,7 @@ export function migrateCurrentPiSession(
|
|
|
379
407
|
if (version !== 1 && version !== 2) {
|
|
380
408
|
throw new Error(`Unsupported Cloudflare Pi storage version: ${version}`);
|
|
381
409
|
}
|
|
410
|
+
clearTerminalOperationPointers(sql);
|
|
382
411
|
assertActiveToolProvenance(sql);
|
|
383
412
|
if (version === 1) removeRetiredRuntimeArtifacts(sql);
|
|
384
413
|
repairDuplicatedUserSidecars(sql);
|
|
@@ -23,7 +23,7 @@ const TERMINAL_SUBMISSION_STATUSES: ReadonlySet<SubmissionStatus> = new Set([
|
|
|
23
23
|
// 终态集合收口在一处防止各生命周期的判断漂移;增加状态时必须先确认它是否真的不可再继续。
|
|
24
24
|
export function isTerminalSubmissionStatus(
|
|
25
25
|
status: SubmissionStatus,
|
|
26
|
-
):
|
|
26
|
+
): status is Exclude<SubmissionStatus, "pending" | "running"> {
|
|
27
27
|
return TERMINAL_SUBMISSION_STATUSES.has(status);
|
|
28
28
|
}
|
|
29
29
|
|
package/src/tools/base.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type Static,
|
|
9
9
|
type TSchema,
|
|
10
10
|
} from "@earendil-works/pi-ai";
|
|
11
|
-
import { estimateStringTokens } from "
|
|
11
|
+
import { estimateStringTokens } from "../lib/token-estimate";
|
|
12
12
|
import type { RuntimeMemoryPort } from "../config/ports";
|
|
13
13
|
import type { RuntimeMemoryProfile } from "../config/profile";
|
|
14
14
|
import { serializeOutput } from "../lib/artifacts";
|