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
@@ -0,0 +1,128 @@
1
+ # Agent Runtime 接入与运行
2
+
3
+ 更新时间:2026-07-25。
4
+
5
+ Windy 的 Agent Runtime 是可选平台能力。业务模块声明稳定的 AI Operation 和允许调用的
6
+ Tool;Southwind AI Server 负责身份、Feature、Permission、License、Scope、Execution
7
+ Grant 与审计;独立 Agent Server 只负责模型循环、canonical event 和受限 Tool Host
8
+ 回调。业务模块不选择 Provider、模型、Endpoint 或凭据。
9
+
10
+ ## 当前可用边界
11
+
12
+ - OpenAI-compatible `/chat/completions` Provider,支持流式文本和 function calling。
13
+ - `QWEN_API_KEY` 使用服务端 `SecretRef`,只在 Provider Adapter 发请求前解析。
14
+ - 公网 Endpoint 必须为 HTTPS;`localhost`、容器服务名、RFC 1918 地址和
15
+ `.internal`/`.local` 主机可使用 HTTP。URL 禁止内嵌凭据、query、fragment 和云
16
+ metadata 地址。
17
+ - Provider 有连接/请求超时、响应大小上限、安全错误映射和 `/models` readiness 检查。
18
+ - Run 支持幂等创建、canonical SSE、cursor/`Last-Event-ID` 重放、取消和唯一终态。
19
+ - SQLite Store 为单副本有界持久化:默认最多 10,000 个 Run、每个 Run 2,000 个事件、
20
+ 16 MiB 输出、终态保留 7 天。它不保存原始输入,只保存请求指纹;进程重启会把未完成
21
+ Run 收敛为一个可重试失败终态。
22
+
23
+ SQLite 模式不能作为多副本共享 Store。需要水平扩容时,必须先提供实现同一 `RunStore`
24
+ 契约的外部持久化 Adapter。
25
+
26
+ ## Module 接入
27
+
28
+ Manifest 的 `aiOperations` 是声明事实。每个 Operation 必须声明自己的 Feature、
29
+ Permission、可选 Entitlement、输入/输出 schema 和 `allowedToolKeys`。模型只会收到该
30
+ 集合内的 Tool 定义。
31
+
32
+ 模块在 `setup(host)` 中注册模型无关的输入准备器:
33
+
34
+ ```ts
35
+ host.registerAiOperationHandlers([
36
+ {
37
+ operationKey: "example.summary.generate",
38
+ prepare(input, context) {
39
+ return {
40
+ instructions: "只根据用户提供的数据生成简洁摘要。",
41
+ userPrompt: JSON.stringify({
42
+ input,
43
+ actorId: context.actor.id,
44
+ }),
45
+ };
46
+ },
47
+ },
48
+ ]);
49
+ ```
50
+
51
+ 不要把 Permission、Feature、License、Scope、Execution Grant、Provider 或 API Key
52
+ 放进返回值。`prepare` 只负责把已经过宿主 Guard 的类型化输入转成指令和用户消息。
53
+
54
+ Tool handler 仍通过 `registerToolHandlers` 注册。模型提出 Tool Call 后,Agent Server
55
+ 携带短时 opaque grant 回调 Southwind AI Server;受限 Tool Host 会验证:
56
+
57
+ 1. grant 是否绑定当前 Run、Operation、Actor 且未过期;
58
+ 2. Tool 是否属于 Operation 的 `allowedToolKeys`;
59
+ 3. Operation 与 Tool 当前的 Feature、Permission 和 License;
60
+ 4. 宿主创建的 Actor 与请求 Scope。
61
+
62
+ Tool 请求不能携带权限列表、License、Scope 或“已授权”标志。客户端或模型提供的这些
63
+ 字段会被拒绝,未知 Tool 也不会形成注册表探测通道。
64
+
65
+ ## 本地 Compose profile
66
+
67
+ 选择 `system.agent` 的 create-windy 项目会生成 `agent` profile 和随机内部 Token。
68
+ 在 `.env` 中填写:
69
+
70
+ ```dotenv
71
+ PLATFORM_ENABLE_AI=true
72
+ AGENT_SERVER_INTERNAL_TOKEN=<至少 12 字符的随机值>
73
+ QWEN_API_URL=https://dashscope-intl.aliyuncs.com/compatible-mode/v1
74
+ QWEN_API_KEY=<服务端密钥>
75
+ QWEN_MODEL=qwen-plus
76
+
77
+ # 可选安全上限
78
+ # AI_PROVIDER_CONNECT_TIMEOUT_MS=5000
79
+ # AI_PROVIDER_REQUEST_TIMEOUT_MS=60000
80
+ # AI_PROVIDER_MAX_RESPONSE_BYTES=2097152
81
+ ```
82
+
83
+ 然后启动:
84
+
85
+ ```bash
86
+ bun run dev:agent
87
+ ```
88
+
89
+ `QWEN_*` 只注入 `agent-server` 容器;Web service 没有这些环境变量。不要增加 `VITE_`
90
+ 前缀,也不要把 `.env`、日志、错误响应或截图中的真实 Key 提交到仓库。
91
+
92
+ 健康检查:
93
+
94
+ ```text
95
+ GET http://agent-server:8080/health/live
96
+ GET http://agent-server:8080/health/ready
97
+ ```
98
+
99
+ `ready` 会访问 Provider 的 `/models`。失败只返回稳定错误,不回显 Provider body、
100
+ Authorization、Secret、Endpoint query 或底层异常文本。
101
+
102
+ ## 客户端协议
103
+
104
+ 浏览器继续使用 `@southwind-ai/ai-client` 访问同源 Southwind AI Server:
105
+
106
+ ```text
107
+ POST /api/agent/runs
108
+ GET /api/agent/runs/:runId/events
109
+ GET /api/agent/runs/:runId/result
110
+ POST /api/agent/runs/:runId/cancel
111
+ ```
112
+
113
+ SSE 事件包括 `output.delta`、`tool.requested`、`tool.completed`、usage,以及唯一一个
114
+ `run.completed`、`run.failed` 或 `run.cancelled` 终态。断线后使用已确认 sequence 作为
115
+ cursor 恢复;不要重新创建 Run 或重复执行 Tool。
116
+
117
+ ## 从 create-windy 0.2.21 升级
118
+
119
+ 先检查计划,再应用连续 recipe:
120
+
121
+ ```bash
122
+ bunx --bun create-windy@0.2.22 update --check
123
+ bunx --bun create-windy@0.2.22 update
124
+ bun install
125
+ ```
126
+
127
+ 升级器会更新受管模板文件;本地修改产生冲突时会 fail closed 并保留事务备份,不会静默
128
+ 覆盖。升级后按上面的环境变量启用 `agent` profile。
@@ -10,6 +10,11 @@ Server 端已提供 Agent Tool 的最小闭环:
10
10
  - 执行前使用 Permission Guard 校验 `permissionKey`。
