@standardagents/builder 0.40.5 → 0.40.6
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/dist/built-in-routes.js +46 -76
- package/dist/built-in-routes.js.map +1 -1
- package/dist/{index-0NI3m5DR.d.ts → index-DYhbT5Eq.d.ts} +44 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +256 -154
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +17 -17
- package/dist/runtime.js +256 -154
- package/dist/runtime.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/package.json +5 -5
package/dist/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { j as AuthContext, e as ThreadEnv, w as Agent, y as ThreadMetadata, z as ToolResult, M as Message, o as ClientToolRequest, p as ClientToolResponse, q as ClientStreamRequest, t as ClientStreamResponse, H as FileRecord,
|
|
2
|
-
export { A as AuthUser, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, u as ClientDuplex, v as ClientForwarding, B as Controller, a as ControllerContext, D as FlowResult, P as ImageContentPart, I as ImageMetadata, L as LLMResponse, Q as MultimodalContent, R as RequestContext, S as StorageBackend, E as TelemetryEvent, O as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as authenticate, k as callClient, l as callClientStream, c as createThreadEndpointHandler, n as decodeStreamChunk, d as defineController, m as encodeStreamChunk, i as endpointCallerFromAuth, h as requireAdmin, r as requireAuth, s as supportsClientForwarding } from './index-
|
|
1
|
+
import { j as AuthContext, e as ThreadEnv, w as Agent, y as ThreadMetadata, z as ToolResult, M as Message, o as ClientToolRequest, p as ClientToolResponse, V as ProxyStreamReceiver, W as ProxyStreamFrame, q as ClientStreamRequest, X as ProxyStreamTarget, t as ClientStreamResponse, Y as ProxyStreamOpenResult, H as FileRecord, Z as SkillStorageScope, F as FlowState, N as FileStats, K as GrepResult, J as AttachmentRef, U as MessageContent, G as Env, x as ThreadInstance } from './index-DYhbT5Eq.js';
|
|
2
|
+
export { A as AuthUser, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, u as ClientDuplex, v as ClientForwarding, B as Controller, a as ControllerContext, D as FlowResult, P as ImageContentPart, I as ImageMetadata, L as LLMResponse, Q as MultimodalContent, R as RequestContext, S as StorageBackend, E as TelemetryEvent, O as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as authenticate, k as callClient, l as callClientStream, c as createThreadEndpointHandler, n as decodeStreamChunk, d as defineController, m as encodeStreamChunk, i as endpointCallerFromAuth, h as requireAdmin, r as requireAuth, s as supportsClientForwarding } from './index-DYhbT5Eq.js';
|
|
3
3
|
import { CodeExecutionOptions, CodeExecutionResult, SubagentRegistryEntry, InjectMessageInput, QueueMessageInput, CredentialGrant, SkillMetadata, SkillFileInput, ModelDefinition as ModelDefinition$1, ToolArgs, PromptTextPart, PromptEnvPart, VariableDefinition, SubpromptConfig as SubpromptConfig$1, PromptToolConfig as PromptToolConfig$1, SubagentToolConfig as SubagentToolConfig$1, ReasoningConfig, SideConfig as SideConfig$1, ParentHookRequirement, LLMProviderInterface, ContentPart, TextPart, ImagePart, FilePart, ThreadState, NamespaceContext, DefinitionLoader } from '@standardagents/spec';
|
|
4
4
|
export { AgentType, DefinitionLoader, GlobalNamespaceContext, HookExecutionContext, HookExecutionSource, HookSignatures, ImageContent, ModelCapabilities, ModelProvider, NamespaceContext, PackageSignature, PackedExports, PackedMeta, PackedMetadata, PackedNamespaceContext, ParentHookRequirement, ParentHookStage, PromptInput, PromptTextPart, ProviderAssistantMessage, ProviderError, ProviderErrorCode, ProviderFactory, ProviderFactoryConfig, ProviderFinishReason, ProviderGeneratedImage, ProviderMessage, ProviderMessageContent, ModelCapabilities as ProviderModelCapabilities, ProviderReasoningDetail, ProviderRequest, ProviderResponse, ProviderStreamChunk, ProviderSystemMessage, ProviderTool, ProviderToolCallPart, ProviderToolMessage, ProviderToolResultContent, ProviderUsage, ProviderUserMessage, ReasoningConfig, StructuredPrompt, TextContent, ThreadHookName, Tool, ToolArgs, ToolArgsNode, ToolArgsRawShape, ToolContent, belongsToPackage, defineAgent, defineHook, defineModel, definePrompt, defineProvider, defineTool, isPacked, isVisibleInNamespace, mapReasoningLevel, providerConst, providerEnv, providerValue } from '@standardagents/spec';
|
|
5
5
|
import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
|
|
@@ -234,6 +234,8 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
|
|
|
234
234
|
private executionOwnerCache;
|
|
235
235
|
private pendingClientCalls;
|
|
236
236
|
private clientStreams;
|
|
237
|
+
private proxyStreamReceivers;
|
|
238
|
+
private eventProxyStreams;
|
|
237
239
|
private alarmQueue;
|
|
238
240
|
private runtimeSafety;
|
|
239
241
|
private alarmQueueRearmed;
|
|
@@ -1256,25 +1258,23 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
|
|
|
1256
1258
|
* match the CURRENT owner.
|
|
1257
1259
|
*/
|
|
1258
1260
|
private awaitOwnerBridgeSocket;
|
|
1261
|
+
private parseProxyStreamAttachments;
|
|
1262
|
+
/** Keep live proxy routes on the hibernating bridge socket for restart cleanup. */
|
|
1263
|
+
private updateBridgeProxyAttachment;
|
|
1264
|
+
private deliverProxyStreamFrame;
|
|
1265
|
+
/** Abort live proxy routes restored without their in-memory stream state. */
|
|
1266
|
+
private recoverInterruptedProxyStreams;
|
|
1267
|
+
/** Register the child side before any ancestor opens an owner stream. */
|
|
1268
|
+
createProxyStreamReceiver(onCancel: (error?: string) => void): ProxyStreamReceiver;
|
|
1269
|
+
proxyStreamReceiverExists(receiverId: string): Promise<boolean>;
|
|
1270
|
+
proxyStreamReceive(receiverId: string, frame: ProxyStreamFrame): Promise<boolean>;
|
|
1259
1271
|
/** Open a bidirectional byte stream to the connected owner client. */
|
|
1260
|
-
openClientStream(request: ClientStreamRequest): Promise<ClientStreamResponse>;
|
|
1261
|
-
|
|
1262
|
-
proxyStreamOpen(request: ClientStreamRequest): Promise<{
|
|
1263
|
-
ok: boolean;
|
|
1264
|
-
id?: string;
|
|
1265
|
-
error?: string;
|
|
1266
|
-
noClient?: boolean;
|
|
1267
|
-
parkedPull?: boolean;
|
|
1268
|
-
code?: 'unsupported' | 'unavailable' | 'refused' | 'timeout';
|
|
1269
|
-
}>;
|
|
1272
|
+
openClientStream(request: ClientStreamRequest, proxyTarget?: ProxyStreamTarget | null): Promise<ClientStreamResponse>;
|
|
1273
|
+
proxyStreamOpen(request: ClientStreamRequest, target: ProxyStreamTarget): Promise<ProxyStreamOpenResult>;
|
|
1270
1274
|
proxyStreamWrite(id: string, dataB64: string): Promise<void>;
|
|
1271
|
-
proxyStreamPull(id: string, maxWaitMs?: number): Promise<{
|
|
1272
|
-
chunks: string[];
|
|
1273
|
-
done: boolean;
|
|
1274
|
-
error?: string;
|
|
1275
|
-
}>;
|
|
1276
1275
|
proxyStreamEnd(id: string): Promise<void>;
|
|
1277
1276
|
proxyStreamAbort(id: string): Promise<void>;
|
|
1277
|
+
private forgetEventProxyStream;
|
|
1278
1278
|
/** Settle and remove a client stream exactly once. */
|
|
1279
1279
|
private settleClientStream;
|
|
1280
1280
|
/** Abort a stream from the Worker side, telling the client to tear down. */
|
package/dist/runtime.js
CHANGED
|
@@ -14715,27 +14715,51 @@ var init_ThreadStateImpl = __esm({
|
|
|
14715
14715
|
const withWake = (threadId) => wakeUserId ? { ...request, wake: { threadId, userId: wakeUserId } } : request;
|
|
14716
14716
|
const local = await instance.openClientStream(withWake(this._metadata.id));
|
|
14717
14717
|
if (local.ok || !this.isNoClient(local)) return local;
|
|
14718
|
-
|
|
14719
|
-
|
|
14718
|
+
if (typeof instance.createProxyStreamReceiver !== "function" || typeof instance.proxyStreamReceive !== "function") {
|
|
14719
|
+
return local;
|
|
14720
|
+
}
|
|
14721
|
+
let abortRemote = null;
|
|
14722
|
+
const receiver = instance.createProxyStreamReceiver((error) => abortRemote?.(error));
|
|
14723
|
+
const discardReceiver = async (error) => {
|
|
14724
|
+
await instance.proxyStreamReceive(receiver.id, { type: "abort", error });
|
|
14725
|
+
};
|
|
14726
|
+
const namespace = instance.env.AGENT_BUILDER_THREAD;
|
|
14720
14727
|
const seen = /* @__PURE__ */ new Set([this._metadata.id]);
|
|
14721
14728
|
let parentId = await this.resolveParentThreadId(this._metadata.id);
|
|
14722
14729
|
while (parentId && !seen.has(parentId)) {
|
|
14723
14730
|
seen.add(parentId);
|
|
14724
14731
|
try {
|
|
14725
|
-
const stub =
|
|
14732
|
+
const stub = namespace.get(namespace.idFromName(parentId));
|
|
14726
14733
|
if (typeof stub.proxyStreamOpen === "function") {
|
|
14727
|
-
const opened = await stub.proxyStreamOpen(withWake(parentId)
|
|
14734
|
+
const opened = await stub.proxyStreamOpen(withWake(parentId), {
|
|
14735
|
+
threadId: this._metadata.id,
|
|
14736
|
+
receiverId: receiver.id
|
|
14737
|
+
});
|
|
14728
14738
|
if (opened.ok && opened.id) {
|
|
14739
|
+
if (opened.eventDriven !== true) {
|
|
14740
|
+
await stub.proxyStreamAbort(opened.id).catch(() => {
|
|
14741
|
+
});
|
|
14742
|
+
await discardReceiver("The ancestor runtime does not support event-driven streams.");
|
|
14743
|
+
return {
|
|
14744
|
+
ok: false,
|
|
14745
|
+
error: "The ancestor runtime does not support event-driven streams.",
|
|
14746
|
+
data: { code: "unsupported" }
|
|
14747
|
+
};
|
|
14748
|
+
}
|
|
14749
|
+
abortRemote = (error) => {
|
|
14750
|
+
void stub.proxyStreamAbort(opened.id).catch(() => {
|
|
14751
|
+
});
|
|
14752
|
+
if (error) {
|
|
14753
|
+
void instance.proxyStreamReceive(receiver.id, { type: "abort", error });
|
|
14754
|
+
}
|
|
14755
|
+
};
|
|
14729
14756
|
return {
|
|
14730
14757
|
ok: true,
|
|
14731
|
-
duplex: this.
|
|
14732
|
-
stub,
|
|
14733
|
-
opened.id,
|
|
14734
|
-
opened.parkedPull === true
|
|
14735
|
-
)
|
|
14758
|
+
duplex: this.buildEventProxyDuplex(stub, opened.id, receiver, instance)
|
|
14736
14759
|
};
|
|
14737
14760
|
}
|
|
14738
14761
|
if (opened.error && !opened.noClient) {
|
|
14762
|
+
await discardReceiver(opened.error);
|
|
14739
14763
|
return {
|
|
14740
14764
|
ok: false,
|
|
14741
14765
|
error: opened.error,
|
|
@@ -14747,69 +14771,21 @@ var init_ThreadStateImpl = __esm({
|
|
|
14747
14771
|
}
|
|
14748
14772
|
parentId = await this.resolveParentThreadId(parentId);
|
|
14749
14773
|
}
|
|
14774
|
+
await discardReceiver(local.error ?? "No execution-owner bridge is available.");
|
|
14750
14775
|
return local;
|
|
14751
14776
|
}
|
|
14752
14777
|
/**
|
|
14753
14778
|
* Build a client duplex that bridges to a stream opened on an ancestor DO
|
|
14754
|
-
* (the bridge holder)
|
|
14755
|
-
*
|
|
14756
|
-
* error, or abort, so an idle open stream performs zero repeated pulls.
|
|
14757
|
-
* Older ancestors use the bounded long-pull compatibility path. `writable`
|
|
14758
|
-
* forwards each chunk with `proxyStreamWrite`; teardown calls
|
|
14759
|
-
* `proxyStreamAbort`.
|
|
14779
|
+
* (the bridge holder). The readable is the receiver registered on this DO;
|
|
14780
|
+
* writes and terminal actions are the only child-to-ancestor RPCs.
|
|
14760
14781
|
*/
|
|
14761
|
-
|
|
14762
|
-
buildProxyDuplex(stub, id, supportsParkedPull = false) {
|
|
14763
|
-
let settleDone;
|
|
14764
|
-
const done = new Promise((r) => settleDone = r);
|
|
14782
|
+
buildEventProxyDuplex(stub, id, receiver, instance) {
|
|
14765
14783
|
let closed = false;
|
|
14766
|
-
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
settleDone(result);
|
|
14784
|
+
const settleLocal = (error) => {
|
|
14785
|
+
if (typeof instance.proxyStreamReceive !== "function") return;
|
|
14786
|
+
const frame = error ? { type: "abort", error } : { type: "end" };
|
|
14787
|
+
void instance.proxyStreamReceive(receiver.id, frame);
|
|
14771
14788
|
};
|
|
14772
|
-
const readable = new ReadableStream({
|
|
14773
|
-
async pull(controller) {
|
|
14774
|
-
while (!closed) {
|
|
14775
|
-
const pullStartedAt = Date.now();
|
|
14776
|
-
let res;
|
|
14777
|
-
try {
|
|
14778
|
-
res = await stub.proxyStreamPull(
|
|
14779
|
-
id,
|
|
14780
|
-
supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
|
|
14781
|
-
);
|
|
14782
|
-
} catch (error) {
|
|
14783
|
-
controller.error(error instanceof Error ? error : new Error(String(error)));
|
|
14784
|
-
settle({ ok: false, error: "proxy stream pull failed" });
|
|
14785
|
-
return;
|
|
14786
|
-
}
|
|
14787
|
-
for (const chunk of res.chunks) {
|
|
14788
|
-
controller.enqueue(decodeStreamChunk(chunk));
|
|
14789
|
-
}
|
|
14790
|
-
if (res.done) {
|
|
14791
|
-
controller.close();
|
|
14792
|
-
settle(res.error ? { ok: false, error: res.error } : { ok: true });
|
|
14793
|
-
return;
|
|
14794
|
-
}
|
|
14795
|
-
if (res.chunks.length > 0) return;
|
|
14796
|
-
if (supportsParkedPull) {
|
|
14797
|
-
controller.error(new Error("parked proxy pull returned without data"));
|
|
14798
|
-
settle({ ok: false, error: "parked proxy pull returned without data" });
|
|
14799
|
-
return;
|
|
14800
|
-
}
|
|
14801
|
-
if (Date.now() - pullStartedAt < 250) {
|
|
14802
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
14803
|
-
}
|
|
14804
|
-
}
|
|
14805
|
-
},
|
|
14806
|
-
cancel() {
|
|
14807
|
-
closed = true;
|
|
14808
|
-
void stub.proxyStreamAbort(id).catch(() => {
|
|
14809
|
-
});
|
|
14810
|
-
settle({ ok: false, error: "proxy stream reader canceled" });
|
|
14811
|
-
}
|
|
14812
|
-
});
|
|
14813
14789
|
const writable = new WritableStream({
|
|
14814
14790
|
async write(chunk) {
|
|
14815
14791
|
await stub.proxyStreamWrite(id, encodeStreamChunk(chunk));
|
|
@@ -14822,20 +14798,20 @@ var init_ThreadStateImpl = __esm({
|
|
|
14822
14798
|
closed = true;
|
|
14823
14799
|
void stub.proxyStreamAbort(id).catch(() => {
|
|
14824
14800
|
});
|
|
14825
|
-
|
|
14801
|
+
settleLocal("The proxy stream writer was aborted.");
|
|
14826
14802
|
}
|
|
14827
14803
|
});
|
|
14828
14804
|
return {
|
|
14829
|
-
readable,
|
|
14805
|
+
readable: receiver.readable,
|
|
14830
14806
|
writable,
|
|
14831
14807
|
close: (error) => {
|
|
14832
14808
|
if (closed) return;
|
|
14833
14809
|
closed = true;
|
|
14834
14810
|
void stub.proxyStreamAbort(id).catch(() => {
|
|
14835
14811
|
});
|
|
14836
|
-
|
|
14812
|
+
settleLocal(error);
|
|
14837
14813
|
},
|
|
14838
|
-
done
|
|
14814
|
+
done: receiver.done
|
|
14839
14815
|
};
|
|
14840
14816
|
}
|
|
14841
14817
|
isNoClient(res) {
|
|
@@ -22266,6 +22242,8 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
22266
22242
|
// opened on and dies with it. Streams are in-memory only — they exist solely
|
|
22267
22243
|
// while the client is attached and never park durably.
|
|
22268
22244
|
clientStreams = /* @__PURE__ */ new Map();
|
|
22245
|
+
proxyStreamReceivers = /* @__PURE__ */ new Map();
|
|
22246
|
+
eventProxyStreams = /* @__PURE__ */ new Map();
|
|
22269
22247
|
alarmQueue;
|
|
22270
22248
|
runtimeSafety;
|
|
22271
22249
|
alarmQueueRearmed = false;
|
|
@@ -22377,6 +22355,10 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
22377
22355
|
clientName: typeof attachment.clientName === "string" ? attachment.clientName : null,
|
|
22378
22356
|
clientKind: typeof attachment.clientKind === "string" ? attachment.clientKind : null
|
|
22379
22357
|
});
|
|
22358
|
+
const interrupted = this.parseProxyStreamAttachments(attachment.proxyStreams);
|
|
22359
|
+
if (interrupted.length > 0) {
|
|
22360
|
+
this.recoverInterruptedProxyStreams(ws, interrupted);
|
|
22361
|
+
}
|
|
22380
22362
|
}
|
|
22381
22363
|
}
|
|
22382
22364
|
}
|
|
@@ -26632,8 +26614,138 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26632
26614
|
});
|
|
26633
26615
|
}
|
|
26634
26616
|
}
|
|
26617
|
+
parseProxyStreamAttachments(value) {
|
|
26618
|
+
if (!Array.isArray(value)) return [];
|
|
26619
|
+
const parsed = [];
|
|
26620
|
+
for (const item of value) {
|
|
26621
|
+
if (!item || typeof item !== "object") continue;
|
|
26622
|
+
const candidate = item;
|
|
26623
|
+
if (typeof candidate.id === "string" && candidate.target && typeof candidate.target.threadId === "string" && typeof candidate.target.receiverId === "string") {
|
|
26624
|
+
parsed.push({
|
|
26625
|
+
id: candidate.id,
|
|
26626
|
+
target: {
|
|
26627
|
+
threadId: candidate.target.threadId,
|
|
26628
|
+
receiverId: candidate.target.receiverId
|
|
26629
|
+
}
|
|
26630
|
+
});
|
|
26631
|
+
}
|
|
26632
|
+
}
|
|
26633
|
+
return parsed;
|
|
26634
|
+
}
|
|
26635
|
+
/** Keep live proxy routes on the hibernating bridge socket for restart cleanup. */
|
|
26636
|
+
updateBridgeProxyAttachment(socket) {
|
|
26637
|
+
if (typeof socket.serializeAttachment !== "function") return;
|
|
26638
|
+
let attachment = { type: "bridge" };
|
|
26639
|
+
try {
|
|
26640
|
+
const current = socket.deserializeAttachment();
|
|
26641
|
+
if (current && typeof current === "object") {
|
|
26642
|
+
attachment = { ...current };
|
|
26643
|
+
}
|
|
26644
|
+
} catch {
|
|
26645
|
+
}
|
|
26646
|
+
const proxyStreams = Array.from(this.clientStreams.entries()).filter(([, state]) => state.socket === socket && state.proxyTarget !== null).map(([id, state]) => ({ id, target: state.proxyTarget }));
|
|
26647
|
+
try {
|
|
26648
|
+
socket.serializeAttachment({ ...attachment, proxyStreams });
|
|
26649
|
+
} catch {
|
|
26650
|
+
}
|
|
26651
|
+
}
|
|
26652
|
+
async deliverProxyStreamFrame(target, frame) {
|
|
26653
|
+
try {
|
|
26654
|
+
const namespace = this.env.AGENT_BUILDER_THREAD;
|
|
26655
|
+
const stub = namespace.get(namespace.idFromName(target.threadId));
|
|
26656
|
+
return await stub.proxyStreamReceive(target.receiverId, frame);
|
|
26657
|
+
} catch {
|
|
26658
|
+
return false;
|
|
26659
|
+
}
|
|
26660
|
+
}
|
|
26661
|
+
/** Abort live proxy routes restored without their in-memory stream state. */
|
|
26662
|
+
recoverInterruptedProxyStreams(socket, streams) {
|
|
26663
|
+
const error = "The stream ended because its runtime restarted.";
|
|
26664
|
+
for (const stream of streams) {
|
|
26665
|
+
try {
|
|
26666
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
26667
|
+
socket.send(JSON.stringify({ type: "stream_abort", id: stream.id, error }));
|
|
26668
|
+
}
|
|
26669
|
+
} catch {
|
|
26670
|
+
}
|
|
26671
|
+
this.ctx.waitUntil(
|
|
26672
|
+
this.deliverProxyStreamFrame(stream.target, { type: "abort", error }).then(() => {
|
|
26673
|
+
})
|
|
26674
|
+
);
|
|
26675
|
+
}
|
|
26676
|
+
this.updateBridgeProxyAttachment(socket);
|
|
26677
|
+
}
|
|
26678
|
+
/** Register the child side before any ancestor opens an owner stream. */
|
|
26679
|
+
createProxyStreamReceiver(onCancel) {
|
|
26680
|
+
const id = crypto.randomUUID();
|
|
26681
|
+
let controller = null;
|
|
26682
|
+
let settleDone;
|
|
26683
|
+
const done = new Promise((resolve) => {
|
|
26684
|
+
settleDone = resolve;
|
|
26685
|
+
});
|
|
26686
|
+
const readable = new ReadableStream({
|
|
26687
|
+
start(value) {
|
|
26688
|
+
controller = value;
|
|
26689
|
+
},
|
|
26690
|
+
cancel: (reason) => {
|
|
26691
|
+
const receiver2 = this.proxyStreamReceivers.get(id);
|
|
26692
|
+
if (!receiver2 || receiver2.settled) return;
|
|
26693
|
+
const error = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "The proxy stream reader was canceled.";
|
|
26694
|
+
receiver2.settle({ ok: false, error });
|
|
26695
|
+
receiver2.onCancel(error);
|
|
26696
|
+
}
|
|
26697
|
+
});
|
|
26698
|
+
const receiver = {
|
|
26699
|
+
controller,
|
|
26700
|
+
settle: (result) => {
|
|
26701
|
+
if (receiver.settled) return;
|
|
26702
|
+
receiver.settled = true;
|
|
26703
|
+
this.proxyStreamReceivers.delete(id);
|
|
26704
|
+
settleDone(result);
|
|
26705
|
+
},
|
|
26706
|
+
onCancel,
|
|
26707
|
+
settled: false
|
|
26708
|
+
};
|
|
26709
|
+
this.proxyStreamReceivers.set(id, receiver);
|
|
26710
|
+
return { id, readable, done };
|
|
26711
|
+
}
|
|
26712
|
+
async proxyStreamReceiverExists(receiverId) {
|
|
26713
|
+
return this.proxyStreamReceivers.has(receiverId);
|
|
26714
|
+
}
|
|
26715
|
+
async proxyStreamReceive(receiverId, frame) {
|
|
26716
|
+
const receiver = this.proxyStreamReceivers.get(receiverId);
|
|
26717
|
+
if (!receiver || receiver.settled) return false;
|
|
26718
|
+
if (frame.type === "data") {
|
|
26719
|
+
try {
|
|
26720
|
+
receiver.controller?.enqueue(decodeStreamChunk(frame.data));
|
|
26721
|
+
} catch (error2) {
|
|
26722
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
26723
|
+
try {
|
|
26724
|
+
receiver.controller?.error(error2 instanceof Error ? error2 : new Error(message));
|
|
26725
|
+
} catch {
|
|
26726
|
+
}
|
|
26727
|
+
receiver.settle({ ok: false, error: message });
|
|
26728
|
+
}
|
|
26729
|
+
return true;
|
|
26730
|
+
}
|
|
26731
|
+
if (frame.type === "end") {
|
|
26732
|
+
try {
|
|
26733
|
+
receiver.controller?.close();
|
|
26734
|
+
} catch {
|
|
26735
|
+
}
|
|
26736
|
+
receiver.settle({ ok: true });
|
|
26737
|
+
return true;
|
|
26738
|
+
}
|
|
26739
|
+
const error = frame.error ?? "The proxied client stream was aborted.";
|
|
26740
|
+
try {
|
|
26741
|
+
receiver.controller?.error(new Error(error));
|
|
26742
|
+
} catch {
|
|
26743
|
+
}
|
|
26744
|
+
receiver.settle({ ok: false, error });
|
|
26745
|
+
return true;
|
|
26746
|
+
}
|
|
26635
26747
|
/** Open a bidirectional byte stream to the connected owner client. */
|
|
26636
|
-
async openClientStream(request) {
|
|
26748
|
+
async openClientStream(request, proxyTarget = null) {
|
|
26637
26749
|
const owner = await this.getExecutionOwner();
|
|
26638
26750
|
let socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
|
|
26639
26751
|
if (!socket && owner) {
|
|
@@ -26666,9 +26778,12 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26666
26778
|
opened: null,
|
|
26667
26779
|
openTimer: null,
|
|
26668
26780
|
settleDone,
|
|
26669
|
-
settled: false
|
|
26781
|
+
settled: false,
|
|
26782
|
+
proxyTarget,
|
|
26783
|
+
delivery: Promise.resolve()
|
|
26670
26784
|
};
|
|
26671
26785
|
this.clientStreams.set(id, state);
|
|
26786
|
+
this.updateBridgeProxyAttachment(socket);
|
|
26672
26787
|
this.bridgeLastSeen.set(socket, Date.now());
|
|
26673
26788
|
const openResult = await new Promise((resolve) => {
|
|
26674
26789
|
state.opened = resolve;
|
|
@@ -26704,6 +26819,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26704
26819
|
}
|
|
26705
26820
|
if (!openResult.ok) {
|
|
26706
26821
|
this.clientStreams.delete(id);
|
|
26822
|
+
this.updateBridgeProxyAttachment(socket);
|
|
26707
26823
|
state.settled = true;
|
|
26708
26824
|
settleDone({ ok: false, error: openResult.error });
|
|
26709
26825
|
return {
|
|
@@ -26745,14 +26861,28 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26745
26861
|
};
|
|
26746
26862
|
return { ok: true, duplex };
|
|
26747
26863
|
}
|
|
26748
|
-
// ───
|
|
26749
|
-
// A
|
|
26750
|
-
//
|
|
26751
|
-
//
|
|
26752
|
-
//
|
|
26753
|
-
|
|
26754
|
-
|
|
26755
|
-
|
|
26864
|
+
// ─── Event-driven subagent stream proxy ──────────────────────────────────
|
|
26865
|
+
// A child registers a local receiver before the bridge-holding ancestor
|
|
26866
|
+
// opens the client stream. The ancestor invokes that receiver only for real
|
|
26867
|
+
// data/end/abort events; there is no pull loop or permanently outstanding
|
|
26868
|
+
// cross-DO RPC while the stream is idle.
|
|
26869
|
+
async proxyStreamOpen(request, target) {
|
|
26870
|
+
const namespace = this.env.AGENT_BUILDER_THREAD;
|
|
26871
|
+
let receiverExists = false;
|
|
26872
|
+
try {
|
|
26873
|
+
const destination = namespace.get(namespace.idFromName(target.threadId));
|
|
26874
|
+
receiverExists = await destination.proxyStreamReceiverExists(target.receiverId);
|
|
26875
|
+
} catch {
|
|
26876
|
+
receiverExists = false;
|
|
26877
|
+
}
|
|
26878
|
+
if (!receiverExists) {
|
|
26879
|
+
return {
|
|
26880
|
+
ok: false,
|
|
26881
|
+
error: "The destination stream receiver is unavailable.",
|
|
26882
|
+
code: "unavailable"
|
|
26883
|
+
};
|
|
26884
|
+
}
|
|
26885
|
+
const opened = await this.openClientStream(request, target);
|
|
26756
26886
|
if (!opened.ok) {
|
|
26757
26887
|
const failureData = opened.data;
|
|
26758
26888
|
const noClient = failureData?.noClient === true;
|
|
@@ -26764,82 +26894,23 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26764
26894
|
};
|
|
26765
26895
|
}
|
|
26766
26896
|
const id = crypto.randomUUID();
|
|
26767
|
-
|
|
26768
|
-
|
|
26769
|
-
|
|
26770
|
-
|
|
26771
|
-
writer,
|
|
26772
|
-
reader,
|
|
26773
|
-
buffer: [],
|
|
26774
|
-
done: false,
|
|
26775
|
-
error: void 0,
|
|
26776
|
-
pump: Promise.resolve(),
|
|
26777
|
-
close: (error) => duplex.close(error),
|
|
26778
|
-
waiter: null,
|
|
26779
|
-
notify: () => {
|
|
26780
|
-
const waiter = entry.waiter;
|
|
26781
|
-
entry.waiter = null;
|
|
26782
|
-
waiter?.();
|
|
26783
|
-
}
|
|
26784
|
-
};
|
|
26785
|
-
entry.pump = (async () => {
|
|
26786
|
-
try {
|
|
26787
|
-
while (true) {
|
|
26788
|
-
const { value, done } = await reader.read();
|
|
26789
|
-
if (done) break;
|
|
26790
|
-
if (value) {
|
|
26791
|
-
entry.buffer.push(value);
|
|
26792
|
-
entry.notify();
|
|
26793
|
-
}
|
|
26794
|
-
}
|
|
26795
|
-
} catch (error) {
|
|
26796
|
-
entry.error = error instanceof Error ? error.message : String(error);
|
|
26797
|
-
} finally {
|
|
26798
|
-
entry.done = true;
|
|
26799
|
-
entry.notify();
|
|
26800
|
-
}
|
|
26801
|
-
})();
|
|
26802
|
-
void duplex.done.then((d) => {
|
|
26803
|
-
entry.done = true;
|
|
26804
|
-
if (!d.ok && !entry.error) entry.error = d.error;
|
|
26805
|
-
entry.notify();
|
|
26897
|
+
this.eventProxyStreams.set(id, {
|
|
26898
|
+
writer: opened.duplex.writable.getWriter(),
|
|
26899
|
+
close: (error) => opened.duplex.close(error),
|
|
26900
|
+
target
|
|
26806
26901
|
});
|
|
26807
|
-
|
|
26808
|
-
return { ok: true, id, parkedPull: true };
|
|
26902
|
+
return { ok: true, id, eventDriven: true };
|
|
26809
26903
|
}
|
|
26810
26904
|
async proxyStreamWrite(id, dataB64) {
|
|
26811
|
-
const entry = this.
|
|
26905
|
+
const entry = this.eventProxyStreams.get(id);
|
|
26812
26906
|
if (!entry) return;
|
|
26813
26907
|
try {
|
|
26814
26908
|
await entry.writer.write(decodeStreamChunk(dataB64));
|
|
26815
26909
|
} catch {
|
|
26816
26910
|
}
|
|
26817
26911
|
}
|
|
26818
|
-
async proxyStreamPull(id, maxWaitMs) {
|
|
26819
|
-
const entry = this.proxyStreams.get(id);
|
|
26820
|
-
if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
|
|
26821
|
-
if (entry.buffer.length === 0 && !entry.done) {
|
|
26822
|
-
await new Promise((resolve) => {
|
|
26823
|
-
const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
|
|
26824
|
-
if (entry.waiter === wake) entry.waiter = null;
|
|
26825
|
-
resolve();
|
|
26826
|
-
}, maxWaitMs) : null;
|
|
26827
|
-
const wake = () => {
|
|
26828
|
-
if (deadline) clearTimeout(deadline);
|
|
26829
|
-
resolve();
|
|
26830
|
-
};
|
|
26831
|
-
entry.waiter = wake;
|
|
26832
|
-
});
|
|
26833
|
-
}
|
|
26834
|
-
const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
|
|
26835
|
-
if (entry.done && entry.buffer.length === 0) {
|
|
26836
|
-
this.proxyStreams.delete(id);
|
|
26837
|
-
return { chunks, done: true, error: entry.error };
|
|
26838
|
-
}
|
|
26839
|
-
return { chunks, done: false };
|
|
26840
|
-
}
|
|
26841
26912
|
async proxyStreamEnd(id) {
|
|
26842
|
-
const entry = this.
|
|
26913
|
+
const entry = this.eventProxyStreams.get(id);
|
|
26843
26914
|
if (!entry) return;
|
|
26844
26915
|
try {
|
|
26845
26916
|
await entry.writer.close();
|
|
@@ -26847,30 +26918,49 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26847
26918
|
}
|
|
26848
26919
|
}
|
|
26849
26920
|
async proxyStreamAbort(id) {
|
|
26850
|
-
const entry = this.
|
|
26921
|
+
const entry = this.eventProxyStreams.get(id);
|
|
26851
26922
|
if (!entry) return;
|
|
26852
|
-
this.
|
|
26853
|
-
entry.done = true;
|
|
26854
|
-
if (!entry.error) entry.error = "aborted";
|
|
26855
|
-
entry.notify();
|
|
26923
|
+
this.eventProxyStreams.delete(id);
|
|
26856
26924
|
try {
|
|
26857
26925
|
entry.close("aborted");
|
|
26858
26926
|
} catch {
|
|
26859
26927
|
}
|
|
26860
26928
|
}
|
|
26929
|
+
forgetEventProxyStream(clientStreamId, target) {
|
|
26930
|
+
const matchesTarget = (candidate) => candidate.threadId === target.threadId && candidate.receiverId === target.receiverId;
|
|
26931
|
+
for (const [id, entry] of Array.from(this.eventProxyStreams.entries())) {
|
|
26932
|
+
if (id !== clientStreamId && !matchesTarget(entry.target)) continue;
|
|
26933
|
+
this.eventProxyStreams.delete(id);
|
|
26934
|
+
try {
|
|
26935
|
+
entry.writer.releaseLock();
|
|
26936
|
+
} catch {
|
|
26937
|
+
}
|
|
26938
|
+
}
|
|
26939
|
+
}
|
|
26861
26940
|
/** Settle and remove a client stream exactly once. */
|
|
26862
26941
|
settleClientStream(id, result, openCode) {
|
|
26863
26942
|
const state = this.clientStreams.get(id);
|
|
26864
26943
|
if (!state) return;
|
|
26865
26944
|
this.clientStreams.delete(id);
|
|
26945
|
+
this.updateBridgeProxyAttachment(state.socket);
|
|
26866
26946
|
if (state.openTimer) clearTimeout(state.openTimer);
|
|
26867
26947
|
if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed.", code: openCode });
|
|
26868
26948
|
if (!state.settled) {
|
|
26869
26949
|
state.settled = true;
|
|
26870
|
-
|
|
26871
|
-
|
|
26872
|
-
|
|
26873
|
-
|
|
26950
|
+
if (state.proxyTarget) {
|
|
26951
|
+
const target = state.proxyTarget;
|
|
26952
|
+
const frame = result.ok ? { type: "end" } : { type: "abort", error: result.error ?? "Client stream aborted." };
|
|
26953
|
+
state.delivery = state.delivery.then(async () => {
|
|
26954
|
+
await this.deliverProxyStreamFrame(target, frame);
|
|
26955
|
+
});
|
|
26956
|
+
this.ctx.waitUntil(state.delivery);
|
|
26957
|
+
this.forgetEventProxyStream(id, target);
|
|
26958
|
+
} else {
|
|
26959
|
+
try {
|
|
26960
|
+
if (result.ok) state.controller?.close();
|
|
26961
|
+
else state.controller?.error(new Error(result.error ?? "Client stream aborted."));
|
|
26962
|
+
} catch {
|
|
26963
|
+
}
|
|
26874
26964
|
}
|
|
26875
26965
|
state.settleDone(result);
|
|
26876
26966
|
}
|
|
@@ -26913,6 +27003,18 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26913
27003
|
}
|
|
26914
27004
|
case "stream_data": {
|
|
26915
27005
|
if (typeof parsed.data !== "string") return;
|
|
27006
|
+
if (state.proxyTarget) {
|
|
27007
|
+
const target = state.proxyTarget;
|
|
27008
|
+
const frame = { type: "data", data: parsed.data };
|
|
27009
|
+
state.delivery = state.delivery.then(async () => {
|
|
27010
|
+
const delivered = await this.deliverProxyStreamFrame(target, frame);
|
|
27011
|
+
if (!delivered && this.clientStreams.get(id) === state) {
|
|
27012
|
+
this.abortClientStream(id, "The destination stream receiver is unavailable.");
|
|
27013
|
+
}
|
|
27014
|
+
});
|
|
27015
|
+
this.ctx.waitUntil(state.delivery);
|
|
27016
|
+
return;
|
|
27017
|
+
}
|
|
26916
27018
|
try {
|
|
26917
27019
|
state.controller?.enqueue(decodeStreamChunk(parsed.data));
|
|
26918
27020
|
} catch {
|