OneSayドキュメント

ドキュメント

JavaScriptクイックスタート

プレビュー版:機能とAPIは変更される場合があります。

PDFはブラウザーの印刷ダイアログで作成します。ホスト型のエクスポートサービスではありません。

明示的な音声入力とタスクの結果が必要な場合はタスク音声を、ライブのパブリケーションとサブスクリプションが必要な場合はリアルタイム音声(RTC)を、ルームでAgentと音声で会話する場合はOneSay Agentを選んでください。いずれもOneSay JavaScript SDKを使いますが、ライフサイクルは別々です。SDKはプレビュー版で、まだ公開されていません。

JavaScript + タスク音声#

まず、下記の公開タスクサンプルと、そのコンシューマーレイアウトから始めてください。このサンプルはrunPublicTaskSample(input, ports)(examples/javascript-quickstart/public-task.mjs)をエクスポートします。アプリケーション側のアドミッション(接続の許可)には、runAuthorizedTaskSample(input, ports)(examples/javascript-quickstart/authorized-task.mjs)が公開APIと明示的な同意の判断を組み合わせます。その入力は認可の手順に記載しています。

SDKが公開されるまでは、@onesay/sdkをプレビュー用に提供されるパッケージアーカイブ(.tgz)からインストールします。これをpackage.jsonの隣に置き、file:参照で依存関係に指定し(examples/javascript-quickstart/package.jsonと同様)、pnpm install --ignore-scriptsでインストールします。現在のプロジェクト/環境とソースのバインディング、SDKのトークンプロバイダーを通じた短期の認証情報、キャプチャ済みの16 kHzモノラルPCM s16leを渡してください。タスククライアントはWSS/OSAFのフレーミング、シーケンス、ACK、バックプレッシャー、リプレイ/再開、キャンセルを担います。音声のキャプチャやマイクの許可要求は行いません。タスク用に作成したルームでは、RTCセッションもAgentも開始しません。

タスク音声のセッションは次の順序で進みます。

  1. Testのプロジェクト環境を選びます。
  2. ブラウザーでは、tokenProviderを通じて短期のアクセストークンを提供します。osp_*認証情報は決して埋め込まないでください。
  3. 対象のルームとプロジェクト環境用の使い捨てのリアルタイムチケットを、Control APIから取得します。
  4. onesay.realtime.v1で接続し、Audio SignalStreamを開いて、アプリケーションがキャプチャした16 kHzモノラルPCM s16leフレームを送信します。ブラウザーでは、AudioWorkletがキャプチャ手段の選択肢の1つです。
  5. サーバーはtask.startedを送信し、音声の送信中はaudio.ackとartifact.deltaを送信します。各フレームは、連続したaudio.ackがそれをカバーするまで保持します。
  6. signal.endを送信します。サーバーはsignal.ended、artifact.finalized、task.completedを返します。このタスクの終了状態を待ちます。

チケットは1回しか使用できません。同じチケットで2つ目の接続を行うと、1つ目の接続がまだ開いていても、クローズコード1008 ticket_invalidで拒否されます。代わりに新しいチケットを要求してください。

コンソールでタスク音声を試す#

コードを書く前にタスク音声のディクテーションを確かめたい場合は、Stagingでコンソールにサインインし、「タスク音声」セクションを開いてください。マイクのソースと言語を選んで話すと、届いた文字がその場で表示され、停止すると最終的な文字と使用量が表示されます。このセクションは上記のサンプルと同じチェーンを実行します。つまり、サインイン中のIDのもと、選択したプロジェクトと環境で、SDKのタスククライアントを使い、タスク用のWSS/OSAF経由でOneSayのディクテーションタスクを実行します。ページ自身が担うのは、マイクを16 kHzのPCMフレームとして取り込むことだけです。

作成・送信・終了を伴う各ステップ(ルーム、マイクの信号、ディクテーションのキャンセル、ルームを閉じる操作)は、まず同意のリクエストとして準備され、ページで許可した後にだけ実行されます。拒否すると実行されません。サーバーは各アクションをあらためて確認するため、ページ自体が権限を与えることはありません。各ディクテーションはトライアルの音声の利用枠を使い、パネルはタスクの音声上限に達する前に自動で停止します。停止する代わりにディクテーションをキャンセルすることもでき、終わったら退出してルームを閉じます。

このセクションはStaging専用で、トライアルに登録されたアカウントだけが利用できます。チェーンの動作を示すものであり、認識品質を測るものではありません。

JavaScript + リアルタイム音声(RTC)#

examples/javascript-rtc/にあるブラウザーのサンプルを使います。そのsample.mjsはcreateRTCExampleをエクスポートし、ページはインストール済みの@onesay/sdkパッケージと、公開のclient.rtc.connectファサードを使います。ディレクトリをコピーし、SDKのパッケージアーカイブを、そのpackage.jsonのfile:参照が想定するファイル名で隣に置いてから、インストールしてローカルサーバーを起動します。サーバーはページのローカルURLを表示します。

Text
pnpm install --ignore-scripts
pnpm start

Ctrl+Cでサーバーを停止します。サンプルはAPIプロキシを作成しません。すべての操作例はexamples/javascript-rtc/README.mdにあります。

ページのSDK環境はstagingです。接続するには、使用が認可されている環境、Control CORSで許可されたアプリケーションのオリジン、既存のルーム/AppSession/ソースのリソース、人間のユーザーまたはAppSessionの短期トークンが必要です。ページはトークンをメモリ内に保持します。Platform APIキーやプロバイダーキーは決して指定しないでください。

