Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { HttpClient } from "effect/http";

import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";
import type { PreparedConnection } from "../connection/model.ts";
import { environmentEndpointUrl } from "../environment/endpoint.ts";
import * as ManagedRelay from "../relay/managedRelay.ts";
import {
executeAuthenticatedEnvironmentHttpRequest,
Expand All @@ -35,20 +34,19 @@ export const fetchEnvironmentBoundedThreadSnapshot = Effect.fn(
>;
readonly timeoutMs?: number;
}) {
const endpoint = {
params: { threadId: input.threadId },
query: { compactTurnItems: "1" },
};
return yield* executeAuthenticatedEnvironmentHttpRequest({
...input,
group: "orchestration",
method: "GET",
url: (httpBaseUrl) =>
environmentEndpointUrl(
httpBaseUrl,
`/api/orchestration/threads/${encodeURIComponent(input.threadId)}/bounded`,
),
url: (urls) => urls.threadBoundedSnapshot(endpoint),
timeoutMs: input.timeoutMs ?? DEFAULT_BOUNDED_THREAD_SNAPSHOT_TIMEOUT_MS,
request: ({ client, headers }) =>
client.threadBoundedSnapshot({
params: { threadId: input.threadId },
query: { compactTurnItems: "1" },
...endpoint,
headers: withOrchestrationProtocolHeader(headers),
}),
}).pipe(
Expand Down
2 changes: 1 addition & 1 deletion packages/client-runtime/src/state/deviceHubAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const resolveDeviceHubAccess = Effect.fn("clientRuntime.state.resolveDevi
remoteAuthorization,
group: "auth",
method: "POST",
url: (httpBaseUrl) => environmentEndpointUrl(httpBaseUrl, "/api/auth/websocket-ticket"),
url: (urls) => urls.webSocketTicket(),
timeoutMs: TICKET_TIMEOUT_MS,
request: ({ client, headers }) => client.webSocketTicket({ headers }),
});
Expand Down
16 changes: 6 additions & 10 deletions packages/client-runtime/src/state/environmentHttpAuth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,9 @@ describe("authenticated environment HTTP requests", () => {
);
}
expect(harness.authorizations).toEqual([{ expectedEnvironmentId: TARGET.environmentId }]);
// The proof signs exactly the URL sent; signers drop the query for `htu`.
expect(harness.proofs).toEqual([
{
method: loader.method,
url: `${CURRENT_ORIGIN}${loader.path}`,
accessToken: "current-token",
},
{ method: loader.method, url: call.url, accessToken: "current-token" },
]);
if (loader.name === "older thread history") {
expect(url.searchParams.get("cursor")).toBe("older-page");
Expand Down Expand Up @@ -345,10 +342,9 @@ describe("authenticated environment HTTP requests", () => {
const harness = makeHarness(() => Response.json(loader.response));
yield* loader.load(harness.input).pipe(Effect.provide(harness.httpLayer));

const sent = new URL(harness.calls[0]!.url);
expect(sent.pathname).toContain("/mcp%3A3534bc83-");
sent.search = "";
expect(harness.proofs.map((proof) => proof.url)).toEqual([sent.toString()]);
const sent = harness.calls[0]!.url;
expect(new URL(sent).pathname).toContain("/mcp%3A3534bc83-");
expect(harness.proofs.map((proof) => proof.url)).toEqual([sent]);
}),
);

Expand Down Expand Up @@ -408,7 +404,7 @@ describe("authenticated environment HTTP requests", () => {
);
expect(harness.proofs[1]).toEqual({
method: "GET",
url: `${RENEWED_ORIGIN}${loader.path}`,
url: retried.url,
accessToken: "renewed-token",
});
if (loader.name === "older thread history") {
Expand Down
16 changes: 13 additions & 3 deletions packages/client-runtime/src/state/environmentHttpAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { ManagedRelayDpopSigner } from "../relay/managedRelay.ts";
import {
executeEnvironmentHttpRequest,
makeEnvironmentHttpApiGroupClient,
makeEnvironmentHttpApiUrlBuilder,
RemoteEnvironmentAuthFetchError,
RemoteEnvironmentAuthTimeoutError,
type RemoteEnvironmentRequestError,
Expand Down Expand Up @@ -131,7 +132,12 @@ const executeEnvironmentRequest = Effect.fnUntraced(function* <
readonly signer: Option.Option<ManagedRelayDpopSigner["Service"]>;
readonly remoteAuthorization?: Option.Option<RemoteEnvironmentAuthorization["Service"]>;
readonly method: HttpMethod.HttpMethod;
readonly url: (httpBaseUrl: string) => string;
/**
* The URL to sign, from the group's contract URL builder. It encodes path
* params exactly like the request client, so the DPoP proof matches the
* URL actually sent.
*/
readonly url: (urls: ReturnType<typeof makeEnvironmentHttpApiUrlBuilder>[Group]) => string;
readonly timeoutMs: number;
readonly group: Group;
readonly request: (input: {
Expand All @@ -146,6 +152,8 @@ const executeEnvironmentRequest = Effect.fnUntraced(function* <
Effect.Services<ReturnType<typeof makeEnvironmentHttpApiGroupClient<Group>>> | R
> {
let httpBaseUrl = input.prepared.httpBaseUrl;
const requestUrlFor = (baseUrl: string) =>
input.url(makeEnvironmentHttpApiUrlBuilder(baseUrl)[input.group]);
return yield* Effect.gen(function* () {
let rejectedAccessToken: string | undefined;
for (;;) {
Expand Down Expand Up @@ -179,7 +187,7 @@ const executeEnvironmentRequest = Effect.fnUntraced(function* <
authorization = current.httpAuthorization;
}

const requestUrl = input.url(httpBaseUrl);
const requestUrl = requestUrlFor(httpBaseUrl);
const client = yield* makeEnvironmentHttpApiGroupClient(httpBaseUrl, input.group);
const headers = yield* buildEnvironmentAuthHeaders(
authorization,
Expand Down Expand Up @@ -225,7 +233,9 @@ const executeEnvironmentRequest = Effect.fnUntraced(function* <
Effect.timeoutOrElse({
duration: input.timeoutMs,
orElse: () =>
Effect.fail(new RemoteEnvironmentAuthTimeoutError(input.url(httpBaseUrl), input.timeoutMs)),
Effect.fail(
new RemoteEnvironmentAuthTimeoutError(requestUrlFor(httpBaseUrl), input.timeoutMs),
),
}),
);
});
7 changes: 2 additions & 5 deletions packages/client-runtime/src/state/pullRequestDiffHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import { HttpClient } from "effect/http";
import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";
import type { PreparedConnection } from "../connection/model.ts";
import * as ManagedRelay from "../relay/managedRelay.ts";
import {
makeEnvironmentHttpApiUrlBuilder,
type RemoteEnvironmentRequestError,
} from "../rpc/http.ts";
import { type RemoteEnvironmentRequestError } from "../rpc/http.ts";
import { executeAuthenticatedEnvironmentHttpRequest } from "./environmentHttpAuth.ts";

const DEFAULT_PULL_REQUEST_DIFF_TIMEOUT_MS = 60_000;
Expand Down Expand Up @@ -54,7 +51,7 @@ export const fetchEnvironmentPullRequestDiff = Effect.fn(
...input,
group: "pullRequests",
method: "POST",
url: (httpBaseUrl) => makeEnvironmentHttpApiUrlBuilder(httpBaseUrl).pullRequests.diff(),
url: (urls) => urls.diff(),
timeoutMs: input.timeoutMs ?? DEFAULT_PULL_REQUEST_DIFF_TIMEOUT_MS,
request: ({ client, headers }) => client.diff({ payload: input.diff, headers }),
}).pipe(
Expand Down
3 changes: 1 addition & 2 deletions packages/client-runtime/src/state/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";
import * as EnvironmentRegistry from "../connection/registry.ts";
import type { PreparedConnection } from "../connection/model.ts";
import * as EnvironmentSupervisor from "../connection/supervisor.ts";
import { environmentEndpointUrl } from "../environment/endpoint.ts";
import * as ManagedRelay from "../relay/managedRelay.ts";
import { safeErrorLogAttributes } from "../errors/safeLog.ts";
import { executeAuthenticatedEnvironmentHttpRequest } from "./environmentHttpAuth.ts";
Expand Down Expand Up @@ -59,7 +58,7 @@ export const fetchEnvironmentSessionState = Effect.fn(
...input,
group: "auth",
method: "GET",
url: (httpBaseUrl) => environmentEndpointUrl(httpBaseUrl, "/api/auth/session"),
url: (urls) => urls.session(),
timeoutMs: input.timeoutMs ?? DEFAULT_SESSION_STATE_TIMEOUT_MS,
request: ({ client, headers }) => client.session({ headers }),
// This endpoint returns 200 with authenticated:false for expired credentials.
Expand Down
3 changes: 1 addition & 2 deletions packages/client-runtime/src/state/shellSnapshotHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { HttpClient, HttpClientError, type HttpClientResponse } from "effect/htt

import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";
import type { PreparedConnection } from "../connection/model.ts";
import { environmentEndpointUrl } from "../environment/endpoint.ts";
import * as ManagedRelay from "../relay/managedRelay.ts";
import {
executeAuthenticatedEnvironmentHttpRequest,
Expand Down Expand Up @@ -48,7 +47,7 @@ export const fetchEnvironmentShellSnapshot = Effect.fn(
...input,
group: "orchestration",
method: "GET",
url: (httpBaseUrl) => environmentEndpointUrl(httpBaseUrl, "/api/orchestration/shell"),
url: (urls) => urls.shellSnapshot(),
timeoutMs: input.timeoutMs ?? DEFAULT_SHELL_SNAPSHOT_TIMEOUT_MS,
// The body is decoded here so each thread's pull request links can decode after its
// rows. Every declared error is an `EnvironmentHttpCommonError`, so decoding one keeps
Expand Down
25 changes: 10 additions & 15 deletions packages/client-runtime/src/state/threadHistoryHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as Option from "effect/Option";

import type { RemoteEnvironmentAuthorization } from "../authorization/service.ts";
import type { PreparedConnection } from "../connection/model.ts";
import { environmentEndpointUrl } from "../environment/endpoint.ts";
import * as ManagedRelay from "../relay/managedRelay.ts";
import {
executeAuthenticatedEnvironmentHttpRequest,
Expand All @@ -25,25 +24,21 @@ export const fetchEnvironmentThreadHistoryPage = Effect.fn(
readonly remoteAuthorization?: Option.Option<RemoteEnvironmentAuthorization["Service"]>;
readonly timeoutMs?: number;
}) {
const endpoint = {
params: { threadId: input.threadId },
query: {
cursor: input.cursor,
...(input.view === undefined ? {} : { view: input.view }),
...(input.throughEntryId === undefined ? {} : { throughEntryId: input.throughEntryId }),
},
};
return yield* executeAuthenticatedEnvironmentHttpRequest({
...input,
group: "orchestration",
method: "GET",
url: (httpBaseUrl) =>
environmentEndpointUrl(
httpBaseUrl,
`/api/orchestration/threads/${encodeURIComponent(input.threadId)}/history`,
),
url: (urls) => urls.threadHistoryPage(endpoint),
timeoutMs: input.timeoutMs ?? DEFAULT_THREAD_HISTORY_TIMEOUT_MS,
request: ({ client, headers }) =>
client.threadHistoryPage({
params: { threadId: input.threadId },
query: {
cursor: input.cursor,
...(input.view === undefined ? {} : { view: input.view }),
...(input.throughEntryId === undefined ? {} : { throughEntryId: input.throughEntryId }),
},
headers: withOrchestrationProtocolHeader(headers),
}),
client.threadHistoryPage({ ...endpoint, headers: withOrchestrationProtocolHeader(headers) }),
});
});
7 changes: 1 addition & 6 deletions packages/client-runtime/src/state/threadSnapshotHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as Option from "effect/Option";

import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";
import type { PreparedConnection } from "../connection/model.ts";
import { environmentEndpointUrl } from "../environment/endpoint.ts";
import * as ManagedRelay from "../relay/managedRelay.ts";
import type { RemoteEnvironmentRequestError } from "../rpc/http.ts";
import {
Expand Down Expand Up @@ -65,11 +64,7 @@ export const fetchEnvironmentThreadSnapshot = Effect.fn(
...input,
group: "orchestration",
method: "GET",
url: (httpBaseUrl) =>
environmentEndpointUrl(
httpBaseUrl,
`/api/orchestration/threads/${encodeURIComponent(input.threadId)}`,
),
url: (urls) => urls.threadSnapshot({ params: { threadId: input.threadId } }),
timeoutMs: input.timeoutMs ?? DEFAULT_THREAD_SNAPSHOT_TIMEOUT_MS,
request: ({ client, headers }) =>
client.threadSnapshot({
Expand Down
Loading