Appearance
TypeScript SDK
@hxsyai/aiadp 是 AI 应用开发平台 OpenAPI 的 TypeScript SDK。它负责请求封装、字段转换、SSE / WebSocket 通信、类型和错误处理;登录、令牌刷新与凭据保存由调用方负责。
- 当前版本:
0.3.0 - 浏览器:现代浏览器
- Node.js:18+
- OpenAPI 地址:
https://runtime-api.invalid/v1/openapi
选择引入方式
| 方式 | 适用场景 | Tree Shaking | 页面全局变量 |
|---|---|---|---|
| npm / pnpm / yarn | Vue、React、Node.js、TypeScript 等工程化项目,推荐 | 支持 | 无 |
| CDN 全局脚本 | 无构建工具的传统 HTML 页面 | 不支持 | AIADP |
| CDN 异步加载 | 希望按需加载 SDK 的页面 | 不支持全量 global 包裁剪 | AIADP 或 ESM 模块 |
三种方式提供相同的 Client 和业务能力,区别只在加载与打包方式。
方式一:包管理器安装(推荐)
SDK 发布在公司 Nexus npm 仓库。首次使用时,为 @hxsyai scope 配置仓库地址:
bash
npm config set @hxsyai:registry https://repo.i.huaxisy.com/repository/npm-releases/安装 SDK:
bash
npm install @hxsyai/aiadp也可使用 pnpm 或 yarn:
bash
pnpm add @hxsyai/aiadp
# 或
yarn add @hxsyai/aiadp导入完整客户端:
ts
import { Client } from "@hxsyai/aiadp";
const client = new Client({
baseUrl: "https://runtime-api.invalid/v1/openapi",
auth: async () => ({
Authorization: `Bearer ${await getApiKey()}`,
}),
});如果只使用某一类能力,可以从子入口导入,减少应用最终打包体积:
ts
import { ChatClient } from "@hxsyai/aiadp/chat";
const chat = new ChatClient({
baseUrl: "https://runtime-api.invalid/v1/openapi",
auth: getAuthHeaders,
});支持的子入口包括:apps、chat、files、knowledge、workflow、thirdparty、voice 和 node。
方式二:CDN 全局脚本
通过普通 script 标签加载:
html
<script src="https://runtime-docs.invalid/sdk/aiadp/aiadp.global.min.js"></script>
<script>
const client = new AIADP.Client({
baseUrl: "https://runtime-api.invalid/v1/openapi",
auth: () => ({
Authorization: `Bearer ${getApiKey()}`,
}),
});
</script>加载完成后,SDK 暴露在 globalThis.AIADP。该文件是浏览器全量包,不支持 Tree Shaking。
方式三:CDN 异步加载
动态导入 ESM
现代浏览器可以直接动态导入 aiadp.esm.min.js:
html
<script type="module">
const { Client } = await import(
"https://runtime-docs.invalid/sdk/aiadp/aiadp.esm.min.js"
);
const client = new Client({
baseUrl: "https://runtime-api.invalid/v1/openapi",
auth: () => ({
Authorization: `Bearer ${getApiKey()}`,
}),
});
</script>使用异步 Loader
传统页面也可以先加载体积较小的 Loader,再由 Loader 加载 global 包:
html
<script src="https://runtime-docs.invalid/sdk/aiadp/aiadp-loader.min.js"></script>
<script>
AIADPLoader.load({
src: "https://runtime-docs.invalid/sdk/aiadp/aiadp.global.min.js",
expectedVersion: "0.3.0",
timeout: 15000,
}).then(({ Client }) => {
const client = new Client({
baseUrl: "https://runtime-api.invalid/v1/openapi",
auth: () => ({
Authorization: `Bearer ${getApiKey()}`,
}),
});
});
</script>Loader 会复用同一地址正在进行的加载,并支持超时、CSP nonce、SRI、跨域模式和版本校验。Loader 只负责异步加载,不包含 SDK 业务代码。
本地 HTML 测试
浏览器不能通过 file:// 动态加载 ESM 文件。请将测试页面与 SDK 文件放在静态资源目录中,通过任意 HTTP 静态服务器访问,并确保服务器为 .js 文件返回正确的 JavaScript MIME 类型。
CDN 文件说明
| 文件 | 完整访问地址 | 用途 |
|---|---|---|
aiadp.global.min.js | https://runtime-docs.invalid/sdk/aiadp/aiadp.global.min.js | 普通 script 标签使用的全量包,暴露 globalThis.AIADP |
aiadp.esm.min.js | https://runtime-docs.invalid/sdk/aiadp/aiadp.esm.min.js | 供现代浏览器 import / import() 使用的 ESM 全量包 |
aiadp-loader.min.js | https://runtime-docs.invalid/sdk/aiadp/aiadp-loader.min.js | 异步加载 aiadp.global.min.js 的小型加载器 |
manifest.json | https://runtime-docs.invalid/sdk/aiadp/manifest.json | 当前版本与 CDN 文件清单 |
ESM 跨域加载
业务页面与文档站不同源时,浏览器动态导入 aiadp.esm.min.js 还要求文档站响应允许 CORS。如果部署环境未开启 CORS,请使用 aiadp.global.min.js 或 Loader 方式。
鉴权
SDK 不接管登录、令牌刷新或凭据保存。调用方通过 auth(context) 返回当前请求需要的请求头:
ts
const client = new Client({
baseUrl: "https://runtime-api.invalid/v1/openapi",
auth: async (context) => {
const apiKey = await credentialStore.getApiKey();
return {
Authorization: apiKey.startsWith("Bearer ") ? apiKey : `Bearer ${apiKey}`,
};
},
});auth() 会在每次请求时调用,并且可以返回 Promise,因此可接入调用方已有的令牌刷新逻辑。context 包含 method、path、operation 和当前重试次数 attempt。
固定请求头也可以通过 headers 传入,但不适合需要刷新的凭据:
ts
const client = new Client({
baseUrl: "https://runtime-api.invalid/v1/openapi",
headers: {
Authorization: `Bearer ${apiKey}`,
},
});浏览器凭据安全
写入网页 JavaScript、HTML、Local Storage 或请求头的 API Key 都可以被浏览器用户查看。auth() 用于统一管理和动态获取凭据,并不能在浏览器中隐藏凭据。不要把长期有效或高权限密钥提交到前端源码;生产环境应根据安全要求使用受限密钥、短期凭据或由自己的服务端代理请求。
应用列表
ts
const result = await client.apps.list({
appType: 2,
keyword: "助手",
page: 1,
pageSize: 10,
});
for (const app of result.items) {
console.log(app.id, app.name, app.appType);
}JavaScript SDK 对外统一使用 camelCase,例如请求中的 pageSize 和响应中的 appType;发送给 OpenAPI 时会自动转换为接口字段。
应用详情会把 app_schema 解码为有类型的 appSchema。脚手架可以直接使用变量、模型、工具和技能信息生成运行界面:
ts
const app = await client.apps.detail({ appId: "<your-app-id>" });
for (const variable of app.appSchema?.prompt?.variables ?? []) {
renderInput({
key: variable.key,
label: variable.name,
required: variable.required,
dataType: variable.dataType,
options: variable.options,
});
}
console.log(app.appSchema?.freeModels);
console.log(app.appSchema?.toolList);
console.log(app.appSchema?.skillList);
console.log(app.appSchema?.versionMeta?.version);appSchema.versionMeta 提供版本号、版本名称、版本说明、是否最新/草稿/自动版本及发布时间;顶层 app.version 同步取自该元数据中的版本号。
Agent / AgentLite 流式对话
先绑定应用类型、应用 ID 和业务用户 ID,再发送消息:
ts
const agent = client.chat.bind({
appType: "agent", // AgentLite 使用 'agentLite'
appId: "<your-app-id>",
userId: "user-1",
});
const stream = await agent.sendMessagesStream({ query: "你好" });
try {
for await (const event of stream) {
if (event.type === "chunk") {
console.log(event.data.content);
}
if (event.type === "interrupt") {
console.log("等待用户操作", event.data);
}
}
} finally {
await stream.close();
}上面的调用默认返回原始事件,适合需要完全控制事件处理的项目。若界面希望直接得到可渲染消息,传入 outputMode: 'aggregate':
ts
const stream = await agent.sendMessagesStream(
{ query: "你好" },
{ outputMode: "aggregate" },
);
for await (const { event, message } of stream) {
renderMessage(message);
// message.content、thinking、items、toolCalls、subAgents 会持续更新
// message.usage 保留 promptTokens、completionTokens、totalTokens 及耗时
// event 仍保留,便于处理 TTS、HITL 等特殊事件
}outputMode 只控制 SDK 的返回形式,不会发送给后端。默认值为 raw,因此旧代码无需修改。
实时聚合结果与历史消息都使用 ChatMessage 结构。同一个消息组件可以直接渲染历史数据:
ts
const messages = await client.chat.history({
appId: "<your-app-id>",
conversationId,
pageSize: 100,
});
messages.forEach(renderMessage);message.items 按后端 sort_order 排序,包含思考、正文、工具和子 Agent 等过程项。
SDK 不会在断流后自动重连。业务方应保存流中的 taskId 和 stream.lastEventId,需要恢复时显式调用:
ts
const stream = await agent.reconnectStream({
taskId,
lastEventId,
});收到 interrupt 事件后,由业务界面收集用户输入并恢复执行:
ts
const resumed = await agent.resumeStream({
conversationId: interruptEvent.conversationId,
checkpointId: interruptEvent.data.checkpointId,
responses: {
[interruptEvent.data.interrupts[0].interruptId]: {
type: "confirm",
confirm: true,
},
},
});聚合模式恢复或重连时,把上一段消息作为 initialMessage 传入即可继续累积,不需要业务层重新拼接:
ts
const resumed = await agent.resumeStream(
{
conversationId: interruptEvent.conversationId,
checkpointId: interruptEvent.data.checkpointId,
responses,
},
{
outputMode: "aggregate",
initialMessage: stream.message,
},
);停止生成:
ts
await client.chat.stop(taskId);文本生成
文本生成应用使用 textCompletion 类型。阻塞调用直接返回最终结果:
ts
const text = client.chat.bind({
appType: "textCompletion",
appId: "<your-app-id>",
userId: "user-1",
});
const result = await text.sendMessages({
input: {
title: "季度报告",
tone: "正式",
},
});
console.log(result.output.content);也可以使用 SSE 流式生成:
ts
const stream = await text.sendMessagesStream({
input: { title: "季度报告", tone: "正式" },
});
try {
for await (const event of stream) {
if (event.type === "chunk") console.log(event.data.content);
}
} finally {
await stream.close();
}文本生成流同样支持 { outputMode: 'aggregate' }。它返回统一的 ChatMessage,但仍不提供多轮会话和 HITL 恢复。
查询当前应用的生成记录和单条详情:
ts
const records = await text.records({
page: 1,
pageSize: 10,
});
for (const record of records.items) {
console.log(record.id, record.status, record.totalTokens, record.duration);
}
const detail = await text.recordDetail({
completionId: records.items[0].id,
});
console.log(detail.input, detail.content);
for (const item of detail.items) {
console.log(item.sortOrder, item.itemType, item.content.text);
}records() 对应文本生成记录列表,状态为 0 执行中、1 成功、2 失败、3 取消;recordDetail() 返回提交时的 input、完整 content 和已按 sortOrder 升序排列的过程片段。
文本生成不支持会话、HITL 恢复或断流重连。
取消、超时与重试
HTTP 和 SSE 方法的最后一个参数可以传入请求选项:
ts
const controller = new AbortController();
const request = client.apps.list(
{ page: 1, pageSize: 10 },
{
signal: controller.signal,
timeout: 15000,
retry: { maxRetries: 2, baseDelay: 500 },
},
);
controller.abort();
await request;只读、可重试请求默认会在网络错误或 502、503、504 时重试;发送消息等非幂等请求不会自动重试。
SSE 将建立连接和读取事件分开控制:
ts
const controller = new AbortController();
const stream = await agent.sendMessagesStream(
{ query: "你好" },
{
signal: controller.signal,
connectTimeout: 10000,
idleTimeout: 30000,
},
);connectTimeout:等待 SSE 响应建立的最长毫秒数;未传时依次使用请求timeout、客户端streamConnectTimeout,最后默认 10 秒。idleTimeout:连接建立后等待下一条完整 SSE 事件的最长毫秒数;默认不启用,传0也表示不启用。服务端ping事件即使未通过迭代器输出,也会刷新空闲计时。signal:建立连接或读取过程中取消请求。超时抛出TimeoutError,主动取消抛出AbortError,并保留对应的operation。
语音 WebSocket 流式接口也支持连接超时和取消:
ts
const controller = new AbortController();
const session = await client.voice.recognizeStream(
{ provider: "provider-id" },
{ signal: controller.signal, connectTimeout: 5000 },
);单次 connectTimeout 优先于客户端 webSocketConnectTimeout,两者均未设置时默认 10 秒。连接超时或取消会关闭尚未打开的 WebSocket。自定义 webSocketFactory 的第三个参数包含 headers、operation、signal 和最终生效的 connectTimeout。浏览器原生 WebSocket 无法添加 upgrade 请求头,需要自定义 factory 或按网关约定接入。
错误处理
建议使用稳定的 kind、operation、httpStatus 和 code 判断错误:
ts
import { AiAdpError } from "@hxsyai/aiadp";
try {
await client.apps.list();
} catch (error) {
if (error instanceof AiAdpError) {
console.error({
kind: error.kind,
operation: error.operation,
httpStatus: error.httpStatus,
code: error.code,
traceId: error.traceId,
});
} else {
throw error;
}
}使用 CDN 全局脚本时,错误基类位于 AIADP:
js
if (error instanceof AIADP.AiAdpError) {
console.error(error.kind, error.operation, error.httpStatus);
}kind 可能为:configuration、api、http、transport、timeout、abort、decode、stream、unsupported_runtime 或 missing_dependency。
其他能力
完整客户端还提供以下命名空间:
| 命名空间 | 能力 |
|---|---|
client.apps | 应用列表、详情、运行参数 |
client.chat | Agent、AgentLite、文本生成、生成记录、会话和消息历史 |
client.files | Agent 工作空间文件操作 |
client.knowledge | 知识库列表、详情和检索 |
client.workflow | 工作流阻塞与流式执行 |
client.thirdparty | 三方服务、工具列表和调用 |
client.voice | 语音合成、识别、音色及 WebSocket 流式能力 |
接口请求字段和响应含义以对应的 OpenAPI 接口文档 为准。
