@h-ai/api-client 0.1.0-alpha.67 → 0.1.0-alpha.69

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/README.en.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
4
4
 
5
+ Requests and token refresh use the current `core.i18n` locale as `Accept-Language`, so server errors follow the interface language. Set this header in `headers` to override it.
6
+
5
7
  [Framework home](../../README.en.md) · Related modules: [@h-ai/api-contract](../api-contract/README.en.md) · [@h-ai/core](../core/README.en.md) · [@h-ai/crypto](../crypto/README.en.md)
6
8
 
7
9
  ## Introduction
@@ -12,7 +14,7 @@ A cross-platform oRPC/OpenAPI typed client for hai-framework, with consistent AP
12
14
 
13
15
  - `apiClient`: the default typed client for the iam/storage/ai domains.
14
16
  - `apiClient.create(contract)`: create a typed client for a custom contract.
15
- - `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()`: token storage factories.
17
+ - `apiClient.tokenStorage.{memory,httpOnlyCookie}()`: token storage factories. Browsers keep access tokens in memory and refresh tokens in server-managed httpOnly cookies; native clients inject platform secure storage. Credentials are never written to Web Storage.
16
18
  - Custom `fetch` support for browsers, Node, Capacitor, and mini-program bridges.
17
19
  - Automatic Bearer token injection, with token refresh and one retry after a 401.
18
20
  - Network failures are converted to `HaiResult` errors.
@@ -53,7 +55,9 @@ await apiClient.close()
53
55
 
54
56
  #### Enable transport encryption
55
57
 
56
- First enable `serv.createApp({ transport: { crypto } })` on the server. The client only needs the same `@h-ai/crypto` service instance; no manual key exchange code is required.
58
+ First enable `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })` on the server. The client only needs the same `@h-ai/crypto` service instance; no manual key exchange code is required.
59
+
60
+ The server injects a managed serverKeyRing and shared registry. Clients receive only serverKeyId/public keys and include the negotiated version in encrypted requests; private keys never enter client configuration. After retirement of a key version, destroy the transport session and negotiate again; do not automatically replay non-idempotent operations.
57
61
 
58
62
  ```ts
59
63
  import { apiClient } from '@h-ai/api-client'
@@ -71,8 +75,14 @@ await apiClient.close()
71
75
  await crypto.close()
72
76
  ```
73
77
 
78
+ Only authenticated protocol v2 is accepted. Independent random SM4-CBC and HMAC-SM3 keys are wrapped with SM2; verify the sender signature and MAC before CBC decryption. Clients sign requests and the selected server key version signs responses. Authentication covers version, direction, serverKeyId, clientId, a random 128-bit requestId acting as the nonce, issuedAt, HTTP method, the SM3 pathHash of pathname plus the original search, and response status. Requests have a five-minute lifetime and at most 30 seconds of future clock skew. The shared registry atomically consumes (clientId, requestId), retaining it until at least issuedAt plus six minutes to cover node skew; storage failures reject requests. Synchronize node clocks and use a Redis noeviction/durable policy that preserves live nonces. Transport replay prevention does not replace business idempotency keys.
79
+
80
+ Empty POSTs still send an authenticated envelope for empty plaintext. GET/HEAD carry it in X-Hai-Transport; bodyless responses such as 204/304/HEAD use the same header. Other requests/responses carry a JSON envelope and X-Encrypted: true. The client must match the original request context and HTTP status; missing markers, old formats and authentication failures reject. Exclude plaintext endpoints explicitly with excludePaths. CORS must allow and expose X-Hai-Transport. TLS authenticates handshake keys; this protocol does not replace IAM authorization.
81
+
74
82
  ### API contracts
75
83
 
84
+ The default `apiClient.ai.chats.createCompletion({ messages, tools, tool_choice })` uses a non-streaming JSON contract and retains complete nested function JSON Schema and `strict`. Check `success` before reading `data.choices[].message.tool_calls`. After authorization and execution, include the assistant tool calls and tool results with matching `tool_call_id` in the next `messages`. The input does not support `stream`; the server rejects it even if TypeScript is bypassed. Bind an application's own stream contract for streaming.
85
+
76
86
  Custom applications can bind their own oRPC contract:
77
87
 
