| author | |
| committer | |
| log | 0e7abea7c98060db537fe81f31ce5ba7c6d376d0 |
| tree | 63dfd3d797acab2226d57e8e1b253394893c906d |
| parent | a1db449fe4fecafcb473340771163e791b7fb3d6 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>9 files changed, 41 insertions(+), 52 deletions(-)
packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts+6-8| ... | ... | @@ -8,7 +8,6 @@ import { HttpClient } from "effect/http"; |
| 8 | 8 | |
| 9 | 9 | import * as RemoteEnvironmentAuthorization from "../authorization/service.ts"; |
| 10 | 10 | import type { PreparedConnection } from "../connection/model.ts"; |
| 11 | import { environmentEndpointUrl } from "../environment/endpoint.ts"; | |
| 12 | 11 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 13 | 12 | import { |
| 14 | 13 | executeAuthenticatedEnvironmentHttpRequest, |
| ... | ... | @@ -35,20 +34,19 @@ export const fetchEnvironmentBoundedThreadSnapshot = Effect.fn( |
| 35 | 34 | >; |
| 36 | 35 | readonly timeoutMs?: number; |
| 37 | 36 | }) { |
| 37 | const endpoint = { | |
| 38 | params: { threadId: input.threadId }, | |
| 39 | query: { compactTurnItems: "1" }, | |
| 40 | }; | |
| 38 | 41 | return yield* executeAuthenticatedEnvironmentHttpRequest({ |
| 39 | 42 | ...input, |
| 40 | 43 | group: "orchestration", |
| 41 | 44 | method: "GET", |
| 42 | url: (httpBaseUrl) => | |
| 43 | environmentEndpointUrl( | |
| 44 | httpBaseUrl, | |
| 45 | `/api/orchestration/threads/${encodeURIComponent(input.threadId)}/bounded`, | |
| 46 | ), | |
| 45 | url: (urls) => urls.threadBoundedSnapshot(endpoint), | |
| 47 | 46 | timeoutMs: input.timeoutMs ?? DEFAULT_BOUNDED_THREAD_SNAPSHOT_TIMEOUT_MS, |
| 48 | 47 | request: ({ client, headers }) => |
| 49 | 48 | client.threadBoundedSnapshot({ |
| 50 | params: { threadId: input.threadId }, | |
| 51 | query: { compactTurnItems: "1" }, | |
| 49 | ...endpoint, | |
| 52 | 50 | headers: withOrchestrationProtocolHeader(headers), |
| 53 | 51 | }), |
| 54 | 52 | }).pipe( |
packages/client-runtime/src/state/deviceHubAccess.ts+1-1| ... | ... | @@ -46,7 +46,7 @@ export const resolveDeviceHubAccess = Effect.fn("clientRuntime.state.resolveDevi |
| 46 | 46 | remoteAuthorization, |
| 47 | 47 | group: "auth", |
| 48 | 48 | method: "POST", |
| 49 | url: (httpBaseUrl) => environmentEndpointUrl(httpBaseUrl, "/api/auth/websocket-ticket"), | |
| 49 | url: (urls) => urls.webSocketTicket(), | |
| 50 | 50 | timeoutMs: TICKET_TIMEOUT_MS, |
| 51 | 51 | request: ({ client, headers }) => client.webSocketTicket({ headers }), |
| 52 | 52 | }); |
packages/client-runtime/src/state/environmentHttpAuth.test.ts+6-10| ... | ... | @@ -294,12 +294,9 @@ describe("authenticated environment HTTP requests", () => { |
| 294 | 294 | ); |
| 295 | 295 | } |
| 296 | 296 | expect(harness.authorizations).toEqual([{ expectedEnvironmentId: TARGET.environmentId }]); |
| 297 | // The proof signs exactly the URL sent; signers drop the query for `htu`. | |
| 297 | 298 | expect(harness.proofs).toEqual([ |
| 298 | { | |
| 299 | method: loader.method, | |
| 300 | url: `${CURRENT_ORIGIN}${loader.path}`, | |
| 301 | accessToken: "current-token", | |
| 302 | }, | |
| 299 | { method: loader.method, url: call.url, accessToken: "current-token" }, | |
| 303 | 300 | ]); |
| 304 | 301 | if (loader.name === "older thread history") { |
| 305 | 302 | expect(url.searchParams.get("cursor")).toBe("older-page"); |
| ... | ... | @@ -345,10 +342,9 @@ describe("authenticated environment HTTP requests", () => { |
| 345 | 342 | const harness = makeHarness(() => Response.json(loader.response)); |
| 346 | 343 | yield* loader.load(harness.input).pipe(Effect.provide(harness.httpLayer)); |
| 347 | 344 | |
| 348 | const sent = new URL(harness.calls[0]!.url); | |
| 349 | expect(sent.pathname).toContain("/mcp%3A3534bc83-"); | |
| 350 | sent.search = ""; | |
| 351 | expect(harness.proofs.map((proof) => proof.url)).toEqual([sent.toString()]); | |
| 345 | const sent = harness.calls[0]!.url; | |
| 346 | expect(new URL(sent).pathname).toContain("/mcp%3A3534bc83-"); | |
| 347 | expect(harness.proofs.map((proof) => proof.url)).toEqual([sent]); | |
| 352 | 348 | }), |
| 353 | 349 | ); |
| 354 | 350 | |
| ... | ... | @@ -408,7 +404,7 @@ describe("authenticated environment HTTP requests", () => { |
| 408 | 404 | ); |
| 409 | 405 | expect(harness.proofs[1]).toEqual({ |
| 410 | 406 | method: "GET", |
| 411 | url: `${RENEWED_ORIGIN}${loader.path}`, | |
| 407 | url: retried.url, | |
| 412 | 408 | accessToken: "renewed-token", |
| 413 | 409 | }); |
| 414 | 410 | if (loader.name === "older thread history") { |
packages/client-runtime/src/state/environmentHttpAuth.ts+13-3| ... | ... | @@ -14,6 +14,7 @@ import type { ManagedRelayDpopSigner } from "../relay/managedRelay.ts"; |
| 14 | 14 | import { |
| 15 | 15 | executeEnvironmentHttpRequest, |
| 16 | 16 | makeEnvironmentHttpApiGroupClient, |
| 17 | makeEnvironmentHttpApiUrlBuilder, | |
| 17 | 18 | RemoteEnvironmentAuthFetchError, |
| 18 | 19 | RemoteEnvironmentAuthTimeoutError, |
| 19 | 20 | type RemoteEnvironmentRequestError, |
| ... | ... | @@ -131,7 +132,12 @@ const executeEnvironmentRequest = Effect.fnUntraced(function* < |
| 131 | 132 | readonly signer: Option.Option<ManagedRelayDpopSigner["Service"]>; |
| 132 | 133 | readonly remoteAuthorization?: Option.Option<RemoteEnvironmentAuthorization["Service"]>; |
| 133 | 134 | readonly method: HttpMethod.HttpMethod; |
| 134 | readonly url: (httpBaseUrl: string) => string; | |
| 135 | /** | |
| 136 | * The URL to sign, from the group's contract URL builder. It encodes path | |
| 137 | * params exactly like the request client, so the DPoP proof matches the | |
| 138 | * URL actually sent. | |
| 139 | */ | |
| 140 | readonly url: (urls: ReturnType<typeof makeEnvironmentHttpApiUrlBuilder>[Group]) => string; | |
| 135 | 141 | readonly timeoutMs: number; |
| 136 | 142 | readonly group: Group; |
| 137 | 143 | readonly request: (input: { |
| ... | ... | @@ -146,6 +152,8 @@ const executeEnvironmentRequest = Effect.fnUntraced(function* < |
| 146 | 152 | Effect.Services<ReturnType<typeof makeEnvironmentHttpApiGroupClient<Group>>> | R |
| 147 | 153 | > { |
| 148 | 154 | let httpBaseUrl = input.prepared.httpBaseUrl; |
| 155 | const requestUrlFor = (baseUrl: string) => | |
| 156 | input.url(makeEnvironmentHttpApiUrlBuilder(baseUrl)[input.group]); | |
| 149 | 157 | return yield* Effect.gen(function* () { |
| 150 | 158 | let rejectedAccessToken: string | undefined; |
| 151 | 159 | for (;;) { |
| ... | ... | @@ -179,7 +187,7 @@ const executeEnvironmentRequest = Effect.fnUntraced(function* < |
| 179 | 187 | authorization = current.httpAuthorization; |
| 180 | 188 | } |
| 181 | 189 | |
| 182 | const requestUrl = input.url(httpBaseUrl); | |
| 190 | const requestUrl = requestUrlFor(httpBaseUrl); | |
| 183 | 191 | const client = yield* makeEnvironmentHttpApiGroupClient(httpBaseUrl, input.group); |
| 184 | 192 | const headers = yield* buildEnvironmentAuthHeaders( |
| 185 | 193 | authorization, |
| ... | ... | @@ -225,7 +233,9 @@ const executeEnvironmentRequest = Effect.fnUntraced(function* < |
| 225 | 233 | Effect.timeoutOrElse({ |
| 226 | 234 | duration: input.timeoutMs, |
| 227 | 235 | orElse: () => |
| 228 | Effect.fail(new RemoteEnvironmentAuthTimeoutError(input.url(httpBaseUrl), input.timeoutMs)), | |
| 236 | Effect.fail( | |
| 237 | new RemoteEnvironmentAuthTimeoutError(requestUrlFor(httpBaseUrl), input.timeoutMs), | |
| 238 | ), | |
| 229 | 239 | }), |
| 230 | 240 | ); |
| 231 | 241 | }); |
packages/client-runtime/src/state/pullRequestDiffHttp.ts+2-5| ... | ... | @@ -13,10 +13,7 @@ import { HttpClient } from "effect/http"; |
| 13 | 13 | import * as RemoteEnvironmentAuthorization from "../authorization/service.ts"; |
| 14 | 14 | import type { PreparedConnection } from "../connection/model.ts"; |
| 15 | 15 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 16 | import { | |
| 17 | makeEnvironmentHttpApiUrlBuilder, | |
| 18 | type RemoteEnvironmentRequestError, | |
| 19 | } from "../rpc/http.ts"; | |
| 16 | import { type RemoteEnvironmentRequestError } from "../rpc/http.ts"; | |
| 20 | 17 | import { executeAuthenticatedEnvironmentHttpRequest } from "./environmentHttpAuth.ts"; |
| 21 | 18 | |
| 22 | 19 | const DEFAULT_PULL_REQUEST_DIFF_TIMEOUT_MS = 60_000; |
| ... | ... | @@ -54,7 +51,7 @@ export const fetchEnvironmentPullRequestDiff = Effect.fn( |
| 54 | 51 | ...input, |
| 55 | 52 | group: "pullRequests", |
| 56 | 53 | method: "POST", |
| 57 | url: (httpBaseUrl) => makeEnvironmentHttpApiUrlBuilder(httpBaseUrl).pullRequests.diff(), | |
| 54 | url: (urls) => urls.diff(), | |
| 58 | 55 | timeoutMs: input.timeoutMs ?? DEFAULT_PULL_REQUEST_DIFF_TIMEOUT_MS, |
| 59 | 56 | request: ({ client, headers }) => client.diff({ payload: input.diff, headers }), |
| 60 | 57 | }).pipe( |
packages/client-runtime/src/state/session.ts+1-2| ... | ... | @@ -11,7 +11,6 @@ import * as RemoteEnvironmentAuthorization from "../authorization/service.ts"; |
| 11 | 11 | import * as EnvironmentRegistry from "../connection/registry.ts"; |
| 12 | 12 | import type { PreparedConnection } from "../connection/model.ts"; |
| 13 | 13 | import * as EnvironmentSupervisor from "../connection/supervisor.ts"; |
| 14 | import { environmentEndpointUrl } from "../environment/endpoint.ts"; | |
| 15 | 14 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 16 | 15 | import { safeErrorLogAttributes } from "../errors/safeLog.ts"; |
| 17 | 16 | import { executeAuthenticatedEnvironmentHttpRequest } from "./environmentHttpAuth.ts"; |
| ... | ... | @@ -59,7 +58,7 @@ export const fetchEnvironmentSessionState = Effect.fn( |
| 59 | 58 | ...input, |
| 60 | 59 | group: "auth", |
| 61 | 60 | method: "GET", |
| 62 | url: (httpBaseUrl) => environmentEndpointUrl(httpBaseUrl, "/api/auth/session"), | |
| 61 | url: (urls) => urls.session(), | |
| 63 | 62 | timeoutMs: input.timeoutMs ?? DEFAULT_SESSION_STATE_TIMEOUT_MS, |
| 64 | 63 | request: ({ client, headers }) => client.session({ headers }), |
| 65 | 64 | // This endpoint returns 200 with authenticated:false for expired credentials. |
packages/client-runtime/src/state/shellSnapshotHttp.ts+1-2| ... | ... | @@ -9,7 +9,6 @@ import { HttpClient, HttpClientError, type HttpClientResponse } from "effect/htt |
| 9 | 9 | |
| 10 | 10 | import * as RemoteEnvironmentAuthorization from "../authorization/service.ts"; |
| 11 | 11 | import type { PreparedConnection } from "../connection/model.ts"; |
| 12 | import { environmentEndpointUrl } from "../environment/endpoint.ts"; | |
| 13 | 12 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 14 | 13 | import { |
| 15 | 14 | executeAuthenticatedEnvironmentHttpRequest, |
| ... | ... | @@ -48,7 +47,7 @@ export const fetchEnvironmentShellSnapshot = Effect.fn( |
| 48 | 47 | ...input, |
| 49 | 48 | group: "orchestration", |
| 50 | 49 | method: "GET", |
| 51 | url: (httpBaseUrl) => environmentEndpointUrl(httpBaseUrl, "/api/orchestration/shell"), | |
| 50 | url: (urls) => urls.shellSnapshot(), | |
| 52 | 51 | timeoutMs: input.timeoutMs ?? DEFAULT_SHELL_SNAPSHOT_TIMEOUT_MS, |
| 53 | 52 | // The body is decoded here so each thread's pull request links can decode after its |
| 54 | 53 | // rows. Every declared error is an `EnvironmentHttpCommonError`, so decoding one keeps |
packages/client-runtime/src/state/threadHistoryHttp.ts+10-15| ... | ... | @@ -4,7 +4,6 @@ import * as Option from "effect/Option"; |
| 4 | 4 | |
| 5 | 5 | import type { RemoteEnvironmentAuthorization } from "../authorization/service.ts"; |
| 6 | 6 | import type { PreparedConnection } from "../connection/model.ts"; |
| 7 | import { environmentEndpointUrl } from "../environment/endpoint.ts"; | |
| 8 | 7 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 9 | 8 | import { |
| 10 | 9 | executeAuthenticatedEnvironmentHttpRequest, |
| ... | ... | @@ -25,25 +24,21 @@ export const fetchEnvironmentThreadHistoryPage = Effect.fn( |
| 25 | 24 | readonly remoteAuthorization?: Option.Option<RemoteEnvironmentAuthorization["Service"]>; |
| 26 | 25 | readonly timeoutMs?: number; |
| 27 | 26 | }) { |
| 27 | const endpoint = { | |
| 28 | params: { threadId: input.threadId }, | |
| 29 | query: { | |
| 30 | cursor: input.cursor, | |
| 31 | ...(input.view === undefined ? {} : { view: input.view }), | |
| 32 | ...(input.throughEntryId === undefined ? {} : { throughEntryId: input.throughEntryId }), | |
| 33 | }, | |
| 34 | }; | |
| 28 | 35 | return yield* executeAuthenticatedEnvironmentHttpRequest({ |
| 29 | 36 | ...input, |
| 30 | 37 | group: "orchestration", |
| 31 | 38 | method: "GET", |
| 32 | url: (httpBaseUrl) => | |
| 33 | environmentEndpointUrl( | |
| 34 | httpBaseUrl, | |
| 35 | `/api/orchestration/threads/${encodeURIComponent(input.threadId)}/history`, | |
| 36 | ), | |
| 39 | url: (urls) => urls.threadHistoryPage(endpoint), | |
| 37 | 40 | timeoutMs: input.timeoutMs ?? DEFAULT_THREAD_HISTORY_TIMEOUT_MS, |
| 38 | 41 | request: ({ client, headers }) => |
| 39 | client.threadHistoryPage({ | |
| 40 | params: { threadId: input.threadId }, | |
| 41 | query: { | |
| 42 | cursor: input.cursor, | |
| 43 | ...(input.view === undefined ? {} : { view: input.view }), | |
| 44 | ...(input.throughEntryId === undefined ? {} : { throughEntryId: input.throughEntryId }), | |
| 45 | }, | |
| 46 | headers: withOrchestrationProtocolHeader(headers), | |
| 47 | }), | |
| 42 | client.threadHistoryPage({ ...endpoint, headers: withOrchestrationProtocolHeader(headers) }), | |
| 48 | 43 | }); |
| 49 | 44 | }); |
packages/client-runtime/src/state/threadSnapshotHttp.ts+1-6| ... | ... | @@ -5,7 +5,6 @@ import * as Option from "effect/Option"; |
| 5 | 5 | |
| 6 | 6 | import * as RemoteEnvironmentAuthorization from "../authorization/service.ts"; |
| 7 | 7 | import type { PreparedConnection } from "../connection/model.ts"; |
| 8 | import { environmentEndpointUrl } from "../environment/endpoint.ts"; | |
| 9 | 8 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 10 | 9 | import type { RemoteEnvironmentRequestError } from "../rpc/http.ts"; |
| 11 | 10 | import { |
| ... | ... | @@ -65,11 +64,7 @@ export const fetchEnvironmentThreadSnapshot = Effect.fn( |
| 65 | 64 | ...input, |
| 66 | 65 | group: "orchestration", |
| 67 | 66 | method: "GET", |
| 68 | url: (httpBaseUrl) => | |
| 69 | environmentEndpointUrl( | |
| 70 | httpBaseUrl, | |
| 71 | `/api/orchestration/threads/${encodeURIComponent(input.threadId)}`, | |
| 72 | ), | |
| 67 | url: (urls) => urls.threadSnapshot({ params: { threadId: input.threadId } }), | |
| 73 | 68 | timeoutMs: input.timeoutMs ?? DEFAULT_THREAD_SNAPSHOT_TIMEOUT_MS, |
| 74 | 69 | request: ({ client, headers }) => |
| 75 | 70 | client.threadSnapshot({ |