はじめに
LWCコンポーネントからlightning/accApiを呼び出すと、画面右のAgentforceパネルをコードで開閉したり、自然言語の指示を送ったりできます。
この記事では、Summer '26のリリースで改めて紹介されたこのモジュールを使って、実際にコンポーネントを実装する手順をコード中心にまとめます。
botIdの取得方法やexecute()を呼ぶときの制約など、実装時にハマりやすいポイントも合わせて整理しました。管理画面の設定よりも、まずコードを動かしてみたいエンジニア向けの内容です。
Bridgeの正体、lightning/accApiというモジュール
Salesforceの公式ブログでは、Summer '26のLWCアップデートを「クリーンな構成」「速い編集サイクル」「良い初期設定」、そしてもうひとつ「Agentforceへの新しいBridge」という軸で紹介しています。このBridgeの実体がlightning/accApiです。正式名称はAgentforce Conversation Client API、略してACC APIと呼ばれています。
要するに、自分で作ったLWCコンポーネントから、画面右側に出てくるAgentforceのチャットパネルをリモコンのように操作できる仕組みです。
パネルを開く、閉じる、指示を送る。この3つの操作だけに特化しています。画面を自分で描画する機能は一切持っていません。いわゆるヘッドレスモジュールというやつです。
このBridge自体は、実は2026年4月15日から使える状態になっていました。Summer '26で急に生まれた機能というより、すでに動いていたものが今回の開発者向けリリースブログで「LWCを成熟させる目玉機能のひとつ」として改めて取り上げられた形です。ここは記事を書きながら私も勘違いしかけたところなので、先に断っておきます。
出典:The Salesforce Developer's Guide to the Summer '26 Release
同じSummer '26のLWCアップデートでは、コンポーネントの状態管理を担うLWC State ManagerもGAになりました。State Managerとaccapiは役割がまったく別です。前者はデータの持ち方を整理する仕組み、後者はAgentforceとの橋渡し役です。State Managerの実装パターンは別記事で扱っているので、ここではBridgeの使い方に絞ります。
つまり、あなたのLWCコンポーネントに数行コードを足すだけで、Agentforceパネルの開閉と指示出しができるようになる、という理解でOKです。
State Managerについては以下で解説しています。
実装ステップ、LWCからAgentforceパネルを操作する
ここからは実際にコンポーネントを作りながら、Bridgeを組み込む手順を追っていきます。
ステップ1、モジュールをインポートする
操作したいLWCコンポーネントのJavaScriptファイルで、lightning/accApiから3つの関数を読み込みます。
import { open, close, execute } from "lightning/accApi";
新しいパッケージのインストールは不要です。標準のLWCモジュールとして最初から使えます。
ステップ2、meta.xmlのAPIバージョンを確認する
このモジュールを使うには、コンポーネントのAPIバージョンが59.0以上である必要があります。<apiVersion>タグの値を確認し、古いままなら引き上げておいてください。
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>65.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>
ステップ3、パネルを開く・閉じるボタンを作る
open()とclose()はどちらも引数なしで呼び出せます。open()だけはオプションでbotIdを渡すと、特定のエージェントを指名して開けます。
import { LightningElement } from "lwc";
import { open, close } from "lightning/accApi";
export default class CustomAgentforceLauncher extends LightningElement {
async handleOpenChat() {
await open();
}
async handleCloseChat() {
await close();
}
}
awaitを付けているのは、この3つの関数がすべてPromiseを返す非同期処理だからです。Promiseというのは、要するに「後で結果が返ってくる約束手形」のようなものです。書き忘れても構文エラーにはなりませんが、パネルが開き切る前に次の処理を走らせてしまう原因になるので、律儀に付けておくのが無難です。
ステップ4、botIdを調べて特定のエージェントを指名する
複数のエージェントを組織で運用している場合、open()やexecute()にbotIdを渡さないと、直前に使っていたエージェントが開いてしまいます。botIdは、Agentforce Builderでエージェントを開いたときのエージェントの詳細から確認できます。
async handleOpenSpecificAgent() {
const botId = '0Xx...';
await open(botId);
}
ステップ5、Lightning App Builderで画面に配置する
コンポーネントをデプロイしたら、設定 > Lightningアプリケーションビルダーを開き、レコードページやホームページの好きな場所にドラッグ&ドロップで配置します。ここは通常のカスタムLWC配置と同じ流れなので、迷うことはないはずです。
execute()でエージェントに指示を送るときの挙動
execute()は、パネルにユーザーが直接タイプしたのと同じように、自然言語の指示をエージェントに送り込む関数です。
import { execute } from "lightning/accApi";
async askForSummary() {
const utteranceString = "このアカウントの取引履歴を要約して";
await execute(utteranceString, botId);
}
ここで一つ、実務で引っかかりやすいポイントがあります。execute()はbotIdを省略できません。open()とは違い、必ず対象のエージェントを指定する必要があります。私も最初はopenと同じ感覚で呼び出してしまい、エラーで止まりました。
つまり、開閉は緩め、指示出しは厳密め、というのがこのAPIの温度感です。
実際に作ってみた
理屈がわかったところで、実際に自分のorgでBridgeを組み込んだLWCコンポーネントを作ってみました。ここでは記事の手順通りに進めても、実務では必ず一度は引っかかるであろうポイントを補足します。
作ったのは、パネルの開閉ボタンと、よくある質問を1タップで送れるクイックボタン、自由入力欄をひとまとめにしたランチャーコンポーネントです。
js
import { LightningElement, api } from 'lwc';
import { open, close, execute } from 'lightning/accApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
const QUICK_PROMPTS = [
{ key: 'capabilities', label: 'できることを聞く', utterance: 'あなたは何ができますか?' },
{ key: 'faq', label: '返品ポリシーを聞く', utterance: '返品ポリシーを教えてください。' },
{ key: 'escalation', label: '有人担当者に相談する', utterance: '有人の担当者に相談したいです。' }
];
const DEFAULT_BOT_ID = '0Xx***************';
export default class AgentforceBridgeLauncher extends LightningElement {
@api botId = DEFAULT_BOT_ID;
customUtterance = '';
isBusy = false;
statusMessage = '';
quickPrompts = QUICK_PROMPTS;
get isCustomSendDisabled() {
return this.isBusy || !this.customUtterance || !this.customUtterance.trim();
}
get hasBotId() {
return Boolean(this.botId && this.botId.trim());
}
async handleOpen() {
await this.runExclusive('パネルを開いています…', async () => {
if (this.hasBotId) {
await open(this.botId);
} else {
await open();
}
}, 'Agentforceパネルを開きました。');
}
async handleClose() {
await this.runExclusive('パネルを閉じています…', async () => {
await close();
}, 'Agentforceパネルを閉じました。');
}
async handleQuickPrompt(event) {
const { utterance } = event.currentTarget.dataset;
await this.sendUtterance(utterance);
}
handleCustomInputChange(event) {
this.customUtterance = event.target.value;
}
async handleCustomSend() {
const utterance = this.customUtterance.trim();
if (!utterance) {
return;
}
await this.sendUtterance(utterance);
this.customUtterance = '';
}
async sendUtterance(utterance) {
if (!this.hasBotId) {
this.showToast('エラー', 'botIdが未設定です。コンポーネントのプロパティにエージェントのbotIdを設定してください。', 'error');
return;
}
await this.runExclusive('指示を送信しています…', async () => {
await execute(utterance, this.botId);
}, '指示を送信しました。');
}
async runExclusive(pendingMessage, action, doneMessage) {
if (this.isBusy) {
return;
}
this.isBusy = true;
this.statusMessage = pendingMessage;
try {
await action();
this.statusMessage = doneMessage;
} catch (error) {
this.statusMessage = '';
const message = (error && error.body && error.body.message) || (error && error.message) || '不明なエラーが発生しました。';
this.showToast('Agentforce操作でエラーが発生しました', message, 'error');
} finally {
this.isBusy = false;
}
}
showToast(title, message, variant) {
this.dispatchEvent(new ShowToastEvent({ title, message, variant }));
}
}
HTML
<template>
<div class="slds-card">
<div class="slds-card__header slds-grid">
<header class="slds-media slds-media_center slds-has-flexi-truncate">
<div class="slds-media__figure">
<lightning-icon icon-name="utility:einstein" size="small" alternative-text="Agentforce"></lightning-icon>
</div>
<div class="slds-media__body">
<h2 class="slds-card__header-title">
<span>Agentforce Bridge ランチャー</span>
</h2>
</div>
</header>
</div>
<div class="slds-card__body slds-card__body_inner">
<div class="slds-var-m-bottom_medium slds-grid slds-gutters_x-small slds-wrap">
<div class="slds-col slds-var-m-bottom_x-small">
<lightning-button
label="パネルを開く"
icon-name="utility:chat"
variant="brand"
disabled={isBusy}
onclick={handleOpen}
></lightning-button>
</div>
<div class="slds-col slds-var-m-bottom_x-small">
<lightning-button
label="パネルを閉じる"
icon-name="utility:close"
disabled={isBusy}
onclick={handleClose}
></lightning-button>
</div>
</div>
<div class="slds-var-m-bottom_medium">
<p class="slds-text-title_caps slds-var-m-bottom_x-small">よくある質問を送る</p>
<div class="slds-grid slds-gutters_x-small slds-wrap">
<template for:each={quickPrompts} for:item="prompt">
<div key={prompt.key} class="slds-col slds-var-m-bottom_x-small">
<lightning-button
label={prompt.label}
data-utterance={prompt.utterance}
disabled={isBusy}
onclick={handleQuickPrompt}
></lightning-button>
</div>
</template>
</div>
</div>
<div class="slds-var-m-bottom_small">
<p class="slds-text-title_caps slds-var-m-bottom_x-small">自由入力で指示を送る</p>
<div class="slds-grid slds-gutters_x-small slds-grid_vertical-align-end">
<div class="slds-col slds-grow">
<lightning-input
type="text"
label="エージェントへの指示"
variant="label-hidden"
placeholder="例: 経費精算の手順を教えて"
value={customUtterance}
disabled={isBusy}
onchange={handleCustomInputChange}
></lightning-input>
</div>
<div class="slds-col slds-no-flex">
<lightning-button
label="送信"
variant="brand"
disabled={isCustomSendDisabled}
onclick={handleCustomSend}
></lightning-button>
</div>
</div>
</div>
<template if:true={isBusy}>
<div class="slds-var-m-top_small">
<lightning-spinner alternative-text="処理中" size="x-small"></lightning-spinner>
</div>
</template>
<div class="slds-var-m-top_small slds-text-body_small slds-text-color_weak" aria-live="polite">
{statusMessage}
</div>
</div>
</div>
</template>
js-meta
<property
name="botId"
type="String"
label="Bot ID"
default="0Xx***************"
description="対象エージェントのbotId(0Xx...)。execute()の送信には必須です。"
/>
当記事にある「複数回execute()を連続で呼び出すと順番に積まれる」という挙動、実装する前は「まあ多少連打されても大丈夫だろう」くらいに考えていたのですが、実際にisBusyフラグでボタンを塞ぐ処理を入れないと、ユーザーが連打した分だけ後からエージェントの返答がパネルに積み上がっていく体験になってしまいます。
ローディング表示は飾りではなく、ほぼ必須の実装だと感じました。
botIdに関しては以下SOQLでも確認できました。
SELECT Id, DeveloperName
FROM BotDefinition
WHERE DeveloperName = 'あなたのエージェント名'
👉️ポイント botIdはハードコードせず、App Builderの設定項目にする
botIdは組織ごと・orgごとに変わる値なので、ソースコードに直接書き込むのは筋が悪いです。meta.xmlのtargetConfigsでString型のプロパティとして公開しておくと、Lightning App Builder上でクリック一つでbotIdを差し替えられるようになります。
デプロイ前のエージェントで試したいときと、本番稼働済みのエージェントに向けたいときとで、コードを触らずに切り替えられるのは地味に便利でした。
レコード画面においてみた
ボタンを押すと、Agentforceチャット画面が立ち上がりました。
画面右に置く場合は、Agentforceチャット画面を固定(チャット右上のピン留めマーク)しておくと、LWCコンポーネントが隠れずに使いやすかったです。
レコード画面で使うなら、事前にLWC側でレコード情報を取得しておき、ボタン押下と同時にレコード情報をAgentへ渡して会話を始めるようにすれば、このレコード画面に特化した内容を会話できます。
作る前は「3関数だけのシンプルなAPI」という理解で止まっていましたが、実際に手を動かしてみると、つまずくのはAPIそのものよりも「そもそも自分が今どのbotIdを相手にしているのか」という、Agentforce側の運用理解のほうだと実感しました。
ここは注意!つまずきポイント
execute()はエージェントの返答テキストを戻り値として受け取れません。指示を送った後の会話は、あくまでパネルの中に表示されるだけです。ボタンを押した直後に返答をJavaScript側で加工したい、という設計は今のところできません。実務で使うなら、返答を使った後続処理はパネルの外に出せない前提で設計しておいた方が良さそうです。
複数回execute()を連続で呼び出すと、リクエストは順番に積まれて一つずつ処理されます。同時並行では動きません。連打できるボタンを作ると、ユーザーが体感する反応が遅れて見えることがあるので、ローディング表示を挟むなどの工夫が要りそうです。
このBridgeが使えるのはSalesforce Lightning Experienceのデスクトップ画面だけです。Tableauの中や、モバイルアプリのネイティブ画面では動きません。モバイルでAgentforceを組み込みたい場合は、別途Agentforce Mobile SDKという仕組みが用意されているので、そちらを検討してください。
利用にはAgentforceが有効化された組織であることが前提になります。Agentforce関連の機能はEnterprise、Performance、Unlimited、Developerエディションで、なおかつAgentforceのアドオンライセンスが必要になるケースがほとんどです。この機能単体の追加費用は公式ドキュメント上では明記されていませんでしたが、Agentforce自体がFlex Creditsという従量課金の仕組みに乗っている以上、無条件の無料機能ではないと考えておくのが安全です。正確な費用感は契約内容によって変わるので、導入前に自組織のAgentforce契約を確認してください。
出典:Embed Agentforce Employee Agent Experiences Across Salesforce and External Web Apps
まとめ
- BridgeことAgentforce Conversation Client API、実体は
lightning/accApiという3関数だけのヘッドレスモジュール -
open()とclose()はパネルの開閉、execute()は自然言語での指示出しを担当する -
execute()はbotIdが必須で、返答テキストは受け取れず、リクエストは順番に処理される - デスクトップのLightning Experience限定、Agentforceが有効な組織でのみ動作する
BridgeもState Managerも、Summer '26で一気に増えたLWCの新しい引き出しです。全部を一度に覚える必要はありません。
まずはパネルの開閉だけでも、既存のレコードページに組み込んでみてください。触っているうちに、次に何を試したくなるかが見えてくるはずです。
資格に関することや、現場での気づきをnoteで発信しています。
→ note
