Skip to content
Open
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
7 changes: 4 additions & 3 deletions apps/code/vite.workspace-server.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ const nativeModules = new Set([
"file-icon",
]);

const isExternal = (id: string): boolean =>
nodeBuiltins.has(id) || nativeModules.has(id);
// Rolldown-backed Vite requires external entries to be strings or RegExps;
// a predicate function is rejected when merged with ssr.external.
const externalModules = [...nodeBuiltins, ...nativeModules];

export default defineConfig({
resolve: {
Expand Down Expand Up @@ -55,7 +56,7 @@ export default defineConfig({
output: {
entryFileNames: "workspace-server.js",
},
external: isExternal,
external: externalModules,
},
},
});
1 change: 1 addition & 0 deletions packages/agent/src/server/agent-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ export class AgentServer {
if (config.eventIngestToken) {
this.eventStreamSender = new TaskRunEventStreamSender({
apiUrl: config.apiUrl,
eventIngestBaseUrl: config.eventIngestBaseUrl,
projectId: config.projectId,
taskId: config.taskId,
runId: config.runId,
Expand Down
4 changes: 4 additions & 0 deletions packages/agent/src/server/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const envSchema = z.object({
.enum(["low", "medium", "high", "xhigh", "max"])
.optional(),
POSTHOG_TASK_RUN_EVENT_INGEST_TOKEN: z.string().min(1).optional(),
// Routes the event-ingest POST to the standalone agent-proxy; other API calls keep using
// POSTHOG_API_URL. Falls back to POSTHOG_API_URL when unset.
POSTHOG_TASK_RUN_EVENT_INGEST_URL: z.url().optional(),
POSTHOG_TASK_RUN_EVENT_INGEST_STREAM_WINDOW_MS: z
.string()
.regex(
Expand Down Expand Up @@ -158,6 +161,7 @@ program
port: parseInt(options.port, 10),
jwtPublicKey: env.JWT_PUBLIC_KEY,
eventIngestToken: env.POSTHOG_TASK_RUN_EVENT_INGEST_TOKEN,
eventIngestBaseUrl: env.POSTHOG_TASK_RUN_EVENT_INGEST_URL,
eventIngestStreamWindowMs:
env.POSTHOG_TASK_RUN_EVENT_INGEST_STREAM_WINDOW_MS,
repositoryPath: options.repositoryPath,
Expand Down
20 changes: 20 additions & 0 deletions packages/agent/src/server/event-stream-sender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,26 @@ describe("TaskRunEventStreamSender", () => {
]);
});

it("routes the ingest POST to the agent-proxy run-scoped path when eventIngestBaseUrl is set", async () => {
const fetchMock = vi.fn(
async (_url: string | URL | Request, init?: RequestInit) => {
const body = await readRequestBody(init);
return responseForBody(body);
},
);
vi.stubGlobal("fetch", fetchMock);

const sender = createSender({
eventIngestBaseUrl: "http://agent-proxy:8003/",
});
sender.enqueue({ type: "notification", notification: { method: "first" } });
await sender.stop();

expect(fetchMock).toHaveBeenCalled();
const lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1];
expect(lastCall[0]).toBe("http://agent-proxy:8003/v1/runs/run-1/ingest");
});

it("keeps the active ingest request open across scheduled flushes", async () => {
const requestBodies: string[] = [];
let activeStreamClosed = false;
Expand Down
23 changes: 19 additions & 4 deletions packages/agent/src/server/event-stream-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {

interface TaskRunEventStreamSenderConfig {
apiUrl: string;
// Base URL for the event-ingest POST only. Lets the deployment route ingest to the
// standalone agent-proxy while the rest of the agent's API calls stay on apiUrl.
eventIngestBaseUrl?: string;
projectId: number;
taskId: string;
runId: string;
Expand Down Expand Up @@ -85,10 +88,22 @@ export class TaskRunEventStreamSender {
private bufferRevision = 0;

constructor(private readonly config: TaskRunEventStreamSenderConfig) {
const apiUrl = config.apiUrl.replace(/\/$/, "");
this.ingestUrl = `${apiUrl}/api/projects/${config.projectId}/tasks/${encodeURIComponent(
config.taskId,
)}/runs/${encodeURIComponent(config.runId)}/event_stream/`;
// When routed to the agent-proxy, use its clean run-scoped path; the run-scoped
// token carries team and task. Falling back to apiUrl keeps the Django path.
const usingProxy = Boolean(config.eventIngestBaseUrl);
const ingestBase = (config.eventIngestBaseUrl ?? config.apiUrl).replace(
/\/$/,
"",
);
this.ingestUrl = usingProxy
? `${ingestBase}/v1/runs/${encodeURIComponent(config.runId)}/ingest`
: `${ingestBase}/api/projects/${config.projectId}/tasks/${encodeURIComponent(
config.taskId,
)}/runs/${encodeURIComponent(config.runId)}/event_stream/`;
config.logger.info("[agent-proxy debug] event ingest target resolved", {
ingestUrl: this.ingestUrl,
routedToProxy: usingProxy,
});
this.maxBufferedEvents =
config.maxBufferedEvents ?? DEFAULT_MAX_BUFFERED_EVENTS;
this.maxStreamEvents = config.maxStreamEvents ?? DEFAULT_MAX_STREAM_EVENTS;
Expand Down
3 changes: 3 additions & 0 deletions packages/agent/src/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export interface AgentServerConfig {
projectId: number;
jwtPublicKey: string; // RS256 public key for JWT verification
eventIngestToken?: string;
// Optional base URL for the event-ingest POST only (routes ingest to the agent-proxy);
// all other agent API calls keep using apiUrl. Falls back to apiUrl when unset.
eventIngestBaseUrl?: string;
eventIngestStreamWindowMs?: number;
mode: AgentMode;
taskId: string;
Expand Down
Loading
Loading