@inflexa-ai/harness 0.42.0 → 0.42.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.
|
@@ -24,7 +24,8 @@ export type ChatPartCheck = {
|
|
|
24
24
|
export declare function toChatFrame(event: EmitEvent | ChatStreamEvent | ChatDataPart, fallbackSource: EmitEvent["source"]): ChatFrame | null;
|
|
25
25
|
/**
|
|
26
26
|
* Check a part frame against the schema of its type, one time, where it arrives. The schema drops a field that it
|
|
27
|
-
* does not know. A type that the registry does not know passes unchanged, because a newer emitter can send it.
|
|
27
|
+
* does not know. A type that the registry does not know passes unchanged, because a newer emitter can send it. The
|
|
28
|
+
* source is checked for each type, because {@link isRootFrame} reads its call path.
|
|
28
29
|
*/
|
|
29
30
|
export declare function checkChatPart(frame: ChatPartFrame): ChatPartCheck;
|
|
30
31
|
/** A frame of the root agent of a chat turn, whose call path is `[agent.id]`. A longer path is a sub-agent. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The one translation path of a chat stream. Browser-safe: each import from outside `contracts/` is a type import.
|
|
2
2
|
import { isReconciling, PART_REGISTRY } from "./part-registry.js";
|
|
3
|
+
import { EventSourceSchema } from "./schemas/chat-events.js";
|
|
3
4
|
import { ChatPartSchema } from "./schemas/chat-parts.js";
|
|
4
5
|
function toSource(source) {
|
|
5
6
|
return { agentId: source.agentId, callPath: [...source.callPath] };
|
|
@@ -44,12 +45,18 @@ export function toChatFrame(event, fallbackSource) {
|
|
|
44
45
|
}
|
|
45
46
|
/**
|
|
46
47
|
* Check a part frame against the schema of its type, one time, where it arrives. The schema drops a field that it
|
|
47
|
-
* does not know. A type that the registry does not know passes unchanged, because a newer emitter can send it.
|
|
48
|
+
* does not know. A type that the registry does not know passes unchanged, because a newer emitter can send it. The
|
|
49
|
+
* source is checked for each type, because {@link isRootFrame} reads its call path.
|
|
48
50
|
*/
|
|
49
51
|
export function checkChatPart(frame) {
|
|
52
|
+
const { source, ...part } = frame;
|
|
53
|
+
if (source !== undefined) {
|
|
54
|
+
const checkedSource = EventSourceSchema.safeParse(source);
|
|
55
|
+
if (!checkedSource.success)
|
|
56
|
+
return { ok: false, error: checkedSource.error.message };
|
|
57
|
+
}
|
|
50
58
|
if (!Object.hasOwn(PART_REGISTRY, frame.type))
|
|
51
59
|
return { ok: true, frame };
|
|
52
|
-
const { source, ...part } = frame;
|
|
53
60
|
const parsed = ChatPartSchema.safeParse(part);
|
|
54
61
|
if (!parsed.success)
|
|
55
62
|
return { ok: false, error: parsed.error.message };
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
* stream value to the chat data-part contract,
|
|
4
|
-
* fold
|
|
2
|
+
* The rules of the run-event read side that need no database and no durability
|
|
3
|
+
* engine: narrowing a raw durable stream value to the chat data-part contract,
|
|
4
|
+
* and the latest-wins reconciling fold, as a batch for `createRunEventStream`
|
|
5
|
+
* and live for an embedder that reads the run streams itself.
|
|
5
6
|
*
|
|
6
|
-
* They live apart from the reader
|
|
7
|
-
*
|
|
8
|
-
* durability engine, no clock — and keeping them here is what lets that be
|
|
9
|
-
* proven directly instead of inferred from a driven subscription.
|
|
7
|
+
* They live apart from the reader, thus a test drives them directly instead of
|
|
8
|
+
* through a subscription.
|
|
10
9
|
*/
|
|
11
10
|
import type { ChatPart } from "../contracts/chat-parts.js";
|
|
12
11
|
/**
|
|
@@ -42,3 +41,12 @@ export declare function parseRunEventPart(value: unknown): ChatPart | null;
|
|
|
42
41
|
* the parts that are delivered is the order they were written.
|
|
43
42
|
*/
|
|
44
43
|
export declare function foldRunEventParts(parts: readonly ChatPart[]): ChatPart[];
|
|
44
|
+
/**
|
|
45
|
+
* The live form of {@link foldRunEventParts}, for a stream that stays open. An
|
|
46
|
+
* entry with a reconcile key waits `flushMs`, and a later entry with the same
|
|
47
|
+
* key replaces it. Thus a catch-up burst delivers each key once, and a live
|
|
48
|
+
* update lands one window later. Every other entry, also one of a type that the
|
|
49
|
+
* registry does not know, goes to `emit` at once, thus it can overtake a held
|
|
50
|
+
* entry. The held entries go to `emit` when the stream ends.
|
|
51
|
+
*/
|
|
52
|
+
export declare function pipeFoldedRunEvents<T>(entries: AsyncIterable<T>, emit: (entry: T) => Promise<void>, flushMs: number): Promise<void>;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
* stream value to the chat data-part contract,
|
|
4
|
-
* fold
|
|
2
|
+
* The rules of the run-event read side that need no database and no durability
|
|
3
|
+
* engine: narrowing a raw durable stream value to the chat data-part contract,
|
|
4
|
+
* and the latest-wins reconciling fold, as a batch for `createRunEventStream`
|
|
5
|
+
* and live for an embedder that reads the run streams itself.
|
|
5
6
|
*
|
|
6
|
-
* They live apart from the reader
|
|
7
|
-
*
|
|
8
|
-
* durability engine, no clock — and keeping them here is what lets that be
|
|
9
|
-
* proven directly instead of inferred from a driven subscription.
|
|
7
|
+
* They live apart from the reader, thus a test drives them directly instead of
|
|
8
|
+
* through a subscription.
|
|
10
9
|
*/
|
|
11
10
|
import { reconcileKey } from "../contracts/chat-frame.js";
|
|
12
11
|
import { PART_REGISTRY } from "../contracts/part-registry.js";
|
|
@@ -67,3 +66,59 @@ export function foldRunEventParts(parts) {
|
|
|
67
66
|
}
|
|
68
67
|
return folded;
|
|
69
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* The live form of {@link foldRunEventParts}, for a stream that stays open. An
|
|
71
|
+
* entry with a reconcile key waits `flushMs`, and a later entry with the same
|
|
72
|
+
* key replaces it. Thus a catch-up burst delivers each key once, and a live
|
|
73
|
+
* update lands one window later. Every other entry, also one of a type that the
|
|
74
|
+
* registry does not know, goes to `emit` at once, thus it can overtake a held
|
|
75
|
+
* entry. The held entries go to `emit` when the stream ends.
|
|
76
|
+
*/
|
|
77
|
+
export async function pipeFoldedRunEvents(entries, emit, flushMs) {
|
|
78
|
+
const latestByKey = new Map();
|
|
79
|
+
const pendingKeys = new Set();
|
|
80
|
+
let flushTimer = null;
|
|
81
|
+
let flushChain = Promise.resolve();
|
|
82
|
+
const flushPending = async () => {
|
|
83
|
+
flushTimer = null;
|
|
84
|
+
const keys = [...pendingKeys];
|
|
85
|
+
pendingKeys.clear();
|
|
86
|
+
for (const key of keys) {
|
|
87
|
+
const entry = latestByKey.get(key);
|
|
88
|
+
if (entry !== undefined)
|
|
89
|
+
await emit(entry);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const scheduleFlush = () => {
|
|
93
|
+
if (flushTimer !== null)
|
|
94
|
+
return;
|
|
95
|
+
flushTimer = setTimeout(() => {
|
|
96
|
+
// A timed flush has no caller to report to. A lasting emit failure
|
|
97
|
+
// still reaches the caller, through the next direct emit or the
|
|
98
|
+
// final flush.
|
|
99
|
+
flushChain = flushChain.then(flushPending).catch(() => undefined);
|
|
100
|
+
}, flushMs);
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
for await (const entry of entries) {
|
|
104
|
+
const part = parseRunEventPart(entry);
|
|
105
|
+
const key = part === null ? undefined : reconcileKey(part);
|
|
106
|
+
if (key === undefined) {
|
|
107
|
+
await emit(entry);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
latestByKey.set(key, entry);
|
|
111
|
+
pendingKeys.add(key);
|
|
112
|
+
scheduleFlush();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
if (flushTimer !== null) {
|
|
117
|
+
clearTimeout(flushTimer);
|
|
118
|
+
flushTimer = null;
|
|
119
|
+
}
|
|
120
|
+
await flushChain;
|
|
121
|
+
if (pendingKeys.size > 0)
|
|
122
|
+
await flushPending();
|
|
123
|
+
}
|
|
124
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -170,6 +170,7 @@ export { registerSandboxStep } from "./workflows/sandbox-step.js";
|
|
|
170
170
|
export type { SandboxStepDeps, SandboxStepInput, SandboxStepResult, SandboxAgentBuildContext } from "./workflows/sandbox-step.js";
|
|
171
171
|
export { createRunEventStream } from "./execution/run-event-stream.js";
|
|
172
172
|
export type { RunEventStream, RunEventStreamDeps, RunEventSubscribeOptions, RunEventPartHandler } from "./execution/run-event-stream.js";
|
|
173
|
+
export { pipeFoldedRunEvents } from "./execution/run-event-parts.js";
|
|
173
174
|
export { createSandboxAgents, SANDBOX_AGENT_META } from "./agents/sandbox/index.js";
|
|
174
175
|
export type { SandboxAgentDeps, SandboxStepCoords, AgentMeta } from "./agents/sandbox/index.js";
|
|
175
176
|
export { AnalysisPlanSchema } from "./schemas/workflow-state.js";
|
package/dist/index.js
CHANGED
|
@@ -271,6 +271,7 @@ export { registerSandboxStep } from "./workflows/sandbox-step.js";
|
|
|
271
271
|
// Nothing in the signature belongs to the durability engine, so an embedder
|
|
272
272
|
// consumes run events without depending on it.
|
|
273
273
|
export { createRunEventStream } from "./execution/run-event-stream.js";
|
|
274
|
+
export { pipeFoldedRunEvents } from "./execution/run-event-parts.js";
|
|
274
275
|
// Sandbox-agent catalog. `buildAgent` maps a `SandboxAgentBuildContext` onto
|
|
275
276
|
// `SandboxAgentDeps`, then selects the per-step agent by id from this record;
|
|
276
277
|
// `SANDBOX_AGENT_META` is the planner-facing meta the same ids key.
|
package/package.json
CHANGED