create-windy 0.2.20 → 0.2.22

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 (58) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +93 -11
  3. package/package.json +1 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/README.md +1 -0
  6. package/template/apps/agent-server/Dockerfile +20 -0
  7. package/template/apps/agent-server/README.md +46 -0
  8. package/template/apps/agent-server/pyproject.toml +44 -0
  9. package/template/apps/agent-server/src/southwind_agent_server/__init__.py +5 -0
  10. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +39 -0
  11. package/template/apps/agent-server/src/southwind_agent_server/app.py +263 -0
  12. package/template/apps/agent-server/src/southwind_agent_server/config.py +141 -0
  13. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +99 -0
  14. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +52 -0
  15. package/template/apps/agent-server/src/southwind_agent_server/engine.py +85 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +197 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +246 -0
  18. package/template/apps/agent-server/src/southwind_agent_server/service.py +180 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +317 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/sse.py +41 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/store.py +144 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +80 -0
  23. package/template/apps/agent-server/tests/test_api.py +207 -0
  24. package/template/apps/agent-server/tests/test_config.py +56 -0
  25. package/template/apps/agent-server/tests/test_openai_provider.py +224 -0
  26. package/template/apps/agent-server/tests/test_sqlite_store.py +93 -0
  27. package/template/apps/agent-server/uv.lock +925 -0
  28. package/template/apps/server/src/agent/execution-grants.ts +89 -0
  29. package/template/apps/server/src/agent/remote-runtime.ts +128 -0
  30. package/template/apps/server/src/agent/run-contracts.ts +43 -0
  31. package/template/apps/server/src/agent/run-facade.test.ts +257 -0
  32. package/template/apps/server/src/agent/run-facade.ts +190 -0
  33. package/template/apps/server/src/agent/run-routes.ts +222 -0
  34. package/template/apps/server/src/agent/runtime-bootstrap.ts +53 -0
  35. package/template/apps/server/src/agent/tool-host.test.ts +242 -0
  36. package/template/apps/server/src/agent/tool-host.ts +153 -0
  37. package/template/apps/server/src/index.ts +26 -22
  38. package/template/apps/server/src/module-composition.test.ts +9 -0
  39. package/template/apps/server/src/module-composition.ts +13 -0
  40. package/template/apps/server/src/module-host/initialize-contracts.ts +67 -0
  41. package/template/apps/server/src/module-host/initialize.test.ts +69 -0
  42. package/template/apps/server/src/module-host/initialize.ts +50 -53
  43. package/template/apps/server/src/module-host.ts +7 -0
  44. package/template/apps/server/src/module-runtime-validation.ts +39 -0
  45. package/template/docker-compose.yml +25 -0
  46. package/template/docs/architecture/ai-runtime.md +744 -0
  47. package/template/docs/platform/agent-runtime.md +128 -0
  48. package/template/docs/platform/agent-tools.md +8 -1
  49. package/template/package.json +1 -0
  50. package/template/packages/config/index.test.ts +43 -0
  51. package/template/packages/config/index.ts +1 -0
  52. package/template/packages/config/package.json +2 -1
  53. package/template/packages/config/src/ai-openai-compatible.ts +131 -0
  54. package/template/packages/server-sdk/index.ts +11 -0
  55. package/template/packages/server-sdk/package.json +4 -2
  56. package/template/packages/server-sdk/src/ai-operation-registration.ts +31 -0
  57. package/template/packages/server-sdk/src/module-host.ts +5 -0
  58. package/template/packages/server-sdk/src/tool-host-port.ts +22 -0
package/README.md CHANGED
@@ -68,7 +68,7 @@ npx create-windy@latest my-dashboard \
68
68
 
69
69
  选择 `system.license` 时,Server 与 Web 通过 npm 依赖
70
70
  `@southwind-ai/license@^0.1.1` 使用运行时能力;生成项目及 `create-windy` 模板快照都不包含
