Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules/
dist/
*.tgz
package-lock.json
.pnpm-store/
examples/config.local.json
154 changes: 51 additions & 103 deletions README.md

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions examples/08-local-sandbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Example 08: Local Runtime Sandbox
*
* 演示 agent 在宿主进程本地工作区用 Claude SDK 内置工具读写文件:
* 1. 写一个 README.md
* 2. 跑 `ls` 列目录
* 3. 读回 README.md 验证
*
* 配置:
* - examples/config.local.json: envId / model / tcbApiKey
* - credentials.secretId/secretKey 可选;缺省或占位符时回退到 tcbApiKey(accessKey)
* 供 workspacePersist(cwd.tar.gz → COS)鉴权
*
* 默认:
* - 省略 sandbox → enabled local
* - 省略 cwd → os.tmpdir()/oak-local-sandbox
*
* 运行:
* pnpm dlx tsx examples/08-local-sandbox.ts
*/
import * as os from 'node:os'
import * as path from 'node:path'

import { printAcpUpdate } from './_shared/acp.js'
import { getEnvId, getModel, getPlatformCredentialsOrApiKey } from './_shared/env.js'

import { createAgent } from '@cloudbase/open-agent-kernel'

async function main(): Promise<void> {
const envId = getEnvId()
const credentials = getPlatformCredentialsOrApiKey()
const cwd = path.join(os.tmpdir(), 'oak-local-sandbox')

const agent = createAgent({
envId,
credentials,
model: getModel(),
// cwd / sandbox 均可省略:默认 local + tmpdir/oak-local-sandbox
systemPrompt:
'You are a helpful coding assistant working in a local workspace. ' +
'You have access to Bash / Read / Write / Edit / Glob / Grep tools. ' +
'Always use the tools to interact with the filesystem—never fabricate output. ' +
'Reply concisely in Chinese.',
})

const session = await agent.startSession({ userId: 'u1' })

const prompt =
'请完成以下任务:\n' +
'1. 在工作目录用 Write 工具创建一个 README.md,内容是 "# Hello from open-agent-kernel local sandbox"\n' +
'2. 用 Bash 跑 `ls -la` 看下当前目录\n' +
'3. 用 Read 工具读 README.md 的内容并展示给我\n' +
'完成后告诉我结果。'

console.log('cwd (default):', cwd)
console.log('User:', prompt, '\n')
process.stdout.write('Assistant: ')

for await (const e of session.send(prompt)) {
printAcpUpdate(e)
}

console.log('\n\n--- Done ---')
}

main().catch((err) => {
console.error('[fatal]', err)
process.exit(1)
})
78 changes: 78 additions & 0 deletions examples/09-local-sandbox-cloudbase-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Example 09: Local Runtime Sandbox + CloudBase MCP(进程内)
*
* 对照内部示例 `_10-sandbox-cloudbase-tools.ts`(远程 AGS 沙箱内 HTTP MCP),
* 本脚本验证 local sandbox 路径:kernel 在宿主进程内挂载 `@cloudbase/cloudbase-mcp`
* (optional peer,本仓库已放在 devDependencies),自动暴露 `mcp__cloudbase__*` 工具给 agent。
*
* 工具族:
* - Bash / Read / Write / Edit / Glob / Grep(SDK 内置,操作本地 cwd)
* - mcp__cloudbase__*(进程内 CloudBase MCP:数据库 / 存储 / 云函数 / …)
*
* 配置:
* - examples/config.local.json: envId / model / tcbApiKey
* - credentials.secretId/secretKey 可选;缺省或占位符时回退到 tcbApiKey(accessKey)
* 供 workspacePersist(cwd.tar.gz → COS)鉴权
*
* 默认:
* - 省略 sandbox → enabled local
* - 省略 cwd → os.tmpdir()/oak-local-sandbox
* - 默认 cloudbaseTools: true → 进程内注入 mcp__cloudbase__*
*
* 运行:
* pnpm dlx tsx examples/09-local-sandbox-cloudbase-tools.ts
*/
import * as os from 'node:os'
import * as path from 'node:path'

import { printAcpUpdate } from './_shared/acp.js'
import { getEnvId, getModel, getPlatformCredentialsOrApiKey } from './_shared/env.js'

import { createAgent } from '@cloudbase/open-agent-kernel'