明示的な初期受信セットを指定して接続し、名前付きのコントロールを通じてパブリケーションと再生を要求します。サブスクリプションが空の場合、すべての受信が拒否されます。RTCの接続はタスクもAgentも作成しません。Agentの作成と停止は、別々の認可済みオペレーションです。セッションをローカルでクローズしても、サーバーが接続を取り消したことの確認にはなりません。

JavaScript + OneSay Agent#

OneSay Agentは、ルームに参加する音声の参加者です。選択したパブリケーションを聞き取り、音声で応答します。応答中に話しかければ割り込めます。Agentは話すのをやめて聞き取りに戻ります。コードを書かずに試すには、コンソールにサインインし、「音声通話トライアル」のセクションを使います(Stagingのみ。アカウントでトライアルが有効になっている必要があります)。

コードでは、RTCの節と同じようにルームに接続してマイクを公開し、明示的な入力セットと時間を指定してAgentを開始します。

TypeScript
const idempotencyKey = crypto.randomUUID();
const agent = await client.rtc.agents.create({
  roomId, appSessionId, mode: "realtime", publicationIds: [microphonePublicationId],
  maxDurationSeconds: 120, idempotencyKey,
});

作成、変更、終了を伴う呼び出しには、ユーザーの確認(同意)が必要です。ユーザーが確認するまで、サーバーはconsent_requiredを返します。確認後、同じidempotencyKeyで再試行してください。

冪等キーは保持してください。応答が失われても、同じキーで同じ呼び出しを行えば、別のAgentを開始せずに同じAgentが返ります。client.rtc.agents.get(roomId, agent.agentExecutionId)でAgentを読み取ります(2秒に1回まで)。playback.facts.outputPublicationIdが出力を示したら、そのパブリケーションをサブスクライブすると音声が聞こえます。

observedState意味
pending受け付け済み。Agentはまだ動作していません
runningAgentが開始しています
failed停止する前にAgentが自ら終了しました
unknownAgentの状態をOneSayが確認できません(報告が途絶えた、停止後に終了した、または最長時間に達した)
stopped停止が確認されました

2026-10-26時点のStagingは、Agentが動作している間(停止を要求した後も)runningを返し、Agentが終了するとunknownを返します(停止の要求より前に自ら終了した場合はfailed)。遅れて届いた報告で、Agentが先に自ら終了していたとわかった場合、unknownがfailedに変わることがあります。OneSayはまだstoppedを返しません。effectiveStoppedAtがnullの場合、停止はまだ確認されていません。

desiredStateは要求した状態、observedStateはOneSayが観測した状態です。聞いている間、RTCセッションのaudio.agentStatesはAgentの出力をinitializing、idle、listening、thinking、speaking、unknownのいずれかとして報告します。これは画面表示のための低頻度のヒントであり、Agentが何をしたかの証明ではありません。停止するにはclient.rtc.agents.stop(roomId, agentExecutionId, { idempotencyKey })を呼び出します。停止が受け付けられても、それは要求であり、Agentが退出したことの証明ではありません。

2026-10-26時点のStaging(標準のトライアル):1回の会話は最長120秒で、コストの予算があります。割り込まれた応答も同じように予算を使います。アカウントで何回会話できるかは、回数ではなく無料の利用枠で決まります。新しいアカウントの自動登録はまだ有効になっていません。より長い会話は実装済みで、今後のStaging更新で提供されます。その場合、非常に長い応答は早めに打ち切られます(早くても約30秒話した後)。

インストール済みパッケージを使う公開タスクサンプル#

エントリはrunPublicTaskSample(input, ports) (../../examples/javascript-quickstart/public-task.mjs)です。公開の@onesay/sdkエントリをインポートします。ID、プロジェクト環境、ルーム、ソース、同意、グラントのプロビジョニングは行わず、接続チケットを自ら発行することもありません。呼び出し元は、認可済みのリソースIDと、対象のルームとプロジェクト環境用に新たに発行された使い捨てチケットをあらかじめ保持している必要があります。ルームの権限は、サーバーが発行したそのチケットが担います。この関数は別個のroomId引数を受け取らず、自身を認可するためにチケットをデコードすることもありません。

コンシューマーレイアウトを準備する#

タスク音声の説明どおりに@onesay/sdkをインストールした、独立したプロジェクトを使います。ワークスペースエイリアスで置き換えたり、宣言された依存関係を削除したりしないでください。

public-task.mjsは./support/から2つのサポートモジュールをインポートします。pcm-session.mjsはサンプルのPCMをフレーム化し、ACK、バックプレッシャー、リプレイを観測します。task-usage-readback.mjsは公開APIを通じてタスクとその使用量を読み戻します。これらをsupport/フォルダーに入れ、public-task.mjsと同じディレクトリに置きます。

Text
consumer/
  package.json
  node_modules/@onesay/sdk/
  public-task.mjs
  support/
    pcm-session.mjs
    task-usage-readback.mjs

エクスポートされた関数を呼び出す#

次のコードは、public-task.mjsの隣に置くアプリケーションのコードです。パラメーターは認可されたアプリケーションが渡す値で、トークンを埋め込んだり出力したりすることはありません。下記のobserveAuthorizedTaskはこのガイド用のラッパーであり、OneSay SDKの追加のエクスポートではありません。

JavaScript
import { buildRealtimeConnectPlan } from "@onesay/sdk";
import { runPublicTaskSample } from "./public-task.mjs";

