Skip to content

Commit 59cd1a8

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feat/provider-usage-limits
2 parents 05a9850 + 1234708 commit 59cd1a8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3023
-199
lines changed

apps/marketing/src/pages/download.astro

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import Layout from "../layouts/Layout.astro";
3+
import { RELEASES_URL } from "../lib/releases";
34
---
45

56
<Layout title="Download — T3 Code" description="Download T3 Code for macOS, Windows, or Linux.">
@@ -17,11 +18,11 @@ import Layout from "../layouts/Layout.astro";
1718
<h2 class="platform-name">macOS</h2>
1819
</div>
1920
<div class="download-cards">
20-
<a class="download-card" data-asset="arm64.dmg" href="#">
21+
<a class="download-card" data-asset="arm64.dmg" href={RELEASES_URL}>
2122
<span class="card-arch">Apple Silicon (arm64)</span>
2223
<span class="card-format">.dmg</span>
2324
</a>
24-
<a class="download-card" data-asset="x64.dmg" href="#">
25+
<a class="download-card" data-asset="x64.dmg" href={RELEASES_URL}>
2526
<span class="card-arch">Intel (x64)</span>
2627
<span class="card-format">.dmg</span>
2728
</a>
@@ -35,7 +36,7 @@ import Layout from "../layouts/Layout.astro";
3536
<h2 class="platform-name">Windows</h2>
3637
</div>
3738
<div class="download-cards">
38-
<a class="download-card" data-asset="x64.exe" href="#">
39+
<a class="download-card" data-asset="x64.exe" href={RELEASES_URL}>
3940
<span class="card-arch">Windows 10, 11</span>
4041
<span class="card-format">.exe</span>
4142
</a>
@@ -49,7 +50,7 @@ import Layout from "../layouts/Layout.astro";
4950
<h2 class="platform-name">Linux</h2>
5051
</div>
5152
<div class="download-cards">
52-
<a class="download-card" data-asset="x86_64.AppImage" href="#">
53+
<a class="download-card" data-asset="x86_64.AppImage" href={RELEASES_URL}>
5354
<span class="card-arch">x86_64</span>
5455
<span class="card-format">AppImage</span>
5556
</a>

