Skip to content

chore(ts): migrate use-runtime-adapter to TypeScript#50

Merged
oxyc merged 1 commit into
mainfrom
chore/ts-runtime-adapter
May 29, 2026
Merged

chore(ts): migrate use-runtime-adapter to TypeScript#50
oxyc merged 1 commit into
mainfrom
chore/ts-runtime-adapter

Conversation

@oxyc

@oxyc oxyc commented May 29, 2026

Copy link
Copy Markdown
Member

PR 4 of 4 for #41 — the big one (~1500 LOC, the largest single migration). No behaviour change; pure type addition over the existing logic. Wires in the shared RuntimeEvent, UiMessage, WireMessage, UiContentPart, WireContentBlock, and SessionUsageSnapshot types from PR 3 (#49).

Hook signature

useAssistantRuntime() now returns a typed AssistantRuntimeHandle:

interface AssistantRuntimeHandle {
  runtime: ReturnType<typeof useExternalStoreRuntime>;
  loadConversation: (uuid: string) => Promise<void>;
  approveToolCall: (options?: { trustHost?: boolean }) => void;
  denyToolCall: () => void;
  pendingApprovals: PendingApproval[];
  undoableActions: Record<string, UndoableActionState>;
  undoAction: (toolCallId: string, auditId: number) => Promise<void>;
  retryToolCall: (toolCallId: string) => Promise<void>;
  retryingIds: Set<string>;
}

window.gdsAssistant

window.gdsAssistant is now declared via declare global with the GdsAssistantGlobal interface: localised config (restUrl, restBase, nonce, modelPricing) plus the two hooks we attach ourselves (sendChatMessage, openChat). No more silent as any access.

SSE event narrowing

The big for await (const event of parseSSE(...)) switch now narrows on event.type against the discriminated RuntimeEvent union. Each case's event.data is the precise per-event shape — event.data.input_tokens, event.data.tool_use_id, event.data.trustable_host etc. all resolve to real types. parseSSE itself returns AsyncGenerator<RuntimeEvent> rather than a loose {type, data} stream.

Type interop boundary

assistant-ui's ExternalStoreAdapter types onNew/onEdit with its own (narrower) AppendMessage shape. Our adapter accepts a superset (control messages, client-tool-result resumes, programmatic retry from a text string), so we cast at the useExternalStoreRuntime(adapter as …) boundary rather than weakening the internal OnNewMessage type. Comment explains why.

Consumers

app.jsx, components/Composer.jsx, components/SidePanels.jsx, components/Messages.jsx, components/Thread.jsx all import without an extension — webpack + TS resolve to the .ts file automatically. No caller changes needed in this PR.

Verification

  • npm run typecheck — clean
  • npm run build — bundle includes useAssistantRuntime, loadConversation, approveToolCall
  • npm run test:unit — 17/17 pass (restorePendingApprovalsFromHistory test still passes with its existing inputs)
  • npm run lint:js — only the pre-existing no-nested-ternary (line 973) warning carried over from the JS source

Closes #41

🤖 Generated with Claude Code

PR 4 of 4 for #41 — the big one. No behaviour change; pure type
addition over the existing logic. Renames
`use-runtime-adapter.js` → `use-runtime-adapter.ts` and wires in the
shared `RuntimeEvent`, `UiMessage`, `WireMessage`, `UiContentPart`,
`WireContentBlock`, and `SessionUsageSnapshot` types from PR 3.

## Hook signature

`useAssistantRuntime()` now returns a typed `AssistantRuntimeHandle`:

```ts
interface AssistantRuntimeHandle {
  runtime: ReturnType<typeof useExternalStoreRuntime>;
  loadConversation: (uuid: string) => Promise<void>;
  approveToolCall: (options?: { trustHost?: boolean }) => void;
  denyToolCall: () => void;
  pendingApprovals: PendingApproval[];
  undoableActions: Record<string, UndoableActionState>;
  undoAction: (toolCallId: string, auditId: number) => Promise<void>;
  retryToolCall: (toolCallId: string) => Promise<void>;
  retryingIds: Set<string>;
}
```

## Globals

`window.gdsAssistant` is now declared via `declare global` with the
`GdsAssistantGlobal` interface: localised config (`restUrl`, `restBase`,
`nonce`, `modelPricing`) plus the two hooks we attach ourselves
(`sendChatMessage`, `openChat`). Eliminates the silent `as any` access
pattern.

## SSE event narrowing

The big `for await (const event of parseSSE(...))` switch now narrows
on `event.type` against the discriminated `RuntimeEvent` union, so each
case's `event.data` is the precise per-event shape (no more `event.data
.input_tokens` reads against `mixed`). `parseSSE` itself returns
`AsyncGenerator<RuntimeEvent>` rather than a loose object stream.

## Type interop boundary

assistant-ui's `ExternalStoreAdapter` types `onNew`/`onEdit` with its
own (narrower) `AppendMessage` shape. Our adapter accepts a superset
(control messages, client-tool-result resumes), so we cast at the
`useExternalStoreRuntime(adapter as …)` boundary rather than weakening
the internal `OnNewMessage` type. Comment explains why.

## Test helper

`restorePendingApprovalsFromHistory` keeps its existing signature
(`WireMessage[] | unknown`) and the Jest unit test continues to pass
unchanged.

## Consumers

`app.jsx`, `components/Composer.jsx`, `components/SidePanels.jsx`,
`components/Messages.jsx`, `components/Thread.jsx` all import without
an extension — webpack + TS resolve to the `.ts` file automatically. No
caller changes needed.

## Verification

- `npm run typecheck` — clean
- `npm run build` — bundle includes `useAssistantRuntime`,
  `loadConversation`, `approveToolCall`
- `npm run test:unit` — 17/17 pass
- `npm run lint:js` — only the pre-existing `no-nested-ternary` /
  `no-unused-vars-before-return` warnings carried over from the JS
  source; same in `editor-bridge.ts`

## Wraps up #41

PR 1 (#47): setup + UndoContext smoke
PR 2 (#48): editor-bridge.ts
PR 3 (#49): types/runtime.ts shared module
PR 4 (this): use-runtime-adapter.ts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oxyc oxyc merged commit 17d3078 into main May 29, 2026
3 checks passed
@oxyc oxyc deleted the chore/ts-runtime-adapter branch May 29, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate runtime-adapter + editor-bridge to TypeScript

1 participant