11
11
  - 权限拒绝记录 `security.denied` 审计事件。
12
12
  - 执行成功记录 `agent.tool.execute` 审计事件。
13
+ - ModuleHost 通过 `registerAiOperationHandlers` 注册模型无关 Operation plan。
14
+ - Guarded Run facade 只把 Operation `allowedToolKeys` 中的定义交给模型。
15
+ - Agent Server 的 Tool Call 必须通过短时 Execution Grant 回到受限 Tool Host。
16
+ - Tool Host 使用宿主绑定的 Actor 与 Scope 重新执行 Operation/Tool Guard;客户端和模型
17
+ 不能传权限、License、Scope 或授权结论。
13
18
 
14
19
  OpenAPI 文档由 `@elysiajs/swagger` 提供,随 Server 启动自动暴露。
15
20
 
@@ -34,4 +39,6 @@ Authorization: Bearer dev-admin-token
34
39
  - 工具执行复用系统资源 Repository;未配置 `DATABASE_URL` 时使用内存仓储,配置后使用 Drizzle 仓储。
35
40
  - 工具参数当前只支持基础分页参数。
36
41
  - 审计事件写入内存 sink;配置 `DATABASE_URL` 后会通过有界重试写入 PostgreSQL `audit_logs`,持续失败会增加计数并输出告警,审计查询优先读取该表。详细降级语义见 [平台审计可靠写入](./audit-reliability.md)。