78
88
  ```ts
@@ -90,10 +100,12 @@ const result = await client.iam.auth.login({ identifier: 'alice', password: 'sec
90
100
 
91
101
  - `apiClient.init(config)`: initialize the default client.
92
102
  - `apiClient.close()`: clear client state.
93
- - `apiClient.auth.setTokens(tokens)`: store the access token; storage other than httpOnly also stores the refresh token.
103
+ - `apiClient.auth.setTokens(tokens)`: replace credentials and store the access token; non-httpOnly storage saves the supplied refresh token or clears its previous value when omitted.
94
104
  - `apiClient.auth.clear()`: clear tokens.
95
105
  - `apiClient.create(contract)`: create a custom typed client.
96
106
 
107
+ `auth.clear()` and `auth.setTokens()` immediately cancel and invalidate this client's previous refresh request. A late success or 401 cannot restore a logged-out session, overwrite a new account, or notify stale refresh callbacks. Storage writes are serialized, so an earlier asynchronous write cannot overwrite credentials after either method completes. Use these methods when logging out or switching accounts instead of writing to storage directly. The server logout endpoint must still revoke the httpOnly cookie and server session.
108
+
97
109
  Calling `init()` on an initialized client returns `CONFIG_ERROR` and leaves the existing client intact. To change `baseUrl` / `fetch` / `transport`, call `await apiClient.close()` before initializing again.
98
110
 
99
111
  ### Configuration
@@ -110,6 +122,8 @@ Calling `init()` on an initialized client returns `CONFIG_ERROR` and leaves the
110
122
 
111
123
  ### Configuration driven authentication
112
124
 
125
+ Password login submits only `identifier` and `password`; `AuthPanel` maps the `LoginForm` field `username` to `identifier`. Server settings `session.maxAge` / `session.refreshTokenMaxAge` control session lifetimes. The login form has no per-login persistent-session option.
126
+
113
127
  Agreements support configuration-loaded userAgreementMarkdown/privacyPolicyMarkdown and URLs. Markdown takes priority for the same agreement and is safely rendered in a modal.
114
128
 
115
129
  apiClient.createAuthActions(client) supplies publicConfig, login, register, sendOtp and password-reset actions for a client containing the standard IAM contract. Login uses identifier/password/method/code; successful authentication saves tokens through the existing configured storage. Applications only refresh their own state or navigate after authentication.
@@ -134,6 +148,10 @@ Client-side failures such as missing initialization, network errors, timeouts, a
134
148
 
135
149
  Automatic refresh handles an actual HTTP 401 (`Response.status`) or a valid failed HaiResult over HTTP 200 (`success=false`, string `error.code/message` and `error.httpStatus=401`). After a successful refresh, the original request is retried once. Built-in login and registration routes and the configured `auth.refreshPath` never trigger automatic refresh; other failures such as 403/500 do not trigger it either. Ordinary serv procedures still return HTTP 200. Callers must check the final `success` and `error.code` and handle expired authentication when refresh fails.
136
150
 
151
+ ## Input limits and HTTP capabilities
152
+
153
+ Object keys are limited to 1024 UTF-8 bytes and reject absolute paths, backslashes, control characters and dot segments. Each public HTTP batch delete accepts 1–1000 entries. `@h-ai/storage/schemas` exposes the shared browser-safe rules. Presign expiry is an integer from 1 to 604800 seconds (Node default 3600, HTTP default 900). Upload signing accepts only key/contentType/expiresIn; contentLength/maxSize/metadata/checksum are unsupported. Invalid input returns HTTP 400 and the typed client maps it to VALIDATION_FAILED. FileUpload limits guide file selection; applications must validate actual objects on the server. Node streaming/checksum/conditional-write options have no default HTTP endpoints. See the [capability and authorization matrix (Chinese)](../storage/README.md#存储输入约束与跨端能力).
154
+
137
155
  ## Development and testing
138
156
 
139
157
  ```bash
@@ -141,6 +159,24 @@ pnpm --filter @h-ai/api-client test
141
159
  pnpm --filter @h-ai/api-client typecheck
