feat: chat runtime - pause/resume, SSE transport, React bindings#5
Open
marslavish wants to merge 23 commits into
Open
feat: chat runtime - pause/resume, SSE transport, React bindings#5marslavish wants to merge 23 commits into
marslavish wants to merge 23 commits into
Conversation
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Builds on
feat/features-complete. Adds the chat-runtime layer on top of the redesigned core: pausable tool execution, an SSE-serializable run handle, a headless React hook, a Next.js reference demo, and shared test infrastructure.Summary
@agentic-kit/agent— pausable tools,AgentRunHandle(events /ReadableStream/ SSEResponse),maxSteps, decision lookup bytoolCallId.@agentic-kit/react(new package) —useChathook that POSTs to an SSE endpoint and folds events into messages, streaming snapshot, pending decisions, and executing tools.apps/nextjs-chat-demo(new) — Next.js App Router demo wiringagent.prompt(...).toResponse()touseChat, with a tool-approval UI.agentic-kit—injectDeferralResultshelper for the "user types instead of approving" flow;cross-fetchdropped in the OpenAI adapter in favor of nativefetch.tools/test/(scripted provider, SSE stub, fixtures), SSE parser tests, run-handle tests (443 LOC),useChattests (1011 LOC).What's New
@agentic-kit/agent— pause/resume + SSEdecisionJSON Schema. When the agent reaches a call with no attached decision, it emitstool_decision_pendingand stops. Attach the decision to the matchingtoolCallblock and callcontinue()to resume.AgentRunHandlereturned byprompt()/continue(), consumable exactly once as:await handle— run to completionhandle.events()— async iterator ofAgentEventshandle.toReadableStream()—ReadableStream<AgentEvent>handle.toResponse()— SSEResponseready to return from a Next.js / Hono / Express handlerparseSSEStream()exported from the package for clients consumingtoResponse().maxStepscap on model invocations per run (resets inprompt(), persists acrosscontinue());stopReason: 'completed' | 'max_steps'onagent_end.continue()and the underlying loop walk the message log backwards to find the most recent un-decidedtoolCallmatching a giventoolCallId, so callers may append unrelated messages between the pause and the response.@agentic-kit/react— new packageuseChat({ api, body?, initialMessages?, fetch?, on* }).messages,streamingMessage,isStreaming,pendingDecisions: ReadonlyMap<string, ToolDecisionPendingEvent>,executingToolCallIds: ReadonlySet<string>,error.send,sendMessages,setMessages(array or updater),respondWithDecision(toolCallId, value),abort().abort()finalizes any visible streamed text as an assistant message and drops orphantoolCallblocks so the next call doesn't re-pause.onMessage,onFinish,onDecisionPending,onToolExecutionStart/End,onError.runId. State lives in the message log.agentic-kit—injectDeferralResultsFor the case where the user types a new message while a tool is paused: synthesizes a stand-in
toolResultfor everytoolCallthat lacks both a decision and a paired result, so the server picks up a well-formed transcript.apps/nextjs-chat-demo/api/chat/route.tsconstructs anAgent, applies prior messages, and returnsagent.prompt(...).toResponse().useChatwithchat-input,chat-messages,tool-call-card,tool-approval-cardcomponents.Test infrastructure
tools/test/— repo-internal helpers (nopackage.json, imported via tsconfigpaths). Scripted provider, SSE stub, fixtures, shared index.pnpm teststays deterministic and offline.sse.test.ts(parser),run-handle.test.ts(443 LOC),use-chat.test.ts(1011 LOC underjsdom),inject-deferral-results.test.ts.@agentic-kit/reactis the only package onjsdom; everything else stays onnode.Cleanup
cross-fetchremoved from the OpenAI adapter — runtimes are expected to providefetch.sourceexport condition so workspace consumers can resolve TypeScript directly.Test Plan
pnpm install && pnpm build && pnpm testis green across packagesapps/nextjs-chat-demoboots, streams a chat turn, and a paused tool can be approved/denied viarespondWithDecisionsend()does not re-pauseinjectDeferralResultsflow: pause a tool, send a fresh user message instead of deciding, verify the next request carries synthesized stand-in results