跳至主要内容

REFERENCE

開發者參考

認證、API、SDK、Flow Agent 處理節點與參數取值的完整參考。

JavaScript SDK (@asgard-js/core)

@asgard-js/core JavaScript SDK 參考文件

@asgard-js/core 是框架無關的 JavaScript SDK,處理 SSE 串流連線、Channel 管理與 Conversation 狀態。適用於 Node.js、瀏覽器,以及不使用 @asgard-js/react 元件的場景(Headless、Vue、Svelte 等)。

使用 React?

若你用 React,通常不需要直接使用 @asgard-js/core。改用 @asgard-js/react 的 <Chatbot> 元件即可,詳見 React SDK 參考文件。

  1. 安裝​

    npm install @asgard-js/core
  2. 核心概念​

    類別說明
    AsgardServiceClient連線設定(botProviderEndpoint、apiKey、customHeaders)
    Channel一個對話頻道,透過 SSE 與 Bot Provider 通訊
    Conversation儲存訊息狀態的容器
  3. 基本使用​

    import { AsgardServiceClient, Channel, Conversation } from '@asgard-js/core';

    const client = new AsgardServiceClient({
    botProviderEndpoint:
    'https://api.asgard-ai.com/ns/{namespace}/bot-provider/{botProviderId}',
    });

    const conversation = new Conversation({ messages: new Map() });

    const channel = await Channel.reset({
    client,
    customChannelId: 'channel-123',
    conversation,
    statesObserver: (states) => {
    console.log('isConnecting:', states.isConnecting);
    console.log(
    'Messages:',
    Array.from(states.conversation.messages.values()),
    );
    },
    });

    await channel.sendMessage({ text: '你好!' });
  4. AsgardServiceClient 設定​

    參數型別必填說明
    botProviderEndpointstring是Bot Provider 的 base URL
    apiKeystring否API 金鑰(Direct Connect 模式)
    customHeadersRecord<string, string>否自訂 HTTP header
  5. 通道與檔案操作​

    AsgardServiceClient 除了建立連線之外,還提供這些方法:

    方法簽章說明
    uploadFile(file: File, customChannelId: string) => Promise<BlobUploadResponse>上傳檔案,回傳的 blob 資訊可以帶進後續訊息
    downloadChannelHomeFile(relativePath: string, customChannelId: string) => Promise<ChannelHomeDownloadResult>下載該 channel home 底下的檔案
    channelMetadata(customChannelId: string) => Promise<ChannelMetadata | null>查詢 channel 是否存在,不存在回傳 null
    suspendChannel(customChannelId: string, options?) => Promise<void>中止該 channel 上正在執行的 run
    deleteChannel(customChannelId: string) => Promise<void>刪除 channel
    detach(options: { timeoutMs: number }) => void停止接受新請求,等進行中的請求結束後才真正關閉
    close() => void立即關閉

    detach 和 close 的差別在於進行中的請求。close 直接關掉;detach 會標記為不再接受新請求,若當下沒有進行中的請求就立即關閉,否則等它們結束,適合頁面離開前的收尾。

    這些方法都需要設定 botProviderEndpoint,因為各自的端點是從它推導出來的。沒設定時會丟出說明是哪個端點推導失敗的錯誤。

  6. 訊息回饋​

    使用者對某一則回覆按讚或倒讚,透過 sendMessageFeedback 送出:

    import {
    FEEDBACK_COMMENT_MAX_BYTES,
    feedbackCommentByteLength,
    composeFeedbackMessage,
    } from '@asgard-js/core';

    await client.sendMessageFeedback({
    customChannelId,
    messageId, // 被評價的那則回覆的 messageId
    verdict: 'BAD', // 'GOOD' | 'BAD'
    comment: '答案跟我問的不是同一件事',
    });

    回傳的 messageId 是這筆回饋自己的 id,和被評價的那則回覆不同,另外還有 transcript 的 seq,重新加入 channel 時會在該位置重播成 asgard.message.feedback 事件。

    留言長度上限是 8 KiB 而不是 8192 字元​

    FEEDBACK_COMMENT_MAX_BYTES 是 8 * 1024,單位是 UTF-8 位元組。中文一個字佔三個位元組,所以用字元數檢查會讓一則伺服器會用 400 拒絕的留言通過。要用 feedbackCommentByteLength(comment) 量:

    if (feedbackCommentByteLength(comment) > FEEDBACK_COMMENT_MAX_BYTES) {
    // 提示使用者縮短
    }

    把回饋一併說給 AI 聽​

    composeFeedbackMessage(verdict, comment?) 組出要送進對話的後續訊息:開頭是該 verdict 對應的前綴,使用者有留言時空一行後接原文。

    composeFeedbackMessage('BAD', '答案跟我問的不是同一件事');
    // "[Response Feedback: Bad]\n\n答案跟我問的不是同一件事"

    RESPONSE_FEEDBACK_PREFIX 是那兩個前綴字串([Response Feedback: Good] / [Response Feedback: Bad])。它們是逐位元組的平台約定:後端的 system prompt 認得的就是這兩個字串,據此讓 agent 把這則訊息當成對前一則回覆的插話,簡短回應、必要時調整,然後繼續對話。不要自己改寫這段前綴。

  7. 錯誤處理​

    @asgard-js/core 匯出三個錯誤類別與各自的 type guard。它們都是 Error 的子類別,用 instanceof 或 guard 判斷皆可。

    類別Type guard何時丟出
    HttpErrorisHttpErrorHTTP 請求回傳非 2xx
    ChannelBusyErrorisChannelBusyError該 channel 上已有一個 run 在執行中
    ChannelAwaitingConsentErrorisChannelAwaitingConsentErrorchannel 停在工具呼叫的同意提示上
    import {
    isHttpError,
    isChannelBusyError,
    isChannelAwaitingConsentError,
    } from '@asgard-js/core';

    try {
    await channel.sendMessage({ text: input });
    } catch (error) {
    if (isChannelAwaitingConsentError(error)) {
    // 先回答同意提示,這一輪才送得出去
    } else if (isChannelBusyError(error)) {
    // 上一輪還沒結束,例如把送出按鈕維持在停用狀態
    } else if (isHttpError(error) && error.status === 429) {
    // 依 error.body 判斷是額度用完還是速率限制
    }
    }

    HttpError​

    帶著 status、statusText 與 body 三個唯讀屬性。body 是解析後的錯誤內容,實務上是分辨同一個狀態碼底下不同原因的依據,例如以 body 裡的 reason_code 區分 429 是額度用盡還是單純的速率限制。

    body 有可能是 undefined:回應不一定帶得動 JSON,解析失敗是正常結果而不是呼叫端的錯,所以讀之前要先確認。

    ChannelBusyError​

    Channel.sendMessage() 在該 channel 已有 run 執行中時會 reject 這個錯誤,屬性 runKind 說明是哪一種 run 佔著。

    run 在伺服器端是背景執行的,再送一個等於讓兩個 run 同時寫同一份對話記錄,所以這裡是明確拒絕而不是靜默丟棄,呼叫端才知道訊息根本沒送出去。

    SDK 內建的輸入框已經自己擋掉這種情況,所以這個錯誤實際上只會出現在你用程式送訊息的路徑上。

    ChannelAwaitingConsentError​

    Channel.sendMessage() 與 Channel.nudge() 在 channel 停在工具呼叫同意提示時會 reject 這個錯誤。伺服器把這種 channel 保持在暫停狀態,只有回覆同意才能繼續,其他任何一輪都會被拒絕,nudge 也一樣。

    這個暫停狀態偵測不到「run 執行中」:同意提示是在 run 結束之後才送達的,所以提示出現在畫面上時 run 早就結束,channel 看起來是閒置的。先用 Channel.replyToolCallConsents() 回答,再送下一輪。

    屬性 processId 是暫停批次的 process id,但重新加入既有 channel 時它會是空字串。重新載入後恢復的提示由後端從持久化的暫停狀態重建,那條路徑刻意不填 process id,因為判斷是否處於同意暫停要看提示存不存在,不是看這個 id。把它當診斷線索用,不要當 key。

  8. Sandbox 檔案系統​

    Agent 執行時掛載的 Sandbox 有一組檔案操作 API,全部以 sandboxName 加路徑定位:

    方法簽章
    sandboxFsList(sandboxName, path) => Promise<SandboxFsListResult>
    sandboxFsStat(sandboxName, path) => Promise<SandboxFsStatResult>
    sandboxFsRead(sandboxName, path, options?) => Promise<SandboxFsReadResult>
    sandboxFsWrite(sandboxName, path, ...) => Promise<void>
    sandboxFsMkdir(sandboxName, path) => Promise<void>
    sandboxFsRemove(sandboxName, path) => Promise<void>
    sandboxFsRemoveAll(sandboxName, path) => Promise<void>
    sandboxFsCopy(sandboxName, src, dst, ...) => Promise<void>
    sandboxFsMove(sandboxName, src, dst, ...) => Promise<void>
    sandboxFsWatch(sandboxName, path) => Observable<SandboxFsWatchEvent>

    sandboxFsWatch 回傳的是 RxJS Observable 而不是 Promise,訂閱後持續收到該路徑的變動事件,不用時要自行 unsubscribe。

    generateSandboxBrowserOpenUrl(sandboxName) 產生可在瀏覽器開啟該 Sandbox 的網址。

    resolveSandboxUri(uri) 把 Sandbox URI 解析成 SandboxUriIntent,無法解析時回傳 null。reconcileLaunched(metadata) 整理已啟動的 Sandbox 清單。

    React 端不必直接呼叫這些方法,createSandboxFsProviders 會把它們包成檔案總管需要的形狀,詳見 React SDK。

  9. SourceSet​

    AsgardSourceSetClient 存取 SourceSet 的內容。相關常數:

    常數值說明
    SOURCE_SET_MAX_PAGE_SIZE1000單次請求的筆數上限
    SOURCE_SET_DEFAULT_MAX_ENTRIES10000預設最多取回的筆數
    SOURCE_SET_VOLUME_ROOT''volume 根路徑,是空字串而不是 /

    assertVolumePath(path, options?) 驗證路徑合法,不合法時丟出錯誤。根路徑是空字串這點容易寫錯,用這個函式檢查而不要自己比對。

  10. 事件與衍生資料​

    fetchSse(payload, options?) 建立 SSE 連線,回傳 RxJS Subscription。rejoinSse(customChannelId, options?) 重新加入既有 channel 並重播 transcript。handleEvent(response) 把單一 SSE 事件餵進 client 的內部狀態,通常由 SDK 自己呼叫。

    從事件推導任務與子代理的工具:

    函式用途
    isTaskTool(call) / isAgentTool(call)判斷一次工具呼叫是否為任務 / 子代理
    isSubagentChildTool(parentToolUseId?)判斷是否為子代理底下的工具呼叫
    reduceTaskEvents(events)把 TaskToolEvent[] 收斂成 Task[]
    reduceSubagents(events)把 SubagentEvent[] 收斂成 Subagent[]
    conversationToSubagentEvents(messages)從對話訊息萃取子代理事件
    tasksEqual(a, b) / subagentsEqual(a, b)比較兩份清單是否相同,用於避免不必要的重繪
  11. Headless 衍生狀態(Task / Subagent / 標題)​

    0.3.x 起,@asgard-js/core 額外提供一組框架無關的衍生狀態 API,讓 Headless / Vue / Svelte 等消費端能在 <Chatbot> 之外自行渲染任務清單、子代理清單與對話標題:

    • Channel 上的響應式 store:channel.tasks$、channel.subagents$、channel.channelTitle$(RxJS Observable,只在該切片變動時發射),以及對應的快照 getter getTasks() / getSubagents() / getChannelTitle()。
    • 從 conversation$ 自行建立:createDerivedStores(conversation$) 回傳 { tasks$, subagents$, getTasks, getSubagents, teardown };或用 deriveTasks(conversation) / deriveSubagents(conversation) 做一次性推導。
    • 加入既有 channel(不清空歷史):Channel.restore(config, options?) 會先透過 client.channelMetadata()(GET /channel/metadata)判斷 channel 是否存在,存在則重播 server transcript(client.rejoinSse())而非送出 RESET_CHANNEL。
    React 消費端

    使用 React 時,直接用 @asgard-js/react 的 useTaskList / useSubagents / useChannelTitle hooks,它們已把上述 store 橋接進 useSyncExternalStore。

  12. Sandbox 瀏覽器​

    Sandbox 裡跑的瀏覽器可以嵌進你自己的畫面直接操作,不必另開分頁。

    createSandboxBrowserSession(sandboxName) 向 POST {botProviderEndpoint}/sandbox/{sandbox_name}/browser/session 取得一組串流憑證,回傳 { wsUrl, token }。拿到之後交給 createSandboxBrowserTransport 建立連線,畫面與輸入都走這條 WebSocket。

    這跟既有的 generateSandboxBrowserOpenUrl(sandboxName) 是兩條路,不是取代關係:後者給你一個一次性網址讓使用者在新分頁開啟,仍然保留作為退路;前者給你一個 socket,由 SDK 在你的頁面裡自己渲染。

    遠端桌面協定用的是 X11 keysym,不是瀏覽器的 KeyboardEvent.key,所以 core 也匯出一組轉換工具:

    匯出用途
    KEYSYM常用按鍵的 keysym 常數表
    charToKeysym(char)單一字元轉 keysym
    keyToKeysym(key)KeyboardEvent.key 轉 keysym
    mapModifierKeysym(...)處理 Shift/Ctrl/Alt 這類修飾鍵
    isPrintableKeysym(keysym)判斷是不是可列印字元

    滾輪事件各瀏覽器的單位不一致,normalizeWheel(event) 把它正規化;WHEEL_MAX 是單次滾動量的上限,WHEEL_THROTTLE_MS 是送出的節流間隔。遠端游標的影格用 decodeCursorFrame 解碼。

    這些低階工具在你用 React SDK 的 SandboxBrowserPanel 時都不必自己碰,詳見 React SDK。

  13. 互動式 Demo​

    @asgard-js/core 的 Headless 用法與事件處理範例:SDK Demo — Headless

  14. 延伸閱讀​

準備好開始了嗎?

想知道 Asgard 怎麼用在你的團隊?跟我們聊聊。

聯絡我們