Pouchy 伴侣接入指南 — 网页游戏 · 实时语音陪玩

面向第三方网页游戏(如德州扑克)接入 Pouchy 伴侣,用实时语音陪玩:伴侣在通话里出声, 跟着你推送的牌局状态实时反应。本文是给对接方的接口说明,配合可运行示例 examples/poker-voice/


一、对接方需要拿到的东西(交付清单)

文件 / 资源 作用
@pouchy_ai/companion-sdk(packages/companion-sdk/) 接入用的 JS SDK。已发布到公共 npm:npm install @pouchy_ai/companion-sdk;也可经 esm.sh / jsDelivr / 自托管 CDN(pouchy.ai/sdk/companion-sdk.js)直接引入。仓库内开发仍可本地构建。
本文档 docs/companion-voice-integration.md 接口说明(语音 + world-state 配方)。
examples/poker-voice/ 可运行的德州扑克语音陪玩示例,复制即用。
OpenAPI GET /api/companion/openapi.json REST 面的机读契约(可生成 client / 导入 Postman)。
协议规范 docs/companion-sdk-protocol.md 底层 wire 协议(只有要自己实现传输时才需要)。

纯 JS/TS SDK,浏览器即可跑(WebRTC + 麦克风)。语音音频走provider 直连(ElevenLabs Convai 主、 OpenAI Realtime 兜底),Pouchy 服务器不在音频链路里。


二、令牌与权限(token)

伴侣属于玩家本人的账号。游戏需要拿到该玩家的一个 Pouchy access token(pchy_…),两种方式都行:

  • 个人令牌(最快): 目前应用内还没有"伴侣接入密钥"面板 —— 玩家登录 pouchy.ai 后调用 POST /api/companion/keys(Bearer 为 Firebase ID token)生成。不传 scopes 即得 "安全默认权限包",已经包含语音陪玩需要的 call / voice / events.subscribe / worldstate.write(以及 chat / files / memory.*:app)。 不要额外传 wallet.spend / social.message / skills.execute 这些敏感权限,陪玩用不到。 token 只在响应里出现一次,记得当场复制。
  • Login with Pouchy(OAuth 2.1 + PKCE): 游戏注册一个 client(/app/developer/oauth),走授权码流程拿 access_token

语音陪玩实际用到的 scope(默认 key 已包含,无需手动勾):call(必需)· voice · events.subscribe · worldstate.write(+ 可选 chat)。陪玩不需要敏感权限。


三、四步接通(核心代码)

import { createCompanion } from '@pouchy_ai/companion-sdk';

// 1) 建客户端(声明语音模态)
const companion = createCompanion({
  baseUrl: 'https://pouchy.ai',
  token: PLAYER_TOKEN,          // 玩家的 pchy_… 令牌
  surface: 'poker-table',       // 标识这个接入点
  modalities: ['text', 'voice']
});

// 2) 握手 + 打开出站事件流
await companion.connect();
companion.start();

// 3) 开实时语音通话(浏览器会请求麦克风)
const call = await companion.connectCall({
  locale: 'zh',
  onTranscript: ({ role, text }) => render(role, text),     // 字幕(可选)
  onSpeakingChange: (speaking) => toggleMicGlow(speaking),  // 说话指示(可选)
  onError: (err) => console.warn(err)
});

// 4) 把牌局状态推给伴侣;⭐ 的时刻它会在通话里出声反应
companion.sendWorldState({ type: 'poker.hand', data: { hole: 'A♠ K♠', blinds: '10/20' }, retained: true });
companion.sendWorldState({
  type: 'poker.event.river',
  data: 'The river is K♦ — top pair, top kicker.',
  salience: 0.85,
  voiceRelevant: true        // ← 这一条会被推进通话,伴侣立刻出声评论
});

// 结束
call.close();

connectCall 一步到位:它申请通话凭据 → 打开到 provider 的 WebRTC(麦克风+扬声器)→ 并自动把服务器 推来的 voiceRelevant 时刻注入到通话里。你不用手动桥接,只管 sendWorldState

陪玩会被记住(跨会话记忆):语音音频是 provider 直连的,Pouchy 听不到通话内容,所以记忆是在 通话结束时固化的。你调 call.close() 时,SDK 会自动把这次通话的字幕 + 你推过的 world-state(发牌、 输赢、关键时刻)一起发回服务器,提炼成长期记忆。所以一定要在结束时调 call.close()(SDK ≥ 0.3.0)。 这样玩家回到 Pouchy 主应用问"刚才德州打得怎样",伴侣就知道。非语音会话在结束时手动调 companion.endSession()。 想让记忆进入跨应用共享的核心大脑,给 token 加 memory.write:core 权限即可。


四、world-state 配方(陪玩好不好,全看这里)