142
160
  ```
143
161
 
162
+ Clients learn `transport.maxPlaintextBytes` from server key exchange. An explicit local limit selects the smaller value. Capacity failures return `HaiApiClientError.PAYLOAD_TOO_LARGE` (`hai:api-client:020`) with direction, stage and byte diagnostics. See [capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md).
163
+
164
+ serv/kit return an encrypted HTTP 400 time error for an out-of-window message only after request binding, signature and MAC verification. The client throws `TransportTimeError` (`hai:crypto:071`) with advice to synchronize device/server clocks and refresh. `api-client` maps it to `VALIDATION_FAILED` and preserves `cause.transportCode`. Requests are not replayed automatically and replay windows remain unchanged; tampered or unauthenticated messages retain the generic authentication failure.
165
+
166
+ ## Logical request observation
167
+
168
+ `client.observeRequests` observes each logical call once and returns an unsubscribe function. Records contain requestId, procedure, duration, HTTP status, final success/errorCode, and business request attempts. A failed HaiResult with HTTP 200 is a failure. Authentication retries retain the same requestId. Records exclude bodies, tokens, and URL query parameters. Refresh and key exchange are not presented as user operations. close clears subscriptions, so subscribe again after reinitializing.
169
+
170
+ ```ts
171
+ import { apiClient } from '@h-ai/api-client'
172
+
173
+ const unsubscribe = apiClient.observeRequests((record) => {
174
+ const failed = !record.success
175
+ const requestId = record.requestId
176
+ })
177
+ unsubscribe()
178
+ ```
179
+
144
180
  ## License
145
181
 
146
182
  Apache-2.0
package/README.ja.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
4
4
 
5
+ 通常のリクエストとトークン更新は、現在の `core.i18n` 言語を `Accept-Language` に設定します。サーバーのエラーも画面の言語に従います。`headers` で明示的に上書きできます。
6
+
5
7
  [フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/api-contract](../api-contract/README.ja.md) · [@h-ai/core](../core/README.ja.md) · [@h-ai/crypto](../crypto/README.ja.md)
6
8
 
7
9
  ## 概要
@@ -12,7 +14,7 @@ hai-framework のクロスプラットフォーム oRPC/OpenAPI 型付きクラ
12
14
 
13
15
  - `apiClient`:iam/storage/ai ドメインに対応する既定の型付きクライアント。
14
16
  - `apiClient.create(contract)`:独自の contract に対応する型付きクライアントを作成。
15
- - `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()`:Token ストレージのファクトリー。
17
+ - `apiClient.tokenStorage.{memory,httpOnlyCookie}()`:Token ストレージのファクトリー。ブラウザーでは access token をメモリー、refresh token をサーバー管理の httpOnly Cookie に保持し、ネイティブではプラットフォームの安全なストレージを注入します。認証情報を Web Storage に保存しません。
16
18
  - ブラウザー、Node、Capacitor、ミニプログラムのブリッジに対応する独自 `fetch` を指定可能。
17
19
  - Bearer Token の自動付与と、401 応答後の更新および 1 回の再試行。
18
20
  - ネットワークエラーを `HaiResult` のエラーに変換。
@@ -53,7 +55,9 @@ await apiClient.close()
53
55
 
54
56
  #### 通信暗号化の有効化
55
57
 
56
- サーバーで先に `serv.createApp({ transport: { crypto } })` を有効にします。クライアントには同じ `@h-ai/crypto` サービスのインスタンスを渡すだけで、鍵交換コードの実装は不要です。
58
+ サーバーで先に `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })` を有効にします。クライアントには同じ `@h-ai/crypto` サービスのインスタンスを渡すだけで、鍵交換コードの実装は不要です。
59
+
60
+ サーバーには管理済み serverKeyRing と共有 registry を渡します。クライアントは serverKeyId と公開鍵だけを受け取り、暗号文リクエストに鍵の版を付けます。秘密鍵・鍵環をクライアント設定に含めません。版の失効後は transport セッションを破棄して再交渉し、非冪等処理を自動再送しません。
57
61
 
58
62
  ```ts
59
63
  import { apiClient } from '@h-ai/api-client'
@@ -71,8 +75,14 @@ await apiClient.close()
71
75
  await crypto.close()
