Initial commit
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { getApiProfile, profileToLlmConfig } from "../config/api-profiles.js";
|
||||
import {
|
||||
OpenAiCompatibleProvider,
|
||||
type LlmProvider,
|
||||
} from "../llm/client.js";
|
||||
import { PresetLlmProvider } from "../llm/preset-wrapper.js";
|
||||
import { resolveActivePreset } from "../preset/store.js";
|
||||
import { loadAppSettings } from "../config/settings.js";
|
||||
import {
|
||||
TokenTrackingProvider,
|
||||
type LlmTrackingContext,
|
||||
} from "../llm/token-tracker.js";
|
||||
import type { ParsedWorkerSkill, SkillWorkerLlmBindings } from "./types.js";
|
||||
|
||||
type LlmTrackingRef = { current: LlmTrackingContext };
|
||||
|
||||
/** 按 ApiProfile.id 构建 LLM;找不到 profile 时回退 fallback */
|
||||
export function createLlmForProfileId(
|
||||
profileId: string,
|
||||
fallback: LlmProvider,
|
||||
trackingRef?: LlmTrackingRef,
|
||||
): LlmProvider {
|
||||
const profile = getApiProfile(profileId);
|
||||
if (!profile?.apiKey?.trim()) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
let inner: LlmProvider = new OpenAiCompatibleProvider(
|
||||
profileToLlmConfig(profile),
|
||||
);
|
||||
const preset = resolveActivePreset(loadAppSettings().activePresetId);
|
||||
if (preset) {
|
||||
inner = new PresetLlmProvider(inner, () =>
|
||||
resolveActivePreset(loadAppSettings().activePresetId),
|
||||
);
|
||||
}
|
||||
if (!trackingRef) return inner;
|
||||
return new TokenTrackingProvider(inner, () => trackingRef.current);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 worker 应使用的 LLM。
|
||||
*
|
||||
* 优先级:
|
||||
* 1. worker SKILL frontmatter `llmProfileId`
|
||||
* 2. llm-bindings.yaml `workers[id].byRole[roleId]`(roleId 来自 slots.世界.当前角色.id)
|
||||
* 3. llm-bindings.yaml `workers[id].profileId`
|
||||
* 4. fallback(会话默认 profile,与总管相同)
|
||||
*/
|
||||
export function resolveWorkerLlmProvider(options: {
|
||||
worker: ParsedWorkerSkill;
|
||||
bindings?: SkillWorkerLlmBindings;
|
||||
slots: Record<string, unknown>;
|
||||
fallbackLlm: LlmProvider;
|
||||
trackingRef?: LlmTrackingRef;
|
||||
}): LlmProvider {
|
||||
const { worker, bindings, slots, fallbackLlm, trackingRef } = options;
|
||||
|
||||
if (worker.llmProfileId?.trim()) {
|
||||
return createLlmForProfileId(
|
||||
worker.llmProfileId.trim(),
|
||||
fallbackLlm,
|
||||
trackingRef,
|
||||
);
|
||||
}
|
||||
|
||||
const workerBinding = bindings?.workers?.[worker.id];
|
||||
if (workerBinding?.byRole) {
|
||||
const roleId = slotString(slots, "世界.当前角色.id");
|
||||
if (roleId && workerBinding.byRole[roleId]?.trim()) {
|
||||
return createLlmForProfileId(
|
||||
workerBinding.byRole[roleId].trim(),
|
||||
fallbackLlm,
|
||||
trackingRef,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (workerBinding?.profileId?.trim()) {
|
||||
return createLlmForProfileId(
|
||||
workerBinding.profileId.trim(),
|
||||
fallbackLlm,
|
||||
trackingRef,
|
||||
);
|
||||
}
|
||||
|
||||
if (bindings?.defaultProfileId?.trim()) {
|
||||
return createLlmForProfileId(
|
||||
bindings.defaultProfileId.trim(),
|
||||
fallbackLlm,
|
||||
trackingRef,
|
||||
);
|
||||
}
|
||||
|
||||
return fallbackLlm;
|
||||
}
|
||||
|
||||
function slotString(slots: Record<string, unknown>, key: string): string | undefined {
|
||||
const val = slots[key];
|
||||
return typeof val === "string" && val.trim() ? val.trim() : undefined;
|
||||
}
|
||||
|
||||
/** 角色 tag:仅用户可见 */
|
||||
export const ROLE_USER_ONLY_SUFFIXES = [".思考", ".推理.候选"] as const;
|
||||
|
||||
/** 角色 tag:对其余角色 agent 可见(经 world-engine 公开) */
|
||||
export const ROLE_AGENT_VISIBLE_SUFFIXES = [".行动", ".行动.候选"] as const;
|
||||
|
||||
export function isRoleUserOnlyTag(tag: string): boolean {
|
||||
return ROLE_USER_ONLY_SUFFIXES.some((s) => tag.endsWith(s));
|
||||
}
|
||||
|
||||
export function isRoleAgentVisibleTag(tag: string): boolean {
|
||||
return ROLE_AGENT_VISIBLE_SUFFIXES.some((s) => tag.endsWith(s));
|
||||
}
|
||||
|
||||
/**
|
||||
* 供 executor:角色 worker 只能看到当前角色的私有 tag。
|
||||
* 其他角色的思考永不可见;对方行动仅经 world-engine 分发后的可见信息/公开叙述获知。
|
||||
*/
|
||||
export function filterInputsForRolePerspective(
|
||||
inputs: Record<string, string>,
|
||||
slots: Record<string, unknown>,
|
||||
): Record<string, string> {
|
||||
const roleId = slotString(slots, "世界.当前角色.id");
|
||||
if (!roleId) return inputs;
|
||||
|
||||
const filtered: Record<string, string> = {};
|
||||
for (const [tag, content] of Object.entries(inputs)) {
|
||||
const rolePrefix = `角色.${roleId}.`;
|
||||
const otherRoleMatch = tag.match(/^角色\.([^.]+)\./);
|
||||
if (otherRoleMatch && otherRoleMatch[1] !== roleId) {
|
||||
continue;
|
||||
}
|
||||
if (tag.startsWith("角色.") && !tag.startsWith(rolePrefix)) {
|
||||
continue;
|
||||
}
|
||||
if (tag.startsWith(rolePrefix) && isRoleUserOnlyTag(tag)) {
|
||||
continue;
|
||||
}
|
||||
filtered[tag] = content;
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
export type { LlmTrackingContext };
|
||||
Reference in New Issue
Block a user