伴侣的"陪玩感"来自你推的牌局上下文。两类事件:

  • retained: true —— 当前状态(按 type 合并/覆盖):当前手牌、盲注、筹码、阶段。
  • transient(不带 retained) —— 一次性事件,带 salience(0–1,越戏剧越高); 关键时刻加 voiceRelevant: true,伴侣会在通话里当场出声。

德州扑克建议推送:

时机 例子 建议
发底牌 { type:'poker.hand', data:{hole:'A♠ K♠', blinds:'10/20'}, retained:true } retained
轮到玩家 { type:'poker.event.your_turn', data:'…pot 30', salience:0.7, voiceRelevant:true } ⭐ 出声
翻牌/转牌/河牌 { type:'poker.event.river', data:'river K♦ …', salience:0.85, voiceRelevant:true } ⭐ 关键牌出声
对手动作 { type:'poker.event.opponent_raise', data:'raised to 120', salience:0.5 } 一般不出声,只做上下文
摊牌/输赢 { type:'poker.event.win', data:'won 260 pot!', salience:0.95, voiceRelevant:true } ⭐ 出声

经验法则:只给真正值得开口的时刻打 voiceRelevant(轮到你、关键牌、输赢)。把每个小动作都标出声, 伴侣会变成话痨。其余只做 retained 状态或低 salience 事件,作为它的背景认知。


五、provider 依赖说明

  • ElevenLabs Convai(主): 需要可选 peer 依赖 @elevenlabs/client(npm i @elevenlabs/client)。 示例已包含。
  • OpenAI Realtime(兜底): 自带 WebRTC,零额外依赖

服务器按账号配置自动选 provider(EL 优先 → OpenAI 兜底),客户端无法强制;两者 connectCall 用法完全一致。

无打包器(no-bundler)项目:直接从 Pouchy 源加载 SDK(自带 CORS,无需 vendor), @elevenlabs/client 走 esm.sh。在 module script 之前加 import map:

<script type="importmap">
{
  "imports": {
    "@pouchy_ai/companion-sdk": "https://pouchy.ai/sdk/companion-sdk.js",
    "@elevenlabs/client": "https://esm.sh/@elevenlabs/client@1.9.0"
  }
}
</script>
<script type="module">
  import { createCompanion } from '@pouchy_ai/companion-sdk';
</script>

详见 companion-integration-faq.md 第 6 条。


六、本地跑通示例

cd packages/companion-sdk && npm i && npm run build   # 先构建 SDK
cd ../../examples/poker-voice && npm i && npm run dev  # 跑示例

打开 localhost 地址 → 粘贴带 voice+call 的 token → 连接 → 开始语音陪玩 → 点牌桌按钮, 听伴侣在通话里反应。


七、让伴侣用语音/文字操作你的游戏(暂停、开关功能…)

声明你的游戏能做的动作,伴侣在语音通话文字里都能调用它们——同一套回调,你只写一份逻辑 (SDK ≥ 0.4.0)。玩家说"暂停一下 / 把提示关掉",伴侣就会触发对应工具:

const companion = createCompanion({
  baseUrl: 'https://pouchy.ai', token: PLAYER_TOKEN, surface: 'poker-table',
  modalities: ['text', 'voice'],
  tools: [
    { name: 'pause_game', description: '暂停当前牌局', parameters: { type:'object', properties:{} } },
    { name: 'set_feature', description: '开关一个功能(如发牌提示)',
      parameters: { type:'object', properties:{ feature:{type:'string'}, on:{type:'boolean'} }, required:['feature','on'] } }
  ]
});
await companion.connect();
companion.start();                 // 文字/事件流
const call = await companion.connectCall({ locale: 'zh' });   // 语音

companion.onToolCall(async ({ id, name, args }) => {           // 语音 + 文字共用
  const a = JSON.parse(args);
  if (name === 'pause_game') game.pause();
  if (name === 'set_feature') ui.toggle(a.feature, a.on);
  await companion.sendToolResult(id, { ok: true });           // 回报结果,语音里它会据此应一句
});
  • 良性的应用内操作(暂停、开关功能、高亮某处)立即执行,不弹确认
  • provider 差异(重要):语音工具调用在 OpenAI Realtime 上是全动态的(你声明什么就能调什么); ElevenLabs Convai 只会调它预先配置过的工具,共享 Convai agent 没法在运行时接收任意第三方工具。 provider 是服务端按账号自动选的,所以要让语音可靠地操作 app,把账号固定到 OpenAI Realtime (或让我们把你的几个工具登记到 Convai agent 上)。文字路径不受此限,任何 provider 都行。

八、(进阶)敏感操作:发消息 / 转账 / 跑技能

如果想让伴侣发起敏感操作(给对手发消息、转筹码/打赏、跑一个技能),那不会被静默执行:服务器会拦截并弹出 Pouchy 托管确认页让玩家本人批准(见 companion.confirm_request 事件 + /companion/confirm 页)。 普通陪玩 + 上面的 app 控制类工具都不需要这些权限。