feat(agent): capability registry, Pi AgentRuntimePort, fake model provider and redaction guard (W1-5)
- CapabilityRegistry: descriptor(id/version/schema/scope/risk/idempotent/preview/replay), invoke = scope limit -> input schema -> server-side authorize -> idempotency key -> handler -> output schema -> audit; capability id <-> tool name mapping (download.submit <-> download__submit) - PiAgentRuntime (pi-agent-core AgentHarness 0.87.1): whitelist tools + before_tool re-check, redacted event stream, JSONL session at sessionDir/<runId>.jsonl, resume/abort/compact, tool-call/duration/token/cost budgets -> abort + run_failed(budget) - AgentSettings -> pi-ai createProvider (anthropic / openai-compatible), compaction mapping - FakeModelProvider + createFakeRuntime (scripted, transcript-derived, survives crash/resume) - buildSystemPrompt, redactForModel; 27 tests incl. SIGKILL + resume Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
1262340856
commit
9c881e737f
24 files changed
+2419
-8
No files matched your search
@@ -0,0 +1,68 @@
|
||||
import type { CompactionSettings } from "@earendil-works/pi-agent-core";
|
||||
import { type Api, createModels, createProvider, type Model, type MutableModels } from "@earendil-works/pi-ai";
|
||||
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
||||
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
||||
import type { ResolvedAgentSettings } from "../settings";
|
||||
|
||||
/** Pi 侧需要的模型装配结果(仅在 src/pi/* 内部流转) */
|
||||
export interface ResolvedPiModel {
|
||||
models: MutableModels;
|
||||
model: Model<Api>;
|
||||
}
|
||||
|
||||
export type PiModelResolver = (settings: ResolvedAgentSettings) => ResolvedPiModel;
|
||||
|
||||
const ANTHROPIC_DEFAULT_BASE = "https://api.anthropic.com";
|
||||
const PROVIDER_ID = { anthropic: "umt-anthropic", "openai-compatible": "umt-openai-compatible" } as const;
|
||||
|
||||
/**
|
||||
* 设置 → pi-ai createProvider。apiKey 只在请求时经 auth.resolve 交给 SDK,不读环境变量。
|
||||
* Anthropic baseUrl 不带 /v1;OpenAI 兼容 baseUrl 带 /v1(与 spike 抓包结论一致)。
|
||||
*/
|
||||
export function resolveModelFromSettings(settings: ResolvedAgentSettings): ResolvedPiModel {
|
||||
const providerId = PROVIDER_ID[settings.provider];
|
||||
const common = {
|
||||
id: settings.model,
|
||||
name: settings.model,
|
||||
provider: providerId,
|
||||
reasoning: settings.thinkingLevel !== "off",
|
||||
input: ["text"] as ("text" | "image")[],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: settings.contextBudgetTokens,
|
||||
maxTokens: Math.min(settings.maxOutputTokens, Math.floor(settings.contextBudgetTokens / 2)),
|
||||
};
|
||||
const apiKey = settings.apiKey;
|
||||
const auth = { apiKey: { name: "umt-secret", resolve: async () => ({ auth: { apiKey } }) } };
|
||||
const models = createModels();
|
||||
let model: Model<Api>;
|
||||
if (settings.provider === "anthropic") {
|
||||
const m: Model<"anthropic-messages"> = {
|
||||
...common,
|
||||
api: "anthropic-messages",
|
||||
baseUrl: (settings.baseUrl ?? ANTHROPIC_DEFAULT_BASE).replace(/\/+$/, "").replace(/\/v1$/, ""),
|
||||
};
|
||||
models.setProvider(createProvider({ id: providerId, auth, models: [m], api: anthropicMessagesApi() }));
|
||||
model = m;
|
||||
} else {
|
||||
if (!settings.baseUrl) throw new Error("openai-compatible 上游必须配置 baseUrl(带 /v1)");
|
||||
const m: Model<"openai-completions"> = {
|
||||
...common,
|
||||
api: "openai-completions",
|
||||
baseUrl: settings.baseUrl.replace(/\/+$/, ""),
|
||||
compat: { supportsDeveloperRole: false },
|
||||
};
|
||||
models.setProvider(createProvider({ id: providerId, auth, models: [m], api: openAICompletionsApi() }));
|
||||
model = m;
|
||||
}
|
||||
return { models, model };
|
||||
}
|
||||
|
||||
/** 压缩设置:threshold 模式下占用达到 threshold 比例时触发;manual 只允许手动 compact() */
|
||||
export function compactionFromSettings(settings: ResolvedAgentSettings, contextWindow: number): CompactionSettings {
|
||||
const reserveTokens = Math.max(256, Math.round(contextWindow * (1 - settings.compaction.threshold)));
|
||||
return {
|
||||
enabled: settings.compaction.mode === "threshold",
|
||||
reserveTokens,
|
||||
keepRecentTokens: Math.max(256, Math.round(contextWindow * 0.25)),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user