export async function observeAuthorizedTask({
  projectId,
  environmentId,
  sourceId,
  issuedSingleUseTicket,
  signal,
  ports,
}) {
  return runPublicTaskSample(
    {
      projectId,
      environmentId,
      sourceId,
      plan: buildRealtimeConnectPlan("staging", issuedSingleUseTicket),
      signal,
      timeoutMs: 5000,
    },
    ports,
  );
}

buildRealtimeConnectPlan(environment, ticket)は公開SDK関数です。WSS接続を計画し、チケットの交換は行いません。サンプルはStagingのControl環境から使用量を読み戻します。本番環境のエンドポイント設定ではありません。使用が認可されている環境に対してのみ実行してください。

4つのポートはすべて必須のアプリケーションコールバックであり、SDKメソッドではありません。

ポートコールバックの形と責務
webSocketFactory(url, protocols, options) => socket。SDK互換のWebSocketトランスポートを返します。指定された接続とサイズの上限に従い、リダイレクトを無効にし、証明書/ホスト名の検証を緩めないでください。
confirmedControlsFactory(client, { signal }) => controls、またはcontrolsのPromise。公開コントラクトを通じて、アプリケーションの明示的なグラント/同意の選択を組み込みます。既定で承認しないでください。
readbackTokenProvider({ signal }) => shortLivedToken、またはそのトークンのPromise。要求されたスコープでタスクと使用量の読み取りが認可された、現在のプリンシパルを渡します。長期のPlatformシークレットやプロバイダーシークレットはここに含めません。
readbackTransport(url, init) => Promise<Response>。Fetch互換のトランスポートを提供し、init.signalに従い、SDKのヘッダー、リダイレクト禁止ポリシー、スコープを維持します。

confirmedControlsFactoryが返すcontrolsは、次の形を実装する必要があります。

Text
send(type, data, messageId, { signal })
flush({ signal })
observe(event, { signal })

sendとflushはPromiseを返してもかまいません。observeは同期的なイベントオブザーバーです。同意の選択は、サーバー権限に裏付けられた、アプリケーションの明示的な判断です。これらのメソッドを返しても許可は付与されません。サーバーは引き続き、チケット、ソース、アクション、グラント、同意を検証します。

PCM、完了、失敗時の動作#

このサンプルは、16 kHzで640サンプルのPCM16LEモノラルフレームをちょうど2つ生成します。これは80 msの固定の合成入力です。上限付きのバックプレッシャーを意図的に試し、SDKを通じて最初の未確認フレームをリプレイします。重複したリプレイは、新しい音声入力でも2つ目のタスクでもありません。サンプルには、マイクのキャプチャ、AudioWorklet、OSへの依存、メディアの許可要求はありません。

ライブ音声を録音するアプリケーションでは、キャプチャと許可は呼び出し元の責務です。AudioWorkletは、任意の取得手段の1つです。SDKに音声キャプチャのエクスポートはありません。タスクのPCMに関するこの境界は、RTCのpublishMicrophoneの動作を変えません。

サンプルは、連続したACK、対応するタスクとアーティファクトの終了、上限付きの公開タスク/使用量の読み戻しを待ち、SDKがトランスポートのクローズを観測した後にのみ結果を返します。結果に含まれるのは件数だけです。pcmは送信、確認、リプレイされたフレーム数、クライアントのバックプレッシャーによる拒否、破棄されたフレーム数、ACK遅延のパーセンタイルを報告し、readbackは観測したタスク数、スキャンした使用量イベント数、読み取ったページ数を報告します。音声、文字起こし、チケット、トークンは含まれず、課金明細でもありません。

HTTP 401/403が返ると、この呼び出しはそこで終了し、自動では再試行しません。使用量の欠落、重複、スコープ外、タスク/アーティファクトの相関の誤り、ACKの欠落、呼び出し元によるキャンセル、期限切れ、クリーンアップの失敗は、呼び出しを失敗(reject)させます。再度呼び出す前に、アプリケーションの明示的なフローを通じて新しい認可を取得してください。消費済みのチケットをリプレイしないでください。

timeoutMsは操作全体を制限し、クローズ待ちも別途制限します。サンプルは、非同期のfactory/control/token/HTTPポートに中断(abort)を伝播します。コールバックは指定されたシグナルに従い、自身のリソースを解放する必要があります。待機に上限があっても、協調しないコールバックを強制的に停止することも、その外部への副作用を元に戻すこともできません。同期コールバックも、イベントループをブロックしないようにする必要があります。失敗を診断するために、計画のURL、チケット、トークン、生のPCM、文字起こしをログに出力しないでください。

既存の公開APIを通じたアプリケーションの認可#

runAuthorizedTaskSample(input, ports)(examples/javascript-quickstart/authorized-task.mjs)は、独自に接続情報を取得し、保護されたオペレーションを明示的に確認するアプリケーションのサンプルです。これはrunPublicTaskSampleを組み合わせたもので、SDKのタスクライフサイクル、ACK、リプレイ、バックプレッシャー、キャンセルのロジックを置き換えるものではありません。これはサンプルのエクスポートであり、新しいOneSay SDK APIではありません。

authorized-task.mjsはpublic-task.mjsの隣に置きます。これはさらに1つのサポートモジュールsupport/confirmed-controls-core.mjsをインポートします。これは、保護された各コマンドを確認が完了するまで保持する上限付きのキューで、自ら承認することはありません。