37
- - 后续应继续完善 Agent Tool 的结构化输入 schema 和执行结果追踪。
42
+ - Agent Runtime、环境变量、SSE Compose profile 见
43
+ [Agent Runtime 接入与运行](./agent-runtime.md)。
44
+ - 后续应继续完善 Agent Tool 的结构化输入 schema、写操作幂等和执行结果追踪。
@@ -15,6 +15,7 @@
15
15
  "dev:host": "bun run scripts/dev.ts",
16
16
  "dev:web": "bun run --cwd apps/web dev",
17
17
  "dev:server": "bun run --cwd apps/server dev",
18
+ "dev:agent": "docker compose --profile agent up --build",
18
19
  "dev:license": "bun run --cwd apps/license dev",
19
20
  "dev:license:docker": "docker compose --profile license up license",
20
21
  "dev:signing": "bun run --cwd apps/signing-service dev",
@@ -4,9 +4,11 @@ import {
4
4
  ConfigLoadError,
5
5
  defineModuleConfig,
6
6
  EnvironmentSecretResolver,
7
+ loadOpenAiCompatibleDeploymentConfig,
7
8
  loadModuleConfigSync,
8
9
  loadPlatformConfig,
9
10
  ModuleConfigRegistry,
11
+ normalizeOpenAiCompatibleBaseUrl,
10
12
  SecretRef,
11
13
  strictBoolean,
12
14
  } from "./index.js";
@@ -169,3 +171,44 @@ test("registry 拒绝重复模块和未知模块", () => {
169
171
  expect(() => registry.register(definition)).toThrow("配置模块重复注册");
170
172
  expect(() => registry.get("example.unknown")).toThrow("未知配置模块");
171
173
  });
174
+
175
+ test("OpenAI-compatible 部署只在服务端解析 Secret 与运行上限", () => {
176
+ const config = loadOpenAiCompatibleDeploymentConfig({
177
+ QWEN_API_URL:
178
+ "https://workspace.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/",
179
+ QWEN_API_KEY: "server-only",
180
+ QWEN_MODEL: "qwen-plus",
181
+ AI_PROVIDER_CONNECT_TIMEOUT_MS: "2500",
182
+ AI_PROVIDER_REQUEST_TIMEOUT_MS: "45000",
183
+ AI_PROVIDER_MAX_RESPONSE_BYTES: "1048576",
184
+ });
185
+
186
+ expect(config).toEqual({
187
+ baseUrl:
188
+ "https://workspace.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
189
+ model: "qwen-plus",
190
+ apiKey: "server-only",
191
+ connectTimeoutMs: 2500,
192
+ requestTimeoutMs: 45000,
193
+ maxResponseBytes: 1048576,
194
+ });
195
+ });
196
+
197
+ test("OpenAI-compatible Endpoint 拒绝公网 HTTP 与 metadata", () => {
198
+ expect(() =>
199
+ normalizeOpenAiCompatibleBaseUrl("http://api.example.com/v1"),
200
+ ).toThrow("公网 OpenAI-compatible Endpoint 必须使用 HTTPS");
201
+ expect(() =>
202
+ normalizeOpenAiCompatibleBaseUrl("http://169.254.169.254/v1"),
203
+ ).toThrow("禁止访问云 metadata");
204
+ expect(() =>
205
+ normalizeOpenAiCompatibleBaseUrl("https://user:pass@example.com/v1?q=1"),
206
+ ).toThrow("不允许凭据、查询串或片段");
207
+
208
+ expect(
209
+ normalizeOpenAiCompatibleBaseUrl("http://qwen.internal:9000/v1/"),
210
+ ).toBe("http://qwen.internal:9000/v1");
211
+ expect(normalizeOpenAiCompatibleBaseUrl("http://10.20.30.40:8000/v1")).toBe(
212
+ "http://10.20.30.40:8000/v1",
213
+ );
214
+ });
@@ -1,4 +1,5 @@
1
1
  export * from "./src/definition.js";
