可執行的瀏覽器安全邊界

從遠端客戶端使用託管執行

當 run event 跨越不受信任的傳輸邊界,或客戶端可能在工作持續時斷線,請安裝獨立的 remote package。

客製化深度

客戶端 protocol

託管深度

瀏覽器或遠端 JavaScript 執行環境

文件狀態

可執行參考範例

前置假設

  • 伺服器公開經授權的 run 生命週期,以及文件化的對外 payload contract。
  • 需要 reload 復原時,客戶端可以保存最後完整處理的 sequence。
  • 你的應用程式仍負責 timer、UI 狀態、重試呈現與產品結果處理。
Heddle 負責
  • 執行期驗證 run envelope 與宿主提供的 activity、result schema。
  • 游標推進、重複抑制、sequence gap failure、終結偵測與有界重試計算。
  • 選用的 fetch 生命週期、漸進式 SSE parsing、response 驗證、identity check 與 reader cleanup。
產品端負責
  • 哪些欄位可供遠端使用者存取,以及強制執行該 allowlist 的 schema。
  • 驗證 header、傳輸 timer、subscription handle、游標持久化、online/offline policy 與 retry UX。
  • 訊息、工具呈現、optimistic state、通知、錯誤與終結結果如何套用至產品。

只安裝瀏覽器安全套件

Shell
npm install @roackb2/heddle-remote zod

不要把 @roackb2/heddle 打包進瀏覽器。獨立的 remote package 不包含 Node Agent 執行環境、model provider、server、CLI 或 control plane。本教學使用 Zod;若改用其他同步 Standard Schema validator,請直接安裝該套件並調整 import。

定義公開事件 contract

TypeScript
import { z } from 'zod'
import { ConversationRunProtocolCodec } from '@roackb2/heddle-remote'

const protocol = new ConversationRunProtocolCodec({
  activity: z.object({
    type: z.string().min(1),
    text: z.string().optional(),
  }),
  result: z.object({
    outcome: z.string().min(1),
    summary: z.string(),
  }),
})

Validator 使用 Standard Schema 介面。Zod 3.24+、Zod 4、Valibot、ArkType 與相容 validator 都不需要 Heddle 專用 adapter。因為 parsing 與 serialization 在串流中同步發生,validation 必須是同步的。

Schema output 就是對外資料投影。Codec 會驗證你的選擇,但不會替你決定授權或清除 secret。一般 Zod object schema 會移除未知欄位;若使用其他 validator,請明確設定等效的 allowlist 行為。

Codec 會驗證非空 runId、正的 safe-integer sequence、ISO timestamp、activityresultcancellederror kind、宿主 payload,以及 JSON-safe value。無法安全跨越 JSON 的 bigint、function、symbol、非有限數值與 undefined 會被拒絕。

讓 consumer 負責游標正確性

以下 composition excerpt 假設 runId 來自已接受的 start response,而 restoredSequence 來自產品持久化的 UI state。完整 lifecycle 請參考頁尾的可執行範例。

TypeScript
import { ConversationRunConsumerService } from '@roackb2/heddle-remote'

const consumer = new ConversationRunConsumerService<{ runId: string }>({
  retry: { maxAttempts: 6, baseDelayMs: 500, maxDelayMs: 4_000 },
})

consumer.select({ runId }, { afterSequence: restoredSequence })

訂閱前呼叫 consumer.subscriptionInput()。每個通過驗證的 event 都要先呼叫 consumer.accept(event) 再 render。Accepted event 會推進 cursor;duplicate 會被忽略;其他 run 的 event 會被忽略;sequence gap 會拋出錯誤;result、cancellation 或 error 會標記 terminal;terminal 之後的 event 會被拒絕。

只有在應用程式成功處理對應事件後,才能保存 subscriptionInput().afterSequence。否則 reload 可能跳過使用者從未看見的資料。

若串流在 terminal 前斷線,請呼叫 consumer.nextRetry()。Heddle 會計算有界 delay 與標準 next cursor。你的應用程式負責實際 timer、abort controller、online/offline 行為與可見重試狀態。成功接受新事件後,attempt budget 會重設。

選用 REST/SSE 客戶端

若伺服器使用慣例 run resource,請匯入 preset:

這是 composition excerpt。StartRunResultSchemaCancelRunResultSchema 必須和 server 使用相同的 public schema;accessTokensessionIdprompt 與名為 subscriptionAbortController 則來自產品已驗證身分的流程。完整檔案請參考頁尾的 runnable browser client。

TypeScript
import { ConversationRunHttpSseClient } from '@roackb2/heddle-remote/http-sse'

const client = new ConversationRunHttpSseClient({
  baseUrl: '/api/agent',
  protocol,
  accepted: StartRunResultSchema,
  cancellation: CancelRunResultSchema,
  getHeaders: () => ({ Authorization: `Bearer ${accessToken}` }),
})

const accepted = await client.start({ sessionId, prompt })
await client.subscribe({
  runId: accepted.runId,
  afterSequence: consumer.subscriptionInput()?.afterSequence,
  signal: subscription.signal,
  onEvent(event) {
    const acceptance = consumer.accept(event)
    if (acceptance.accepted) renderProductEvent(event)
  },
})

Client 負責 URL encoding、header composition、HTTP error decoding、response validation、漸進式 SSE parsing、event identity check、reader cleanup 與 abort propagation,但不負責 reconnect timer 或 UI state。

使用 bearer authentication 的瀏覽器串流要用 fetch,因為原生 EventSource 無法附加 Authorization header。使用 cookie authentication 的產品可以使用 EventSource,讓瀏覽器在重新連線時傳送 Last-Event-ID

支援範圍

受維護的套件以 JavaScript 與 TypeScript 為目標。非 JavaScript 客戶端可以實作文件中的 wire envelope 與 sequence rule,但 Heddle 目前沒有為其他語言提供官方 client。這些整合應視為調整方案,不是可直接使用的支援。

權威來源