async function main(): Promise<void> {
const envId = getEnvId()
const credentials = getPlatformCredentialsOrApiKey()
const cwd = path.join(os.tmpdir(), 'oak-local-sandbox')

const agent = createAgent({
envId,
credentials,
model: getModel(),
// cwd / sandbox 均可省略:默认 local + tmpdir/oak-local-sandbox
// 默认 sandbox.cloudbaseTools: true → 进程内注入 mcp__cloudbase__*
systemPrompt:
'You are a CloudBase coding assistant working in a local workspace. ' +
'You have two tool families:\n' +
' - Bash / Read / Write / Edit / Glob / Grep : local filesystem and shell\n' +
' - mcp__cloudbase__* : CloudBase resources (database / storage / cloudfunction / hosting / ...)\n' +
'Prefer mcp__cloudbase__* when the task is about CloudBase resources. ' +
'Always use the tools to verify—never fabricate output. ' +
'Reply concisely in Chinese.',
})

const session = await agent.startSession({ userId: 'u1' })

const prompt =
'请帮我探索一下当前 CloudBase 环境:\n' +
'1. 用 cloudbase 工具列出当前环境下的云数据库集合(最多 10 个)\n' +
'2. 如果有集合,挑第一个集合查询前 3 条记录\n' +
'3. 如果没有任何集合,告诉我即可,不要尝试创建\n' +
'完成后简单总结你看到了什么。'

console.log('cwd (default):', cwd)
console.log('auth:', credentials.accessKey ? 'accessKey (CLOUDBASE_APIKEY)' : 'CAM secretId/secretKey')
console.log('User:', prompt, '\n')
process.stdout.write('Assistant: ')

for await (const e of session.send(prompt)) {
printAcpUpdate(e)
}

console.log('\n\n--- Done ---')
}

main().catch((err) => {
console.error('[fatal]', err)
process.exit(1)
})
75 changes: 41 additions & 34 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
## 准备

```bash
cd packages/open-agent-kernel/examples
cd examples
cp config.example.json config.local.json
# 编辑 config.local.json,填入 envId / model / tcbApiKey / credentials
```

在仓库根目录先构建 SDK,再运行示例:

```bash
pnpm -F @cloudbase/open-agent-kernel build
pnpm dlx tsx packages/open-agent-kernel/examples/01-quickstart.ts
pnpm build
pnpm dlx tsx examples/01-quickstart.ts
```

`config.local.json` 已被 gitignore,不会被提交。
Expand All @@ -25,7 +25,7 @@ pnpm dlx tsx packages/open-agent-kernel/examples/01-quickstart.ts
|------|------|
| `envId` | CloudBase 环境 ID,示例会显式传给 `createAgent({ envId })`。 |
| `model` | 默认模型 ID,示例会显式传给 `createAgent({ model })`。 |
| `tcbApiKey` | CloudBase 服务端 APIKey;helper 会写入 `process.env.TCB_API_KEY` 供 SDK 默认模型网关和 sandbox 使用。 |
| `tcbApiKey` | CloudBase 服务端 APIKey;helper 会写入 `process.env.CLOUDBASE_APIKEY` 供 SDK 默认模型网关和 sandbox 使用。 |
| `credentials.secretId` / `credentials.secretKey` | CloudBase 平台凭证,示例会显式传给 `createAgent({ credentials })`。 |
| `credentials.sessionToken` | STS 临时凭证,可选。 |
| `examples.resumeConversationId` | example 04 使用;指定上一次输出的 conversationId 做跨进程 resume。 |
Expand All @@ -39,33 +39,39 @@ pnpm dlx tsx packages/open-agent-kernel/examples/01-quickstart.ts
在仓库根目录运行:

```bash
pnpm dlx tsx packages/open-agent-kernel/examples/01-quickstart.ts
pnpm dlx tsx examples/01-quickstart.ts
```