2
2
  export * from "./src/environment-loader.js";
3
+ export * from "./src/ai-openai-compatible.js";
3
4
  export * from "./src/platform.js";
4
5
  export * from "./src/secrets.js";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/config",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "index.ts",
11
+ "src/ai-openai-compatible.ts",
11
12
  "src/**/*.ts",
12
13
  "!src/**/*.test.ts"
13
14
  ],
@@ -0,0 +1,131 @@
1
+ import { isIP } from "node:net";
2
+ import { z } from "zod";
3
+ import { defineModuleConfig } from "./definition.js";
4
+ import {
5
+ loadModuleConfigSync,
6
+ strictInteger,
7
+ type ConfigEnvironment,
8
+ } from "./environment-loader.js";
9
+ import { EnvironmentSecretResolver } from "./secrets.js";
10
+
11
+ export interface OpenAiCompatibleDeploymentConfig {
12
+ baseUrl: string;
13
+ model: string;
14
+ apiKey?: string;
15
+ connectTimeoutMs: number;
16
+ requestTimeoutMs: number;
17
+ maxResponseBytes: number;
18
+ }
19
+
20
+ const deploymentSchema: z.ZodType<OpenAiCompatibleDeploymentConfig> =
21
+ z.strictObject({
22
+ baseUrl: z.string().transform(normalizeOpenAiCompatibleBaseUrl),
23
+ model: z.string().trim().min(1).max(200),
24
+ apiKey: z.string().trim().min(1).optional(),
25
+ connectTimeoutMs: z.number().int().min(100).max(30_000),
26
+ requestTimeoutMs: z.number().int().min(1_000).max(300_000),
27
+ maxResponseBytes: z
28
+ .number()
29
+ .int()
30
+ .min(1_024)
31
+ .max(16 * 1_024 * 1_024),
32
+ });
33
+
34
+ export const openAiCompatibleDeploymentConfigDefinition =
35
+ defineModuleConfig<OpenAiCompatibleDeploymentConfig>({
36
+ key: "ai.openai-compatible",
37
+ label: "OpenAI-compatible 模型部署",
38
+ ownedEnvironmentPrefixes: ["QWEN_", "AI_PROVIDER_"],
39
+ schema: deploymentSchema,
40
+ environment: [
41
+ { key: "QWEN_API_URL", path: "baseUrl" },
42
+ { key: "QWEN_MODEL", path: "model" },
43
+ {
44
+ key: "QWEN_API_KEY",
45
+ path: "apiKey",
46
+ secret: { provider: "env" },
47
+ },
48
+ integer("AI_PROVIDER_CONNECT_TIMEOUT_MS", "connectTimeoutMs", 5_000),
49
+ integer("AI_PROVIDER_REQUEST_TIMEOUT_MS", "requestTimeoutMs", 60_000),
50
+ integer(
51
+ "AI_PROVIDER_MAX_RESPONSE_BYTES",
52
+ "maxResponseBytes",
53
+ 2 * 1_024 * 1_024,
54
+ ),
55
+ ],
56
+ });
57
+
58
+ export function loadOpenAiCompatibleDeploymentConfig(
59
+ environment: ConfigEnvironment = process.env,
60
+ ): OpenAiCompatibleDeploymentConfig {
61
+ return loadModuleConfigSync(
62
+ openAiCompatibleDeploymentConfigDefinition,
63
+ environment,
64
+ new EnvironmentSecretResolver(environment),
65
+ );
66
+ }
67
+
68
+ export function normalizeOpenAiCompatibleBaseUrl(raw: string): string {
69
+ let url: URL;
70
+ try {
71
+ url = new URL(raw.trim());
72
+ } catch {
73
+ throw new Error("OpenAI-compatible Endpoint 无效");
74
+ }
75
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
76
+ throw new Error("OpenAI-compatible Endpoint 只允许 HTTP 或 HTTPS");
77
+ }
78
+ if (url.username || url.password || url.search || url.hash) {
79
+ throw new Error("OpenAI-compatible Endpoint 不允许凭据、查询串或片段");
80
+ }
81
+ const hostname = url.hostname.toLowerCase();
82
+ if (isCloudMetadataHost(hostname)) {
83
+ throw new Error("OpenAI-compatible Endpoint 禁止访问云 metadata");
84
+ }
85
+ if (url.protocol === "http:" && !isPrivateHost(hostname)) {
86
+ throw new Error("公网 OpenAI-compatible Endpoint 必须使用 HTTPS");
87
+ }
88
+ url.pathname = url.pathname.replace(/\/+$/, "");
89
+ return url.toString().replace(/\/$/, "");
90
+ }
91
+
92
+ function integer(key: string, path: string, defaultValue: number) {
93
+ return { key, path, defaultValue, decode: strictInteger };
94
+ }
95
+
96
+ function isPrivateHost(hostname: string): boolean {
97
+ if (
98
+ hostname === "localhost" ||
99
+ hostname.endsWith(".localhost") ||
100
+ hostname.endsWith(".internal") ||
101
+ hostname.endsWith(".local") ||
102
+ (!hostname.includes(".") && isIP(hostname) === 0)
103
+ ) {
104
+ return true;
105
+ }
106
+ if (isIP(hostname) === 4) {
107
+ const [first = 0, second = 0] = hostname.split(".").map(Number);
108
+ return (
109
+ first === 10 ||
110
+ first === 127 ||
111
+ (first === 172 && second >= 16 && second <= 31) ||
112
+ (first === 192 && second === 168)
113
+ );
114
+ }
115
+ if (isIP(hostname) === 6) {
116
+ return (
117
+ hostname === "::1" ||
118
+ hostname.startsWith("fc") ||
119
+ hostname.startsWith("fd")
120
+ );
121
+ }
122
+ return false;
123
+ }
124
+
125
+ function isCloudMetadataHost(hostname: string): boolean {
126
+ return (
127
+ hostname === "169.254.169.254" ||
128
+ hostname === "metadata.google.internal" ||
129
+ hostname === "metadata.azure.internal"
130
+ );
131
+ }
@@ -40,8 +40,19 @@ export type {
40
40
  ModuleTaskHandlerRegistration,
41
41
  ModuleTaskResult,
42
42
  } from "./src/task-registration.js";
