@springbrand/agent-runtime 0.3.0-alpha.16 → 0.3.0-alpha.17
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 +6 -4
- package/src/adapter/cloudflare/agent.ts +66 -122
- package/src/adapter/cloudflare/subagent/definition.ts +11 -9
- package/src/adapter/cloudflare/subagent/runner.ts +20 -47
- package/src/adapter/cloudflare/subagent/tools.ts +2 -2
- package/src/config/harness-runtime.ts +27 -14
- package/src/config/model-guidance.ts +16 -17
- package/src/config/ports.ts +2 -0
- package/src/conversation/harness-resume.ts +69 -234
- package/src/conversation/harness-stream.ts +69 -70
- package/src/conversation/history.ts +118 -153
- package/src/conversation/message/contract.ts +12 -0
- package/src/conversation/session-entry.ts +29 -0
- package/src/harness/index.ts +2 -0
- package/src/harness/session-harness.ts +178 -617
- package/src/harness/submission-operation.ts +15 -40
- package/src/models/index.ts +3 -1
- package/src/models/openrouter/protocol-response.ts +36 -112
- package/src/session/cloudflare/migration.ts +221 -939
- package/src/session/cloudflare/storage.ts +13 -536
- package/src/session/index.ts +22 -35
- package/src/submission/finalization.ts +2 -13
- package/src/submission/repository.ts +1 -22
- package/src/submission/steer-delivery.ts +32 -76
- package/src/telemetry/turn-events.ts +34 -24
- package/src/tools/runtime/compiler.ts +25 -50
- package/src/tools/runtime/decision-gate.ts +5 -5
|
@@ -1,49 +1,24 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import type { EntryId } from "@earendil-works/pi-durable";
|
|
3
3
|
import type { PiCanonicalUserInput } from "../conversation/message/contract";
|
|
4
4
|
import type { StoredSubmission } from "../submission/repository";
|
|
5
|
-
import type { SessionHarness } from "./session-harness";
|
|
5
|
+
import type { SessionHarness, SessionRunRequest } from "./session-harness";
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/** Reacquire Pi's stable Submission before admitting another model run. */
|
|
8
8
|
export async function prepareSubmissionOperation(
|
|
9
|
-
runtime: SessionHarness
|
|
9
|
+
runtime: SessionHarness,
|
|
10
10
|
submission: Pick<StoredSubmission, "submissionId" | "queuedInputJson" | "regenerateMessageId">,
|
|
11
|
-
regenerationTarget: (messageId: string) =>
|
|
11
|
+
regenerationTarget: (messageId: string) => Promise<EntryId | null>,
|
|
12
12
|
context: Context,
|
|
13
|
-
): Promise<
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
const navigation = await runtime.run(undefined, context);
|
|
18
|
-
if (navigation.kind !== "settled" || navigation.outcome.status !== "completed") {
|
|
19
|
-
throw new Error("Could not recover the regeneration branch navigation");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if (runtime.openOperation) {
|
|
23
|
-
if (runtime.openOperation.operationId !== id) {
|
|
24
|
-
throw new Error(`Submission ${id} does not own the open Harness operation`);
|
|
25
|
-
}
|
|
26
|
-
return { kind: "run" };
|
|
27
|
-
}
|
|
28
|
-
const watch = await runtime.lane.watch(context);
|
|
29
|
-
const previous = watch.snapshot.lastResult;
|
|
30
|
-
watch.unsubscribe();
|
|
31
|
-
if (previous?.operationId === id) return { kind: "settled", outcome: previous };
|
|
32
|
-
if (!submission.queuedInputJson) {
|
|
33
|
-
throw new Error(`Submission ${id} has no open Harness operation or queued input`);
|
|
34
|
-
}
|
|
13
|
+
): Promise<SessionRunRequest> {
|
|
14
|
+
const existing = await runtime.submissionFor(submission.submissionId, context);
|
|
15
|
+
if (existing) return { operationId: submission.submissionId };
|
|
16
|
+
if (!submission.queuedInputJson) throw new Error("The admitted Submission has no canonical input");
|
|
35
17
|
const prompt = JSON.parse(submission.queuedInputJson) as PiCanonicalUserInput;
|
|
36
|
-
if (prompt.role !== "user") throw new Error("
|
|
37
|
-
if (submission.regenerateMessageId
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
operationId: navigationId,
|
|
41
|
-
targetId: regenerationTarget(submission.regenerateMessageId),
|
|
42
|
-
options: { summarize: false },
|
|
43
|
-
}, context);
|
|
44
|
-
if (navigation.kind !== "settled" || navigation.outcome.status !== "completed") {
|
|
45
|
-
throw new Error("Could not navigate to the regeneration branch");
|
|
46
|
-
}
|
|
18
|
+
if (prompt.role !== "user") throw new Error("Invalid canonical user input");
|
|
19
|
+
if (submission.regenerateMessageId) {
|
|
20
|
+
const target = await regenerationTarget(submission.regenerateMessageId);
|
|
21
|
+
await runtime.fork(target, context);
|
|
47
22
|
}
|
|
48
|
-
return {
|
|
23
|
+
return { operationId: submission.submissionId, prompt };
|
|
49
24
|
}
|
package/src/models/index.ts
CHANGED
|
@@ -914,7 +914,6 @@ function configuredModel(
|
|
|
914
914
|
supportsEagerToolInputStreaming: false,
|
|
915
915
|
supportsStrictTools:
|
|
916
916
|
anthropicMessagesCompat?.supportsStrictTools ?? false,
|
|
917
|
-
supportsToolReferences: true,
|
|
918
917
|
...(modelId === "anthropic/claude-sonnet-5.5" ||
|
|
919
918
|
modelId === "anthropic/claude-opus-5.5"
|
|
920
919
|
? { forceAdaptiveThinking: true }
|
|
@@ -942,6 +941,9 @@ function configuredModel(
|
|
|
942
941
|
}),
|
|
943
942
|
openRouterRouting: {
|
|
944
943
|
require_parameters: true,
|
|
944
|
+
...(endpoint.openRouterProviderSort
|
|
945
|
+
? { sort: endpoint.openRouterProviderSort }
|
|
946
|
+
: {}),
|
|
945
947
|
...(openRouterProviderPin
|
|
946
948
|
? {
|
|
947
949
|
order: [openRouterProviderPin],
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
fauxAssistantMessage,
|
|
2
|
+
lazyStream,
|
|
4
3
|
type Api,
|
|
5
4
|
type AssistantMessage,
|
|
6
5
|
type AssistantMessageEventStream,
|
|
@@ -13,15 +12,11 @@ import { overlayProviderStreams } from "../provider-streams";
|
|
|
13
12
|
|
|
14
13
|
export type OpenRouterProtocol = Extract<RuntimeModelProtocol, `openrouter-${string}`>;
|
|
15
14
|
|
|
16
|
-
/**
|
|
15
|
+
/** Native output required by OpenRouter's stateless continuation. */
|
|
17
16
|
export interface ModelProtocolResponse {
|
|
18
17
|
readonly output: readonly Readonly<Record<string, unknown>>[];
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
interface ProtocolEvent {
|
|
22
|
-
readonly data: unknown;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
20
|
declare module "@earendil-works/pi-ai" {
|
|
26
21
|
interface AssistantMessage {
|
|
27
22
|
protocolResponse?: ModelProtocolResponse;
|
|
@@ -34,113 +29,46 @@ function record(value: unknown): Record<string, unknown> | undefined {
|
|
|
34
29
|
: undefined;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
function parseProtocolEvents(body: string): ProtocolEvent[] {
|
|
38
|
-
return body.split(/\r?\n\r?\n/).flatMap((frame) => {
|
|
39
|
-
const lines = frame.split(/\r?\n/);
|
|
40
|
-
const dataLines = lines.filter((line) => line.startsWith("data:"))
|
|
41
|
-
.map((line) => line.slice(5).replace(/^ /, ""));
|
|
42
|
-
if (dataLines.length === 0) return [];
|
|
43
|
-
const dataText = dataLines.join("\n");
|
|
44
|
-
let data: unknown = dataText;
|
|
45
|
-
try {
|
|
46
|
-
data = JSON.parse(dataText);
|
|
47
|
-
} catch {
|
|
48
|
-
data = dataText;
|
|
49
|
-
}
|
|
50
|
-
return [{ data }];
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function compactResponsesBody(body: string): ModelProtocolResponse | undefined {
|
|
55
|
-
const completedItems = new Map<number, Record<string, unknown>>();
|
|
56
|
-
let terminalItems: Record<string, unknown>[] | undefined;
|
|
57
|
-
let terminal = false;
|
|
58
|
-
for (const frame of parseProtocolEvents(body)) {
|
|
59
|
-
const event = record(frame.data);
|
|
60
|
-
if (
|
|
61
|
-
event?.type === "response.output_item.done" &&
|
|
62
|
-
typeof event.output_index === "number"
|
|
63
|
-
) {
|
|
64
|
-
const item = record(event.item);
|
|
65
|
-
if (item) completedItems.set(event.output_index, item);
|
|
66
|
-
}
|
|
67
|
-
if (event?.type === "response.completed" || event?.type === "response.incomplete") {
|
|
68
|
-
terminal = true;
|
|
69
|
-
const response = record(event.response);
|
|
70
|
-
if (Array.isArray(response?.output)) {
|
|
71
|
-
terminalItems = response.output.flatMap((value) => {
|
|
72
|
-
const output = record(value);
|
|
73
|
-
return output ? [output] : [];
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (!terminal) return undefined;
|
|
79
|
-
const output = terminalItems?.length
|
|
80
|
-
? terminalItems.map((item, index) => ({ ...completedItems.get(index), ...item }))
|
|
81
|
-
: [...completedItems.entries()]
|
|
82
|
-
.sort(([left], [right]) => left - right)
|
|
83
|
-
.map(([, item]) => item);
|
|
84
|
-
return output.length > 0 ? { output } : undefined;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
32
|
function streamWithProtocolResponse<T extends StreamOptions>(
|
|
88
33
|
model: Model<Api>,
|
|
89
34
|
options: T | undefined,
|
|
90
35
|
start: (options: T) => AssistantMessageEventStream,
|
|
91
36
|
): AssistantMessageEventStream {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
37
|
+
return lazyStream(model, async () => {
|
|
38
|
+
let nativeResponse: ModelProtocolResponse | undefined;
|
|
39
|
+
const source = start({
|
|
40
|
+
...options,
|
|
41
|
+
onProviderStreamEvent: async (data: unknown, activeModel: Model<Api>) => {
|
|
42
|
+
const event = record(data);
|
|
43
|
+
if (event?.type === "response.created") nativeResponse = undefined;
|
|
44
|
+
if (event?.type === "response.completed" || event?.type === "response.incomplete") {
|
|
45
|
+
const output = record(event.response)?.output;
|
|
46
|
+
if (Array.isArray(output)) {
|
|
47
|
+
const items = output.flatMap((value) => {
|
|
48
|
+
const item = record(value);
|
|
49
|
+
return item ? [item] : [];
|
|
50
|
+
});
|
|
51
|
+
nativeResponse = items.length > 0 ? { output: items } : undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
await options?.onProviderStreamEvent?.(data, activeModel);
|
|
55
|
+
},
|
|
56
|
+
} as T);
|
|
57
|
+
return (async function* () {
|
|
58
|
+
let terminal = false;
|
|
59
|
+
for await (const event of source) {
|
|
60
|
+
if (event.type === "done" || event.type === "error") {
|
|
61
|
+
terminal = true;
|
|
62
|
+
if (nativeResponse) {
|
|
63
|
+
const message = event.type === "done" ? event.message : event.error;
|
|
64
|
+
message.protocolResponse = nativeResponse;
|
|
65
|
+
}
|
|
116
66
|
}
|
|
67
|
+
yield event;
|
|
117
68
|
}
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
if (!terminal) throw new Error("Provider stream ended without a terminal event");
|
|
121
|
-
output.end();
|
|
122
|
-
})().catch(async (error: unknown) => {
|
|
123
|
-
if (terminal) {
|
|
124
|
-
output.end();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const reason = options?.signal?.aborted ? "aborted" : "error";
|
|
128
|
-
const failure = fauxAssistantMessage("", {
|
|
129
|
-
stopReason: reason,
|
|
130
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
131
|
-
});
|
|
132
|
-
if (responseOutput) {
|
|
133
|
-
const compact = await responseOutput;
|
|
134
|
-
if (compact) failure.protocolResponse = compact;
|
|
135
|
-
}
|
|
136
|
-
output.push({
|
|
137
|
-
type: "error",
|
|
138
|
-
reason,
|
|
139
|
-
error: { ...failure, api: model.api, provider: model.provider, model: model.id },
|
|
140
|
-
});
|
|
141
|
-
output.end();
|
|
69
|
+
if (!terminal) throw new Error("Provider stream ended without a terminal event");
|
|
70
|
+
})();
|
|
142
71
|
});
|
|
143
|
-
return output;
|
|
144
72
|
}
|
|
145
73
|
|
|
146
74
|
export function withModelProtocolResponse(
|
|
@@ -150,12 +78,8 @@ export function withModelProtocolResponse(
|
|
|
150
78
|
if (protocol !== "openrouter-responses") return api;
|
|
151
79
|
return overlayProviderStreams(api, {
|
|
152
80
|
stream: (model, context, options) =>
|
|
153
|
-
streamWithProtocolResponse(model, options, (requestOptions) =>
|
|
154
|
-
api.stream(model, context, requestOptions)
|
|
155
|
-
),
|
|
81
|
+
streamWithProtocolResponse(model, options, (requestOptions) => api.stream(model, context, requestOptions)),
|
|
156
82
|
streamSimple: (model, context, options) =>
|
|
157
|
-
streamWithProtocolResponse(model, options, (requestOptions) =>
|
|
158
|
-
api.streamSimple(model, context, requestOptions)
|
|
159
|
-
),
|
|
83
|
+
streamWithProtocolResponse(model, options, (requestOptions) => api.streamSimple(model, context, requestOptions)),
|
|
160
84
|
});
|
|
161
85
|
}
|