| Example | 功能 | 运行命令 |
|---------|------|----------|
| `01-quickstart.ts` | 快速开始 | `pnpm dlx tsx packages/open-agent-kernel/examples/01-quickstart.ts` |
| `02-debug.ts` | 打印调试事件 | `pnpm dlx tsx packages/open-agent-kernel/examples/02-debug.ts` |
| `03-multi-turn.ts` | 进程内多轮对话 | `pnpm dlx tsx packages/open-agent-kernel/examples/03-multi-turn.ts` |
| `01-quickstart.ts` | 快速开始 | `pnpm dlx tsx examples/01-quickstart.ts` |
| `02-debug.ts` | 打印调试事件 | `pnpm dlx tsx examples/02-debug.ts` |
| `03-multi-turn.ts` | 进程内多轮对话 | `pnpm dlx tsx examples/03-multi-turn.ts` |
| `04-multi-turn-db.ts` | CloudBase session 持久化 / resume | 第一次跑写入个人信息;把输出的 `conversationId` 填入 `examples.resumeConversationId` 后再跑,验证跨进程回忆 |
| `05-multimodal.ts` | 图片附件 / Storage | `pnpm dlx tsx packages/open-agent-kernel/examples/05-multimodal.ts` |
| `06-mcp-sdk-server.ts` | 进程内 MCP | `pnpm dlx tsx packages/open-agent-kernel/examples/06-mcp-sdk-server.ts` |
| `07-mcp-stdio.ts` | stdio MCP | `pnpm dlx tsx packages/open-agent-kernel/examples/07-mcp-stdio.ts` |
| `08-sandbox.ts` | sandbox 文件系统 / Shell | `pnpm dlx tsx packages/open-agent-kernel/examples/08-sandbox.ts` |
| `09-sandbox-shared.ts` | shared sandbox | `pnpm dlx tsx packages/open-agent-kernel/examples/09-sandbox-shared.ts` |
| `10-sandbox-cloudbase-tools.ts` | sandbox 内 CloudBase MCP 工具 | `pnpm dlx tsx packages/open-agent-kernel/examples/10-sandbox-cloudbase-tools.ts` |
| `11-hitl-approval.ts` | 单进程 HITL 审批 | `pnpm dlx tsx packages/open-agent-kernel/examples/11-hitl-approval.ts` |
| `12-hitl-acp-adapter.ts` | 内置 ACP 审批流 | `pnpm dlx tsx packages/open-agent-kernel/examples/12-hitl-acp-adapter.ts` |
| `13-hitl-distributed-cloudbase.ts` | 分布式 HITL 审批 | `pnpm dlx tsx packages/open-agent-kernel/examples/13-hitl-distributed-cloudbase.ts` |
| `14-session-history.ts` | 历史查询 / 聚合验证 | `pnpm dlx tsx packages/open-agent-kernel/examples/14-session-history.ts` |
| `15-skills.ts` | Skills | `pnpm dlx tsx packages/open-agent-kernel/examples/15-skills.ts` |
| `16-user-memory.ts` | userMemory 单进程 | `pnpm dlx tsx packages/open-agent-kernel/examples/16-user-memory.ts` |
| `17-user-memory-distributed.ts` | userMemory 跨节点 | `pnpm dlx tsx packages/open-agent-kernel/examples/17-user-memory-distributed.ts` |
| `18-workspace-snapshot.ts` | workspace snapshot 单进程 | `pnpm dlx tsx packages/open-agent-kernel/examples/18-workspace-snapshot.ts` |
| `19a-snapshot-write.ts` | workspace snapshot 写入阶段 | `pnpm dlx tsx packages/open-agent-kernel/examples/19a-snapshot-write.ts` |
| `19b-snapshot-read.ts` | workspace snapshot 读取阶段 | `pnpm dlx tsx packages/open-agent-kernel/examples/19b-snapshot-read.ts` |
| `20-acp-stream-adapter-fixture.ts` | ACP adapter fixture(不调用真实模型) | `pnpm dlx tsx packages/open-agent-kernel/examples/20-acp-stream-adapter-fixture.ts` |
| `21-default-acp-session-contract.ts` | 默认 session ACP 类型契约 | `pnpm exec tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck packages/open-agent-kernel/examples/21-default-acp-session-contract.ts` |
| `05-multimodal.ts` | 图片附件 / Storage | `pnpm dlx tsx examples/05-multimodal.ts` |
| `06-mcp-sdk-server.ts` | 进程内 MCP | `pnpm dlx tsx examples/06-mcp-sdk-server.ts` |
| `07-mcp-stdio.ts` | stdio MCP | `pnpm dlx tsx examples/07-mcp-stdio.ts` |
| `08-local-sandbox.ts` | local sandbox 文件系统 / Shell | `pnpm dlx tsx examples/08-local-sandbox.ts` |
| `09-local-sandbox-cloudbase-tools.ts` | local sandbox + 进程内 CloudBase MCP | `pnpm dlx tsx examples/09-local-sandbox-cloudbase-tools.ts` |
| `11-hitl-approval.ts` | 单进程 HITL 审批 | `pnpm dlx tsx examples/11-hitl-approval.ts` |
| `12-hitl-acp-adapter.ts` | 内置 ACP 审批流 | `pnpm dlx tsx examples/12-hitl-acp-adapter.ts` |
| `13-hitl-distributed-cloudbase.ts` | 分布式 HITL 审批 | `pnpm dlx tsx examples/13-hitl-distributed-cloudbase.ts` |
| `14-session-history.ts` | 历史查询 / 聚合验证 | `pnpm dlx tsx examples/14-session-history.ts` |
| `15-skills.ts` | Skills | `pnpm dlx tsx examples/15-skills.ts` |
| `16-user-memory.ts` | userMemory 单进程 | `pnpm dlx tsx examples/16-user-memory.ts` |
| `17-user-memory-distributed.ts` | userMemory 跨节点 | `pnpm dlx tsx examples/17-user-memory-distributed.ts` |
| `20-acp-stream-adapter-fixture.ts` | ACP adapter fixture(不调用真实模型) | `pnpm dlx tsx examples/20-acp-stream-adapter-fixture.ts` |
| `21-default-acp-session-contract.ts` | 默认 session ACP 类型契约 | `pnpm exec tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck examples/21-default-acp-session-contract.ts` |

