@intentcomputing/koko-agent-sdk 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +13 -6
- package/README.zh-CN.md +13 -4
- package/docs/BACKEND_AUTH_DEMOS.md +42 -0
- package/docs/BACKEND_AUTH_DEMOS.zh-CN.md +191 -0
- package/docs/CONFIGURATION.md +188 -0
- package/docs/CONFIGURATION.zh-CN.md +195 -0
- package/docs/INTEGRATION.md +15 -6
- package/docs/INTEGRATION.zh-CN.md +13 -4
- package/lib/commonjs/KokoAgent.js +13 -11
- package/lib/commonjs/KokoAgent.js.map +1 -1
- package/lib/commonjs/client/AgentSession.js +3 -1
- package/lib/commonjs/client/AgentSession.js.map +1 -1
- package/lib/commonjs/client/GatewayClient.js +66 -12
- package/lib/commonjs/client/GatewayClient.js.map +1 -1
- package/lib/commonjs/cursor/CursorOverlay.js +1 -2
- package/lib/commonjs/cursor/CursorOverlay.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/provider/KokoProvider.js +1 -2
- package/lib/commonjs/provider/KokoProvider.js.map +1 -1
- package/lib/commonjs/provider/KokoUiContext.js +5 -7
- package/lib/commonjs/provider/KokoUiContext.js.map +1 -1
- package/lib/commonjs/ui/KokoChatSheet.js +1 -2
- package/lib/commonjs/ui/KokoChatSheet.js.map +1 -1
- package/lib/commonjs/ui/KokoComposer.js +1 -2
- package/lib/commonjs/ui/KokoComposer.js.map +1 -1
- package/lib/commonjs/ui/KokoDialogHost.js +1 -2
- package/lib/commonjs/ui/KokoDialogHost.js.map +1 -1
- package/lib/commonjs/ui/KokoObserver.js +1 -2
- package/lib/commonjs/ui/KokoObserver.js.map +1 -1
- package/lib/commonjs/ui/KokoSubtitle.js +1 -2
- package/lib/commonjs/ui/KokoSubtitle.js.map +1 -1
- package/lib/commonjs/ui/KokoTaskPanel.js +1 -2
- package/lib/commonjs/ui/KokoTaskPanel.js.map +1 -1
- package/lib/module/KokoAgent.js +12 -9
- package/lib/module/KokoAgent.js.map +1 -1
- package/lib/module/client/AgentSession.js +3 -1
- package/lib/module/client/AgentSession.js.map +1 -1
- package/lib/module/client/GatewayClient.js +66 -12
- package/lib/module/client/GatewayClient.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/provider/KokoUiContext.js +4 -5
- package/lib/module/provider/KokoUiContext.js.map +1 -1
- package/lib/typescript/commonjs/KokoAgent.d.ts.map +1 -1
- package/lib/typescript/commonjs/client/AgentSession.d.ts.map +1 -1
- package/lib/typescript/commonjs/client/GatewayClient.d.ts +24 -2
- package/lib/typescript/commonjs/client/GatewayClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/provider/KokoUiContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +7 -2
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/module/KokoAgent.d.ts.map +1 -1
- package/lib/typescript/module/client/AgentSession.d.ts.map +1 -1
- package/lib/typescript/module/client/GatewayClient.d.ts +24 -2
- package/lib/typescript/module/client/GatewayClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/provider/KokoUiContext.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +7 -2
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/KokoAgent.ts +12 -9
- package/src/client/AgentSession.ts +2 -0
- package/src/client/GatewayClient.ts +88 -14
- package/src/index.ts +2 -0
- package/src/provider/KokoUiContext.tsx +4 -5
- package/src/types.ts +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.1
|
|
4
|
+
|
|
5
|
+
- Add complete English and Chinese configuration references covering Provider, Agent, gateway, branding, runtime appearance, per-run overrides, and built-in UI options.
|
|
6
|
+
- Clarify that short-lived access tokens stay in memory and refresh lazily on requests near expiry.
|
|
7
|
+
|
|
8
|
+
## 1.1.0
|
|
9
|
+
|
|
10
|
+
- Add production `tokenProvider` authentication with expiry-aware caching and one forced refresh after a 401.
|
|
11
|
+
- Add host-backend token exchange documentation and Node.js, Java, Go, Python, PHP and C# examples.
|
|
12
|
+
- Keep static app tokens as a documented legacy development/migration path.
|
|
13
|
+
|
|
3
14
|
## 1.0.1
|
|
4
15
|
|
|
5
16
|
- Clarify npm setup, host-provided entry controls, regional gateways, and privacy responsibilities in the published documentation.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Koko Agent SDK
|
|
2
2
|
|
|
3
|
-
React Native SDK for an observable in-app assistant. The host supplies
|
|
3
|
+
React Native SDK for an observable in-app assistant. The host supplies a short-lived access token and presents a user goal; the SDK observes the screen, asks the hosted gateway for a next step, and performs supported actions inside the app.
|
|
4
4
|
|
|
5
5
|
[npm package](https://www.npmjs.com/package/@intentcomputing/koko-agent-sdk) · [中文](README.zh-CN.md) · [Integration guide](docs/INTEGRATION.md) · [Privacy](docs/PRIVACY.md) · [Contributing](CONTRIBUTING.md)
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ React Native SDK for an observable in-app assistant. The host supplies an app to
|
|
|
8
8
|
|
|
9
9
|
- React Native 0.73 or later and React 18 or later; iOS 15 or later or an Android host supporting React Native autolinking.
|
|
10
10
|
- Expo development build or a native build; Expo Go cannot load this native module.
|
|
11
|
-
-
|
|
11
|
+
- A host backend that exchanges its server-only client secret for short-lived mobile access tokens. Neither credential is an OpenAI or DeepSeek API key.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ Rebuild and reinstall the native app after installation or a native SDK upgrade.
|
|
|
21
21
|
|
|
22
22
|
## Quick start
|
|
23
23
|
|
|
24
|
-
Mount the provider around the host navigation tree and connect `openKokoChat()` to a host button.
|
|
24
|
+
Mount the provider around the host navigation tree and connect `openKokoChat()` to a host button. Supply short-lived credentials through `tokenProvider`.
|
|
25
25
|
|
|
26
26
|
```tsx
|
|
27
27
|
import React from 'react';
|
|
@@ -29,8 +29,15 @@ import { Button, View } from 'react-native';
|
|
|
29
29
|
import { KokoProvider, openKokoChat } from '@intentcomputing/koko-agent-sdk';
|
|
30
30
|
|
|
31
31
|
export default function App() {
|
|
32
|
+
const getKokoToken = React.useCallback(async () => {
|
|
33
|
+
const response = await fetch('https://api.your-app.example/koko/access-token', {
|
|
34
|
+
headers: { Authorization: `Bearer ${await getHostSessionToken()}` },
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) throw new Error(`Koko token ${response.status}`);
|
|
37
|
+
return response.json(); // { accessToken, expiresAt } (Unix milliseconds)
|
|
38
|
+
}, []);
|
|
32
39
|
return (
|
|
33
|
-
<KokoProvider config={{ gateway: {
|
|
40
|
+
<KokoProvider config={{ gateway: { tokenProvider: getKokoToken }, locale: 'en' }}>
|
|
34
41
|
<View style={{ flex: 1 }}>
|
|
35
42
|
{/* Your app and navigation go here. */}
|
|
36
43
|
<Button title="Open assistant" onPress={() => openKokoChat()} />
|
|
@@ -40,9 +47,9 @@ export default function App() {
|
|
|
40
47
|
}
|
|
41
48
|
```
|
|
42
49
|
|
|
43
|
-
The default gateway is `https://sdk.intentcomputing.net` (OpenAI). For China, set `gateway.baseUrl` to `https://sdk.intentcomputing.cn` (DeepSeek).
|
|
50
|
+
The default gateway is `https://sdk.intentcomputing.net` (OpenAI). For China, set `gateway.baseUrl` to `https://sdk.intentcomputing.cn` (DeepSeek). The SDK caches short-lived tokens in memory, refreshes lazily when a request occurs near expiry, and retries once after a 401. Long-lived client secrets and model-provider keys must never be bundled into the app. See the [backend authentication demos](docs/BACKEND_AUTH_DEMOS.md) for Node.js, Java, Go, Python, PHP and C#.
|
|
44
51
|
|
|
45
|
-
Call `setKokoAppearance({ locale, theme })` when host preferences change. Configure branding through `config.branding`; custom branding is subject to the app's entitlement. See the [integration guide](docs/INTEGRATION.md) for host actions, privacy gates, and lifecycle details.
|
|
52
|
+
Call `setKokoAppearance({ locale, theme })` when host preferences change. Configure branding through `config.branding`; custom branding is subject to the app's entitlement. See the [complete configuration reference](docs/CONFIGURATION.md) for every Provider, Agent, gateway, runtime, and per-run option, and the [integration guide](docs/INTEGRATION.md) for host actions, privacy gates, and lifecycle details.
|
|
46
53
|
|
|
47
54
|
## Privacy and limits
|
|
48
55
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
面向 React Native 的应用内助手 SDK。用户描述任务后,SDK 观察宿主当前页面,通过网关规划下一步,并在宿主内执行支持的操作,过程对用户可见。
|
|
4
4
|
|
|
5
|
-
[npm 包](https://www.npmjs.com/package/@intentcomputing/koko-agent-sdk) · [English](README.md) · [接入指南](docs/INTEGRATION.zh-CN.md) · [隐私保护](docs/PRIVACY.zh-CN.md)
|
|
5
|
+
[npm 包](https://www.npmjs.com/package/@intentcomputing/koko-agent-sdk) · [English](README.md) · [接入指南](docs/INTEGRATION.zh-CN.md) · [后端鉴权 Demo](docs/BACKEND_AUTH_DEMOS.zh-CN.md) · [隐私保护](docs/PRIVACY.zh-CN.md)
|
|
6
6
|
|
|
7
7
|
## 前置条件
|
|
8
8
|
|
|
9
9
|
- React Native ≥ 0.73、React ≥ 18;iOS ≥ 15,或支持原生自动链接的 Android 宿主。
|
|
10
10
|
- Expo 使用开发构建或原生构建,不支持 Expo Go。
|
|
11
|
-
-
|
|
11
|
+
- 生产环境需要宿主后端:长期 `clientSecret` 只保存在后端,App 仅取得短期 access token。它们都不是模型 API 密钥。
|
|
12
12
|
|
|
13
13
|
## 安装
|
|
14
14
|
|
|
@@ -27,9 +27,16 @@ import { Button, View } from 'react-native';
|
|
|
27
27
|
import { KokoProvider, openKokoChat } from '@intentcomputing/koko-agent-sdk';
|
|
28
28
|
|
|
29
29
|
export default function App() {
|
|
30
|
+
const getKokoToken = React.useCallback(async () => {
|
|
31
|
+
const response = await fetch('https://api.your-app.example/koko/access-token', {
|
|
32
|
+
headers: { Authorization: `Bearer ${await getHostSessionToken()}` },
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) throw new Error(`Koko token ${response.status}`);
|
|
35
|
+
return response.json(); // { accessToken, expiresAt };expiresAt 为 Unix 毫秒
|
|
36
|
+
}, []);
|
|
30
37
|
return (
|
|
31
38
|
<KokoProvider config={{
|
|
32
|
-
gateway: {
|
|
39
|
+
gateway: { tokenProvider: getKokoToken, baseUrl: 'https://sdk.intentcomputing.cn' },
|
|
33
40
|
locale: 'zh',
|
|
34
41
|
}}>
|
|
35
42
|
<View style={{ flex: 1 }}>
|
|
@@ -41,7 +48,9 @@ export default function App() {
|
|
|
41
48
|
}
|
|
42
49
|
```
|
|
43
50
|
|
|
44
|
-
默认网关为海外的 `https://sdk.intentcomputing.net`(OpenAI);国内请显式选择 `https://sdk.intentcomputing.cn`(DeepSeek
|
|
51
|
+
默认网关为海外的 `https://sdk.intentcomputing.net`(OpenAI);国内请显式选择 `https://sdk.intentcomputing.cn`(DeepSeek)。SDK 仅在内存缓存短期 token;当一次请求发生且 token 临近过期时才按需刷新,401 后强制刷新并重试一次。长期 `clientSecret` 和模型 API 密钥都不得进入 App。完整三端流程及 Node.js、Java、Go、Python、PHP、C# Demo 见[后端鉴权 Demo](docs/BACKEND_AUTH_DEMOS.zh-CN.md)。
|
|
52
|
+
|
|
53
|
+
Provider、Agent、网关、品牌、运行时和单次任务的全部选项见 [SDK 完整配置参考](docs/CONFIGURATION.zh-CN.md)。
|
|
45
54
|
|
|
46
55
|
宿主必须在展示密码、私钥、助记词及其验证页面前设置隐私标记和用户操作门禁,由用户亲自完成敏感步骤。脱敏的页面信息和任务上下文会发送到所选网关进行规划,接入方应使用虚构数据验证隐私边界。详见[隐私说明](docs/PRIVACY.zh-CN.md)。
|
|
47
56
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Host-backend authentication demos
|
|
2
|
+
|
|
3
|
+
Production authentication uses three parties: the mobile app authenticates to its existing host backend; the host backend exchanges its server-only `clientId` and `clientSecret` with Koko; the app receives a 5–15 minute access token for the SDK.
|
|
4
|
+
|
|
5
|
+
Each `clientId` may optionally be restricted to exact host-backend egress IPs. Multiple IPv4/IPv6 addresses are comma-separated; omitting the allowlist leaves IP unrestricted. This is defense in depth and does not replace the client secret.
|
|
6
|
+
|
|
7
|
+
Creating an app in the Intent Computing console now issues the `clientId` and one-time `clientSecret`. The Apps / Server credentials page manages IPs, status, and secret rotation; admins can also manage regions and review the audit trail. Only a secret hash is stored. Legacy API keys remain migration-only.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Host app --host session--> Host backend --client secret--> Koko /v1/sdk/token
|
|
11
|
+
Host app <--short token--- Host backend <--short token---- Koko
|
|
12
|
+
Host app --short token-----------------------------------> Koko /v1/agent/*
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- Store `KOKO_CLIENT_SECRET` only in the host backend's secret manager or environment. Never return it to the app or place it in Git or logs.
|
|
18
|
+
- Protect and rate-limit the host `/koko/access-token` endpoint with the host's existing authentication.
|
|
19
|
+
- Derive `subject` on the server from the authenticated user. Send a stable pseudonymous value such as `HMAC-SHA256(hostUserId, separateSalt)`, never an email, phone number, wallet address or advertising ID.
|
|
20
|
+
- Use `https://sdk.intentcomputing.cn` for China and `https://sdk.intentcomputing.net` globally. Credentials and access tokens are region-bound.
|
|
21
|
+
- Koko returns `access_token`, `expires_in`, and `expires_at` (Unix seconds). Return `{ accessToken, expiresAt }` to the SDK, with `expiresAt` expressed in Unix milliseconds.
|
|
22
|
+
- Use a short timeout. Do not log authorization headers or response tokens. Rotate long-lived client secrets.
|
|
23
|
+
|
|
24
|
+
Complete, copyable examples for **Node.js/TypeScript (Express), Java (Spring Boot), Go, Python (FastAPI), PHP (Laravel), and C# (ASP.NET Core)** are maintained in the [Chinese guide](BACKEND_AUTH_DEMOS.zh-CN.md); all examples use standard HTTP Basic authentication for the server-to-server exchange and derive the subject from the host-authenticated user.
|
|
25
|
+
|
|
26
|
+
## React Native
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
const tokenProvider = React.useCallback(async () => {
|
|
30
|
+
const response = await hostApi.get('/koko/access-token');
|
|
31
|
+
return response.data as { accessToken: string; expiresAt: number };
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
<KokoProvider config={{
|
|
35
|
+
gateway: {
|
|
36
|
+
baseUrl: 'https://sdk.intentcomputing.net',
|
|
37
|
+
tokenProvider,
|
|
38
|
+
},
|
|
39
|
+
}}>{children}</KokoProvider>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Do not persist the access token in AsyncStorage or send it to logs/analytics. The SDK keeps it in memory, refreshes shortly before expiry, and clears it before one forced `tokenProvider` retry after a 401.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# 宿主后端鉴权与多语言 Demo
|
|
2
|
+
|
|
3
|
+
生产环境采用三端通信:宿主 App 通过自己的登录态请求宿主后端;宿主后端使用仅存于服务器的 `clientId` / `clientSecret` 向 Koko 换取 5~15 分钟的 access token;App 再把短期 token 交给 SDK。
|
|
4
|
+
|
|
5
|
+
Koko 可为每个 `clientId` 选配宿主后端出口 IP 白名单。多个精确 IPv4/IPv6 地址用逗号分隔;不配置时不限制。启用后,请把宿主后端所有稳定 NAT 出口地址提供给 Intent Computing。IP 白名单是 `clientSecret` 之外的附加约束,不能替代密钥认证。
|
|
6
|
+
|
|
7
|
+
在 Intent Computing 用户后台创建应用时,会自动生成 `clientId` / `clientSecret`。完整 Secret 仅显示一次;之后可在“应用 / 服务端凭证”中维护 IP、启停或轮换 Secret。管理员后台可调整状态、授权区域和 IP,并查看操作审计。Secret 只保存哈希,旧 API Key 仅用于迁移兼容。
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
宿主 App ──宿主登录态──> 宿主后端 ──clientSecret──> Koko /v1/sdk/token
|
|
11
|
+
宿主 App <──短期 token── 宿主后端 <──短期 token── Koko
|
|
12
|
+
宿主 App ──短期 token──> Koko /v1/agent/*
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 安全约定
|
|
16
|
+
|
|
17
|
+
- `KOKO_CLIENT_SECRET` 只能放在宿主后端的密钥系统或环境变量中,不能进入 App、Git、日志或前端响应。
|
|
18
|
+
- 宿主的 `/koko/access-token` 必须验证已有登录态并限流,不能做成匿名转发接口。
|
|
19
|
+
- `subject` 必须由后端根据已认证用户生成稳定的匿名值;不要发送邮箱、手机号、钱包地址或设备广告 ID。建议 `HMAC-SHA256(宿主用户ID, 独立盐)`。
|
|
20
|
+
- 国内使用 `https://sdk.intentcomputing.cn`,海外使用 `https://sdk.intentcomputing.net`;凭证和 token 不跨区域使用。
|
|
21
|
+
- Koko 返回字段为 `access_token`、`expires_in`、`expires_at`(Unix 秒)。宿主接口应转换为 SDK 接受的 `{ accessToken, expiresAt }`,其中 `expiresAt` 是 Unix 毫秒。
|
|
22
|
+
- 请求 Koko 必须设置短超时,不记录 Authorization 头或响应 token。长期凭证应定期轮换。
|
|
23
|
+
|
|
24
|
+
以下示例中的 `authenticatedUserId` 必须来自宿主已经验证的 session/JWT,不得从请求 body 或 query 直接采用。
|
|
25
|
+
|
|
26
|
+
## Node.js / TypeScript(Express)
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import crypto from 'node:crypto';
|
|
30
|
+
import express from 'express';
|
|
31
|
+
|
|
32
|
+
const app = express();
|
|
33
|
+
const gateway = process.env.KOKO_REGION === 'cn'
|
|
34
|
+
? 'https://sdk.intentcomputing.cn'
|
|
35
|
+
: 'https://sdk.intentcomputing.net';
|
|
36
|
+
|
|
37
|
+
app.get('/koko/access-token', requireHostLogin, async (req, res) => {
|
|
38
|
+
const subject = crypto.createHmac('sha256', process.env.KOKO_SUBJECT_SALT!)
|
|
39
|
+
.update(req.user.id) // 只取服务端认证结果
|
|
40
|
+
.digest('hex');
|
|
41
|
+
const basic = Buffer.from(
|
|
42
|
+
`${process.env.KOKO_CLIENT_ID}:${process.env.KOKO_CLIENT_SECRET}`,
|
|
43
|
+
).toString('base64');
|
|
44
|
+
const upstream = await fetch(`${gateway}/v1/sdk/token`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { Authorization: `Basic ${basic}`, 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ app_id: process.env.KOKO_APP_ID, subject }),
|
|
48
|
+
signal: AbortSignal.timeout(5000),
|
|
49
|
+
});
|
|
50
|
+
if (!upstream.ok) return res.status(502).json({ error: 'koko_token_unavailable' });
|
|
51
|
+
const token = await upstream.json();
|
|
52
|
+
res.set('Cache-Control', 'no-store').json({
|
|
53
|
+
accessToken: token.access_token,
|
|
54
|
+
expiresAt: token.expires_at * 1000,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Java(Spring Boot 3)
|
|
60
|
+
|
|
61
|
+
```java
|
|
62
|
+
record KokoToken(String access_token, long expires_at) {}
|
|
63
|
+
record AppToken(String accessToken, long expiresAt) {}
|
|
64
|
+
|
|
65
|
+
AppToken exchange(String authenticatedUserId) throws Exception {
|
|
66
|
+
String subject = hmacSha256Hex(System.getenv("KOKO_SUBJECT_SALT"), authenticatedUserId);
|
|
67
|
+
String basic = Base64.getEncoder().encodeToString(
|
|
68
|
+
(System.getenv("KOKO_CLIENT_ID") + ":" + System.getenv("KOKO_CLIENT_SECRET"))
|
|
69
|
+
.getBytes(StandardCharsets.UTF_8));
|
|
70
|
+
String body = objectMapper.writeValueAsString(Map.of(
|
|
71
|
+
"app_id", System.getenv("KOKO_APP_ID"), "subject", subject));
|
|
72
|
+
HttpRequest request = HttpRequest.newBuilder(URI.create(kokoGateway + "/v1/sdk/token"))
|
|
73
|
+
.timeout(Duration.ofSeconds(5)).header("Authorization", "Basic " + basic)
|
|
74
|
+
.header("Content-Type", "application/json")
|
|
75
|
+
.POST(HttpRequest.BodyPublishers.ofString(body)).build();
|
|
76
|
+
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
77
|
+
if (response.statusCode() != 200) throw new IllegalStateException("Koko token unavailable");
|
|
78
|
+
KokoToken token = objectMapper.readValue(response.body(), KokoToken.class);
|
|
79
|
+
return new AppToken(token.access_token(), token.expires_at() * 1000L);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Spring Controller 必须从 `Principal` / SecurityContext 取得 `authenticatedUserId`,并返回 `Cache-Control: no-store`。
|
|
84
|
+
|
|
85
|
+
## Go(net/http)
|
|
86
|
+
|
|
87
|
+
```go
|
|
88
|
+
func exchangeKokoToken(ctx context.Context, authenticatedUserID string) (string, int64, error) {
|
|
89
|
+
mac := hmac.New(sha256.New, []byte(os.Getenv("KOKO_SUBJECT_SALT")))
|
|
90
|
+
mac.Write([]byte(authenticatedUserID))
|
|
91
|
+
body, _ := json.Marshal(map[string]string{
|
|
92
|
+
"app_id": os.Getenv("KOKO_APP_ID"), "subject": hex.EncodeToString(mac.Sum(nil)),
|
|
93
|
+
})
|
|
94
|
+
ctx, cancel := context.WithTimeout(ctx, 5*time.Second); defer cancel()
|
|
95
|
+
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, kokoGateway+"/v1/sdk/token", bytes.NewReader(body))
|
|
96
|
+
req.SetBasicAuth(os.Getenv("KOKO_CLIENT_ID"), os.Getenv("KOKO_CLIENT_SECRET"))
|
|
97
|
+
req.Header.Set("Content-Type", "application/json")
|
|
98
|
+
resp, err := http.DefaultClient.Do(req); if err != nil { return "", 0, err }
|
|
99
|
+
defer resp.Body.Close()
|
|
100
|
+
if resp.StatusCode != http.StatusOK { return "", 0, fmt.Errorf("koko token: %s", resp.Status) }
|
|
101
|
+
var out struct { AccessToken string `json:"access_token"`; ExpiresAt int64 `json:"expires_at"` }
|
|
102
|
+
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return "", 0, err }
|
|
103
|
+
return out.AccessToken, out.ExpiresAt * 1000, nil
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Python(FastAPI + HTTPX)
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import hashlib, hmac, os
|
|
111
|
+
import httpx
|
|
112
|
+
from fastapi import Depends, HTTPException, Response
|
|
113
|
+
|
|
114
|
+
@app.get('/koko/access-token')
|
|
115
|
+
async def koko_token(response: Response, user=Depends(require_host_user)):
|
|
116
|
+
subject = hmac.new(os.environ['KOKO_SUBJECT_SALT'].encode(), str(user.id).encode(), hashlib.sha256).hexdigest()
|
|
117
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
118
|
+
upstream = await client.post(
|
|
119
|
+
f'{KOKO_GATEWAY}/v1/sdk/token',
|
|
120
|
+
auth=(os.environ['KOKO_CLIENT_ID'], os.environ['KOKO_CLIENT_SECRET']),
|
|
121
|
+
json={'app_id': os.environ['KOKO_APP_ID'], 'subject': subject},
|
|
122
|
+
)
|
|
123
|
+
if upstream.status_code != 200:
|
|
124
|
+
raise HTTPException(502, 'Koko token unavailable')
|
|
125
|
+
token = upstream.json()
|
|
126
|
+
response.headers['Cache-Control'] = 'no-store'
|
|
127
|
+
return {'accessToken': token['access_token'], 'expiresAt': token['expires_at'] * 1000}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## PHP(Laravel)
|
|
131
|
+
|
|
132
|
+
```php
|
|
133
|
+
public function token(Request $request): JsonResponse
|
|
134
|
+
{
|
|
135
|
+
$userId = (string) $request->user()->getAuthIdentifier(); // auth middleware 已验证
|
|
136
|
+
$subject = hash_hmac('sha256', $userId, env('KOKO_SUBJECT_SALT'));
|
|
137
|
+
$upstream = Http::timeout(5)
|
|
138
|
+
->withBasicAuth(env('KOKO_CLIENT_ID'), env('KOKO_CLIENT_SECRET'))
|
|
139
|
+
->post(config('services.koko.gateway').'/v1/sdk/token', [
|
|
140
|
+
'app_id' => env('KOKO_APP_ID'), 'subject' => $subject,
|
|
141
|
+
]);
|
|
142
|
+
abort_unless($upstream->successful(), 502, 'Koko token unavailable');
|
|
143
|
+
return response()->json([
|
|
144
|
+
'accessToken' => $upstream['access_token'],
|
|
145
|
+
'expiresAt' => $upstream['expires_at'] * 1000,
|
|
146
|
+
])->header('Cache-Control', 'no-store');
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## C#(ASP.NET Core)
|
|
151
|
+
|
|
152
|
+
```csharp
|
|
153
|
+
app.MapGet("/koko/access-token", async (ClaimsPrincipal user, IHttpClientFactory factory) => {
|
|
154
|
+
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier)
|
|
155
|
+
?? throw new UnauthorizedAccessException();
|
|
156
|
+
var subject = Convert.ToHexString(HMACSHA256.HashData(
|
|
157
|
+
Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("KOKO_SUBJECT_SALT")!),
|
|
158
|
+
Encoding.UTF8.GetBytes(userId))).ToLowerInvariant();
|
|
159
|
+
var clientId = Environment.GetEnvironmentVariable("KOKO_CLIENT_ID")!;
|
|
160
|
+
var clientSecret = Environment.GetEnvironmentVariable("KOKO_CLIENT_SECRET")!;
|
|
161
|
+
var appId = Environment.GetEnvironmentVariable("KOKO_APP_ID")!;
|
|
162
|
+
var request = new HttpRequestMessage(HttpMethod.Post, $"{kokoGateway}/v1/sdk/token");
|
|
163
|
+
var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes(
|
|
164
|
+
$"{clientId}:{clientSecret}"));
|
|
165
|
+
request.Headers.Authorization = new("Basic", credentials);
|
|
166
|
+
request.Content = JsonContent.Create(new { app_id = appId, subject });
|
|
167
|
+
var upstream = await factory.CreateClient("koko").SendAsync(request);
|
|
168
|
+
if (!upstream.IsSuccessStatusCode) return Results.Problem(statusCode: 502, title: "Koko token unavailable");
|
|
169
|
+
var token = await upstream.Content.ReadFromJsonAsync<KokoToken>();
|
|
170
|
+
return Results.Json(new { accessToken = token!.access_token, expiresAt = token.expires_at * 1000 },
|
|
171
|
+
headers: new Dictionary<string, string> { ["Cache-Control"] = "no-store" });
|
|
172
|
+
}).RequireAuthorization();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## App 端
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const tokenProvider = React.useCallback(async () => {
|
|
179
|
+
const response = await hostApi.get('/koko/access-token');
|
|
180
|
+
return response.data as { accessToken: string; expiresAt: number };
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
<KokoProvider config={{
|
|
184
|
+
gateway: {
|
|
185
|
+
baseUrl: 'https://sdk.intentcomputing.cn',
|
|
186
|
+
tokenProvider,
|
|
187
|
+
},
|
|
188
|
+
}}>{children}</KokoProvider>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
不要把 token 写入 AsyncStorage、日志或分析事件。SDK 自己在内存中缓存,到期前刷新;如果网关返回 401,会清除缓存并强制调用一次 `tokenProvider`。
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Complete SDK configuration reference
|
|
2
|
+
|
|
3
|
+
> 中文版:[CONFIGURATION.zh-CN.md](./CONFIGURATION.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
This reference covers the public host configuration surfaces: `KokoProvider`, `KokoAgentConfig`, runtime appearance, per-run overrides, and built-in UI components. The exported TypeScript types remain the source of truth.
|
|
6
|
+
|
|
7
|
+
## Complete example
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { KokoProvider } from '@intentcomputing/koko-agent-sdk';
|
|
12
|
+
|
|
13
|
+
export function App() {
|
|
14
|
+
const tokenProvider = React.useCallback(async () => {
|
|
15
|
+
const response = await hostApi.get('/koko/access-token');
|
|
16
|
+
return response.data as { accessToken: string; expiresAt: number };
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<KokoProvider
|
|
21
|
+
cursor
|
|
22
|
+
observer
|
|
23
|
+
dialogs
|
|
24
|
+
resumeAfterReload
|
|
25
|
+
config={{
|
|
26
|
+
gateway: {
|
|
27
|
+
baseUrl: 'https://sdk.intentcomputing.net',
|
|
28
|
+
tokenProvider,
|
|
29
|
+
// appId normally comes from the native Bundle ID / Package Name.
|
|
30
|
+
},
|
|
31
|
+
locale: 'en',
|
|
32
|
+
theme: 'light', // The host theme; Koko chrome uses the contrasting theme.
|
|
33
|
+
branding: {
|
|
34
|
+
name: 'My Assistant',
|
|
35
|
+
tagline: 'Control this app with natural language',
|
|
36
|
+
placeholder: 'Tell me what you want to do…',
|
|
37
|
+
primaryColor: '#6D4AFF',
|
|
38
|
+
icon: require('./assistant.png'),
|
|
39
|
+
},
|
|
40
|
+
confirmation: { requireUserConfirm: true, mode: 'preflight' },
|
|
41
|
+
webWalletAliases: ['My Wallet'],
|
|
42
|
+
dumpMaxDepth: 80,
|
|
43
|
+
dumpSkipInvisible: true,
|
|
44
|
+
cursorDurationMs: 480,
|
|
45
|
+
observer: true,
|
|
46
|
+
debug: false,
|
|
47
|
+
onUsageEvent: event => analytics.track('koko_usage', event),
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<HostNavigation />
|
|
51
|
+
</KokoProvider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## `KokoProvider` props
|
|
57
|
+
|
|
58
|
+
| Option | Type | Default | Purpose |
|
|
59
|
+
|---|---|---:|---|
|
|
60
|
+
| `children` | `ReactNode` | Required | Host app or outer navigation tree. |
|
|
61
|
+
| `config` | `KokoAgentConfig` | `{}` | Agent, gateway, branding, and confirmation configuration. |
|
|
62
|
+
| `cursor` | `boolean` | `true` | Show the observable action cursor. |
|
|
63
|
+
| `observer` | `boolean` | Inherits `config.observer` | Mount progress and chat UI; an explicit prop wins. |
|
|
64
|
+
| `dialogs` | `boolean` | `true` | Mount built-in confirm and ask-user dialogs. Provide host callbacks if disabled. |
|
|
65
|
+
| `resumeAfterReload` | `boolean` | `true` | Resume one recent goal after a JS-context reload caused by host locale/theme changes. |
|
|
66
|
+
| `remoteSmokePort` | `number` | `0` | Local development smoke-driver port. Never enable in production. |
|
|
67
|
+
| `composerPlaceholder` | `string` | — | Deprecated; use `config.branding.placeholder`. |
|
|
68
|
+
|
|
69
|
+
`KokoProvider observer={false}` hides built-in progress/chat UI only. `config.observer={false}` also disables automatic task logging. Keep both enabled for the complete built-in experience.
|
|
70
|
+
|
|
71
|
+
## `KokoAgentConfig`
|
|
72
|
+
|
|
73
|
+
| Option | Type | Default | Purpose |
|
|
74
|
+
|---|---|---:|---|
|
|
75
|
+
| `gateway` | `GatewayConfig` | — | Gateway and short-token configuration required for production runs. |
|
|
76
|
+
| `locale` | `'zh' \| 'en'` | `'en'` | Initial built-in UI language. Prefer `setKokoAppearance` for runtime changes. |
|
|
77
|
+
| `theme` | `'light' \| 'dark'` | `'light'` | The **host app's** current theme; Koko chrome uses the opposite theme for contrast. |
|
|
78
|
+
| `branding` | `KokoBrandingConfig` | Koko defaults | Custom name, icon, copy, and accent; requires the `custom_branding` entitlement. |
|
|
79
|
+
| `confirmation` | `ConfirmationPolicy` | Confirm, `preflight` | Human confirmation policy for high-risk goals. |
|
|
80
|
+
| `onConfirmSensitive` | `(ctx) => Promise<boolean>` | Built-in dialog | Custom sensitive-action confirmation. Return `true` to proceed. |
|
|
81
|
+
| `webWalletAliases` | `string[]` | `[]` | Verified public labels for the host's own injected wallet. |
|
|
82
|
+
| `dumpMaxDepth` | `number` | `80` | Maximum native accessibility-tree depth. Small values can truncate RN navigation wrappers. |
|
|
83
|
+
| `dumpSkipInvisible` | `boolean` | `true` | Drop invisible nodes from observations. |
|
|
84
|
+
| `cursorDurationMs` | `number` | `480` | Default cursor animation duration in milliseconds. |
|
|
85
|
+
| `observer` | `boolean` | `true` | Enable task logs and, unless overridden by the Provider prop, built-in progress/chat UI. |
|
|
86
|
+
| `debug` | `boolean` | `true` in Metro Debug; `false` in Release | Capability and execution logs only; it never relaxes security. Set `false` explicitly in production. |
|
|
87
|
+
| `devMode` | `boolean` | Same as `debug` | Deprecated alias for `debug`. |
|
|
88
|
+
| `onUsageEvent` | `(event) => void` | — | Non-blocking local analytics/UX notifications; not authoritative for billing. |
|
|
89
|
+
|
|
90
|
+
### `gateway`
|
|
91
|
+
|
|
92
|
+
| Option | Type | Default | Purpose |
|
|
93
|
+
|---|---|---:|---|
|
|
94
|
+
| `baseUrl` | `string` | `https://sdk.intentcomputing.net` | Global/OpenAI gateway. Use `https://sdk.intentcomputing.cn` for China/DeepSeek. Release builds accept official hosts only. |
|
|
95
|
+
| `tokenProvider` | `() => Promise<string \| { accessToken; expiresAt? }>` | — | Recommended. Fetches a short token from the **host backend**. The SDK caches it in memory, refreshes lazily on a request near expiry, and refreshes/retries once after a 401. |
|
|
96
|
+
| `token` | `string` | — | Legacy static token for local development and migration only. Never ship it in production. |
|
|
97
|
+
| `appId` | `string` | Native Bundle ID / Package Name | Must match the registered host app; normally no explicit value is needed. |
|
|
98
|
+
|
|
99
|
+
`expiresAt` is Unix time in **milliseconds**. If omitted, the SDK reads JWT `exp`; if neither exists, it assumes 60 seconds. Never place `clientSecret` or DeepSeek/OpenAI keys in the app.
|
|
100
|
+
|
|
101
|
+
### `branding`
|
|
102
|
+
|
|
103
|
+
| Option | Type | Default | Purpose |
|
|
104
|
+
|---|---|---:|---|
|
|
105
|
+
| `name` | `string` | `Koko` | Assistant display name. |
|
|
106
|
+
| `icon` | `number \| { uri: string } \| string` | K mark | Accepts `require()`, an image/Data URI, or a short text mark. |
|
|
107
|
+
| `tagline` | `string` | Localized default | Chat header subtitle. |
|
|
108
|
+
| `placeholder` | `string` | Localized default | Composer placeholder. |
|
|
109
|
+
| `primaryColor` | `string` | SDK accent | Badge and send-button accent using a React Native color string. |
|
|
110
|
+
|
|
111
|
+
Without the entitlement, the SDK falls back to Koko defaults and emits one `branding_downgraded` usage event.
|
|
112
|
+
|
|
113
|
+
### `confirmation`
|
|
114
|
+
|
|
115
|
+
| Option | Type | Default | Purpose |
|
|
116
|
+
|---|---|---:|---|
|
|
117
|
+
| `requireUserConfirm` | `boolean` | `true` | Require confirmation for high-risk goals. |
|
|
118
|
+
| `mode` | `'preflight' \| 'inline' \| 'off'` | `'preflight'` | Confirm once before a run, use legacy inline checks, or disable SDK confirmation. |
|
|
119
|
+
| `sensitivePatterns` | `RegExp[]` | Built-in patterns | Custom high-risk goal rules. A non-empty array replaces the defaults. |
|
|
120
|
+
|
|
121
|
+
Disabling confirmation does not bypass local password, recovery-phrase, private-key, or OTP gates. The user must still complete those surfaces personally.
|
|
122
|
+
|
|
123
|
+
### `onUsageEvent` events
|
|
124
|
+
|
|
125
|
+
Events are `task_started`, `task_finished`, `agent_step`, `quota_exceeded`, `sheet_opened`, `sheet_closed`, and `branding_downgraded`. Do not forward goal text to an analytics provider without user authorization.
|
|
126
|
+
|
|
127
|
+
## Runtime locale and theme
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { setKokoAppearance } from '@intentcomputing/koko-agent-sdk';
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
setKokoAppearance({ locale: hostLocale, theme: hostTheme });
|
|
134
|
+
}, [hostLocale, hostTheme]);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- Locale strings beginning with `zh` map to Chinese; all others currently map to English.
|
|
138
|
+
- Theme is the host's actual `'light'` or `'dark'` theme.
|
|
139
|
+
- Runtime appearance takes precedence over static `config.locale` and `config.theme`.
|
|
140
|
+
|
|
141
|
+
`getKokoAgent().configure(patch)` updates configuration at runtime, but performs a shallow merge. Pass complete `gateway`, `branding`, or `confirmation` sub-objects when changing them. Prefer `setKokoAppearance` for locale/theme.
|
|
142
|
+
|
|
143
|
+
## Per-run overrides
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
await getKokoAgent().runGoal('Open settings', {
|
|
147
|
+
locale: 'en-US',
|
|
148
|
+
localRetries: 3,
|
|
149
|
+
userGateTimeoutMs: 300_000,
|
|
150
|
+
onAskUser: question => showHostQuestion(question),
|
|
151
|
+
onNarration: text => setHint(text),
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Option | Default | Purpose |
|
|
156
|
+
|---|---:|---|
|
|
157
|
+
| `baseUrl` / `tokenProvider` / `token` / `appId` | Inherit `config.gateway` | Override the gateway for this run only. |
|
|
158
|
+
| `locale` | Effective current locale | Planner request locale for this run. |
|
|
159
|
+
| `localRetries` | `3` | Retry count `0–3` for read-only `dump/find` failures. Touches and typing are never automatically replayed. |
|
|
160
|
+
| `userGateTimeoutMs` | `300000` | Maximum wait for the user to finish a password/recovery/authorization gate. |
|
|
161
|
+
| `onAskUser` | Built-in ask dialog | Host callback when the agent needs more information. |
|
|
162
|
+
| `onNarration` | — | Receives current task narration. |
|
|
163
|
+
|
|
164
|
+
`resumedFromReload` and `resumes` are SDK-internal recovery fields; hosts should not set them.
|
|
165
|
+
|
|
166
|
+
## Built-in UI component props
|
|
167
|
+
|
|
168
|
+
| Component | Options |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `KokoEntryButton` | `label` (default `Koko`) and `style`. Production hosts should normally draw their own entry and call `openKokoChat()`. |
|
|
171
|
+
| `KokoComposer` | `placeholder` and `disabled` (default `false`). |
|
|
172
|
+
| `KokoObserver` | `cursor` (default `true`) and deprecated `composerPlaceholder`. Normally mounted by the Provider. |
|
|
173
|
+
| `KokoOverlayLayer` | `style`, `pointerEvents` (default `box-none`), and `testID` for custom SDK chrome. |
|
|
174
|
+
|
|
175
|
+
## Low-level action options
|
|
176
|
+
|
|
177
|
+
Most hosts do not call these directly.
|
|
178
|
+
|
|
179
|
+
| Type | Options and defaults |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `DumpOptions` | `maxDepth`, `skipInvisible`, `silent`; absent values inherit Agent configuration. |
|
|
182
|
+
| `TapOptions` | `revealIfNeeded`, `silent`, `cursorDurationMs`; the planner normally decides when to reveal an offscreen control. |
|
|
183
|
+
| `SwipeOptions` | `durationMs`, `silent`. |
|
|
184
|
+
| `PinchOptions` | `durationMs` (default `280`), `silent`. |
|
|
185
|
+
| `TypeOptions` | `clear`, `silent`. |
|
|
186
|
+
|
|
187
|
+
See [Privacy](PRIVACY.md) for data boundaries and [backend authentication demos](BACKEND_AUTH_DEMOS.md) for the three-party token flow.
|
|
188
|
+
|