JavaScript
import { runAuthorizedTaskSample } from "./authorized-task.mjs";

export async function runApplicationTask(resources, signal, ports) {
  return runAuthorizedTaskSample({
    organizationId: resources.organizationId,
    projectId: resources.projectId,
    environmentId: resources.environmentId,
    deviceId: resources.deviceId,
    roomId: resources.roomId,
    sourceId: resources.sourceId,
    signal,
    timeoutMs: 60000,
    delegationTtlSeconds: 120,
  }, ports);
}

すべてのリソースは、対象の環境にあらかじめ存在している必要があります。プロビジョニング、ログイン、デバイスの登録、認証情報、ケイパビリティポリシーは、引き続きアプリケーションの既存の認可済みフローが担います。このサンプルは、グラントの作成、権限の変更、自身へのアクセス付与を行いません。

アプリケーションのポート責務
humanTokenProvider({ signal })公開の読み取り、委任の管理、対話的な同意の判断について認可された、現在有効な人間のユーザーの短期トークンを渡します。
deviceTokenProvider({ signal })対象のデバイス用に、これとは別の、現在有効な短期のデバイストークンを渡します。人間のユーザーのトークンで代用したり、デバイスの長期ブートストラップシークレットを渡したりしないでください。
confirmConsent(snapshot, { signal })検証済みで不変のサーバーの提示内容を表示し、ユーザーが明示的に判断した後に"allow"または"deny"のみを返します。キャンセルに従ってください。決して既定で許可(Allow)にしないでください。
fetch(url, init)SDKのリクエストヘッダー、スコープ、中断シグナル、リダイレクト制限を維持します。
webSocketFactory(url, protocols, options)証明書/ホスト名のチェックと上限付きの接続オプションを備えた、互換トランスポートを供給します。

アプリケーションは、これらのコールバックを実装する必要があります。確認コールバックが提供するのはUXのみです。互換性のあるサードパーティのクライアントも、まったく同じサーバー側のチェックを受けます。対話的な同意には、サーバーが認識する、信頼された署名付きOIDCクライアントIDも必要です。オーナーであること、"allow"を返すこと、Originヘッダーを偽造することでは、これに代えられません。このNodeサンプルはOriginヘッダーを追加しません。アプリケーションのブラウザーログインとCSRFの要件は、引き続き確立済みのブラウザーフローが担います。

このサンプルは、公開のControlオペレーションを通じて/meと、プロジェクト、環境、デバイス、ルーム、ソースを読み取ります。これらの読み取りは人間のユーザーのトークンで行い、接続はデバイストークンで要求します。参加後、サンプルは観測した参加者を読み取り、保護されたアクションを送信する前に、それが設定済みのデバイスに属していることを確認します。これらの観測は、サーバーによる現在の認可チェックに代わるものではありません。

保護された各コマンドについて、サンプルは委任管理のアクションとデバイスのアクションを、それぞれ厳密に特定したうえで明示的に確認します。どちらの確認も、公開の同意ヘルパーと、サーバーのスナップショット、ダイジェスト、文言バージョン、実行バインディングを使用します。呼び出しごとのコマンドIDは、確認時と実際の送信時で同一に保たれます。確認が拒否された場合、期限切れの場合、変更された場合、または一致しない場合は、呼び出しが停止します。

接続レスポンスのwebsocket_urlには、短期チケットが含まれます。レスポンスに別個のticketやAppSessionのフィールドはありません。サンプルはbuildRealtimeConnectPlanを通じてURLを検証し、書き換えは行いません。サンプルはStaging環境を使用します。

結果は、タスクサンプルの結果にauthorization_observationを加えたものです。どの公開チェックが行われたか、作成した委任の数(retained_delegations)、要求したTTL(requested_ttl_seconds)を示します。認可の証明書ではありません。クライアントはトランスポートのクリーンアップを待ちますが、サーバーで作成された委任は、上限付きの有効期限が切れるか、別途確認された取り消しが行われるまで残ります。クライアントのクローズによって委任が取り消されることはありません。要求する委任のTTLは、既定値も最大値も120秒です。クリーンアップの手順が取り消しを自動的に承認することはありません。

通常のID、スコープ、HTTP 401/403、同意の拒否、有効期限切れ、期限超過、トランスポートの失敗は、自動再試行なしで呼び出しを失敗(reject)させます。公開エラーは固定の段階メッセージを使い、サポートされるSDKエラーのメタデータだけを保持します。元の原因、トークン、チケットURL、リソースID、提示内容、プロバイダーの詳細は含めません。次の明示的なアクションは、上限付きの操作のアプリケーションコンテキストに基づいて判断してください。トランスポートエラーや認証情報を決してダンプしないでください。

認可とタスクの操作全体は、呼び出し元のキャンセルと期限を共有します。サンプルは、クローズ待ちを別途制限します。ポートは中断に従う必要があります。待機に上限があっても、すでに受け付けられたサーバーオペレーションを元に戻すことも、協調しないコールバックを強制的に停止することもできません。委任の作成後に失敗した場合でも、その上限付きのTTLは引き続き適用されます。失敗によって委任が取り消されることはありません。

元のMarkdownを表示
# JavaScriptクイックスタート

