@springbrand/agent-runtime 0.1.0
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/package.json +28 -0
- package/src/db/approval.repo.ts +291 -0
- package/src/db/ext-context.repo.ts +34 -0
- package/src/db/index.ts +83 -0
- package/src/db/message-ui.repo.ts +39 -0
- package/src/db/milestone.repo.ts +96 -0
- package/src/db/runtime-event-outbox.repo.ts +89 -0
- package/src/db/schema.ts +164 -0
- package/src/db/settlement.repo.ts +104 -0
- package/src/db/steer.repo.ts +73 -0
- package/src/db/submission.repo.ts +323 -0
- package/src/index.ts +133 -0
- package/src/kernel/approval-lifecycle.ts +552 -0
- package/src/kernel/bindings.ts +898 -0
- package/src/kernel/degradation.ts +15 -0
- package/src/kernel/extensions.ts +108 -0
- package/src/kernel/profile.ts +116 -0
- package/src/kernel/public-contracts.ts +17 -0
- package/src/kernel/receipts.ts +124 -0
- package/src/kernel/recoverable-chat-agent.ts +899 -0
- package/src/kernel/state.ts +76 -0
- package/src/kernel/submission-lifecycle.ts +600 -0
- package/src/layers/context/budget/gate.ts +88 -0
- package/src/layers/orchestration/subagents/agent-types/contract.ts +78 -0
- package/src/layers/orchestration/subagents/agent-types/extract/index.ts +47 -0
- package/src/layers/orchestration/subagents/agent-types/fanout/index.ts +53 -0
- package/src/layers/orchestration/subagents/agent-types/registry.ts +16 -0
- package/src/layers/orchestration/temporary-agent/core.ts +152 -0
- package/src/layers/orchestration/temporary-agent/runner.ts +133 -0
- package/src/layers/orchestration/temporary-agent/workspace.ts +154 -0
- package/src/lib/artifacts.ts +54 -0
- package/src/lib/egress.ts +44 -0
- package/src/lib/execution-level.ts +27 -0
- package/src/lib/extension-name.ts +18 -0
- package/src/lib/host-actions.ts +57 -0
- package/src/lib/mcp.ts +86 -0
- package/src/lib/model-catalog.ts +7 -0
- package/src/lib/prompt.ts +139 -0
- package/src/lib/telemetry-dev.ts +44 -0
- package/src/pi/assembly/context.ts +510 -0
- package/src/pi/assembly/extensions.ts +661 -0
- package/src/pi/assembly/index.ts +19 -0
- package/src/pi/assembly/snapshot.ts +200 -0
- package/src/pi/message/contract.ts +8 -0
- package/src/pi/message/conversion.ts +73 -0
- package/src/pi/message/index.ts +3 -0
- package/src/pi/message/projection.ts +604 -0
- package/src/pi/runtime-adapter/assembly.ts +552 -0
- package/src/pi/runtime-adapter/execution.ts +683 -0
- package/src/pi/runtime-adapter/index.ts +232 -0
- package/src/pi/runtime-adapter/models.ts +243 -0
- package/src/pi/runtime-adapter/recovery.ts +805 -0
- package/src/pi/runtime-adapter/transcript.ts +825 -0
- package/src/pi/session/index.ts +24 -0
- package/src/pi/session/storage.ts +353 -0
- package/src/pi/tool/ai-adapter.ts +100 -0
- package/src/pi/tool/base.ts +110 -0
- package/src/pi/tool/compiler.ts +444 -0
- package/src/pi/tool/core-host.ts +48 -0
- package/src/pi/tool/core.ts +251 -0
- package/src/pi/tool/index.ts +32 -0
- package/src/pi/tool/mcp.ts +319 -0
- package/src/pi/tool/schedule.ts +198 -0
- package/src/pi/tool/skill.ts +455 -0
- package/src/pi/tool/subagent.ts +148 -0
- package/src/pi/tool/web-search/api.ts +1292 -0
- package/src/pi/tool/web-search/index.ts +2 -0
- package/src/pi/tool/web-search/web-search.ts +127 -0
- package/src/pi/tool/workspace-sandbox.ts +664 -0
- package/src/pi/turn/approval.ts +181 -0
- package/src/pi/turn/index.ts +62 -0
- package/src/pi/turn/tool-recovery.ts +792 -0
- package/src/plugins.ts +1024 -0
- package/src/runtime-agent.ts +654 -0
- package/src/runtime.ts +2880 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentMessage,
|
|
3
|
+
ThinkingLevel,
|
|
4
|
+
} from "@earendil-works/pi-agent-core";
|
|
5
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
6
|
+
import type {
|
|
7
|
+
RuntimeMcpServer,
|
|
8
|
+
RuntimeProfile,
|
|
9
|
+
ThinkingEffort,
|
|
10
|
+
} from "../../kernel/profile";
|
|
11
|
+
import type {
|
|
12
|
+
RuntimeExtensionConfig,
|
|
13
|
+
} from "../../kernel/extensions";
|
|
14
|
+
import type { RuntimeProviderPort } from "../../kernel/bindings";
|
|
15
|
+
import { assembleSystemPrompt } from "../../lib/prompt";
|
|
16
|
+
import { resolvePiModel } from "../runtime-adapter/models";
|
|
17
|
+
import type { PiToolCandidate } from "../tool/compiler";
|
|
18
|
+
|
|
19
|
+
/** Immutable inputs consumed directly by Pi Agent Core. */
|
|
20
|
+
export interface PiRuntimeAssembly {
|
|
21
|
+
readonly model: Model<Api>;
|
|
22
|
+
readonly thinkingLevel: ThinkingLevel;
|
|
23
|
+
readonly systemPrompt: string;
|
|
24
|
+
readonly messages: readonly AgentMessage[];
|
|
25
|
+
readonly toolCandidates: readonly PiToolCandidate[];
|
|
26
|
+
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
27
|
+
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface PiRuntimeAssemblyInput {
|
|
31
|
+
readonly profile: Pick<
|
|
32
|
+
RuntimeProfile,
|
|
33
|
+
"model" | "thinking" | "systemPrompt"
|
|
34
|
+
>;
|
|
35
|
+
readonly provider: RuntimeProviderPort;
|
|
36
|
+
readonly messages?: readonly AgentMessage[];
|
|
37
|
+
readonly toolCandidates: readonly PiToolCandidate[];
|
|
38
|
+
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
39
|
+
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 作用:复制并冻结 Pi 模型中会被外部持有的嵌套字段。
|
|
43
|
+
// 调用:创建 Runtime 装配快照时对已解析的 provider 模型调用。
|
|
44
|
+
// 原因:只冻结外层会留下可变数组和配置对象,使已提交 Snapshot 被之后的修改偷偷改写。
|
|
45
|
+
function freezeModel(model: Model<Api>): Model<Api> {
|
|
46
|
+
const input = [...model.input];
|
|
47
|
+
const cost = { ...model.cost };
|
|
48
|
+
Object.freeze(input);
|
|
49
|
+
Object.freeze(cost);
|
|
50
|
+
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
...model,
|
|
53
|
+
input,
|
|
54
|
+
cost,
|
|
55
|
+
...(model.headers
|
|
56
|
+
? { headers: Object.freeze({ ...model.headers }) }
|
|
57
|
+
: {}),
|
|
58
|
+
...(model.compat
|
|
59
|
+
? { compat: Object.freeze({ ...model.compat }) }
|
|
60
|
+
: {}),
|
|
61
|
+
...(model.thinkingLevelMap
|
|
62
|
+
? {
|
|
63
|
+
thinkingLevelMap: Object.freeze({
|
|
64
|
+
...model.thinkingLevelMap,
|
|
65
|
+
}),
|
|
66
|
+
}
|
|
67
|
+
: {}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 作用:把 Runtime 的思考强度名称转成 Pi Agent Core 接受的名称。
|
|
72
|
+
// 调用:创建 Runtime 装配快照时对 profile 中的 `thinking` 调用。
|
|
73
|
+
// 原因:只有 `none` 与 Pi 的 `off` 命名不同,其他级别应保持原值。
|
|
74
|
+
function toPiThinkingLevel(
|
|
75
|
+
thinking: ThinkingEffort,
|
|
76
|
+
): ThinkingLevel {
|
|
77
|
+
return thinking === "none" ? "off" : thinking;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 作用:复制并冻结一个 Extension 配置的 manifest 和权限集合。
|
|
81
|
+
// 调用:创建 Runtime 装配快照时对每个已选 Extension 调用。
|
|
82
|
+
// 原因:权限数组会被 Host 和加载器共同读取,提交后不能再被原配置引用改写。
|
|
83
|
+
function freezeExtension(
|
|
84
|
+
extension: RuntimeExtensionConfig,
|
|
85
|
+
): RuntimeExtensionConfig {
|
|
86
|
+
const permissions = extension.manifest.permissions;
|
|
87
|
+
const contextPermissions = permissions?.context;
|
|
88
|
+
const context = extension.manifest.context?.map(
|
|
89
|
+
(definition) => Object.freeze({ ...definition }),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return Object.freeze({
|
|
93
|
+
source: extension.source,
|
|
94
|
+
manifest: Object.freeze({
|
|
95
|
+
name: extension.manifest.name,
|
|
96
|
+
version: extension.manifest.version,
|
|
97
|
+
...(extension.manifest.description
|
|
98
|
+
? { description: extension.manifest.description }
|
|
99
|
+
: {}),
|
|
100
|
+
...(permissions
|
|
101
|
+
? {
|
|
102
|
+
permissions: Object.freeze({
|
|
103
|
+
...(permissions.network
|
|
104
|
+
? {
|
|
105
|
+
network: Object.freeze([
|
|
106
|
+
...permissions.network,
|
|
107
|
+
]),
|
|
108
|
+
}
|
|
109
|
+
: {}),
|
|
110
|
+
...(permissions.workspace
|
|
111
|
+
? { workspace: permissions.workspace }
|
|
112
|
+
: {}),
|
|
113
|
+
...(contextPermissions
|
|
114
|
+
? {
|
|
115
|
+
context: Object.freeze({
|
|
116
|
+
...(contextPermissions.read
|
|
117
|
+
? {
|
|
118
|
+
read: Array.isArray(
|
|
119
|
+
contextPermissions.read,
|
|
120
|
+
)
|
|
121
|
+
? Object.freeze([
|
|
122
|
+
...contextPermissions.read,
|
|
123
|
+
])
|
|
124
|
+
: contextPermissions.read,
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
127
|
+
...(contextPermissions.write
|
|
128
|
+
? {
|
|
129
|
+
write: Array.isArray(
|
|
130
|
+
contextPermissions.write,
|
|
131
|
+
)
|
|
132
|
+
? Object.freeze([
|
|
133
|
+
...contextPermissions.write,
|
|
134
|
+
])
|
|
135
|
+
: contextPermissions.write,
|
|
136
|
+
}
|
|
137
|
+
: {}),
|
|
138
|
+
}),
|
|
139
|
+
}
|
|
140
|
+
: {}),
|
|
141
|
+
...(permissions.messages
|
|
142
|
+
? { messages: permissions.messages }
|
|
143
|
+
: {}),
|
|
144
|
+
...(permissions.session
|
|
145
|
+
? {
|
|
146
|
+
session: Object.freeze({
|
|
147
|
+
...permissions.session,
|
|
148
|
+
}),
|
|
149
|
+
}
|
|
150
|
+
: {}),
|
|
151
|
+
}),
|
|
152
|
+
}
|
|
153
|
+
: {}),
|
|
154
|
+
...(context
|
|
155
|
+
? { context: Object.freeze(context) }
|
|
156
|
+
: {}),
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 把一个已验证的 Runtime 候选配置生成不可变的 Pi 装配快照。
|
|
163
|
+
*
|
|
164
|
+
* @remarks
|
|
165
|
+
* RuntimeBuilder 在生成 `RuntimeSnapshot` 时调用,调用方应传入已完成业务校验的 profile、provider 和能力列表。
|
|
166
|
+
*
|
|
167
|
+
* 这里在提交边界统一复制和冻结数组及嵌套配置,避免调用方之后的变更穿透 Snapshot;不要改成直接保留输入引用。
|
|
168
|
+
*
|
|
169
|
+
* 核心术语见包入口 `index.ts`。
|
|
170
|
+
*/
|
|
171
|
+
export function createPiRuntimeAssembly(
|
|
172
|
+
input: PiRuntimeAssemblyInput,
|
|
173
|
+
): PiRuntimeAssembly {
|
|
174
|
+
const messages = [...(input.messages ?? [])];
|
|
175
|
+
const toolCandidates = input.toolCandidates.map(
|
|
176
|
+
(candidate) => Object.freeze({ ...candidate }),
|
|
177
|
+
);
|
|
178
|
+
const mcpServers = input.mcpServers.map(
|
|
179
|
+
(server) => Object.freeze({ ...server }),
|
|
180
|
+
);
|
|
181
|
+
const extensions = input.extensions.map(freezeExtension);
|
|
182
|
+
Object.freeze(messages);
|
|
183
|
+
Object.freeze(toolCandidates);
|
|
184
|
+
Object.freeze(mcpServers);
|
|
185
|
+
Object.freeze(extensions);
|
|
186
|
+
|
|
187
|
+
return Object.freeze({
|
|
188
|
+
model: freezeModel(resolvePiModel(input.provider, input.profile.model)),
|
|
189
|
+
thinkingLevel: toPiThinkingLevel(
|
|
190
|
+
input.profile.thinking,
|
|
191
|
+
),
|
|
192
|
+
systemPrompt: assembleSystemPrompt(
|
|
193
|
+
input.profile.systemPrompt,
|
|
194
|
+
),
|
|
195
|
+
messages,
|
|
196
|
+
toolCandidates,
|
|
197
|
+
mcpServers,
|
|
198
|
+
extensions,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { UserMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
|
|
4
|
+
export interface UIUserSidecar {
|
|
5
|
+
readonly metadata?: unknown;
|
|
6
|
+
readonly parts: ReadonlyArray<UIMessage["parts"][number]>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function recordOf(value: unknown): Record<string, unknown> {
|
|
10
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
11
|
+
? value as Record<string, unknown>
|
|
12
|
+
: {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function timestampOf(message: UIMessage): number {
|
|
16
|
+
const createdAt = recordOf(message.metadata).createdAt;
|
|
17
|
+
return typeof createdAt === "number" ? createdAt : Date.now();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function attachmentText(
|
|
21
|
+
part: Extract<UIMessage["parts"][number], { type: "file" }>,
|
|
22
|
+
): string {
|
|
23
|
+
const name = part.filename?.trim() || "attachment";
|
|
24
|
+
return `[Attachment: ${name} (${part.mediaType}) ${part.url}]`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function userContent(
|
|
28
|
+
parts: ReadonlyArray<UIMessage["parts"][number]>,
|
|
29
|
+
): UserMessage["content"] {
|
|
30
|
+
const content: Exclude<UserMessage["content"], string> = [];
|
|
31
|
+
for (const part of parts) {
|
|
32
|
+
if (part.type === "text" && part.text.trim()) {
|
|
33
|
+
content.push({ type: "text", text: part.text });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (part.type !== "file") continue;
|
|
37
|
+
const inlineImage = /^data:([^;,]+);base64,(.+)$/s.exec(part.url);
|
|
38
|
+
if (inlineImage && part.mediaType.startsWith("image/")) {
|
|
39
|
+
content.push({
|
|
40
|
+
type: "image",
|
|
41
|
+
mimeType: part.mediaType,
|
|
42
|
+
data: inlineImage[2]!,
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
content.push({ type: "text", text: attachmentText(part) });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (content.length === 0) throw new Error("User message content is required");
|
|
49
|
+
return content;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Convert an official user UIMessage into canonical Pi model input. */
|
|
53
|
+
export function uiUserMessageToPi(
|
|
54
|
+
message: UIMessage & { readonly role: "user" },
|
|
55
|
+
): UserMessage {
|
|
56
|
+
return {
|
|
57
|
+
role: "user",
|
|
58
|
+
content: userContent(message.parts),
|
|
59
|
+
timestamp: timestampOf(message),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Keep browser-only user metadata and parts outside canonical model input. */
|
|
64
|
+
export function captureUIUserSidecar(
|
|
65
|
+
message: UIMessage & { readonly role: "user" },
|
|
66
|
+
): UIUserSidecar {
|
|
67
|
+
return {
|
|
68
|
+
...(message.metadata === undefined ? {} : { metadata: message.metadata }),
|
|
69
|
+
parts: message.parts.filter(
|
|
70
|
+
(part) => part.type === "text" || part.type === "file",
|
|
71
|
+
),
|
|
72
|
+
};
|
|
73
|
+
}
|