create-windy 0.6.18 → 0.6.19
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/dist/cli.js +126 -54
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/README.md +5 -2
- package/template/apps/agent-server/README.md +6 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +23 -0
- package/template/apps/agent-server/src/southwind_agent_server/health_routes.py +9 -0
- package/template/apps/agent-server/src/southwind_agent_server/runtime_compatibility.py +89 -0
- package/template/apps/agent-server/tests/test_runtime_compatibility.py +119 -0
- package/template/apps/server/src/agent/remote-runtime-compatibility.test.ts +147 -0
- package/template/apps/server/src/agent/remote-runtime.ts +66 -4
- package/template/apps/server/src/agent/runtime-bootstrap.ts +1 -0
- package/template/apps/web/src/components/common/FormDialog.vue +1 -0
- package/template/apps/web/src/components/common/ModalLayout.webtest.ts +1 -0
- package/template/apps/web/src/components/ui/dialog/DialogContent.vue +1 -1
- package/template/apps/web/src/components/ui/dialog/DialogScrollContent.vue +1 -1
- package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +1 -0
- package/template/apps/web/src/pages/system/components/user-account/CreateDepartmentDialog.vue +182 -0
- package/template/apps/web/src/pages/system/components/user-account/CreateDepartmentDialog.webtest.ts +93 -0
- package/template/apps/web/src/pages/system/components/user-account/RolePermissionSummary.vue +59 -0
- package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +21 -71
- package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.webtest.ts +71 -0
- package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.vue +48 -27
- package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.webtest.ts +30 -1
- package/template/docs/architecture/ai-runtime.md +8 -15
- package/template/docs/platform/agent-runtime.md +5 -0
- package/template/packages/server-sdk/contracts/agent-runtime-compatibility/v1/offer.json +22 -0
- package/template/packages/server-sdk/index.ts +19 -0
- package/template/packages/server-sdk/package.json +2 -0
- package/template/packages/server-sdk/src/agent-runtime-compatibility.contract.test.ts +143 -0
- package/template/packages/server-sdk/src/agent-runtime-compatibility.public-api.test.ts +19 -0
- package/template/packages/server-sdk/src/agent-runtime-compatibility.ts +284 -0
package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.webtest.ts
CHANGED
|
@@ -22,13 +22,38 @@ describe("UserRoleSelector", () => {
|
|
|
22
22
|
code: "department-manager",
|
|
23
23
|
name: "部门负责人",
|
|
24
24
|
dataScope: "department-and-children",
|
|
25
|
+
permissionKeys: ["system.user.read", "system.user.manage"],
|
|
25
26
|
},
|
|
26
27
|
],
|
|
27
28
|
total: 1,
|
|
28
29
|
page: 1,
|
|
29
30
|
pageSize: 100,
|
|
30
31
|
});
|
|
31
|
-
const wrapper = mount(UserRoleSelector, {
|
|
32
|
+
const wrapper = mount(UserRoleSelector, {
|
|
33
|
+
props: {
|
|
34
|
+
modelValue: [],
|
|
35
|
+
permissions: [
|
|
36
|
+
{
|
|
37
|
+
key: "system.user.read",
|
|
38
|
+
label: "查看用户",
|
|
39
|
+
module: "system",
|
|
40
|
+
moduleLabel: "系统管理",
|
|
41
|
+
resource: "user",
|
|
42
|
+
resourceLabel: "用户账号",
|
|
43
|
+
action: "read",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "system.user.manage",
|
|
47
|
+
label: "管理用户",
|
|
48
|
+
module: "system",
|
|
49
|
+
moduleLabel: "系统管理",
|
|
50
|
+
resource: "user",
|
|
51
|
+
resourceLabel: "用户账号",
|
|
52
|
+
action: "manage",
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
});
|
|
32
57
|
await flushPromises();
|
|
33
58
|
|
|
34
59
|
expect(api.list).toHaveBeenCalledWith(
|
|
@@ -37,6 +62,10 @@ describe("UserRoleSelector", () => {
|
|
|
37
62
|
);
|
|
38
63
|
expect(wrapper.text()).toContain("部门负责人");
|
|
39
64
|
expect(wrapper.text()).toContain("本部门及下级");
|
|
65
|
+
expect(wrapper.text()).toContain("查看 2 项功能权限");
|
|
66
|
+
await wrapper.get("summary").trigger("click");
|
|
67
|
+
expect(wrapper.text()).toContain("用户账号");
|
|
68
|
+
expect(wrapper.text()).toContain("查看用户、管理用户");
|
|
40
69
|
await wrapper.get('[role="checkbox"]').trigger("click");
|
|
41
70
|
expect(wrapper.emitted("update:modelValue")?.at(-1)).toEqual([
|
|
42
71
|
["role_manager"],
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
# Windy AI Runtime 架构
|
|
2
2
|
|
|
3
|
-
更新时间:2026-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
PostgreSQL 多副本 Store 尚未实现。Document AI 已提供独立
|
|
12
|
-
Server、授权 ArtifactRef、结构化页级结果、本地/内网 Adapter 契约,以及搜索型 PDF
|
|
13
|
-
等派生文件的受治理 Output Artifact producer 协议;项目 composition 尚未接线,不能
|
|
14
|
-
视为生产可用。Speech 已提供批量转写、归一化、VAD/时间戳契约、稳定分段和结果
|
|
15
|
-
Artifact,但未提供实时 ASR、TTS 或 Dictation。不能据此宣称完整 AI Runtime 已交付。
|
|
16
|
-
Vision 已提供 `agent.vision` 的多图片受控输入、像素限制、逐次发送审计和 JSON Schema
|
|
17
|
-
终态校验;视频帧输入与实时视觉不在当前切片。
|
|
3
|
+
更新时间:2026-09-02。
|
|
4
|
+
|
|
5
|
+
状态:文本 Agent、Document、Embedding、批量 Speech/ASR、受控 Vision Artifact、PostgreSQL
|
|
6
|
+
Run Store、持久 Session/Memory 与 Workflow/委派切片均已实现。WAI-1 已为 Windy Server 到
|
|
7
|
+
Agent Server 增加 additive 的 V1 兼容 Offer、release/capability digest 和 Run 创建 pin;详见
|
|
8
|
+
[Remote Agent Runtime 兼容契约](./agent-runtime-compatibility.md)。当前 Provider 仍是单一 Direct
|
|
9
|
+
OpenAI-compatible Adapter;LiteLLM 多 Provider 路由、完整 RAG、外部业务 Host callback 认证与
|
|
10
|
+
Sharp S-Word 接线均未实现。未配置任一可选 Runtime 时继续安全拒绝对应 Operation,不阻止普通业务。
|
|
18
11
|
|
|
19
12
|
## 目标
|
|
20
13
|
|
|
@@ -18,6 +18,11 @@ POST /api/agent/runs/:runId/cancel
|
|
|
18
18
|
创建 Run 必须携带幂等键;SSE 支持 cursor 重放和 heartbeat。Engine 切换不改变 Run DTO、
|
|
19
19
|
canonical Event、唯一终态、取消或 Tool 授权语义。
|
|
20
20
|
|
|
21
|
+
Windy Server 与 Agent Server 之间已经提供 additive 的 V1 兼容 Offer、release/capability digest
|
|
22
|
+
和 Run 创建请求头 pin。当前同部署 Adapter 使用滚动兼容的 `prefer-v1`;任何外部业务 Host Adapter
|
|
23
|
+
必须使用 `require-v1`,且在独立 callback 认证 profile 落地前不得投入生产。完整职责与失败语义见
|
|
24
|
+
[Remote Agent Runtime 兼容契约](../architecture/agent-runtime-compatibility.md)。
|
|
25
|
+
|
|
21
26
|
## 能力与治理边界
|
|
22
27
|
|
|
23
28
|
Direct Engine 已覆盖:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"runtimeKind": "agent",
|
|
4
|
+
"engineId": "windy.direct.openai-compatible.v1",
|
|
5
|
+
"protocolVersions": [1],
|
|
6
|
+
"protocolFeatures": [
|
|
7
|
+
"canonical-sse-replay.v1",
|
|
8
|
+
"execution-grant-forwarding.v1",
|
|
9
|
+
"operation-release-attestation.v1",
|
|
10
|
+
"run-cancel.v1",
|
|
11
|
+
"run-lifecycle.v1",
|
|
12
|
+
"runtime-reporting.v1",
|
|
13
|
+
"tool-callback.v1"
|
|
14
|
+
],
|
|
15
|
+
"toolCallback": {
|
|
16
|
+
"contractVersion": 1,
|
|
17
|
+
"authentication": "windy-internal-bearer-v1"
|
|
18
|
+
},
|
|
19
|
+
"releaseDigest": "sha256:410c49040a4e4de40dd2fed60877b2b08e9bbcb23198f10a7cbb3cf7d1135ce0",
|
|
20
|
+
"capabilitiesDigest": "sha256:bcdd3822f9077ac6d1cd42f8a53dbdb46d9a5bc908821e92bd6fb28e2ec6ab65",
|
|
21
|
+
"offerDigest": "sha256:1375cd3a1511f2d5fdde3cac123d9d6bc603f80ad746758508d0a3da67ed8fad"
|
|
22
|
+
}
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
export type { ModuleActor } from "./src/actor.js";
|
|
2
|
+
export {
|
|
3
|
+
AGENT_RUNTIME_COMPATIBILITY_PATH_V1,
|
|
4
|
+
AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION,
|
|
5
|
+
AGENT_RUNTIME_INTERNAL_CALLBACK_AUTH_V1,
|
|
6
|
+
AGENT_RUNTIME_KIND_V1,
|
|
7
|
+
AGENT_RUNTIME_OFFER_DIGEST_HEADER_V1,
|
|
8
|
+
AGENT_RUNTIME_PROTOCOL_FEATURES_V1,
|
|
9
|
+
AGENT_RUNTIME_PROTOCOL_HEADER_V1,
|
|
10
|
+
AGENT_RUNTIME_PROTOCOL_VERSION_V1,
|
|
11
|
+
AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1,
|
|
12
|
+
createAgentRuntimeCompatibilityOfferV1,
|
|
13
|
+
localAgentRuntimeCompatibilityRequirementsV1,
|
|
14
|
+
negotiateAgentRuntimeCompatibilityV1,
|
|
15
|
+
parseAgentRuntimeCompatibilityOfferV1,
|
|
16
|
+
type AgentRuntimeCompatibilityDigestV1,
|
|
17
|
+
type AgentRuntimeCompatibilityNegotiationV1,
|
|
18
|
+
type AgentRuntimeCompatibilityOfferV1,
|
|
19
|
+
type AgentRuntimeCompatibilityRequirementsV1,
|
|
20
|
+
} from "./src/agent-runtime-compatibility.js";
|
|
2
21
|
export { pageAgentClientRunCapability } from "./src/client-agent-session.js";
|
|
3
22
|
export type {
|
|
4
23
|
ModuleClientAgentHostAuthAdapter,
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"src/ai-runtime-readiness-validation.ts",
|
|
27
27
|
"src/ai-runtime-readiness-validation-values.ts",
|
|
28
28
|
"src/ai-runtime-readiness-values.ts",
|
|
29
|
+
"src/agent-runtime-compatibility.ts",
|
|
29
30
|
"src/agent-session-memory-port.ts",
|
|
30
31
|
"src/agent-session-provider.ts",
|
|
31
32
|
"src/agent-source-invalidation-port.ts",
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
},
|
|
72
73
|
"exports": {
|
|
73
74
|
".": "./index.ts",
|
|
75
|
+
"./contracts/agent-runtime-compatibility/v1/*": "./contracts/agent-runtime-compatibility/v1/*",
|
|
74
76
|
"./contracts/ai-runtime-readiness/v1/*": "./contracts/ai-runtime-readiness/v1/*"
|
|
75
77
|
},
|
|
76
78
|
"publishConfig": {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
import {
|
|
4
|
+
AGENT_RUNTIME_PROTOCOL_FEATURES_V1,
|
|
5
|
+
type AgentRuntimeCompatibilityOfferV1,
|
|
6
|
+
createAgentRuntimeCompatibilityOfferV1,
|
|
7
|
+
localAgentRuntimeCompatibilityRequirementsV1,
|
|
8
|
+
negotiateAgentRuntimeCompatibilityV1,
|
|
9
|
+
parseAgentRuntimeCompatibilityOfferV1,
|
|
10
|
+
} from "./agent-runtime-compatibility.js";
|
|
11
|
+
|
|
12
|
+
const fixture = await loadFixture();
|
|
13
|
+
|
|
14
|
+
describe("Remote Agent Runtime compatibility V1", () => {
|
|
15
|
+
test("公共 builder 与跨语言 fixture 字节语义一致", () => {
|
|
16
|
+
expect(
|
|
17
|
+
createAgentRuntimeCompatibilityOfferV1(
|
|
18
|
+
"windy.direct.openai-compatible.v1",
|
|
19
|
+
),
|
|
20
|
+
).toEqual(fixture);
|
|
21
|
+
expect(parseAgentRuntimeCompatibilityOfferV1(fixture)).toEqual(fixture);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("协商最高共同版本并要求完整协议能力与 callback 认证", () => {
|
|
25
|
+
const result = negotiateAgentRuntimeCompatibilityV1(
|
|
26
|
+
fixture,
|
|
27
|
+
localAgentRuntimeCompatibilityRequirementsV1(),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(result).toEqual({ ok: true, protocolVersion: 1, offer: fixture });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("未知 schema、缺失能力和 callback 认证漂移均失败关闭", () => {
|
|
34
|
+
expect(
|
|
35
|
+
negotiateAgentRuntimeCompatibilityV1(
|
|
36
|
+
{ ...fixture, schemaVersion: 2 },
|
|
37
|
+
localAgentRuntimeCompatibilityRequirementsV1(),
|
|
38
|
+
),
|
|
39
|
+
).toEqual({ ok: false, code: "CONTRACT_UNSUPPORTED" });
|
|
40
|
+
expect(
|
|
41
|
+
negotiateAgentRuntimeCompatibilityV1(fixture, {
|
|
42
|
+
...localAgentRuntimeCompatibilityRequirementsV1(),
|
|
43
|
+
requiredProtocolFeatures: [
|
|
44
|
+
...AGENT_RUNTIME_PROTOCOL_FEATURES_V1,
|
|
45
|
+
"future-required.v1",
|
|
46
|
+
],
|
|
47
|
+
}),
|
|
48
|
+
).toEqual({
|
|
49
|
+
ok: false,
|
|
50
|
+
code: "CAPABILITY_MISSING",
|
|
51
|
+
subject: "future-required.v1",
|
|
52
|
+
});
|
|
53
|
+
expect(
|
|
54
|
+
negotiateAgentRuntimeCompatibilityV1(fixture, {
|
|
55
|
+
...localAgentRuntimeCompatibilityRequirementsV1(),
|
|
56
|
+
requiredToolCallbackAuthentication: "signed-callback-v1",
|
|
57
|
+
}),
|
|
58
|
+
).toEqual({
|
|
59
|
+
ok: false,
|
|
60
|
+
code: "CALLBACK_AUTH_UNSUPPORTED",
|
|
61
|
+
subject: "signed-callback-v1",
|
|
62
|
+
});
|
|
63
|
+
expect(
|
|
64
|
+
negotiateAgentRuntimeCompatibilityV1(fixture, {
|
|
65
|
+
...localAgentRuntimeCompatibilityRequirementsV1(),
|
|
66
|
+
protocolVersions: [2],
|
|
67
|
+
}),
|
|
68
|
+
).toEqual({ ok: false, code: "CONTRACT_UNSUPPORTED" });
|
|
69
|
+
expect(
|
|
70
|
+
negotiateAgentRuntimeCompatibilityV1(fixture, {
|
|
71
|
+
...localAgentRuntimeCompatibilityRequirementsV1(),
|
|
72
|
+
expectedReleaseDigest: `sha256:${"0".repeat(64)}`,
|
|
73
|
+
}),
|
|
74
|
+
).toEqual({ ok: false, code: "RELEASE_DIGEST_MISMATCH" });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("任一受摘要保护字段漂移都不能沿用旧 Offer", () => {
|
|
78
|
+
expect(
|
|
79
|
+
parseAgentRuntimeCompatibilityOfferV1({
|
|
80
|
+
...fixture,
|
|
81
|
+
protocolFeatures: [...fixture.protocolFeatures, "tampered.v1"],
|
|
82
|
+
}),
|
|
83
|
+
).toBeNull();
|
|
84
|
+
expect(
|
|
85
|
+
parseAgentRuntimeCompatibilityOfferV1({
|
|
86
|
+
...fixture,
|
|
87
|
+
engineId: "windy.other-engine.v1",
|
|
88
|
+
}),
|
|
89
|
+
).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("重复或超界集合与非稳定 identity 被拒绝", () => {
|
|
93
|
+
expect(
|
|
94
|
+
parseAgentRuntimeCompatibilityOfferV1({
|
|
95
|
+
...fixture,
|
|
96
|
+
protocolVersions: [1, 1],
|
|
97
|
+
}),
|
|
98
|
+
).toBeNull();
|
|
99
|
+
expect(
|
|
100
|
+
parseAgentRuntimeCompatibilityOfferV1({
|
|
101
|
+
...fixture,
|
|
102
|
+
protocolFeatures: Array.from(
|
|
103
|
+
{ length: 65 },
|
|
104
|
+
(_, index) => `feature-${index}.v1`,
|
|
105
|
+
),
|
|
106
|
+
}),
|
|
107
|
+
).toBeNull();
|
|
108
|
+
expect(() =>
|
|
109
|
+
createAgentRuntimeCompatibilityOfferV1("https://provider.invalid"),
|
|
110
|
+
).toThrow("engineId invalid");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("恶意 getter 或循环对象只形成安全的契约拒绝", () => {
|
|
114
|
+
const throwing = Object.defineProperty({}, "schemaVersion", {
|
|
115
|
+
get() {
|
|
116
|
+
throw new Error("secret getter body");
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
const cyclic: Record<string, unknown> = { ...fixture };
|
|
120
|
+
cyclic.toolCallback = cyclic;
|
|
121
|
+
|
|
122
|
+
expect(parseAgentRuntimeCompatibilityOfferV1(throwing)).toBeNull();
|
|
123
|
+
expect(parseAgentRuntimeCompatibilityOfferV1(cyclic)).toBeNull();
|
|
124
|
+
expect(
|
|
125
|
+
negotiateAgentRuntimeCompatibilityV1(
|
|
126
|
+
throwing,
|
|
127
|
+
localAgentRuntimeCompatibilityRequirementsV1(),
|
|
128
|
+
),
|
|
129
|
+
).toEqual({ ok: false, code: "CONTRACT_INVALID" });
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
async function loadFixture(): Promise<AgentRuntimeCompatibilityOfferV1> {
|
|
134
|
+
return JSON.parse(
|
|
135
|
+
await readFile(
|
|
136
|
+
new URL(
|
|
137
|
+
"../contracts/agent-runtime-compatibility/v1/offer.json",
|
|
138
|
+
import.meta.url,
|
|
139
|
+
),
|
|
140
|
+
"utf8",
|
|
141
|
+
),
|
|
142
|
+
) as AgentRuntimeCompatibilityOfferV1;
|
|
143
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
createAgentRuntimeCompatibilityOfferV1,
|
|
4
|
+
localAgentRuntimeCompatibilityRequirementsV1,
|
|
5
|
+
negotiateAgentRuntimeCompatibilityV1,
|
|
6
|
+
} from "../index.js";
|
|
7
|
+
|
|
8
|
+
test("@southwind-ai/server-sdk 根入口公开 Remote Agent Runtime V1 协商", () => {
|
|
9
|
+
const offer = createAgentRuntimeCompatibilityOfferV1(
|
|
10
|
+
"windy.direct.openai-compatible.v1",
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect(
|
|
14
|
+
negotiateAgentRuntimeCompatibilityV1(
|
|
15
|
+
offer,
|
|
16
|
+
localAgentRuntimeCompatibilityRequirementsV1(),
|
|
17
|
+
),
|
|
18
|
+
).toMatchObject({ ok: true, protocolVersion: 1 });
|
|
19
|
+
});
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION = 1 as const;
|
|
4
|
+
export const AGENT_RUNTIME_PROTOCOL_VERSION_V1 = 1 as const;
|
|
5
|
+
export const AGENT_RUNTIME_KIND_V1 = "agent" as const;
|
|
6
|
+
export const AGENT_RUNTIME_COMPATIBILITY_PATH_V1 =
|
|
7
|
+
"/api/agent/runtime/compatibility" as const;
|
|
8
|
+
export const AGENT_RUNTIME_PROTOCOL_HEADER_V1 =
|
|
9
|
+
"X-Windy-Agent-Runtime-Protocol" as const;
|
|
10
|
+
export const AGENT_RUNTIME_OFFER_DIGEST_HEADER_V1 =
|
|
11
|
+
"X-Windy-Agent-Runtime-Offer-Digest" as const;
|
|
12
|
+
export const AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1 = 1 as const;
|
|
13
|
+
export const AGENT_RUNTIME_INTERNAL_CALLBACK_AUTH_V1 =
|
|
14
|
+
"windy-internal-bearer-v1" as const;
|
|
15
|
+
|
|
16
|
+
export const AGENT_RUNTIME_PROTOCOL_FEATURES_V1 = Object.freeze([
|
|
17
|
+
"canonical-sse-replay.v1",
|
|
18
|
+
"execution-grant-forwarding.v1",
|
|
19
|
+
"operation-release-attestation.v1",
|
|
20
|
+
"run-cancel.v1",
|
|
21
|
+
"run-lifecycle.v1",
|
|
22
|
+
"runtime-reporting.v1",
|
|
23
|
+
"tool-callback.v1",
|
|
24
|
+
] as const);
|
|
25
|
+
|
|
26
|
+
export type AgentRuntimeCompatibilityDigestV1 = `sha256:${string}`;
|
|
27
|
+
|
|
28
|
+
export interface AgentRuntimeCompatibilityOfferV1 {
|
|
29
|
+
readonly schemaVersion: typeof AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION;
|
|
30
|
+
readonly runtimeKind: typeof AGENT_RUNTIME_KIND_V1;
|
|
31
|
+
readonly engineId: string;
|
|
32
|
+
readonly protocolVersions: readonly number[];
|
|
33
|
+
readonly protocolFeatures: readonly string[];
|
|
34
|
+
readonly toolCallback: {
|
|
35
|
+
readonly contractVersion: typeof AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1;
|
|
36
|
+
readonly authentication: string;
|
|
37
|
+
};
|
|
38
|
+
readonly releaseDigest: AgentRuntimeCompatibilityDigestV1;
|
|
39
|
+
readonly capabilitiesDigest: AgentRuntimeCompatibilityDigestV1;
|
|
40
|
+
readonly offerDigest: AgentRuntimeCompatibilityDigestV1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AgentRuntimeCompatibilityRequirementsV1 {
|
|
44
|
+
readonly protocolVersions: readonly number[];
|
|
45
|
+
readonly requiredProtocolFeatures: readonly string[];
|
|
46
|
+
readonly requiredToolCallbackAuthentication: string;
|
|
47
|
+
readonly expectedReleaseDigest?: AgentRuntimeCompatibilityDigestV1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type AgentRuntimeCompatibilityNegotiationV1 =
|
|
51
|
+
| {
|
|
52
|
+
readonly ok: true;
|
|
53
|
+
readonly protocolVersion: number;
|
|
54
|
+
readonly offer: AgentRuntimeCompatibilityOfferV1;
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
readonly ok: false;
|
|
58
|
+
readonly code:
|
|
59
|
+
| "CONTRACT_INVALID"
|
|
60
|
+
| "CONTRACT_UNSUPPORTED"
|
|
61
|
+
| "CAPABILITY_MISSING"
|
|
62
|
+
| "CALLBACK_AUTH_UNSUPPORTED"
|
|
63
|
+
| "RELEASE_DIGEST_MISMATCH";
|
|
64
|
+
readonly subject?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const stableValuePattern = /^[a-z0-9][a-z0-9._-]{0,159}$/;
|
|
68
|
+
|
|
69
|
+
export function createAgentRuntimeCompatibilityOfferV1(
|
|
70
|
+
engineId: string,
|
|
71
|
+
): AgentRuntimeCompatibilityOfferV1 {
|
|
72
|
+
const normalizedEngineId = stableValue(engineId);
|
|
73
|
+
if (!normalizedEngineId) {
|
|
74
|
+
throw new TypeError("Agent Runtime engineId invalid");
|
|
75
|
+
}
|
|
76
|
+
return buildOffer({
|
|
77
|
+
engineId: normalizedEngineId,
|
|
78
|
+
protocolVersions: [AGENT_RUNTIME_PROTOCOL_VERSION_V1],
|
|
79
|
+
protocolFeatures: AGENT_RUNTIME_PROTOCOL_FEATURES_V1,
|
|
80
|
+
toolCallback: {
|
|
81
|
+
contractVersion: AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1,
|
|
82
|
+
authentication: AGENT_RUNTIME_INTERNAL_CALLBACK_AUTH_V1,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function parseAgentRuntimeCompatibilityOfferV1(
|
|
88
|
+
raw: unknown,
|
|
89
|
+
): AgentRuntimeCompatibilityOfferV1 | null {
|
|
90
|
+
try {
|
|
91
|
+
return parseCapturedOffer(raw);
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseCapturedOffer(
|
|
98
|
+
raw: unknown,
|
|
99
|
+
): AgentRuntimeCompatibilityOfferV1 | null {
|
|
100
|
+
if (!isRecord(raw)) return null;
|
|
101
|
+
if (
|
|
102
|
+
raw.schemaVersion !== AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION ||
|
|
103
|
+
raw.runtimeKind !== AGENT_RUNTIME_KIND_V1
|
|
104
|
+
) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const engineId = stableValue(raw.engineId);
|
|
108
|
+
const protocolVersions = versions(raw.protocolVersions);
|
|
109
|
+
const protocolFeatures = stableValues(raw.protocolFeatures, 64);
|
|
110
|
+
const toolCallback = parseToolCallback(raw.toolCallback);
|
|
111
|
+
if (!engineId || !protocolVersions || !protocolFeatures || !toolCallback) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const expected = buildOffer({
|
|
115
|
+
engineId,
|
|
116
|
+
protocolVersions,
|
|
117
|
+
protocolFeatures,
|
|
118
|
+
toolCallback,
|
|
119
|
+
});
|
|
120
|
+
return raw.releaseDigest === expected.releaseDigest &&
|
|
121
|
+
raw.capabilitiesDigest === expected.capabilitiesDigest &&
|
|
122
|
+
raw.offerDigest === expected.offerDigest
|
|
123
|
+
? expected
|
|
124
|
+
: null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function negotiateAgentRuntimeCompatibilityV1(
|
|
128
|
+
raw: unknown,
|
|
129
|
+
requirements: AgentRuntimeCompatibilityRequirementsV1,
|
|
130
|
+
): AgentRuntimeCompatibilityNegotiationV1 {
|
|
131
|
+
const schemaVersion = observedSchemaVersion(raw);
|
|
132
|
+
if (
|
|
133
|
+
schemaVersion !== undefined &&
|
|
134
|
+
schemaVersion !== AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION
|
|
135
|
+
) {
|
|
136
|
+
return { ok: false, code: "CONTRACT_UNSUPPORTED" };
|
|
137
|
+
}
|
|
138
|
+
const offer = parseAgentRuntimeCompatibilityOfferV1(raw);
|
|
139
|
+
if (!offer) return { ok: false, code: "CONTRACT_INVALID" };
|
|
140
|
+
const requiredVersions = versions(requirements.protocolVersions);
|
|
141
|
+
const requiredFeatures = stableValues(
|
|
142
|
+
requirements.requiredProtocolFeatures,
|
|
143
|
+
64,
|
|
144
|
+
);
|
|
145
|
+
const callbackAuthentication = stableValue(
|
|
146
|
+
requirements.requiredToolCallbackAuthentication,
|
|
147
|
+
);
|
|
148
|
+
if (!requiredVersions || !requiredFeatures || !callbackAuthentication) {
|
|
149
|
+
throw new TypeError("Agent Runtime compatibility requirements invalid");
|
|
150
|
+
}
|
|
151
|
+
const protocolVersion = [...offer.protocolVersions]
|
|
152
|
+
.filter((version) => requiredVersions.includes(version))
|
|
153
|
+
.sort((left, right) => right - left)[0];
|
|
154
|
+
if (protocolVersion === undefined) {
|
|
155
|
+
return { ok: false, code: "CONTRACT_UNSUPPORTED" };
|
|
156
|
+
}
|
|
157
|
+
const missing = requiredFeatures.find(
|
|
158
|
+
(feature) => !offer.protocolFeatures.includes(feature),
|
|
159
|
+
);
|
|
160
|
+
if (missing) {
|
|
161
|
+
return { ok: false, code: "CAPABILITY_MISSING", subject: missing };
|
|
162
|
+
}
|
|
163
|
+
if (offer.toolCallback.authentication !== callbackAuthentication) {
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
code: "CALLBACK_AUTH_UNSUPPORTED",
|
|
167
|
+
subject: callbackAuthentication,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (
|
|
171
|
+
requirements.expectedReleaseDigest &&
|
|
172
|
+
requirements.expectedReleaseDigest !== offer.releaseDigest
|
|
173
|
+
) {
|
|
174
|
+
return { ok: false, code: "RELEASE_DIGEST_MISMATCH" };
|
|
175
|
+
}
|
|
176
|
+
return { ok: true, protocolVersion, offer };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function localAgentRuntimeCompatibilityRequirementsV1(): AgentRuntimeCompatibilityRequirementsV1 {
|
|
180
|
+
return {
|
|
181
|
+
protocolVersions: [AGENT_RUNTIME_PROTOCOL_VERSION_V1],
|
|
182
|
+
requiredProtocolFeatures: AGENT_RUNTIME_PROTOCOL_FEATURES_V1,
|
|
183
|
+
requiredToolCallbackAuthentication: AGENT_RUNTIME_INTERNAL_CALLBACK_AUTH_V1,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildOffer(input: {
|
|
188
|
+
readonly engineId: string;
|
|
189
|
+
readonly protocolVersions: readonly number[];
|
|
190
|
+
readonly protocolFeatures: readonly string[];
|
|
191
|
+
readonly toolCallback: AgentRuntimeCompatibilityOfferV1["toolCallback"];
|
|
192
|
+
}): AgentRuntimeCompatibilityOfferV1 {
|
|
193
|
+
const protocolVersions = sortedNumbers(input.protocolVersions);
|
|
194
|
+
const protocolFeatures = sortedStrings(input.protocolFeatures);
|
|
195
|
+
const releaseDigest = digest({
|
|
196
|
+
runtimeKind: AGENT_RUNTIME_KIND_V1,
|
|
197
|
+
engineId: input.engineId,
|
|
198
|
+
protocolVersions,
|
|
199
|
+
});
|
|
200
|
+
const capabilitiesDigest = digest({
|
|
201
|
+
protocolFeatures,
|
|
202
|
+
toolCallback: input.toolCallback,
|
|
203
|
+
});
|
|
204
|
+
const body = {
|
|
205
|
+
schemaVersion: AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION,
|
|
206
|
+
runtimeKind: AGENT_RUNTIME_KIND_V1,
|
|
207
|
+
engineId: input.engineId,
|
|
208
|
+
protocolVersions,
|
|
209
|
+
protocolFeatures,
|
|
210
|
+
toolCallback: { ...input.toolCallback },
|
|
211
|
+
releaseDigest,
|
|
212
|
+
capabilitiesDigest,
|
|
213
|
+
};
|
|
214
|
+
return { ...body, offerDigest: digest(body) };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function digest(value: unknown): AgentRuntimeCompatibilityDigestV1 {
|
|
218
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function parseToolCallback(
|
|
222
|
+
raw: unknown,
|
|
223
|
+
): AgentRuntimeCompatibilityOfferV1["toolCallback"] | null {
|
|
224
|
+
if (!isRecord(raw)) return null;
|
|
225
|
+
const authentication = stableValue(raw.authentication);
|
|
226
|
+
return raw.contractVersion ===
|
|
227
|
+
AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1 && authentication
|
|
228
|
+
? {
|
|
229
|
+
contractVersion: AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1,
|
|
230
|
+
authentication,
|
|
231
|
+
}
|
|
232
|
+
: null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function versions(raw: unknown): readonly number[] | null {
|
|
236
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > 16) return null;
|
|
237
|
+
if (
|
|
238
|
+
raw.some(
|
|
239
|
+
(value) =>
|
|
240
|
+
!Number.isSafeInteger(value) ||
|
|
241
|
+
Number(value) < 1 ||
|
|
242
|
+
Number(value) > 255,
|
|
243
|
+
)
|
|
244
|
+
) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const result = sortedNumbers(raw as number[]);
|
|
248
|
+
return result.length === raw.length ? result : null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function stableValues(raw: unknown, limit: number): readonly string[] | null {
|
|
252
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > limit)
|
|
253
|
+
return null;
|
|
254
|
+
const values = raw.map(stableValue);
|
|
255
|
+
if (values.some((value) => !value)) return null;
|
|
256
|
+
const result = sortedStrings(values as string[]);
|
|
257
|
+
return result.length === raw.length ? result : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function stableValue(raw: unknown): string | null {
|
|
261
|
+
return typeof raw === "string" && stableValuePattern.test(raw) ? raw : null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function sortedNumbers(values: readonly number[]): readonly number[] {
|
|
265
|
+
return [...new Set(values)].sort((left, right) => left - right);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function sortedStrings(values: readonly string[]): readonly string[] {
|
|
269
|
+
return [...new Set(values)].sort((left, right) =>
|
|
270
|
+
left < right ? -1 : left > right ? 1 : 0,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
275
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function observedSchemaVersion(raw: unknown): unknown {
|
|
279
|
+
try {
|
|
280
|
+
return isRecord(raw) ? raw.schemaVersion : undefined;
|
|
281
|
+
} catch {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
}
|