明示的な音声入力とタスクの結果が必要な場合は[タスク音声](#javascript-task-voice)を、ライブのパブリケーションとサブスクリプションが必要な場合は[リアルタイム音声(RTC)](#javascript-realtime-rtc)を、ルームでAgentと音声で会話する場合は[OneSay Agent](#javascript-onesay-agent)を選んでください。いずれもOneSay JavaScript SDKを使いますが、ライフサイクルは別々です。SDKはプレビュー版で、まだ公開されていません。

## JavaScript + タスク音声

まず、下記の[公開タスクサンプル](#public-task-sample-using-the-installed-package)と、そのコンシューマーレイアウトから始めてください。このサンプルは`runPublicTaskSample(input, ports)`(`examples/javascript-quickstart/public-task.mjs`)をエクスポートします。アプリケーション側のアドミッション(接続の許可)には、`runAuthorizedTaskSample(input, ports)`(`examples/javascript-quickstart/authorized-task.mjs`)が公開APIと明示的な同意の判断を組み合わせます。その入力は[認可の手順](#application-authorization-through-existing-public-apis)に記載しています。

SDKが公開されるまでは、`@onesay/sdk`をプレビュー用に提供されるパッケージアーカイブ(`.tgz`)からインストールします。これを`package.json`の隣に置き、`file:`参照で依存関係に指定し(`examples/javascript-quickstart/package.json`と同様)、`pnpm install --ignore-scripts`でインストールします。現在のプロジェクト/環境とソースのバインディング、SDKのトークンプロバイダーを通じた短期の認証情報、キャプチャ済みの16 kHzモノラルPCM s16leを渡してください。タスククライアントはWSS/OSAFのフレーミング、シーケンス、ACK、バックプレッシャー、リプレイ/再開、キャンセルを担います。音声のキャプチャやマイクの許可要求は行いません。タスク用に作成したルームでは、RTCセッションもAgentも開始しません。

タスク音声のセッションは次の順序で進みます。

1. Testのプロジェクト環境を選びます。
2. ブラウザーでは、`tokenProvider`を通じて短期のアクセストークンを提供します。`osp_*`認証情報は決して埋め込まないでください。
3. 対象のルームとプロジェクト環境用の使い捨てのリアルタイムチケットを、Control APIから取得します。
4. `onesay.realtime.v1`で接続し、Audio SignalStreamを開いて、アプリケーションがキャプチャした16 kHzモノラルPCM s16leフレームを送信します。ブラウザーでは、AudioWorkletがキャプチャ手段の選択肢の1つです。
5. サーバーは`task.started`を送信し、音声の送信中は`audio.ack`と`artifact.delta`を送信します。各フレームは、連続した`audio.ack`がそれをカバーするまで保持します。
6. `signal.end`を送信します。サーバーは`signal.ended`、`artifact.finalized`、`task.completed`を返します。このタスクの終了状態を待ちます。

チケットは1回しか使用できません。同じチケットで2つ目の接続を行うと、1つ目の接続がまだ開いていても、クローズコード1008 `ticket_invalid`で拒否されます。代わりに新しいチケットを要求してください。

### コンソールでタスク音声を試す

コードを書く前にタスク音声のディクテーションを確かめたい場合は、Stagingで[コンソール](../developer/CONSOLE.md)にサインインし、「タスク音声」セクションを開いてください。マイクのソースと言語を選んで話すと、届いた文字がその場で表示され、停止すると最終的な文字と使用量が表示されます。このセクションは上記のサンプルと同じチェーンを実行します。つまり、サインイン中のIDのもと、選択したプロジェクトと環境で、SDKのタスククライアントを使い、タスク用のWSS/OSAF経由でOneSayのディクテーションタスクを実行します。ページ自身が担うのは、マイクを16 kHzのPCMフレームとして取り込むことだけです。

作成・送信・終了を伴う各ステップ(ルーム、マイクの信号、ディクテーションのキャンセル、ルームを閉じる操作)は、まず同意のリクエストとして準備され、ページで許可した後にだけ実行されます。拒否すると実行されません。サーバーは各アクションをあらためて確認するため、ページ自体が権限を与えることはありません。各ディクテーションはトライアルの音声の利用枠を使い、パネルはタスクの音声上限に達する前に自動で停止します。停止する代わりにディクテーションをキャンセルすることもでき、終わったら退出してルームを閉じます。

このセクションはStaging専用で、トライアルに登録されたアカウントだけが利用できます。チェーンの動作を示すものであり、認識品質を測るものではありません。

## JavaScript + リアルタイム音声(RTC)

`examples/javascript-rtc/`にあるブラウザーのサンプルを使います。その`sample.mjs`は`createRTCExample`をエクスポートし、ページはインストール済みの`@onesay/sdk`パッケージと、公開の`client.rtc.connect`ファサードを使います。ディレクトリをコピーし、SDKのパッケージアーカイブを、その`package.json`の`file:`参照が想定するファイル名で隣に置いてから、インストールしてローカルサーバーを起動します。サーバーはページのローカルURLを表示します。

```text
pnpm install --ignore-scripts
pnpm start
```

Ctrl+Cでサーバーを停止します。サンプルはAPIプロキシを作成しません。すべての操作例は`examples/javascript-rtc/README.md`にあります。

ページのSDK環境は`staging`です。接続するには、使用が認可されている環境、Control CORSで許可されたアプリケーションのオリジン、既存のルーム/AppSession/ソースのリソース、人間のユーザーまたはAppSessionの短期トークンが必要です。ページはトークンをメモリ内に保持します。Platform APIキーやプロバイダーキーは決して指定しないでください。

明示的な初期受信セットを指定して接続し、名前付きのコントロールを通じてパブリケーションと再生を要求します。サブスクリプションが空の場合、すべての受信が拒否されます。RTCの接続はタスクもAgentも作成しません。Agentの作成と停止は、別々の認可済みオペレーションです。セッションをローカルでクローズしても、サーバーが接続を取り消したことの確認にはなりません。

## JavaScript + OneSay Agent

OneSay Agentは、ルームに参加する音声の参加者です。選択したパブリケーションを聞き取り、音声で応答します。応答中に話しかければ割り込めます。Agentは話すのをやめて聞き取りに戻ります。コードを書かずに試すには、[コンソール](../developer/CONSOLE.md)にサインインし、「音声通話トライアル」のセクションを使います(Stagingのみ。アカウントでトライアルが有効になっている必要があります)。

コードでは、RTCの節と同じようにルームに接続してマイクを公開し、明示的な入力セットと時間を指定してAgentを開始します。

```ts
const idempotencyKey = crypto.randomUUID();
const agent = await client.rtc.agents.create({
  roomId, appSessionId, mode: "realtime", publicationIds: [microphonePublicationId],
  maxDurationSeconds: 120, idempotencyKey,
});
```

作成、変更、終了を伴う呼び出しには、ユーザーの確認(同意)が必要です。ユーザーが確認するまで、サーバーは`consent_required`を返します。確認後、同じ`idempotencyKey`で再試行してください。

冪等キーは保持してください。応答が失われても、同じキーで同じ呼び出しを行えば、別のAgentを開始せずに同じAgentが返ります。`client.rtc.agents.get(roomId, agent.agentExecutionId)`でAgentを読み取ります(2秒に1回まで)。`playback.facts.outputPublicationId`が出力を示したら、そのパブリケーションをサブスクライブすると音声が聞こえます。

| `observedState` | 意味 |
|---|---|
| `pending` | 受け付け済み。Agentはまだ動作していません |
| `running` | Agentが開始しています |
| `failed` | 停止する前にAgentが自ら終了しました |
| `unknown` | Agentの状態をOneSayが確認できません(報告が途絶えた、停止後に終了した、または最長時間に達した) |
| `stopped` | 停止が確認されました |

2026-10-26時点のStagingは、Agentが動作している間(停止を要求した後も)`running`を返し、Agentが終了すると`unknown`を返します(停止の要求より前に自ら終了した場合は`failed`)。遅れて届いた報告で、Agentが先に自ら終了していたとわかった場合、`unknown`が`failed`に変わることがあります。OneSayはまだ`stopped`を返しません。`effectiveStoppedAt`がnullの場合、停止はまだ確認されていません。

`desiredState`は要求した状態、`observedState`はOneSayが観測した状態です。聞いている間、RTCセッションの`audio.agentStates`はAgentの出力を`initializing`、`idle`、`listening`、`thinking`、`speaking`、`unknown`のいずれかとして報告します。これは画面表示のための低頻度のヒントであり、Agentが何をしたかの証明ではありません。停止するには`client.rtc.agents.stop(roomId, agentExecutionId, { idempotencyKey })`を呼び出します。停止が受け付けられても、それは要求であり、Agentが退出したことの証明ではありません。

2026-10-26時点のStaging(標準のトライアル):1回の会話は最長120秒で、コストの予算があります。割り込まれた応答も同じように予算を使います。アカウントで何回会話できるかは、回数ではなく無料の利用枠で決まります。新しいアカウントの自動登録はまだ有効になっていません。より長い会話は実装済みで、今後のStaging更新で提供されます。その場合、非常に長い応答は早めに打ち切られます(早くても約30秒話した後)。

## インストール済みパッケージを使う公開タスクサンプル

エントリは[`runPublicTaskSample(input, ports)`](../../examples/javascript-quickstart/public-task.mjs)です。公開の`@onesay/sdk`エントリをインポートします。ID、プロジェクト環境、ルーム、ソース、同意、グラントのプロビジョニングは行わず、接続チケットを自ら発行することもありません。呼び出し元は、認可済みのリソースIDと、対象のルームとプロジェクト環境用に新たに発行された使い捨てチケットをあらかじめ保持している必要があります。ルームの権限は、サーバーが発行したそのチケットが担います。この関数は別個の`roomId`引数を受け取らず、自身を認可するためにチケットをデコードすることもありません。

### コンシューマーレイアウトを準備する

[タスク音声](#javascript-task-voice)の説明どおりに`@onesay/sdk`をインストールした、独立したプロジェクトを使います。ワークスペースエイリアスで置き換えたり、宣言された依存関係を削除したりしないでください。

`public-task.mjs`は`./support/`から2つのサポートモジュールをインポートします。`pcm-session.mjs`はサンプルのPCMをフレーム化し、ACK、バックプレッシャー、リプレイを観測します。`task-usage-readback.mjs`は公開APIを通じてタスクとその使用量を読み戻します。これらを`support/`フォルダーに入れ、`public-task.mjs`と同じディレクトリに置きます。

```text
consumer/
  package.json
  node_modules/@onesay/sdk/
  public-task.mjs
  support/
    pcm-session.mjs
    task-usage-readback.mjs
```

### エクスポートされた関数を呼び出す

次のコードは、`public-task.mjs`の隣に置くアプリケーションのコードです。パラメーターは認可されたアプリケーションが渡す値で、トークンを埋め込んだり出力したりすることはありません。下記の`observeAuthorizedTask`はこのガイド用のラッパーであり、OneSay SDKの追加のエクスポートではありません。

```js
import { buildRealtimeConnectPlan } from "@onesay/sdk";
import { runPublicTaskSample } from "./public-task.mjs";

export async function observeAuthorizedTask({
  projectId,
  environmentId,
  sourceId,
  issuedSingleUseTicket,
  signal,
  ports,
}) {
  return runPublicTaskSample(
    {
      projectId,
      environmentId,
      sourceId,
      plan: buildRealtimeConnectPlan("staging", issuedSingleUseTicket),
      signal,
      timeoutMs: 5000,
    },
    ports,
  );
}
```

`buildRealtimeConnectPlan(environment, ticket)`は公開SDK関数です。WSS接続を計画し、チケットの交換は行いません。サンプルはStagingのControl環境から使用量を読み戻します。本番環境のエンドポイント設定ではありません。使用が認可されている環境に対してのみ実行してください。

4つのポートはすべて必須のアプリケーションコールバックであり、SDKメソッドではありません。

| ポート | コールバックの形と責務 |
| --- | --- |
| `webSocketFactory` | `(url, protocols, options) => socket`。SDK互換のWebSocketトランスポートを返します。指定された接続とサイズの上限に従い、リダイレクトを無効にし、証明書/ホスト名の検証を緩めないでください。 |
| `confirmedControlsFactory` | `(client, { signal }) => controls`、またはcontrolsのPromise。公開コントラクトを通じて、アプリケーションの明示的なグラント/同意の選択を組み込みます。既定で承認しないでください。 |
| `readbackTokenProvider` | `({ signal }) => shortLivedToken`、またはそのトークンのPromise。要求されたスコープでタスクと使用量の読み取りが認可された、現在のプリンシパルを渡します。長期のPlatformシークレットやプロバイダーシークレットはここに含めません。 |
| `readbackTransport` | `(url, init) => Promise<Response>`。Fetch互換のトランスポートを提供し、`init.signal`に従い、SDKのヘッダー、リダイレクト禁止ポリシー、スコープを維持します。 |

`confirmedControlsFactory`が返すcontrolsは、次の形を実装する必要があります。

```text
send(type, data, messageId, { signal })
flush({ signal })
observe(event, { signal })
```

`send`と`flush`はPromiseを返してもかまいません。`observe`は同期的なイベントオブザーバーです。同意の選択は、サーバー権限に裏付けられた、アプリケーションの明示的な判断です。これらのメソッドを返しても許可は付与されません。サーバーは引き続き、チケット、ソース、アクション、グラント、同意を検証します。

### PCM、完了、失敗時の動作

このサンプルは、16 kHzで640サンプルのPCM16LEモノラルフレームをちょうど2つ生成します。これは80 msの固定の合成入力です。上限付きのバックプレッシャーを意図的に試し、SDKを通じて最初の未確認フレームをリプレイします。重複したリプレイは、新しい音声入力でも2つ目のタスクでもありません。サンプルには、マイクのキャプチャ、AudioWorklet、OSへの依存、メディアの許可要求はありません。

ライブ音声を録音するアプリケーションでは、キャプチャと許可は呼び出し元の責務です。AudioWorkletは、任意の取得手段の1つです。SDKに音声キャプチャのエクスポートはありません。タスクのPCMに関するこの境界は、RTCの`publishMicrophone`の動作を変えません。

サンプルは、連続したACK、対応するタスクとアーティファクトの終了、上限付きの公開タスク/使用量の読み戻しを待ち、SDKがトランスポートのクローズを観測した後にのみ結果を返します。結果に含まれるのは件数だけです。`pcm`は送信、確認、リプレイされたフレーム数、クライアントのバックプレッシャーによる拒否、破棄されたフレーム数、ACK遅延のパーセンタイルを報告し、`readback`は観測したタスク数、スキャンした使用量イベント数、読み取ったページ数を報告します。音声、文字起こし、チケット、トークンは含まれず、課金明細でもありません。

HTTP 401/403が返ると、この呼び出しはそこで終了し、自動では再試行しません。使用量の欠落、重複、スコープ外、タスク/アーティファクトの相関の誤り、ACKの欠落、呼び出し元によるキャンセル、期限切れ、クリーンアップの失敗は、呼び出しを失敗(reject)させます。再度呼び出す前に、アプリケーションの明示的なフローを通じて新しい認可を取得してください。消費済みのチケットをリプレイしないでください。

`timeoutMs`は操作全体を制限し、クローズ待ちも別途制限します。サンプルは、非同期のfactory/control/token/HTTPポートに中断(abort)を伝播します。コールバックは指定されたシグナルに従い、自身のリソースを解放する必要があります。待機に上限があっても、協調しないコールバックを強制的に停止することも、その外部への副作用を元に戻すこともできません。同期コールバックも、イベントループをブロックしないようにする必要があります。失敗を診断するために、計画のURL、チケット、トークン、生のPCM、文字起こしをログに出力しないでください。

### 既存の公開APIを通じたアプリケーションの認可

`runAuthorizedTaskSample(input, ports)`(`examples/javascript-quickstart/authorized-task.mjs`)は、独自に接続情報を取得し、保護されたオペレーションを明示的に確認するアプリケーションのサンプルです。これは`runPublicTaskSample`を組み合わせたもので、SDKのタスクライフサイクル、ACK、リプレイ、バックプレッシャー、キャンセルのロジックを置き換えるものではありません。これはサンプルのエクスポートであり、新しいOneSay SDK APIではありません。

`authorized-task.mjs`は`public-task.mjs`の隣に置きます。これはさらに1つのサポートモジュール`support/confirmed-controls-core.mjs`をインポートします。これは、保護された各コマンドを確認が完了するまで保持する上限付きのキューで、自ら承認することはありません。

```js
import { runAuthorizedTaskSample } from "./authorized-task.mjs";

export async function runApplicationTask(resources, signal, ports) {
  return runAuthorizedTaskSample({
    organizationId: resources.organizationId,
    projectId: resources.projectId,
    environmentId: resources.environmentId,
    deviceId: resources.deviceId,
    roomId: resources.roomId,
    sourceId: resources.sourceId,
    signal,
    timeoutMs: 60000,
    delegationTtlSeconds: 120,
  }, ports);
}
```

すべてのリソースは、対象の環境にあらかじめ存在している必要があります。プロビジョニング、ログイン、デバイスの登録、認証情報、ケイパビリティポリシーは、引き続きアプリケーションの既存の認可済みフローが担います。このサンプルは、グラントの作成、権限の変更、自身へのアクセス付与を行いません。

| アプリケーションのポート | 責務 |
| --- | --- |
| `humanTokenProvider({ signal })` | 公開の読み取り、委任の管理、対話的な同意の判断について認可された、現在有効な人間のユーザーの短期トークンを渡します。 |
| `deviceTokenProvider({ signal })` | 対象のデバイス用に、これとは別の、現在有効な短期のデバイストークンを渡します。人間のユーザーのトークンで代用したり、デバイスの長期ブートストラップシークレットを渡したりしないでください。 |
| `confirmConsent(snapshot, { signal })` | 検証済みで不変のサーバーの提示内容を表示し、ユーザーが明示的に判断した後に`"allow"`または`"deny"`のみを返します。キャンセルに従ってください。決して既定で許可(Allow)にしないでください。 |
| `fetch(url, init)` | SDKのリクエストヘッダー、スコープ、中断シグナル、リダイレクト制限を維持します。 |
| `webSocketFactory(url, protocols, options)` | 証明書/ホスト名のチェックと上限付きの接続オプションを備えた、互換トランスポートを供給します。 |

アプリケーションは、これらのコールバックを実装する必要があります。確認コールバックが提供するのはUXのみです。互換性のあるサードパーティのクライアントも、まったく同じサーバー側のチェックを受けます。対話的な同意には、サーバーが認識する、信頼された署名付きOIDCクライアントIDも必要です。オーナーであること、`"allow"`を返すこと、Originヘッダーを偽造することでは、これに代えられません。このNodeサンプルはOriginヘッダーを追加しません。アプリケーションのブラウザーログインとCSRFの要件は、引き続き確立済みのブラウザーフローが担います。

このサンプルは、公開のControlオペレーションを通じて`/me`と、プロジェクト、環境、デバイス、ルーム、ソースを読み取ります。これらの読み取りは人間のユーザーのトークンで行い、接続はデバイストークンで要求します。参加後、サンプルは観測した参加者を読み取り、保護されたアクションを送信する前に、それが設定済みのデバイスに属していることを確認します。これらの観測は、サーバーによる現在の認可チェックに代わるものではありません。

保護された各コマンドについて、サンプルは委任管理のアクションとデバイスのアクションを、それぞれ厳密に特定したうえで明示的に確認します。どちらの確認も、公開の同意ヘルパーと、サーバーのスナップショット、ダイジェスト、文言バージョン、実行バインディングを使用します。呼び出しごとのコマンドIDは、確認時と実際の送信時で同一に保たれます。確認が拒否された場合、期限切れの場合、変更された場合、または一致しない場合は、呼び出しが停止します。

接続レスポンスの`websocket_url`には、短期チケットが含まれます。レスポンスに別個の`ticket`やAppSessionのフィールドはありません。サンプルは`buildRealtimeConnectPlan`を通じてURLを検証し、書き換えは行いません。サンプルはStaging環境を使用します。

結果は、タスクサンプルの結果に`authorization_observation`を加えたものです。どの公開チェックが行われたか、作成した委任の数(`retained_delegations`)、要求したTTL(`requested_ttl_seconds`)を示します。認可の証明書ではありません。クライアントはトランスポートのクリーンアップを待ちますが、サーバーで作成された委任は、上限付きの有効期限が切れるか、別途確認された取り消しが行われるまで残ります。クライアントのクローズによって委任が取り消されることはありません。要求する委任のTTLは、既定値も最大値も120秒です。クリーンアップの手順が取り消しを自動的に承認することはありません。

通常のID、スコープ、HTTP 401/403、同意の拒否、有効期限切れ、期限超過、トランスポートの失敗は、自動再試行なしで呼び出しを失敗(reject)させます。公開エラーは固定の段階メッセージを使い、サポートされるSDKエラーのメタデータだけを保持します。元の原因、トークン、チケットURL、リソースID、提示内容、プロバイダーの詳細は含めません。次の明示的なアクションは、上限付きの操作のアプリケーションコンテキストに基づいて判断してください。トランスポートエラーや認証情報を決してダンプしないでください。

認可とタスクの操作全体は、呼び出し元のキャンセルと期限を共有します。サンプルは、クローズ待ちを別途制限します。ポートは中断に従う必要があります。待機に上限があっても、すでに受け付けられたサーバーオペレーションを元に戻すことも、協調しないコールバックを強制的に停止することもできません。委任の作成後に失敗した場合でも、その上限付きのTTLは引き続き適用されます。失敗によって委任が取り消されることはありません。