-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
chore(webapp,core,sdk): upgrade @s2-dev/streamstore to 0.25 and migrate S2 hosts #4349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ericallam
wants to merge
29
commits into
main
Choose a base branch
from
feat/s2-caught-up-reads
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
2ac486e
feat(sdk,core): close resumed chat streams promptly when caught up
ericallam 7667d1e
fix(webapp): stop the dev server sending duplicate CORS headers
ericallam 4e0ad53
fix(core): end caught-up tracking on all terminal stream paths
ericallam 5655a2a
fix(core,sdk): settle resumed chat streams correctly on caught-up
ericallam 6deadd9
docs(ai-chat): document tail-on-ping and the client-side caught-up close
ericallam b4500cc
test(webapp): full-stack session-stream e2e over s2-lite
ericallam d56458f
test(webapp): browser-level cross-origin session-stream e2e
ericallam 8774359
test(webapp): make the browser session-stream leg CI-robust
ericallam c417803
test(webapp): cover more session-stream scenarios
ericallam 328faff
test(webapp): full-stack chat.agent e2e with the real turn loop
ericallam 11f0e0f
test(webapp): agent e2e legs for validate rejection, action, and stop
ericallam 1bfc26a
test(webapp): agent e2e legs for tools, HITL, and snapshot restore
ericallam e4ea85c
test(webapp): agent e2e legs for regenerate, tool approval, and sessi…
ericallam 79b6676
test(core,webapp): in-process waitpoint backend for run-lifecycle e2e
ericallam 78929db
test(webapp): suspend/resume, upgrade deferral, and multi-hop continu…
ericallam 21c4bf4
test(core,webapp): suspend-hook, OOM-retry, and idle-timeout e2e legs
ericallam 4b75bd5
fix(sdk): recover the in-flight message on a preloaded chat.agent retry
ericallam 373c421
fix(core): stop reconnecting a session stream after the consumer cancels
ericallam 4cfe780
ci(e2e-webapp): pre-pull the MinIO image for the session-stream e2e
ericallam f6e4a19
test(webapp): make the session-stream e2e reliable in CI
ericallam 863dd36
test(core,webapp): terminate in-process agent e2e runs reliably
ericallam 9964f0d
test(webapp): drain the session .out collector across reconnects
ericallam 8e2329d
fix(webapp): keep session .out writable when S2 access tokens are ski…
ericallam a6e1caf
fix(sdk): request the caught-up settle on AgentChat.reconnect()
ericallam ff17e0e
refactor(core,sdk): drop the client-side caught-up close, rely on the…
ericallam a37396c
fix(core): fire the session-stream onComplete callback at most once
ericallam 5a9b176
docs(sdk): note AgentChat.reconnect resumes a stream, not a fresh turn
ericallam b1c40b3
refactor(webapp): make S2 account and basin hosts env-configurable wi…
ericallam b72a247
fix(core): notify stream completion on connection end, not only on co…
ericallam File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@trigger.dev/sdk": patch | ||
| --- | ||
|
|
||
| Fix a preloaded `chat.agent` run dropping an in-flight message when it retries after an out-of-memory error. The message being processed when the run hit the OOM is now recovered and re-run on the retry, instead of being skipped while the run waited for a new message. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@trigger.dev/core": patch | ||
| "@trigger.dev/sdk": patch | ||
| "trigger.dev": patch | ||
| --- | ||
|
|
||
| `AgentChat.reconnect()` now settles promptly when reconnecting to an idle chat instead of holding the connection open for the full long-poll window. Also upgrades the S2 streamstore client to 0.25 and moves realtime streams to S2's current hosts. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| import { apiClientManager, resourceCatalog } from "@trigger.dev/core/v3"; | ||
| import type { LocalsKey } from "@trigger.dev/core/v3"; | ||
| import type { LanguageModel } from "ai"; | ||
| import { | ||
| installSessionWaitpointBackend, | ||
| runInMockTaskContext, | ||
| StandardSessionStreamManager, | ||
| } from "@trigger.dev/core/v3/test"; | ||
|
|
||
| export type RunRealChatAgentOptions = { | ||
| agentId: string; | ||
| baseUrl: string; | ||
| addressingKey: string; | ||
| /** | ||
| * The environment secret key. The agent writes `.out` and reads `.in` as the | ||
| * backend (PRIVATE auth) — the `.out` channel rejects client session tokens. | ||
| */ | ||
| secretKey: string; | ||
| model: LanguageModel; | ||
| modelLocal: LocalsKey<LanguageModel>; | ||
| runId?: string; | ||
| /** | ||
| * Boot as a continuation of a previous run for the same session. Gates the | ||
| * snapshot + `.out`/`.in` replay boot path, so the agent restores prior | ||
| * history instead of treating the chat as brand new. | ||
| */ | ||
| continuation?: boolean; | ||
| previousRunId?: string; | ||
| /** | ||
| * Idle window (seconds) before the turn loop falls through from the SSE | ||
| * once() to the suspending `session.in.wait()`. Set this low to force the | ||
| * suspend/resume path in a test. | ||
| */ | ||
| idleTimeoutInSeconds?: number; | ||
| /** | ||
| * `ctx.attempt.number`. A value greater than 1 makes the boot treat the run | ||
| * as a retry (`couldHavePriorState`), restoring from the snapshot + `.in` | ||
| * replay. Used to model an OOM retry re-dispatch. | ||
| */ | ||
| attemptNumber?: number; | ||
| }; | ||
|
|
||
| export type RunningAgent = { | ||
| done: Promise<void>; | ||
| close: () => Promise<void>; | ||
| }; | ||
|
|
||
| /** | ||
| * Run the real `chat.agent` turn loop in-process, wired to a running webapp: | ||
| * `apiClientManager` + a real `StandardSessionStreamManager` point the agent's | ||
| * `.in`/`.out` at the webapp's Session streams (real S2 + SSE), the model is | ||
| * injected via locals (so it survives without serialization), and turns are | ||
| * driven by appending to `.in` over HTTP. Callers keep each message inside the | ||
| * idle window and `close()` promptly so the run-engine suspend path is never | ||
| * reached. | ||
| */ | ||
| export function runRealChatAgent(opts: RunRealChatAgentOptions): RunningAgent { | ||
| apiClientManager.setGlobalAPIClientConfiguration({ | ||
| baseURL: opts.baseUrl, | ||
| accessToken: opts.secretKey, | ||
| }); | ||
| const apiClient = apiClientManager.clientOrThrow(); | ||
| const manager = new StandardSessionStreamManager(apiClient, opts.baseUrl); | ||
| const { backend, runtimeManager, restore } = installSessionWaitpointBackend(apiClient); | ||
|
|
||
| const taskEntry = resourceCatalog.getTask(opts.agentId); | ||
| if (!taskEntry) { | ||
| restore(); | ||
| throw new Error(`runRealChatAgent: agent "${opts.agentId}" is not registered`); | ||
| } | ||
| const runFn = taskEntry.fns.run as ( | ||
| payload: unknown, | ||
| params: { ctx: unknown; signal: AbortSignal } | ||
| ) => Promise<unknown>; | ||
|
|
||
| const runSignal = new AbortController(); | ||
| runSignal.signal.addEventListener("abort", () => { | ||
| try { | ||
| backend.disable(); | ||
| } catch {} | ||
| }); | ||
| const runId = opts.runId ?? `run_${opts.addressingKey}`; | ||
|
|
||
| const idle = | ||
| opts.idleTimeoutInSeconds !== undefined | ||
| ? { idleTimeoutInSeconds: opts.idleTimeoutInSeconds } | ||
| : {}; | ||
|
|
||
| const done = ( | ||
| runInMockTaskContext( | ||
| async (drivers) => { | ||
| drivers.locals.set(opts.modelLocal, opts.model); | ||
| const payload = opts.continuation | ||
| ? { | ||
| chatId: opts.addressingKey, | ||
| continuation: true, | ||
| metadata: {}, | ||
| ...idle, | ||
| ...(opts.previousRunId ? { previousRunId: opts.previousRunId } : {}), | ||
| } | ||
| : { chatId: opts.addressingKey, trigger: "preload", metadata: {}, ...idle }; | ||
| await runFn(payload, { ctx: drivers.ctx, signal: runSignal.signal }); | ||
| }, | ||
| { | ||
| ctx: { | ||
| run: { id: runId }, | ||
| ...(opts.attemptNumber !== undefined ? { attempt: { number: opts.attemptNumber } } : {}), | ||
| }, | ||
| sessionStreamManager: manager, | ||
| runtimeManager, | ||
| } | ||
| ) as Promise<void> | ||
| ).finally(restore); | ||
|
|
||
| return { | ||
| done, | ||
| close: async () => { | ||
| try { | ||
| await fetch( | ||
| `${opts.baseUrl}/realtime/v1/sessions/${encodeURIComponent(opts.addressingKey)}/in/append`, | ||
| { | ||
| method: "POST", | ||
| headers: { | ||
| Authorization: `Bearer ${opts.secretKey}`, | ||
| "Content-Type": "application/json", | ||
| "X-Part-Id": "close", | ||
| }, | ||
| body: JSON.stringify({ | ||
| kind: "message", | ||
| payload: { chatId: opts.addressingKey, trigger: "close" }, | ||
| }), | ||
| } | ||
| ); | ||
| } catch {} | ||
| runSignal.abort(); | ||
| await done.catch(() => {}); | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| export type ChatAgentSessionOptions = Omit< | ||
| RunRealChatAgentOptions, | ||
| "runId" | "continuation" | "previousRunId" | ||
| >; | ||
|
|
||
| export type ChatAgentSession = { | ||
| /** How many runs the session has spawned so far (1 fresh + N continuations). */ | ||
| runCount: () => number; | ||
| /** Close the currently-active run and stop spawning continuations. */ | ||
| close: () => Promise<void>; | ||
| }; | ||
|
|
||
| /** | ||
| * A session-scoped orchestrator that stands in for the run-engine's run | ||
| * lifecycle: it starts a run, and whenever that run exits on its own | ||
| * (`chat.endRun()` / `chat.requestUpgrade()`), spawns the next run as a | ||
| * continuation (new run id, `continuation: true`, `previousRunId` threaded) | ||
| * for the same session. That mirrors the server triggering a fresh run on the | ||
| * next append after the previous run went terminal, and lets each continuation | ||
| * restore prior history from the persisted snapshot. Runs never overlap: the | ||
| * next spawn is chained on the previous run's `done` (after its manager | ||
| * teardown), so the process-global managers are never installed twice at once. | ||
| */ | ||
| export function runChatAgentSession(opts: ChatAgentSessionOptions): ChatAgentSession { | ||
| let closed = false; | ||
| let index = 0; | ||
| let current: RunningAgent | undefined; | ||
| let previousRunId: string | undefined; | ||
|
|
||
| const spawn = () => { | ||
| index += 1; | ||
| const runId = `run_${opts.addressingKey}_${index}`; | ||
| current = runRealChatAgent({ | ||
| ...opts, | ||
| runId, | ||
| continuation: index > 1, | ||
| previousRunId, | ||
| }); | ||
| previousRunId = runId; | ||
| const settle = () => { | ||
| if (!closed) { | ||
| spawn(); | ||
| } | ||
| }; | ||
| current.done.then(settle, settle); | ||
| }; | ||
|
|
||
| spawn(); | ||
|
|
||
| return { | ||
| runCount: () => index, | ||
| close: async () => { | ||
| closed = true; | ||
| await current?.close(); | ||
| }, | ||
| }; | ||
|
ericallam marked this conversation as resolved.
|
||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 New S2 host env vars only steer the server half of the stream path
REALTIME_STREAMS_S2_ACCOUNT_URL/REALTIME_STREAMS_S2_BASIN_URLare consumed only by the webapp (apps/webapp/app/services/realtime/s2realtimeStreams.server.ts:123-126andstreamBasinProvisioner.server.ts:188,225). The task-side writer gets onlyX-S2-Basin,X-S2-Access-Tokenand (when set)X-S2-Endpointfrom#initializeStreamByName, so it resolves hosts from the@s2-dev/streamstorebuilt-in defaults (packages/core/src/v3/realtimeStreams/streamsWriterV2.ts:70-77). Consequence: an operator who overridesREALTIME_STREAMS_S2_BASIN_URL/ACCOUNT_URLwithout also settingREALTIME_STREAMS_S2_ENDPOINTgets a split brain — the server appends/reads on the overridden host while deployed tasks write to the library default. If the intent of these vars is "be able to follow an S2 host migration by config alone", the basin URL likely needs to be forwarded to the client (e.g. as a header the writer honours), otherwise they are effectively webapp-only knobs and the client still requires an SDK upgrade.Was this helpful? React with 👍 or 👎 to provide feedback.