Skip to content

Commit d1cf7dd

Browse files
authored
fix(core): send unchanged hooked requests without copying the body (#59097)
1 parent 6f861e2 commit d1cf7dd

2 files changed

Lines changed: 62 additions & 11 deletions

File tree

‎packages/core/src/session/model-request.ts‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -321,17 +321,20 @@ export const layer = Layer.effect(
321321
const http: StreamOptions["http"] = hasHttpHooks
322322
? (req, handler) =>
323323
Effect.gen(function* () {
324-
const before = yield* hooks.trigger("session", "http.request", {
325-
...scope,
326-
request: yield* HttpClientRequest.toWeb(req),
327-
})
328-
let sent = HttpClientRequest.fromWeb(before.request)
329-
if (before.request.body)
330-
sent = HttpClientRequest.bodyUint8Array(
331-
sent,
332-
new Uint8Array(yield* Effect.promise(() => before.request.clone().arrayBuffer())),
333-
before.request.headers.get("content-type") ?? undefined,
334-
)
324+
const request = yield* HttpClientRequest.toWeb(req)
325+
const before = yield* hooks.trigger("session", "http.request", { ...scope, request })
326+
// A hook that kept the request object cannot have replaced its body, so the original body is sent as is.
327+
// Reading it back would hold a second full copy of the request, screenshots included, for the whole call.
328+
const sent =
329+
before.request === request
330+
? HttpClientRequest.setBody(HttpClientRequest.fromWeb(request), req.body)
331+
: before.request.body
332+
? HttpClientRequest.bodyUint8Array(
333+
HttpClientRequest.fromWeb(before.request),
334+
new Uint8Array(yield* Effect.promise(() => before.request.clone().arrayBuffer())),
335+
before.request.headers.get("content-type") ?? undefined,
336+
)
337+
: HttpClientRequest.fromWeb(before.request)
335338
const res = yield* handler(sent)
336339
const after = yield* hooks.trigger("session", "http.response", {
337340
...scope,

‎packages/core/test/session-model-request-hooks.test.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,54 @@ describe("SessionModelRequest HTTP hooks", () => {
8181
}).pipe(Effect.provideService(SessionModelTransport.Service, transport)),
8282
)
8383

84+
it.effect("sends a kept request's original body and a replaced request's new body", () =>
85+
Effect.gen(function* () {
86+
const hooks = yield* PluginHooks.Service
87+
let replace = false
88+
yield* hooks.register("session", "http.request", (event) =>
89+
Effect.sync(() => {
90+
if (replace) {
91+
event.request = new Request(event.request.url, { method: "POST", body: '{"replaced":true}' })
92+
return
93+
}
94+
event.request.headers.set("x-plugin", "kept")
95+
}),
96+
)
97+
const requests = yield* SessionModelRequest.Service.pipe(Effect.provide(SessionModelRequest.layer))
98+
const prepared = yield* requests.primary({
99+
session,
100+
agent: Agent.ID.make("build"),
101+
model,
102+
system: [],
103+
messages: [],
104+
})
105+
const http = prepared.options.http
106+
if (!http) throw new Error("Expected HTTP middleware")
107+
const original = HttpClientRequest.post("https://example.test/v1/chat/completions").pipe(
108+
HttpClientRequest.bodyText('{"messages":[]}', "application/json"),
109+
)
110+
const send = () => {
111+
const sent: HttpClientRequest.HttpClientRequest[] = []
112+
return http(original, (request) =>
113+
Effect.sync(() => {
114+
sent.push(request)
115+
return HttpClientResponse.fromWeb(request, new Response("{}", { status: 200 }))
116+
}),
117+
).pipe(Effect.map(() => sent[0]!))
118+
}
119+
120+
const kept = yield* send()
121+
expect(kept.body).toBe(original.body)
122+
expect(kept.headers["x-plugin"]).toBe("kept")
123+
124+
replace = true
125+
const replaced = yield* send()
126+
expect(replaced.body._tag).toBe("Uint8Array")
127+
if (replaced.body._tag !== "Uint8Array") return
128+
expect(new TextDecoder().decode(replaced.body.body)).toBe('{"replaced":true}')
129+
}).pipe(Effect.provideService(SessionModelTransport.Service, transport)),
130+
)
131+
84132
it.effect("keeps hook edits to a tool schema out of later requests", () =>
85133
Effect.gen(function* () {
86134
const hooks = yield* PluginHooks.Service

0 commit comments

Comments
 (0)