71
- `packages/license-sdk` 源码。公网 npm 已发布兼容的 `@southwind-ai/license@0.1.1`;
71
+ `packages/license-sdk` 源码。公网 npm 已发布兼容的 `@southwind-ai/license@0.1.2`;
72
72
  发布包含该模块的新版创建器前,必须先确认目标 registry 中存在兼容版本,并完成真实
73
73
  安装回归。
74
74
 
package/dist/cli.js CHANGED
@@ -213,7 +213,11 @@ var optionalModuleManifests = [
213
213
  "system",
214
214
  "system.settings"
215
215
  ]),
216
- fileRoots: ["apps/server/src/storage"],
216
+ fileRoots: [
217
+ "apps/server/src/storage",
218
+ "apps/server/src/module-host/route-installer.test.ts",
219
+ "apps/server/src/module-host/storage-port.test.ts"
220
+ ],
217
221
  documentation: ["docs/architecture/object-storage.md"],
218
222
  schemaFiles: ["packages/database/src/schema/file-storage.ts"],
219
223
  databaseTables: [
@@ -228,10 +232,16 @@ var optionalModuleManifests = [
228
232
  "system.audit"
229
233
  ]),
230
234
  fileRoots: [
235
+ "apps/agent-server",
231
236
  "apps/server/src/agent",
237
+ "apps/server/src/module-host/tool-handlers.ts",
232
238
  "packages/modules/src/system-agent-tools.ts"
233
239
  ],
234
- documentation: ["docs/platform/agent-tools.md"]
240
+ documentation: [
241
+ "docs/architecture/ai-runtime.md",
242
+ "docs/platform/agent-runtime.md",
243
+ "docs/platform/agent-tools.md"
244
+ ]
235
245
  },
