Files
UHD_Media_Tool/packages/agent/src/pi/model.ts
T
rootandClaude Opus 5.5 003815591c feat(agent): 运行时从 pi-agent-core 0.99.2 迁到 pi-durable 1.1.0 + pi-ai 1.1.0
- PiAgentRuntime 改用 pi-durable Harness:每个 run 一个 JSONL 存储目录 sessionDir/<runId>/ + root 对话;
  系统提示 / 能力工具 / 白名单与预算钩子装在每次运行自己的扩展里;watchEvents 翻译成 AgentRunEvent
- 挂起(工具内 close)立即封口,调用保持未完成,重开后 resume 重放;replayGate 用调用级 memo
- 旧格式 <runId>.jsonl 打开时抛 LegacySessionError;skills 加载与 <skill> 块格式内置(源自 pi-agent-core,MIT)
- tool_call_end 新增可选 durationMs;用量取 pi.usage 合计(整段会话总量)
- Azure provider 改名 azure,迁移 0093 改存量;移除 pi-agent-core,锁 pi-durable/pi-ai/chord 1.1.0
- 文档:ADR-003、pi-providers、runbook、agent README、CHANGELOG

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-08 13:42:36 +08:00

78 lines
3.6 KiB
TypeScript

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 { CompactionPolicy } from "@earendil-works/pi-durable";
import type { ResolvedAgentSettings } from "../settings.ts";
/** Pi 侧需要的模型装配结果(仅在 src/pi/* 内部流转) */
export interface ResolvedPiModel {
models: MutableModels;
model: Model<Api>;
/** 夹到模型支持范围后的思考档位;缺省用设置值 */
thinkingLevel?: ResolvedAgentSettings["thinkingLevel"];
}
export type PiModelResolver = (settings: ResolvedAgentSettings) => ResolvedPiModel | Promise<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 {
if (settings.provider !== "anthropic" && settings.provider !== "openai-compatible")
throw new Error(`provider ${settings.provider} 需要按模型服务装配(PiAgentRuntimeOptions.upstream)`);
if (!settings.apiKey) throw new Error("未配置模型服务密钥");
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()。
* backgroundTokens=0:不做 pi-durable 的后台提前压缩,行为与 pi-agent-core 0.99 的阈值压缩一致。
*/
export function compactionFromSettings(settings: ResolvedAgentSettings, contextWindow: number): CompactionPolicy {
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)),
backgroundTokens: 0,
};
}