@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.
Files changed (68) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +13 -6
  3. package/README.zh-CN.md +13 -4
  4. package/docs/BACKEND_AUTH_DEMOS.md +42 -0
  5. package/docs/BACKEND_AUTH_DEMOS.zh-CN.md +191 -0
  6. package/docs/CONFIGURATION.md +188 -0
  7. package/docs/CONFIGURATION.zh-CN.md +195 -0
  8. package/docs/INTEGRATION.md +15 -6
  9. package/docs/INTEGRATION.zh-CN.md +13 -4
  10. package/lib/commonjs/KokoAgent.js +13 -11
  11. package/lib/commonjs/KokoAgent.js.map +1 -1
  12. package/lib/commonjs/client/AgentSession.js +3 -1
  13. package/lib/commonjs/client/AgentSession.js.map +1 -1
  14. package/lib/commonjs/client/GatewayClient.js +66 -12
  15. package/lib/commonjs/client/GatewayClient.js.map +1 -1
  16. package/lib/commonjs/cursor/CursorOverlay.js +1 -2
  17. package/lib/commonjs/cursor/CursorOverlay.js.map +1 -1
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/commonjs/provider/KokoProvider.js +1 -2
  20. package/lib/commonjs/provider/KokoProvider.js.map +1 -1
  21. package/lib/commonjs/provider/KokoUiContext.js +5 -7
  22. package/lib/commonjs/provider/KokoUiContext.js.map +1 -1
  23. package/lib/commonjs/ui/KokoChatSheet.js +1 -2
  24. package/lib/commonjs/ui/KokoChatSheet.js.map +1 -1
  25. package/lib/commonjs/ui/KokoComposer.js +1 -2
  26. package/lib/commonjs/ui/KokoComposer.js.map +1 -1
  27. package/lib/commonjs/ui/KokoDialogHost.js +1 -2
  28. package/lib/commonjs/ui/KokoDialogHost.js.map +1 -1
  29. package/lib/commonjs/ui/KokoObserver.js +1 -2
  30. package/lib/commonjs/ui/KokoObserver.js.map +1 -1
  31. package/lib/commonjs/ui/KokoSubtitle.js +1 -2
  32. package/lib/commonjs/ui/KokoSubtitle.js.map +1 -1
  33. package/lib/commonjs/ui/KokoTaskPanel.js +1 -2
  34. package/lib/commonjs/ui/KokoTaskPanel.js.map +1 -1
  35. package/lib/module/KokoAgent.js +12 -9
  36. package/lib/module/KokoAgent.js.map +1 -1
  37. package/lib/module/client/AgentSession.js +3 -1
  38. package/lib/module/client/AgentSession.js.map +1 -1
  39. package/lib/module/client/GatewayClient.js +66 -12
  40. package/lib/module/client/GatewayClient.js.map +1 -1
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/provider/KokoUiContext.js +4 -5
  43. package/lib/module/provider/KokoUiContext.js.map +1 -1
  44. package/lib/typescript/commonjs/KokoAgent.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/client/AgentSession.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/client/GatewayClient.d.ts +24 -2
  47. package/lib/typescript/commonjs/client/GatewayClient.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/index.d.ts +1 -1
  49. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/provider/KokoUiContext.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/types.d.ts +7 -2
  52. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  53. package/lib/typescript/module/KokoAgent.d.ts.map +1 -1
  54. package/lib/typescript/module/client/AgentSession.d.ts.map +1 -1
  55. package/lib/typescript/module/client/GatewayClient.d.ts +24 -2
  56. package/lib/typescript/module/client/GatewayClient.d.ts.map +1 -1
  57. package/lib/typescript/module/index.d.ts +1 -1
  58. package/lib/typescript/module/index.d.ts.map +1 -1
  59. package/lib/typescript/module/provider/KokoUiContext.d.ts.map +1 -1
  60. package/lib/typescript/module/types.d.ts +7 -2
  61. package/lib/typescript/module/types.d.ts.map +1 -1
  62. package/package.json +5 -1
  63. package/src/KokoAgent.ts +12 -9
  64. package/src/client/AgentSession.ts +2 -0
  65. package/src/client/GatewayClient.ts +88 -14
  66. package/src/index.ts +2 -0
  67. package/src/provider/KokoUiContext.tsx +4 -5
  68. 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 an app 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.
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
- - An Intent Computing app token bound to the host bundle ID or Android application ID. The app token is not an OpenAI or DeepSeek API key.
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. Replace the placeholder with the token issued for your host app and environment.
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: { token: 'YOUR_APP_TOKEN' }, locale: 'en' }}>
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). Use a token valid for the chosen environment. Provider API keys stay on the gateway and must not be bundled into the app.
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
- - 使用绑定宿主 Bundle ID / Android Application ID 的应用 token;它不是模型 API 密钥。
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: { token: 'YOUR_APP_TOKEN', baseUrl: 'https://sdk.intentcomputing.cn' },
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)。使用对应环境的应用 token,模型 API 密钥只保留在网关。语言或主题变化时调用 `setKokoAppearance({ locale, theme })`;品牌配置通过 `config.branding` 传入,并受应用权益控制。
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
+