





















@@ -1,5 +1,11 @@
1+import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
12import { AcpRuntimeError, withAcpRuntimeErrorBoundary } from "../runtime/errors.js";
2-import type { AcpRuntime, AcpRuntimeCapabilities, AcpRuntimeHandle } from "../runtime/types.js";
3+import type {
4+AcpRuntime,
5+AcpRuntimeCapabilities,
6+AcpRuntimeHandle,
7+AcpRuntimeStatus,
8+} from "../runtime/types.js";
39import type { SessionAcpMeta } from "./manager.types.js";
410import { createUnsupportedControlError } from "./manager.utils.js";
511import type { CachedRuntimeState } from "./runtime-cache.js";
@@ -10,9 +16,39 @@ import {
1016resolveRuntimeOptionsFromMeta,
1117} from "./runtime-options.js";
121819+function asRecord(value: unknown): Record<string, unknown> | null {
20+return value && typeof value === "object" && !Array.isArray(value)
21+ ? (value as Record<string, unknown>)
22+ : null;
23+}
24+25+function extractConfigOptionKeys(value: unknown): string[] {
26+if (!Array.isArray(value)) {
27+return [];
28+}
29+return value
30+.map((entry) => {
31+if (typeof entry === "string") {
32+return normalizeText(entry);
33+}
34+const record = asRecord(entry);
35+return normalizeText(record?.id ?? record?.key);
36+})
37+.filter(Boolean) as string[];
38+}
39+40+function extractRuntimeStatusConfigOptionKeys(status: AcpRuntimeStatus | undefined): string[] {
41+const details = asRecord(status?.details);
42+return [
43+ ...extractConfigOptionKeys(details?.configOptions),
44+ ...extractConfigOptionKeys(details?.config_options),
45+];
46+}
47+1348export async function resolveManagerRuntimeCapabilities(params: {
1449runtime: AcpRuntime;
1550handle: AcpRuntimeHandle;
51+includeStatusConfigOptionKeys?: boolean;
1652}): Promise<AcpRuntimeCapabilities> {
1753let reported: AcpRuntimeCapabilities | undefined;
1854if (params.runtime.getCapabilities) {
@@ -32,12 +68,30 @@ export async function resolveManagerRuntimeCapabilities(params: {
3268if (params.runtime.getStatus) {
3369controls.add("session/status");
3470}
35-const normalizedKeys = (reported?.configOptionKeys ?? [])
36-.map((entry) => normalizeText(entry))
37-.filter(Boolean) as string[];
71+const normalizedKeys = new Set(
72+(reported?.configOptionKeys ?? [])
73+.map((entry) => normalizeText(entry))
74+.filter(Boolean) as string[],
75+);
76+if (
77+normalizedKeys.size === 0 &&
78+params.includeStatusConfigOptionKeys &&
79+params.runtime.getStatus
80+) {
81+try {
82+const status = await params.runtime.getStatus({ handle: params.handle });
83+for (const key of extractRuntimeStatusConfigOptionKeys(status)) {
84+normalizedKeys.add(key);
85+}
86+} catch {
87+// Status-derived option keys are an optional refinement. Keep the
88+// capability result usable for runtimes that expose controls but cannot
89+// answer status before a turn.
90+}
91+}
3892return {
3993controls: [...controls].toSorted(),
40- ...(normalizedKeys.length > 0 ? { configOptionKeys: normalizedKeys } : {}),
94+ ...(normalizedKeys.size > 0 ? { configOptionKeys: [...normalizedKeys] } : {}),
4195};
4296}
4397@@ -55,17 +109,19 @@ export async function applyManagerRuntimeControls(params: {
55109return;
56110}
57111112+const needsConfigOptionKeys = buildRuntimeConfigOptionPairs(options).length > 0;
58113const capabilities = await resolveManagerRuntimeCapabilities({
59114runtime: params.runtime,
60115handle: params.handle,
116+includeStatusConfigOptionKeys: needsConfigOptionKeys,
61117});
62118const backend = params.handle.backend || params.meta.backend;
63119const runtimeMode = normalizeText(options.runtimeMode);
64-const configOptions = buildRuntimeConfigOptionPairs(options);
120+const configOptions = buildRuntimeConfigOptionPairs(options, capabilities.configOptionKeys);
65121const advertisedKeys = new Set(
66122(capabilities.configOptionKeys ?? [])
67-.map((entry) => normalizeText(entry))
68-.filter(Boolean) as string[],
123+.map((entry) => normalizeLowercaseStringOrEmpty(entry))
124+.filter(Boolean),
69125);
7012671127await withAcpRuntimeErrorBoundary({
@@ -94,7 +150,10 @@ export async function applyManagerRuntimeControls(params: {
94150});
95151}
96152for (const [key, value] of configOptions) {
97-if (advertisedKeys.size > 0 && !advertisedKeys.has(key)) {
153+if (
154+advertisedKeys.size > 0 &&
155+!advertisedKeys.has(normalizeLowercaseStringOrEmpty(key))
156+) {
98157throw new AcpRuntimeError(
99158"ACP_BACKEND_UNSUPPORTED_CONTROL",
100159`ACP backend "${backend}" does not accept config key "${key}".`,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。