43
+ export type {
44
+ ModuleAgentToolHostPort,
45
+ ModuleAgentToolInvocation,
46
+ ModuleAgentToolResult,
47
+ } from "./src/tool-host-port.js";
43
48
  export type {
44
49
  ModuleToolContext,
45
50
  ModuleToolHandlerRegistration,
46
51
  ModuleToolResult,
47
52
  } from "./src/tool-registration.js";
53
+ export type {
54
+ ModuleAiJsonValue,
55
+ ModuleAiOperationContext,
56
+ ModuleAiOperationHandlerRegistration,
57
+ ModuleAiOperationPlan,
58
+ } from "./src/ai-operation-registration.js";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/server-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "index.ts",
11
11
  "src/actor.ts",
12
+ "src/ai-operation-registration.ts",
12
13
  "src/audit-port.ts",
13
14
  "src/jobs-port.ts",
14
15
  "src/module-host.ts",
@@ -19,7 +20,8 @@
19
20
  "src/search-provider-port.ts",
20
21
  "src/storage-port.ts",
21
22
  "src/task-registration.ts",
22
- "src/tool-registration.ts"
23
+ "src/tool-registration.ts",
24
+ "src/tool-host-port.ts"
23
25
  ],
24
26
  "scripts": {
25
27
  "test": "bun test",
@@ -0,0 +1,31 @@
1
+ import type { ModuleActor } from "./actor.js";
2
+
3
+ export type ModuleAiJsonValue =
4
+ | string
5
+ | number
6
+ | boolean
7
+ | null
8
+ | ModuleAiJsonValue[]
9
+ | { [key: string]: ModuleAiJsonValue };
10
+
11
+ export interface ModuleAiOperationContext {
12
+ readonly actor: ModuleActor;
13
+ readonly requestId: string;
14
+ }
15
+
16
+ /**
17
+ * 业务模块只负责把类型化输入收敛为模型无关的指令和用户消息。
18
+ * Provider、Deployment、Secret、Execution Grant 与 Tool 授权均由宿主持有。
19
+ */
20
+ export interface ModuleAiOperationPlan {
21
+ readonly instructions: string;
22
+ readonly userPrompt: string;
23
+ }
24
+
25
+ export interface ModuleAiOperationHandlerRegistration {
26
+ readonly operationKey: string;
27
+ prepare(
28
+ input: ModuleAiJsonValue,
29
+ context: ModuleAiOperationContext,
30
+ ): Promise<ModuleAiOperationPlan> | ModuleAiOperationPlan;
31
+ }
@@ -1,4 +1,5 @@
1
1
  import type { DurableHandlerDefinition } from "@southwind-ai/jobs";
2
+ import type { ModuleAiOperationHandlerRegistration } from "./ai-operation-registration.js";
2
3
  import type { ModuleRolePreset } from "./role-preset.js";
3
4
  import type { ModuleRouteDefinition } from "./route-definition.js";
4
5
  import type { ModuleSearchProvider } from "./search-provider-port.js";
@@ -35,6 +36,10 @@ export interface ModuleHost {
35
36
  registerToolHandlers(
36
37
  registrations: readonly ModuleToolHandlerRegistration[],
37
38
  ): void;
39
+ /** 注册 Manifest `aiOperations` 已声明的模型无关 Operation 执行计划。 */
40
+ registerAiOperationHandlers(
41
+ registrations: readonly ModuleAiOperationHandlerRegistration[],
42
+ ): void;
38
43
  /** 注册角色预设;permissionKeys 须属于本模块 Manifest 权限声明。 */
39
44
  registerRolePresets(presets: readonly ModuleRolePreset[]): void;
40
45
  /** 注册 Manifest `providers` 已声明的搜索 Provider。 */
@@ -0,0 +1,22 @@
1
+ import type { ModuleAiJsonValue } from "./ai-operation-registration.js";
2
+
3
+ export interface ModuleAgentToolInvocation {
4
+ readonly runId: string;
5
+ readonly toolCallId: string;
6
+ readonly toolKey: string;
7
+ readonly arguments: Readonly<Record<string, ModuleAiJsonValue>>;
8
+ }
9
+
10
+ export interface ModuleAgentToolResult {
11
+ readonly value: ModuleAiJsonValue;
12
+ }
13
+
14
+ /**
15
+ * 宿主创建并绑定 Operation、Actor 与 Scope 的受限 Tool Host Port。
16
+ * 调用方不能传 Permission、Feature、License 或数据范围作为授权事实。
17
+ */
18
+ export interface ModuleAgentToolHostPort {
19
+ execute(
20
+ invocation: ModuleAgentToolInvocation,
21
+ ): Promise<ModuleAgentToolResult>;
22
+ }