72
76
  ```
73
77
 
78
+ 認証済み v2 のみを受け付けます。独立したランダム SM4-CBC 鍵と HMAC-SM3 鍵を SM2 で包み、送信者署名、MAC の順に検証してから CBC を復号します。要求はクライアント秘密鍵、応答は選択されたサーバー鍵バージョンで署名します。version、direction、serverKeyId、clientId、nonce を兼ねるランダム 128 ビット requestId、issuedAt、HTTP method、pathname と元の search の SM3 pathHash、応答 status を認証します。要求の有効期限は 5 分、未来方向の時計差は最大 30 秒です。共有 registry が (clientId, requestId) を原子的に消費し、ノード間の時計差を含め issuedAt + 6 分まで保持します。保存障害では拒否します。時計同期と Redis の noeviction/永続化により有効な nonce を保持してください。業務の再試行には別の冪等キーが必要です。
79
+
80
+ 空の POST も空平文の認証封装を送ります。GET/HEAD は X-Hai-Transport ヘッダー、204/304/HEAD など本文なし応答も同じヘッダーを使います。他の要求・応答は JSON 本文と X-Encrypted: true を使います。クライアントは元の要求コンテキストと HTTP status を照合し、マーカー欠落、旧形式、認証失敗では reject します。平文 endpoint は excludePaths で明示的に除外し、CORS では X-Hai-Transport を許可・公開してください。鍵交換の公開鍵認証は TLS に依存し、IAM の業務認可は別に必要です。
81
+
74
82
  ### API 契約
75
83
 
84
+ 既定の `apiClient.ai.chats.createCompletion({ messages, tools, tool_choice })` は非ストリーミング JSON 契約で、function のネストした JSON Schema と `strict` を保持します。`success` を確認してから `data.choices[].message.tool_calls` を読み、アプリで認可・実行した後、助手の呼び出しと対応する `tool_call_id` 付きツール結果を次の `messages` に含めます。`stream` は未対応で、TypeScript を回避してもサーバーが拒否します。ストリーミングにはアプリ独自の対応契約を利用します。
85
+
76
86
  独自のアプリケーションでは、専用の oRPC contract を指定できます:
77
87
 
78
88
  ```ts