### Experimental 示例(远程沙箱 / snapshot)

| Example | 功能 |
|---------|------|
| `_08-sandbox.ts` | 远程 AGS sandbox 文件系统 / Shell |
| `_09-sandbox-shared.ts` | shared 远程 sandbox |
| `_10-sandbox-cloudbase-tools.ts` | 远程 sandbox 内 CloudBase MCP |
| `_18-workspace-snapshot.ts` | workspace snapshot 单进程 |
| `_19a-snapshot-write.ts` / `_19b-snapshot-read.ts` | workspace snapshot 跨进程 restore |

## 凭证依赖矩阵

Expand All @@ -75,29 +81,30 @@ pnpm dlx tsx packages/open-agent-kernel/examples/01-quickstart.ts
| 04 | ✅ | ✅ | ✅ | 默认 CloudBase FlexDB session store。 |
| 05 | ✅ | ✅ | CloudBase Storage 模式需要 | `examples.storage=memory` 时不需要平台凭证。 |
| 06 / 07 | ✅ | ✅ | | MCP 工具示例。 |
| 08 / 09 / 10 | ✅ | ✅ | ✅ | sandbox / CloudBase MCP 工具。 |
| 08-local-sandbox | ✅ | ✅ | 可选 | local sandbox;CAM 缺省/占位符时回退 `tcbApiKey`(accessKey)做 workspacePersist。 |
| 09-local-sandbox-cloudbase-tools | ✅ | ✅ | 可选 | local sandbox + 进程内 `mcp__cloudbase__*`;凭证回退同 08。 |
| 11 / 12 | ✅ | ✅ | | 单进程审批。 |
| 13 | ✅ | ✅ | ✅ | 分布式审批状态写入 CloudBase DB。 |
| 14 | ✅ | ✅ | | 历史查询聚合示例。 |
| 15 | ✅ | ✅ | | Skills 示例。 |
| 16 / 17 | ✅ | ✅ | ✅ | userMemory 需要 CloudBase Storage。 |
| 18 / 19a / 19b | ✅ | ✅ | ✅ | workspace snapshot 需要 sandbox 和 Storage。 |
| `_08` / `_09` / `_10` / `_18` / `_19*` | ✅ | ✅ | ✅ | 内部远程沙箱 / snapshot。 |

## 共享工具

`_shared/env.ts` 读取 `config.local.json`,并提供:

- `loadEnv()` / `getEnvId()` / `getModel()`
- `getPlatformCredentials()`
- `getPlatformCredentials()` / `tryGetPlatformCredentials()` / `getPlatformCredentialsOrApiKey()`
- `getSandboxApiKey()`
- `getResumeConversationId()` / `getExampleStorage()` / `getExampleImagePath()`

示例层从 `config.local.json` 读取配置,再通过 `createAgent({ envId, model, credentials })` 显式传给 SDK。常规 sandbox 示例只写 `sandbox: { enabled: true }`,SDK 会复用 `TCB_API_KEY` 作为默认 AGS 数据面凭证
示例层从 `config.local.json` 读取配置,再通过 `createAgent({ envId, model, credentials })` 显式传给 SDK。公开 local sandbox 示例写 `sandbox: { enabled: true }`(默认 `LocalRuntimeSandbox`)。内部远程沙箱示例通过 `runtime: new AgsStatefulSandbox({ apiKey })` 注入