236
246
  {
237
247
  ...module("system.scheduler", "任务调度", "任务、重试、历史和恢复。", [
@@ -242,6 +252,7 @@ var optionalModuleManifests = [
242
252
  "apps/server/src/scheduler",
243
253
  "apps/server/src/jobs",
244
254
  "apps/server/src/application-services.ts",
255
+ "apps/server/src/module-host/task-definitions.ts",
245
256
  "apps/web/src/pages/system/components/SchedulerOperationsPanel.vue",
246
257
  "apps/web/src/pages/system/components/SchedulerOperationsPanel.webtest.ts",
247
258
  "apps/web/src/pages/system/components/SchedulerRunTable.vue",
@@ -328,6 +339,7 @@ var optionalModuleManifests = [
328
339
  ]),
329
340
  fileRoots: [
330
341
  "apps/server/src/search",
342
+ "apps/server/src/module-host/search-providers.ts",
331
343
  "apps/web/src/layout/NavigationSearchDialog.vue",
332
344
  "apps/web/src/layout/NavigationSearchDialog.webtest.ts"
333
345
  ],
@@ -881,7 +893,10 @@ async function initializeLocalEnvironment(projectRoot, generateSecret = defaultS
881
893
  if (!gitignore.split(/\r?\n/).some((line) => line.trim() === ".env")) {
882
894
  throw new Error("生成项目的 .gitignore 未排除 .env,拒绝写入本地密码");
883
895
  }
884
- const content = setEmptyVariable(setEmptyVariable(example, "POSTGRES_PASSWORD", generateSecret()), "WINDY_BOOTSTRAP_ADMIN_PASSWORD", generateSecret());
896
+ let content = setEmptyVariable(setEmptyVariable(example, "POSTGRES_PASSWORD", generateSecret()), "WINDY_BOOTSTRAP_ADMIN_PASSWORD", generateSecret());
897
+ if (/^AGENT_SERVER_INTERNAL_TOKEN=[ \t\r]*$/m.test(content)) {
898
+ content = setEmptyVariable(content, "AGENT_SERVER_INTERNAL_TOKEN", generateSecret());
899
+ }
885
900
  let handle;
886
901
  try {
887
902
  handle = await open(target, "wx", 384);
@@ -1441,8 +1456,8 @@ function isMissing2(error) {
1441
1456
  return error instanceof Error && "code" in error && error.code === "ENOENT";
1442
1457
  }
1443
1458
 
1444
- // src/project-files.ts
1445
- var starterCompose = `services:
1459
+ // src/runtime-profile-files.ts
1460
+ var starterComposeBase = `services:
1446
1461
  postgres:
1447
1462
  image: postgres:17-alpine
1448
1463
  environment:
@@ -1486,6 +1501,10 @@ var starterCompose = `services:
1486
1501
  WINDY_CONTAINERIZED: "1"
1487
1502
  BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD: "true"
1488
1503
  WINDY_SYSTEM_USAGE_PATHS: /app/data/audit
1504
+ PLATFORM_ENABLE_AI: \${PLATFORM_ENABLE_AI:-false}
1505
+ AGENT_SERVER_URL: http://agent-server:8080
1506
+ AGENT_TOOL_HOST_URL: http://server:9747
1507
+ AGENT_SERVER_INTERNAL_TOKEN: \${AGENT_SERVER_INTERNAL_TOKEN:-}
1489
1508
  ports:
1490
1509
  - "9747:9747"
1491
1510
  volumes:
@@ -1529,11 +1548,50 @@ var starterCompose = `services:
1529
1548
  volumes:
1530
1549
  postgres-data:
1531
1550
  server-audit-data:
1551
+ __AGENT_VOLUME__
1552
+ `;
1553
+ var agentComposeService = ` agent-server:
1554
+ profiles: ["agent"]
1555
+ build:
1556
+ context: .
1557
+ dockerfile: apps/agent-server/Dockerfile
1558
+ environment:
1559
+ AGENT_SERVER_INTERNAL_TOKEN: \${AGENT_SERVER_INTERNAL_TOKEN:-}
1560
+ AGENT_RUN_STORE_PATH: /app/data/agent/runs.sqlite3
1561
+ QWEN_API_URL: \${QWEN_API_URL:-}
1562
+ QWEN_API_KEY: \${QWEN_API_KEY:-}
1563
+ QWEN_MODEL: \${QWEN_MODEL:-}
1564
+ AI_PROVIDER_CONNECT_TIMEOUT_MS: \${AI_PROVIDER_CONNECT_TIMEOUT_MS:-5000}
1565
+ AI_PROVIDER_REQUEST_TIMEOUT_MS: \${AI_PROVIDER_REQUEST_TIMEOUT_MS:-60000}
1566
+ AI_PROVIDER_MAX_RESPONSE_BYTES: \${AI_PROVIDER_MAX_RESPONSE_BYTES:-2097152}
1567
+ volumes:
1568
+ - agent-run-data:/app/data/agent
1569
+ depends_on:
1570
+ server:
1571
+ condition: service_started
1572
+
1532
1573
  `;
1533
- function environmentExample(includeExample) {
1574
+ function starterComposeFor(includeAgent) {
1575
+ return starterComposeBase.replace(` web:
1576
+ `, `${includeAgent ? agentComposeService : ""} web:
1577
+ `).replace("__AGENT_VOLUME__", includeAgent ? " agent-run-data:" : "");
1578
+ }
1579
+ var starterCompose = starterComposeFor(true);
1580
+ function environmentExample(includeExample, includeAgent = true) {
1534
1581
  return `# 复制为 .env 后填写。不要提交真实密码。
1535
1582
  POSTGRES_PASSWORD=
1536
1583
  WINDY_BOOTSTRAP_ADMIN_PASSWORD=
1584
+ ${includeAgent ? `
1585
+ # Agent profile 仅由 Server 读取;不要使用 VITE_ 前缀。
1586
+ PLATFORM_ENABLE_AI=false
1587
+ AGENT_SERVER_INTERNAL_TOKEN=
1588
+ QWEN_API_URL=
1589
+ QWEN_API_KEY=
1590
+ QWEN_MODEL=
1591
+ # AI_PROVIDER_CONNECT_TIMEOUT_MS=5000
1592
+ # AI_PROVIDER_REQUEST_TIMEOUT_MS=60000
1593
+ # AI_PROVIDER_MAX_RESPONSE_BYTES=2097152
1594
+ ` : ""}
1537
1595
 
1538
1596
  # 可选搜索配置;不配置时启用 Manifest 中默认开启的 Provider。${includeExample ? `
1539
1597
  # SEARCH_PROVIDER_KEYS=work-order.ticket.search` : `
@@ -1543,7 +1601,8 @@ WINDY_BOOTSTRAP_ADMIN_PASSWORD=
1543
1601
  # SEARCH_MAX_REQUESTS_PER_MINUTE=30
1544
1602
  `;
1545
1603
  }
1546
- function starterDocsReadme(includeOfflineLicense = true) {
1604
+ // src/project-files.ts
1605
+ function starterDocsReadme(includeOfflineLicense = true, includeAgent = true) {
1547
1606
  return `# Windy Starter 文档
1548
1607
 
1549
1608
  本目录只包含客户运行项目需要的平台架构、开发接入和运维可观测性文档。
@@ -1554,11 +1613,13 @@ License 签发中心、独立签名服务、Vault、发布治理、内部任务
1554
1613
  - 平台开发:从 [系统 CRUD API](./platform/system-crud-api.md) 和 [CRUD 生成器](./development/crud-generator.md) 开始。
1555
1614
  ${includeOfflineLicense ? "- License 消费端:[离线激活与安装协议](./license/license-offline-activation-code.md);运行时能力由 npm 依赖 `@southwind-ai/license` 提供。" : ""}
1556
1615
  - 运维:[可观测性](./operations/observability.md) 与 [运维总览](./platform/operations-overview.md)。
1616
+ ${includeAgent ? "- Agent:[Agent Runtime 接入](./platform/agent-runtime.md) 与 [受控工具](./platform/agent-tools.md)。" : ""}
1557
1617
  - 兼容:[RuoYi-Vue Adapter](./adapters/ruoyi-vue-compatibility.md)。
1558
1618
  `;
1559
1619
  }
1560
1620
  function starterReadme(projectName, includeExample = true, includeOxc = true, projectLicense = "UNLICENSED", selectedModules = []) {
1561
1621
  const selected = new Set(selectedModules);
1622
+ const includeAgent = selected.has("system.agent");
1562
1623
  const moduleRows = moduleDistributionCatalog.map(({ name, description, selection }) => `| \`${name}\` | ${selected.has(name) ? "已包含" : "未包含"} | ${selection === "required" ? "H0" : "H2"} | ${description} |`).join(`
1563
1624
  `);
1564
1625
  return `# ${projectName}
@@ -1614,6 +1675,21 @@ bun run dev:server
1614
1675
  直接输出已有地址,其它服务占用时会自动使用后续端口,最多检查 100 个。实际地址始终
1615
1676
  以终端输出为准。\`dev:server\` 不会自动启动 PostgreSQL 或执行 migration;未配置
1616
1677
  \`DATABASE_URL\` 时使用内存模式。
1678
+ ${includeAgent ? `
1679
+ ## OpenAI-compatible Agent profile
1680
+
1681
+ Agent Runtime 默认不启动。先在仅供本机使用的 \`.env\` 中设置
1682
+ \`PLATFORM_ENABLE_AI=true\`、\`AGENT_SERVER_INTERNAL_TOKEN\`、\`QWEN_API_URL\`、
1683
+ \`QWEN_API_KEY\` 与 \`QWEN_MODEL\`,再运行:
1684
+
1685
+ \`\`\`bash
1686
+ bun run dev:agent
1687
+ \`\`\`
1688
+
1689
+ 公网 Provider URL 必须使用 HTTPS;本机、容器服务名和内网地址允许 HTTP。
1690
+ \`QWEN_API_KEY\` 只进入 Agent Server,不会进入 Web 环境或返回给客户端。运行与模块
1691
+ 接入细节见 [Agent Runtime 接入](./docs/platform/agent-runtime.md)。
1692
+ ` : ""}
1617
1693
 
1618
1694
  ## 页面与登录
1619
1695
 
@@ -1738,6 +1814,7 @@ function createStarterPackage(source, projectName, includeOxc = true, projectLic
1738
1814
  dev: "docker compose up",
1739
1815
  "docker:sync": "create-windy sync-docker",
1740
1816
  "dev:build": "bun run docker:sync && docker compose up --build",
1817
+ ...selectedModules.includes("system.agent") ? { "dev:agent": "docker compose --profile agent up --build" } : {},
1741
1818
  ...generatorVersion ? { windy: "create-windy" } : {},
1742
1819
  "dev:web": "bun run --cwd apps/web dev",
1743
1820
  "dev:server": "bun run --cwd apps/server dev",
@@ -1785,6 +1862,7 @@ var templateRootFiles = [
1785
1862
  ];
1786
1863
  var templateDirectories = [
1787
1864
  "apps/server",
1865
+ "apps/agent-server",
1788
1866
  "apps/web",
1789
1867
  "examples",
1790
1868
  "packages/ai-client",
@@ -1801,6 +1879,7 @@ var templateDirectories = [
1801
1879
  var templateDocumentationFiles = [
1802
1880
  "docs/adapters/ruoyi-vue-compatibility.md",
1803
1881
  "docs/architecture/admin-surface-boundary.md",
1882
+ "docs/architecture/ai-runtime.md",
1804
1883
  "docs/architecture/data-scope-query-enforcement.md",
1805
1884
  "docs/architecture/durable-jobs.md",
1806
1885
  "docs/architecture/minimal-approval-workflow.md",
@@ -1811,6 +1890,7 @@ var templateDocumentationFiles = [
1811
1890
  "docs/license/license-offline-activation-code.md",
1812
1891
  "docs/operations/observability.md",
1813
1892
  "docs/platform/agent-tools.md",
1893
+ "docs/platform/agent-runtime.md",
1814
1894
  "docs/platform/audit-reliability.md",
1815
1895
  "docs/platform/bulk-data-jobs.md",
1816
1896
  "docs/platform/data-access-control.md",
@@ -2025,10 +2105,10 @@ async function generateProject(input) {
2025
2105
  const packageJson = JSON.parse(await readFile10(packagePath, "utf8"));
2026
2106
  await writeFile9(packagePath, `${JSON.stringify(createStarterPackage(packageJson, input.projectName, includeOxc, projectLicense, selectedModules, templateMetadata.generatorVersion), null, 2)}
2027
2107
  `);
2028
- await writeFile9(join10(input.targetDirectory, "docker-compose.yml"), starterCompose);
2029
- await writeFile9(join10(input.targetDirectory, ".env.example"), environmentExample(includeExample));
2108
+ await writeFile9(join10(input.targetDirectory, "docker-compose.yml"), starterComposeFor(selectedModules.includes("system.agent")));
2109
+ await writeFile9(join10(input.targetDirectory, ".env.example"), environmentExample(includeExample, selectedModules.includes("system.agent")));
2030
2110
  await writeFile9(join10(input.targetDirectory, "README.md"), starterReadme(input.projectName, includeExample, includeOxc, projectLicense, selectedModules));
2031
- await writeFile9(join10(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license")));
2111
+ await writeFile9(join10(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license"), selectedModules.includes("system.agent")));
2032
2112
  await reflectCodeQualityChoice(input.targetDirectory, includeOxc);
2033
2113
  await applyWindyBaseLicense(input.targetDirectory);
2034
2114
  await applyProjectLicense(input.targetDirectory, projectLicense, input.licenseHolder);
@@ -4205,7 +4285,9 @@ var recipes = [
4205
4285
  { id: "starter-0.2.16-to-0.2.17", from: "0.2.16", to: "0.2.17" },
4206
4286
  { id: "starter-0.2.17-to-0.2.18", from: "0.2.17", to: "0.2.18" },
4207
4287
  { id: "starter-0.2.18-to-0.2.19", from: "0.2.18", to: "0.2.19" },
4208
- { id: "starter-0.2.19-to-0.2.20", from: "0.2.19", to: "0.2.20" }
4288
+ { id: "starter-0.2.19-to-0.2.20", from: "0.2.19", to: "0.2.20" },
4289
+ { id: "starter-0.2.20-to-0.2.21", from: "0.2.20", to: "0.2.21" },
4290
+ { id: "starter-0.2.21-to-0.2.22", from: "0.2.21", to: "0.2.22" }
4209
4291
  ];
4210
4292
  function resolveRecipeChain(sourceVersion, targetVersion) {
4211
4293
  if (sourceVersion === targetVersion)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-windy",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "description": "创建单组织、单租户、私有部署优先的 Windy 企业 Dashboard Starter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "templateCommit": "0219ad152473b48f39df25b3ea261dcef52171c7",
4
- "generatorVersion": "0.2.20",
3
+ "templateCommit": "0888f6c444ba594106e747c8f3114545f24b3bc7",
4
+ "generatorVersion": "0.2.22",
5
5
  "modules": [
6
6
  {
7
7
  "name": "system",
@@ -52,6 +52,7 @@ bun run dev:server
52
52
  以终端输出为准。`dev:server` 不会自动启动 PostgreSQL 或执行 migration;未配置
53
53
  `DATABASE_URL` 时使用内存模式。
54
54
 
55
+
55
56
  ## 页面与登录
56
57
 
57
58
  全新数据库的初始平台账号为 `admin`。初始密码不是所有项目共用的固定值,而是
@@ -0,0 +1,20 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM python:3.12-slim AS runtime
4
+ WORKDIR /app/apps/agent-server
5
+
6
+ RUN pip install --no-cache-dir uv==0.9.27 \
7
+ && useradd --create-home --uid 10001 windy
8
+ COPY apps/agent-server/pyproject.toml apps/agent-server/uv.lock ./
9
+ RUN uv sync --frozen --no-dev --no-install-project
10
+ COPY apps/agent-server/src ./src
11
+ RUN uv sync --frozen --no-dev
12
+
13
+ ENV PATH="/app/apps/agent-server/.venv/bin:${PATH}"
14
+ ENV PYTHONUNBUFFERED=1
15
+ EXPOSE 8080
16
+ RUN mkdir -p /app/data/agent && chown -R windy:windy /app/data
17
+ USER windy
18
+ HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=4 \
19
+ CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/ready', timeout=3)"]
20
+ CMD ["uvicorn", "southwind_agent_server.app:app", "--host", "0.0.0.0", "--port", "8080"]
@@ -0,0 +1,46 @@
1
+ # Southwind AI Agent Server
2
+
3
+ Python 3.12 Agent Runtime。HTTP 层只公开 Southwind AI canonical Agent
4
+ contracts。当前可用 Adapter 连接 OpenAI-compatible `/chat/completions`;Google ADK
5
+ 仍只是未配置 Provider 时安全拒绝的内部 Adapter。
6
+
7
+ ```bash
8
+ uv sync --frozen
9
+ uv run pytest
10
+ uv run ruff check .
11
+ uv run ruff format --check .
12
+ uv run mypy
13
+ ```
14
+
15
+ HTTP Interface:
16
+
17
+ - `POST /api/agent/runs`
18
+ - `GET /api/agent/runs/:runId/events`
19
+ - `GET /api/agent/runs/:runId/result`
20
+ - `POST /api/agent/runs/:runId/cancel`
21
+
22
+ Run 创建使用 `Idempotency-Key`。事件以 SSE 传输,支持 `Last-Event-ID` 和 `after`
23
+ 断线重放,并周期发送 comment heartbeat。Provider 或框架异常只会映射为 canonical
24
+ 安全错误,不会把原始异常正文暴露给调用方。
25
+
26
+ 配置以下仅服务端可见的环境变量后,默认运行 OpenAI-compatible Adapter:
27
+
28
+ ```bash
29
+ export AGENT_SERVER_INTERNAL_TOKEN='<random-token>'
30
+ export QWEN_API_URL='https://dashscope-intl.aliyuncs.com/compatible-mode/v1'
31
+ export QWEN_API_KEY='<server-secret>'
32
+ export QWEN_MODEL='qwen-plus'
33
+ export AGENT_RUN_STORE_PATH='.data/agent-runs.sqlite3'
34
+ uv run uvicorn southwind_agent_server.app:app
35
+ ```
36
+
37
+ 公网 URL 必须使用 HTTPS,本机、容器服务名和内网地址允许 HTTP。Provider 请求包含连接
38
+ 和总超时、响应大小限制、错误脱敏与 `/models` readiness。缺少 URL/Model 时回退
39
+ `AdkEngineAdapter` 并返回 unavailable,不调用模型。测试另使用
40
+ `DeterministicEngineAdapter`。
41
+
42
+ 未设置 `AGENT_RUN_STORE_PATH` 时使用的 `InMemoryRunStore` **仅用于 deterministic
43
+ 测试**。配置路径后使用有界 SQLite Store,可在单副本重启后保留 Run/Event/Result 和
44
+ cursor;多副本部署仍必须替换为共享 Store。Execution Grant 与 Tool 授权由
45
+ Southwind AI Server 签发和复核,Agent Server 不能自行扩大 Operation 的
46
+ `allowedToolKeys`。
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "southwind-agent-server"
3
+ version = "0.1.0"
4
+ description = "Southwind AI canonical Agent Runtime server"
5
+ requires-python = ">=3.12,<3.13"
6
+ dependencies = [
7
+ "fastapi==0.139.2",
8
+ "google-adk==2.5.0",
9
+ "httpx==0.28.1",
10
+ "pydantic==2.13.4",
11
+ "uvicorn==0.51.0",
12
+ ]
13
+
14
+ [dependency-groups]
15
+ dev = [
16
+ "mypy==2.3.0",
17
+ "pytest==9.1.1",
18
+ "pytest-asyncio==1.4.0",
19
+ "ruff==0.15.22",
20
+ ]
21
+
22
+ [tool.pytest.ini_options]
23
+ asyncio_mode = "auto"
24
+ pythonpath = ["src"]
25
+ testpaths = ["tests"]
26
+
27
+ [tool.ruff]
28
+ line-length = 88
29
+ target-version = "py312"
30
+
31
+ [tool.ruff.lint]
32
+ select = ["E", "F", "I", "UP", "B", "ASYNC"]
33
+
34
+ [tool.mypy]
35
+ python_version = "3.12"
36
+ strict = true
37
+ files = ["src", "tests"]
38
+
39
+ [build-system]
40
+ requires = ["hatchling"]
41
+ build-backend = "hatchling.build"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/southwind_agent_server"]
@@ -0,0 +1,5 @@
1
+ """Southwind AI Agent Server."""
2
+
3
+ from .app import create_app
4
+
5
+ __all__ = ["create_app"]
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import AsyncIterator, Mapping
4
+
5
+ from .engine import (
6
+ AgentEngineAdapter,
7
+ EngineRunRequest,
8
+ EngineSignal,
9
+ SafeEngineError,
10
+ )
11
+
12
+
13
+ class AdkEngineAdapter(AgentEngineAdapter):
14
+ """Google ADK implementation skeleton.
15
+
16
+ Agent objects remain opaque and internal. A later slice will create ADK Runners,
17
+ consume ADK Events, and translate them into EngineSignal values at this seam.
18
+ """
19
+
20
+ def __init__(self, agents: Mapping[str, object] | None = None) -> None:
21
+ self._agents = dict(agents or {})
22
+
23
+ def supports(self, operation_key: str) -> bool:
24
+ return operation_key in self._agents
25
+
26
+ async def execute(
27
+ self,
28
+ request: EngineRunRequest,
29
+ ) -> AsyncIterator[EngineSignal]:
30
+ del request
31
+ raise SafeEngineError(
32
+ "RUNTIME_UNAVAILABLE",
33
+ "Agent Runtime 尚未配置",
34
+ retryable=True,
35
+ )
36
+ yield # pragma: no cover - keeps this method an async generator
37
+
38
+ async def cancel(self, run_id: str) -> None:
39
+ del run_id