@@ -90,10 +100,12 @@ const result = await client.iam.auth.login({ identifier: 'alice', password: 'sec
90
100
 
91
101
  - `apiClient.init(config)`:既定のクライアントを初期化。
92
102
  - `apiClient.close()`:クライアントの状態を解放。
93
- - `apiClient.auth.setTokens(tokens)`:access token を保存。httpOnly 以外のストレージでは refresh token も保存します。
103
+ - `apiClient.auth.setTokens(tokens)`:認証情報を置き換えて access token を保存。httpOnly 以外では指定された refresh token も保存し、省略時は古い値を削除します。
94
104
  - `apiClient.auth.clear()`:Token を削除。
95
105
  - `apiClient.create(contract)`:独自の型付きクライアントを作成。
96
106
 
107
+ `auth.clear()` と `auth.setTokens()` は、このクライアントの既存の更新リクエストを直ちにキャンセルして無効化します。遅れて届いた成功応答や 401 は、ログアウト済みの認証状態を復元したり、新しいアカウントを上書きしたり、古い更新コールバックを通知したりしません。ストレージへの書き込みは直列化され、メソッド完了後に以前の非同期書き込みが認証情報を上書きすることはありません。ログアウトとアカウント切り替えには、ストレージを直接変更せずこれらのメソッドを使ってください。httpOnly Cookie とサーバーセッションは、引き続きサーバーのログアウトエンドポイントで失効させる必要があります。
108
+
97
109
  初期化済みのクライアントで `init()` を再度呼ぶと `CONFIG_ERROR` が返り、既存のクライアントは維持されます。`baseUrl` / `fetch` / `transport` を変更する場合は、先に `await apiClient.close()` を呼んでから再初期化してください。
98
110
 
99
111
  ### 設定
@@ -110,6 +122,8 @@ const result = await client.iam.auth.login({ identifier: 'alice', password: 'sec
110
122
 
111
123
  ### 設定による認証画面
112
124
 
125
+ パスワードログインの送信項目は `identifier` と `password` のみです。`AuthPanel` は `LoginForm` の `username` を `identifier` に変換します。有効期間はサーバーの `session.maxAge` / `session.refreshTokenMaxAge` で設定し、フォームにログインごとの永続セッション選択肢はありません。
126
+
113
127
  規約は設定の userAgreementMarkdown/privacyPolicyMarkdown または URL を使用できます。同じ規約は Markdown 本文を優先して安全な Modal で描画します。
114
128
 
115
129
  標準 IAM contract のクライアントには apiClient.createAuthActions(client) を使用します。複数方式、登録、OTP、復旧を統一し、認証成功時に設定済み TokenStorage に保存します。アプリ側は認証後の状態更新と遷移だけを担当します。
@@ -134,6 +148,10 @@ else {
134
148
 
135
149
  自動更新は実際の HTTP 401(`Response.status`)、または HTTP 200 の有効な失敗 HaiResult(`success=false`、文字列の `error.code/message`、`error.httpStatus=401`)で動作します。更新が成功すると元のリクエストを一度だけ再試行します。組み込みのログイン・登録と設定された `auth.refreshPath` は自動更新の対象外で、403/500 など他の失敗でも更新しません。serv の通常の procedure は引き続き HTTP 200 を返します。呼び出し側は最終結果の `success` と `error.code` を確認し、更新失敗による認証失効を処理してください。
136
150
 
151
+ ## 入力制約と HTTP 機能
152
+
153
+ キーは UTF-8 で最大 1024 バイトで、絶対パス、バックスラッシュ、制御文字、ドットセグメントを拒否します。公開 HTTP 一括削除は一度に 1–1000 件です。共通規則はブラウザー対応の `@h-ai/storage/schemas` から参照できます。署名有効期限は整数 1–604800 秒(Node 既定 3600、HTTP 既定 900)。アップロード署名の入力は key/contentType/expiresIn のみで、contentLength/maxSize/metadata/checksum は未対応です。不正入力は HTTP 400、typed client では VALIDATION_FAILED です。FileUpload の制限は選択時の補助であり、実際のオブジェクトはサーバーで検証してください。Node のストリーム/checksum/条件付き書き込みには既定の HTTP エンドポイントがありません。[機能と認可の対応表(中国語)](../storage/README.md#存储输入约束与跨端能力)を参照してください。
154
+
137
155
  ## 開発とテスト
138
156
 
139
157
  ```bash
@@ -141,6 +159,24 @@ pnpm --filter @h-ai/api-client test
141
159
  pnpm --filter @h-ai/api-client typecheck
142
160
  ```
143
161
 
162
+ `transport.maxPlaintextBytes` は鍵交換で取得します。ローカル上限を明示した場合は小さい値を採用します。容量エラーは `HaiApiClientError.PAYLOAD_TOO_LARGE`(`hai:api-client:020`)と方向・段階・バイト診断を返します。[容量と移行](../crypto/TRANSPORT-CAPACITY.md)を参照してください。
163
+
164
+ serv/kit は要求の結合、署名、MAC を検証した後、時間窓を超えた報文に暗号化した HTTP 400 時刻エラーを返します。クライアントは `TransportTimeError`(`hai:crypto:071`)で端末とサーバーの時刻同期・再読み込みを案内します。`api-client` は `VALIDATION_FAILED` に変換し `cause.transportCode` に原因を保持します。自動再送やリプレイ時間窓の緩和は行わず、改ざん・未認証の要求は従来の認証エラーです。
165
+
166
+ ## 論理リクエストの監視
167
+
168
+ `client.observeRequests` は論理呼び出しごとに一度通知し、解除関数を返します。requestId、procedure、所要時間、HTTP 状態、最終 success/errorCode、業務リクエストの attempts を含みます。HTTP 200 の失敗 HaiResult も失敗として扱い、認証更新後の再試行も同じ requestId を保持します。本文・Token・URL クエリーは含みません。認証更新や鍵交換をユーザー操作としません。close は購読を解除するため、再初期化後は再購読してください。
169
+
170
+ ```ts
171
+ import { apiClient } from '@h-ai/api-client'
172
+
173
+ const unsubscribe = apiClient.observeRequests((record) => {
174
+ const failed = !record.success
175
+ const requestId = record.requestId
176
+ })
177
+ unsubscribe()
178
+ ```
179
+
144
180
  ## ライセンス
145
181
 
146
182
  Apache-2.0
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
4
4
 
5
+ 普通请求与 Token 刷新默认使用当前 `core.i18n` 语言作为 `Accept-Language`,服务端错误随界面语言切换。可通过 `headers` 显式覆盖该请求头。
6
+
5
7
  [框架首页](../../README.md) · 相关模块:[@h-ai/api-contract](../api-contract/README.md) · [@h-ai/core](../core/README.md) · [@h-ai/crypto](../crypto/README.md)
6
8
 
7
9
  ## 简介
@@ -12,7 +14,7 @@ hai-framework 的跨端 oRPC/OpenAPI typed client,面向 Web、App、小程序
12
14
 
13
15
  - `apiClient`:默认绑定 iam/storage/ai 领域的统一 typed client 入口。
14
16
  - `apiClient.create(contract)`:为自定义 contract 创建 typed client。
15
- - `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()`:Token 存储工厂。
17
+ - `apiClient.tokenStorage.{memory,httpOnlyCookie}()`:Token 存储工厂。浏览器使用内存 access token + 服务端 httpOnly refresh Cookie;原生端注入平台安全存储,不将凭据写入 Web Storage。
16
18
  - 支持自定义 `fetch`,适配浏览器、Node、Capacitor、小程序桥接层。
17
19
  - 支持 Bearer Token 自动注入、401 后刷新并重试一次。
18
20
  - 网络错误统一转换为 `HaiResult` 错误。
@@ -53,7 +55,9 @@ await apiClient.close()
53
55
 
54
56
  #### 启用传输加密
55
57
 
56
- 服务端需先启用 `serv.createApp({ transport: { crypto } })`;客户端只注入同一个 `@h-ai/crypto` 服务实例,无需手写密钥协商代码。
58
+ 服务端需先启用 `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`;客户端只注入同一个 `@h-ai/crypto` 服务实例,无需手写密钥协商代码。
59
+
60
+ 服务端必须注入受管 serverKeyRing 和共享 registry;客户端只接收 serverKeyId/公钥,并在密文请求带回版本。密钥环/私钥不进入客户端配置。旧版本撤销后,销毁 transport 会话并重新协商;不要自动重放非幂等业务。
57
61
 
58
62
  ```ts
59
63
  import { apiClient } from '@h-ai/api-client'
@@ -71,8 +75,14 @@ await apiClient.close()
71
75
  await crypto.close()
72
76
  ```
73
77
 
78
+ 认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求有效期 5 分钟,未来时钟偏差最多 30 秒;共享 registry 原子消费 (clientId, requestId),nonce 至少保留到 issuedAt + 6 分钟以覆盖节点偏差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
79
+
80
+ 空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口只能经 excludePaths 显式排除。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
81
+
74
82
  ### API 契约
75
83
 
84
+ 默认 `apiClient.ai.chats.createCompletion({ messages, tools, tool_choice })` 使用非流式 JSON 契约,完整传递 function 工具的嵌套 JSON Schema 与 `strict`。先判断 `success`,再读取 `data.choices[].message.tool_calls`;业务方授权执行后,把助手工具调用和带对应 `tool_call_id` 的工具结果放入下一次 `messages`。输入不支持 `stream`,即使绕过 TypeScript 传入也会被服务端拒绝;流式接口须绑定应用自己的流契约。
85
+
76
86
  自定义应用可以绑定自己的 oRPC contract:
77
87
 
78
88
  ```ts
@@ -90,10 +100,12 @@ const result = await client.iam.auth.login({ identifier: 'alice', password: 'sec
90
100
 
91
101
  - `apiClient.init(config)`:初始化默认 client。
92
102
  - `apiClient.close()`:清理 client 状态。
93
- - `apiClient.auth.setTokens(tokens)`:写入 access token;非 httpOnly 存储会同时写入 refresh token。
103
+ - `apiClient.auth.setTokens(tokens)`:替换凭据,写入 access token;非 httpOnly 存储写入提供的 refresh token,缺省时清除旧值。
94
104
  - `apiClient.auth.clear()`:清理 token。
95
105
  - `apiClient.create(contract)`:创建自定义 typed client。
96
106
 
107
+ `auth.clear()` 和 `auth.setTokens()` 会立即取消并作废当前客户端的旧刷新请求。迟到的成功或 401 响应不会恢复已退出会话、覆盖新账号或触发旧刷新回调;存储写入串行提交,方法完成后不会再被之前已开始的异步写入覆盖。退出和切换账号须通过这些方法更新凭据,不直接修改 storage。httpOnly Cookie 和服务端会话仍由服务端退出接口撤销。
108
+
97
109
  已初始化后再次调用 `init()` 会返回 `CONFIG_ERROR`,不会覆盖现有 client;如需切换 `baseUrl` / `fetch` / `transport`,请先 `await apiClient.close()` 再重新初始化。
98
110
 
99
111
  ### 配置
@@ -110,6 +122,8 @@ const result = await client.iam.auth.login({ identifier: 'alice', password: 'sec
110
122
 
111
123
  ### 配置驱动认证页面
112
124
 
125
+ 密码登录只提交 `identifier` 和 `password`;`AuthPanel` 将 `LoginForm` 的 `username` 映射为 `identifier`。会话有效期由服务端 `session.maxAge` / `session.refreshTokenMaxAge` 配置决定,登录表单不提供按次登录的持久会话选项。
126
+
113
127
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
114
128
 
115
129
  `apiClient.createAuthActions(client)` 为含标准 iam contract 的客户端提供 publicConfig、login、register、sendOtp、requestPasswordReset、confirmPasswordReset。login 输入使用 identifier/password/method/code,method 为 password、ldap、email、sms。成功认证自动调用既有安全 TokenStorage;应用只需把动作传给 AuthPanel,并在 onauthenticated 中刷新自己的用户状态或导航。
@@ -134,6 +148,10 @@ else {
134
148
 
135
149
  自动刷新在真实 HTTP 401(`Response.status`)或 HTTP 200 的有效失败 HaiResult(`success=false`、`error.code/message` 为字符串且 `error.httpStatus=401`)后触发,刷新成功后只重试一次原请求。内置登录、注册与配置的 `auth.refreshPath` 不触发自动刷新,403/500 等其它失败也不会触发。serv 普通 procedure 的 HTTP 状态仍为 200;调用方须检查最终结果的 `success` 与 `error.code`,并处理刷新失败后的登录失效。
136
150
 
151
+ ## 输入约束与 HTTP 能力
152
+
153
+ 存储 key 按 UTF-8 最多 1024 字节,并拒绝绝对路径、反斜线、控制字符及点路径段;公开 HTTP 批量删除每次 1–1000 项。`@h-ai/storage/schemas` 提供浏览器安全的共同规则。预签名有效期为整数 1–604800 秒(Node 默认 3600,HTTP 默认 900);HTTP 上传仅接受 key/contentType/expiresIn,不支持 contentLength/maxSize/metadata/checksum。无效输入返回 HTTP 400,typed client 映射 VALIDATION_FAILED。FileUpload 的大小/类型限制仅为前端体验,实际对象须由应用服务端复核。Node 流式读写/checksum/条件写等没有默认 HTTP 端点。完整映射与授权边界见 [存储 API 能力](../storage/README.md#存储输入约束与跨端能力)。
154
+
137
155
  ## 开发测试
138
156
 
139
157
  ```bash
@@ -141,6 +159,24 @@ pnpm --filter @h-ai/api-client test
141
159
  pnpm --filter @h-ai/api-client typecheck
142
160
  ```
143
161
 
162
+ 客户端默认从服务端握手获取 `transport.maxPlaintextBytes`;可显式设置本地同名字段取较小值。容量失败返回 `HaiApiClientError.PAYLOAD_TOO_LARGE`(`hai:api-client:020`),保留方向、阶段和字节诊断。见 [容量配置与升级](../crypto/TRANSPORT-CAPACITY.md)。
163
+
164
+ serv/kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的报文返回加密的 HTTP 400 时间错误;客户端抛 `TransportTimeError`(`hai:crypto:071`),提示同步设备和服务器时间后刷新重试。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 保留该原因。不会自动重放请求或放宽防重放窗口;篡改或未认证报文仍返回通用认证失败。
165
+
166
+ ## 逻辑请求观察
167
+
168
+ `client.observeRequests` 返回可取消的逻辑请求观察:requestId、procedure、耗时、HTTP 状态、最终 success/errorCode 与实际业务请求 attempts。HTTP 200 的失败 HaiResult 也记为失败,自动刷新后的重试保留同一 requestId,整个逻辑调用只产生一次结果。记录不包含正文、Token 或 URL 查询参数。刷新/密钥协商不伪装成用户业务操作;close 清理本实例的订阅,重新 init 后需重新订阅。
169
+
170
+ ```ts
171
+ import { apiClient } from '@h-ai/api-client'
172
+
173
+ const unsubscribe = apiClient.observeRequests((record) => {
174
+ const failed = !record.success
175
+ const requestId = record.requestId
176
+ })
177
+ unsubscribe()
178
+ ```
179
+
144
180
  ## 许可证
145
181
 
146
182
  Apache-2.0