## workspace snapshot 验证顺序
## workspace snapshot 验证顺序(内部)

`19-workspace-snapshot-distributed.ts` 已废弃,因为同一进程无法真实验证跨节点 restore。正确流程是:
`_19-workspace-snapshot-distributed.ts` 已废弃。正确流程是:

1. 运行 `19a-snapshot-write.ts`,让 Agent sandbox 中写文件并触发 snapshot。
1. 运行 `_19a-snapshot-write.ts`,让 Agent 在远程 sandbox 中写文件并触发 snapshot。
2. 手动停止对应 AGS sandbox instance,确保下次启动会走 COS restore。
3. 运行 `19b-snapshot-read.ts`,观察 `restoreStatus=full` 并验证文件内容。
3. 运行 `_19b-snapshot-read.ts`,观察 `restoreStatus=full` 并验证文件内容。
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
54 changes: 48 additions & 6 deletions examples/_shared/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,64 @@ export function getVisionModel(defaultModel = 'glm-5v-turbo'): string {
return visionModel && visionModel.length > 0 ? visionModel : defaultModel
}

export function getPlatformCredentials(): PlatformCredentials {
/** config.example.json 占位值 / 空串,视为「未配置有效 CAM」。 */
function isUsableCamSecret(value: string | undefined): value is string {
if (!value || !value.trim()) return false
const v = value.trim()
if (/^AKIDx+$/i.test(v)) return false
if (/^x+$/i.test(v)) return false
if (v.includes('xxxxxxxx')) return false
return true
}

/**
* 读取 CAM 平台凭证;缺省或占位符时返回 undefined(不抛错)。
* 调用方可再回退到 `accessKey` / `CLOUDBASE_APIKEY`。
*/
export function tryGetPlatformCredentials(): PlatformCredentials | undefined {
const config = loadConfig()
const credentials = config.credentials

if (!credentials?.secretId || !credentials.secretKey) {
throw new Error('config.local.json: credentials.secretId and credentials.secretKey are required')
if (!isUsableCamSecret(credentials?.secretId) || !isUsableCamSecret(credentials?.secretKey)) {
return undefined
}

return {
envId: config.envId,
secretId: credentials.secretId,
secretKey: credentials.secretKey,
secretId: credentials.secretId.trim(),
secretKey: credentials.secretKey.trim(),
...(credentials.sessionToken ? { sessionToken: credentials.sessionToken } : {}),
}
}

export function getPlatformCredentials(): PlatformCredentials {
const credentials = tryGetPlatformCredentials()
if (!credentials) {
throw new Error('config.local.json: credentials.secretId and credentials.secretKey are required')
}
return credentials
}

/**
* CAM 可用则用 CAM;否则用 `tcbApiKey` → `credentials.accessKey`(kernel 会换临时 CAM)。
* 两者都缺时抛错。
*/
export function getPlatformCredentialsOrApiKey(): PlatformCredentials {
const cam = tryGetPlatformCredentials()
if (cam) return cam

const config = loadConfig()
const accessKey = process.env.CLOUDBASE_APIKEY ?? config.tcbApiKey
if (!accessKey) {
throw new Error(
'config.local.json: need usable credentials.secretId/secretKey, or tcbApiKey (CLOUDBASE_APIKEY fallback)',
)
}

// eslint-disable-next-line no-console
console.log('[env] CAM credentials missing/placeholder; falling back to credentials.accessKey (tcbApiKey)')
return { envId: config.envId, accessKey }
}

export function getResumeConversationId(): string | undefined {
const id = loadConfig().examples?.resumeConversationId
return id && id.length > 0 ? id : undefined
Expand Down
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,16 @@
"tar": "^7.5.16",
"zod": "^4.3.6"
},
"peerDependencies": {
"@cloudbase/cloudbase-mcp": "^2.26.0"
},
"peerDependenciesMeta": {
"@cloudbase/cloudbase-mcp": {
"optional": true
}
},
"devDependencies": {
"@cloudbase/cloudbase-mcp": "^2.26.0",
"tsup": "^8.0.0",
"typescript": "~5.7.0",
"vitest": "^3.2.0"
Expand Down
Loading