apps/server/integration/OrchestrationEngineHarness.integration.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import { CheckpointReactorLive } from "../src/orchestration/Layers/CheckpointRea
4949
import { OrchestrationEngineLive } from "../src/orchestration/Layers/OrchestrationEngine.ts";
5050
import { OrchestrationProjectionPipelineLive } from "../src/orchestration/Layers/ProjectionPipeline.ts";
5151
import { OrchestrationProjectionSnapshotQueryLive } from "../src/orchestration/Layers/ProjectionSnapshotQuery.ts";
52-
import { RuntimeReceiptBusLive } from "../src/orchestration/Layers/RuntimeReceiptBus.ts";
52+
import { RuntimeReceiptBusTest } from "../src/orchestration/Layers/RuntimeReceiptBus.ts";
5353
import { OrchestrationReactorLive } from "../src/orchestration/Layers/OrchestrationReactor.ts";
5454
import { ProviderCommandReactorLive } from "../src/orchestration/Layers/ProviderCommandReactor.ts";
5555
import { ProviderRuntimeIngestionLive } from "../src/orchestration/Layers/ProviderRuntimeIngestion.ts";
@@ -301,7 +301,7 @@ export const makeOrchestrationIntegrationHarness = (
301301
ProjectionPendingApprovalRepositoryLive,
302302
checkpointStoreLayer,
303303
providerLayer.pipe(Layer.provide(usageLimitsRepositoryLayer)),
304-
RuntimeReceiptBusLive,
304+
RuntimeReceiptBusTest,
305305
);
306306
const serverSettingsLayer = ServerSettingsService.layerTest();
307307
const runtimeIngestionLayer = ProviderRuntimeIngestionLive.pipe(
@@ -380,7 +380,7 @@ export const makeOrchestrationIntegrationHarness = (
380380
runtime.runPromise(reactor.start().pipe(Scope.provide(scope))),
381381
).pipe(Effect.orDie);
382382
const receiptHistory = yield* Ref.make<ReadonlyArray<OrchestrationRuntimeReceipt>>([]);
383-
yield* Stream.runForEach(runtimeReceiptBus.stream, (receipt) =>
383+
yield* Stream.runForEach(runtimeReceiptBus.streamEventsForTest, (receipt) =>
384384
Ref.update(receiptHistory, (history) => [...history, receipt]).pipe(Effect.asVoid),
385385
).pipe(Effect.forkIn(scope));
386386
yield* Effect.sleep(10);

apps/server/integration/providerService.integration.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ const collectEventsDuring = <A, E, R>(
9191
Effect.forkScoped,
9292
);
9393

94+
yield* Effect.sleep("50 millis");
9495
yield* action;
9596

9697
return yield* Effect.forEach(
@@ -109,7 +110,6 @@ const runTurn = (input: {
109110
}) =>
110111
Effect.gen(function* () {
111112
yield* input.harness.queueTurnResponse(input.threadId, input.response);
112-
113113
return yield* collectEventsDuring(
114114
input.provider.streamEvents,
115115
input.response.events.length,
@@ -121,7 +121,7 @@ const runTurn = (input: {
121121
);
122122
});
123123

124-
it.effect("replays typed runtime fixture events", () =>
124+
it.live("replays typed runtime fixture events", () =>
125125
Effect.gen(function* () {
126126
const fixture = yield* makeIntegrationFixture;
127127

@@ -154,7 +154,7 @@ it.effect("replays typed runtime fixture events", () =>
154154
}).pipe(Effect.provide(NodeServices.layer)),
155155
);
156156

157-
it.effect("replays file-changing fixture turn events", () =>
157+
it.live("replays file-changing fixture turn events", () =>
158158
Effect.gen(function* () {
159159
const fixture = yield* makeIntegrationFixture;
160160
const { join } = yield* Path.Path;
@@ -193,7 +193,7 @@ it.effect("replays file-changing fixture turn events", () =>
193193
}).pipe(Effect.provide(NodeServices.layer)),
194194
);
195195

196-
it.effect("runs multi-turn tool/approval flow", () =>
196+
it.live("runs multi-turn tool/approval flow", () =>
197197
Effect.gen(function* () {
198198
const fixture = yield* makeIntegrationFixture;
199199
const { join } = yield* Path.Path;
@@ -247,7 +247,7 @@ it.effect("runs multi-turn tool/approval flow", () =>
247247
}).pipe(Effect.provide(NodeServices.layer)),
248248
);
249249

250-
it.effect("rolls back provider conversation state only", () =>
250+
it.live("rolls back provider conversation state only", () =>
251251
Effect.gen(function* () {
252252
const fixture = yield* makeIntegrationFixture;
253253
const { join } = yield* Path.Path;

apps/server/src/http.ts

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import Mime from "@effect/platform-node/Mime";
2-
import { Effect, FileSystem, Option, Path } from "effect";
3-
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
2+
import { Data, Effect, FileSystem, Layer, Option, Path } from "effect";
3+
import { cast } from "effect/Function";
4+
import {
5+
HttpBody,
6+
HttpClient,
7+
HttpClientResponse,
8+
HttpRouter,
9+
HttpServerResponse,
10+
HttpServerRequest,
11+
} from "effect/unstable/http";
12+
import { OtlpTracer } from "effect/unstable/observability";
413

514
import {
615
ATTACHMENTS_ROUTE_PREFIX,
@@ -9,10 +18,74 @@ import {
918
} from "./attachmentPaths";
1019
import { resolveAttachmentPathById } from "./attachmentStore";
1120
import { ServerConfig } from "./config";
21+
import { decodeOtlpTraceRecords } from "./observability/TraceRecord.ts";
22+
import { BrowserTraceCollector } from "./observability/Services/BrowserTraceCollector.ts";
1223
import { ProjectFaviconResolver } from "./project/Services/ProjectFaviconResolver";
1324

1425
const PROJECT_FAVICON_CACHE_CONTROL = "public, max-age=3600";
1526
const FALLBACK_PROJECT_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#6b728080" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" data-fallback="project-favicon"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-8l-2-2H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2Z"/></svg>`;
27+
const OTLP_TRACES_PROXY_PATH = "/api/observability/v1/traces";
28+
29+
class DecodeOtlpTraceRecordsError extends Data.TaggedError("DecodeOtlpTraceRecordsError")<{
30+
readonly cause: unknown;
31+
readonly bodyJson: OtlpTracer.TraceData;
32+
}> {}
33+
34+
export const otlpTracesProxyRouteLayer = HttpRouter.add(
35+
"POST",
36+
OTLP_TRACES_PROXY_PATH,
37+
Effect.gen(function* () {
38+
const request = yield* HttpServerRequest.HttpServerRequest;
39+
const config = yield* ServerConfig;
40+
const otlpTracesUrl = config.otlpTracesUrl;
41+
const browserTraceCollector = yield* BrowserTraceCollector;
42+
const httpClient = yield* HttpClient.HttpClient;
43+
const bodyJson = cast<unknown, OtlpTracer.TraceData>(yield* request.json);
44+
45+
yield* Effect.try({
46+
try: () => decodeOtlpTraceRecords(bodyJson),
47+
catch: (cause) => new DecodeOtlpTraceRecordsError({ cause, bodyJson }),
48+
}).pipe(
49+
Effect.flatMap((records) => browserTraceCollector.record(records)),
50+
Effect.catch((cause) =>
51+
Effect.logWarning("Failed to decode browser OTLP traces", {
52+
cause,
53+
bodyJson,
54+
}),
55+
),
56+
);
57+
58+
if (otlpTracesUrl === undefined) {
59+
return HttpServerResponse.empty({ status: 204 });
60+
}
61+
62+
return yield* httpClient
63+
.post(otlpTracesUrl, {
64+
body: HttpBody.jsonUnsafe(bodyJson),
65+
})
66+
.pipe(
67+
Effect.flatMap(HttpClientResponse.filterStatusOk),
68+
Effect.as(HttpServerResponse.empty({ status: 204 })),
69+
Effect.tapError((cause) =>
70+
Effect.logWarning("Failed to export browser OTLP traces", {
71+
cause,
72+
otlpTracesUrl,
73+
}),
74+
),
75+
Effect.catch(() =>
76+
Effect.succeed(HttpServerResponse.text("Trace export failed.", { status: 502 })),
77+
),
78+
);
79+
}),
80+
).pipe(
81+
Layer.provide(
82+
HttpRouter.cors({
83+
allowedMethods: ["POST", "OPTIONS"],
84+
allowedHeaders: ["content-type"],
85+
maxAge: 600,
86+
}),
87+
),
88+
);
1689

1790
export const attachmentsRouteLayer = HttpRouter.add(
1891
"GET",

apps/server/src/observability/Layers/Observability.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/obse
44
import { ServerConfig } from "../../config.ts";
55
import { ServerLoggerLive } from "../../serverLogger.ts";
66
import { makeLocalFileTracer } from "../LocalFileTracer.ts";
7+
import { BrowserTraceCollector } from "../Services/BrowserTraceCollector.ts";
8+
import { makeTraceSink } from "../TraceSink.ts";
79

810
const otlpSerializationLayer = OtlpSerialization.layerJson;
911

@@ -16,9 +18,14 @@ export const ObservabilityLive = Layer.unwrap(
1618
Layer.succeed(References.TracerTimingEnabled, config.traceTimingEnabled),
1719
);
1820

19-
const tracerLayer = Layer.effect(
20-
Tracer.Tracer,
21+
const tracerLayer = Layer.unwrap(
2122
Effect.gen(function* () {
23+
const sink = yield* makeTraceSink({
24+
filePath: config.serverTracePath,
25+
maxBytes: config.traceMaxBytes,
26+
maxFiles: config.traceMaxFiles,
27+
batchWindowMs: config.traceBatchWindowMs,
28+
});
2229
const delegate =
2330
config.otlpTracesUrl === undefined
2431
? undefined
@@ -34,13 +41,26 @@ export const ObservabilityLive = Layer.unwrap(
3441
},
3542
});
3643

37-
return yield* makeLocalFileTracer({
44+
const tracer = yield* makeLocalFileTracer({
3845
filePath: config.serverTracePath,
3946
maxBytes: config.traceMaxBytes,
4047
maxFiles: config.traceMaxFiles,
4148
batchWindowMs: config.traceBatchWindowMs,
49+
sink,
4250
...(delegate ? { delegate } : {}),
4351
});
52+
53+
return Layer.mergeAll(
54+
Layer.succeed(Tracer.Tracer, tracer),
55+
Layer.succeed(BrowserTraceCollector, {
56+
record: (records) =>
57+
Effect.sync(() => {
58+
for (const record of records) {
59+
sink.push(record);
60+
}
61+
}),
62+
}),
63+
);
4464
}),
4565
).pipe(Layer.provideMerge(otlpSerializationLayer));
4666

apps/server/src/observability/LocalFileTracer.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import path from "node:path";
55
import { assert, describe, it } from "@effect/vitest";
66
import { Effect, Layer, Logger, References, Tracer } from "effect";
77

8-
import type { TraceRecord } from "./TraceRecord.ts";
8+
import type { EffectTraceRecord } from "./TraceRecord.ts";
99
import { makeLocalFileTracer } from "./LocalFileTracer.ts";
1010

1111
const makeTestLayer = (tracePath: string) =>
@@ -23,13 +23,13 @@ const makeTestLayer = (tracePath: string) =>
2323
Layer.succeed(References.MinimumLogLevel, "Info"),
2424
);
2525

26-
const readTraceRecords = (tracePath: string): Array<TraceRecord> =>
26+
const readTraceRecords = (tracePath: string): Array<EffectTraceRecord> =>
2727
fs
2828
.readFileSync(tracePath, "utf8")
2929
.trim()
3030
.split("\n")
3131
.filter((line) => line.length > 0)
32-
.map((line) => JSON.parse(line) as TraceRecord);
32+
.map((line) => JSON.parse(line) as EffectTraceRecord);
3333

3434
describe("LocalFileTracer", () => {
3535
it.effect("writes nested spans to disk and captures log messages as span events", () =>

apps/server/src/observability/LocalFileTracer.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import type * as Exit from "effect/Exit";
22
import { Effect, Option, Tracer } from "effect";
33

4-
import { spanToTraceRecord } from "./TraceRecord.ts";
5-
import { makeTraceSink } from "./TraceSink.ts";
4+
import { EffectTraceRecord, spanToTraceRecord } from "./TraceRecord.ts";
5+
import { makeTraceSink, type TraceSink } from "./TraceSink.ts";
66

77
export interface LocalFileTracerOptions {
88
readonly filePath: string;
99
readonly maxBytes: number;
1010
readonly maxFiles: number;
1111
readonly batchWindowMs: number;
1212
readonly delegate?: Tracer.Tracer;
13+
readonly sink?: TraceSink;
1314
}
1415

1516
class LocalFileSpan implements Tracer.Span {
@@ -30,7 +31,7 @@ class LocalFileSpan implements Tracer.Span {
3031
constructor(
3132
options: Parameters<Tracer.Tracer["span"]>[0],
3233
private readonly delegate: Tracer.Span,
33-
private readonly push: (record: ReturnType<typeof spanToTraceRecord>) => void,
34+
private readonly push: (record: EffectTraceRecord) => void,
3435
) {
3536
this.name = delegate.name;
3637
this.spanId = delegate.spanId;
@@ -82,12 +83,14 @@ class LocalFileSpan implements Tracer.Span {
8283
export const makeLocalFileTracer = Effect.fn("makeLocalFileTracer")(function* (
8384
options: LocalFileTracerOptions,
8485
) {
85-
const sink = yield* makeTraceSink({
86-
filePath: options.filePath,
87-
maxBytes: options.maxBytes,
88-
maxFiles: options.maxFiles,
89-
batchWindowMs: options.batchWindowMs,
90-
});
86+
const sink =
87+
options.sink ??
88+
(yield* makeTraceSink({
89+
filePath: options.filePath,
90+
maxBytes: options.maxBytes,
91+
maxFiles: options.maxFiles,
92+
batchWindowMs: options.batchWindowMs,
93+
}));
9194

9295
const delegate =
9396
options.delegate ??
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ServiceMap } from "effect";
2+
import type { Effect } from "effect";
3+
4+
import type { TraceRecord } from "../TraceRecord.ts";
5+
6+
export interface BrowserTraceCollectorShape {
7+
readonly record: (records: ReadonlyArray<TraceRecord>) => Effect.Effect<void>;
8+
}
9+
10+
export class BrowserTraceCollector extends ServiceMap.Service<
11+
BrowserTraceCollector,
12+
BrowserTraceCollectorShape
13+
>()("t3/observability/Services/BrowserTraceCollector") {}

0 commit comments

Comments
 (0)