



















@@ -0,0 +1,146 @@
1+import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry";
2+import { resolveProviderRequestHeaders } from "openclaw/plugin-sdk/provider-http";
3+import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
4+5+const KILOCODE_FEATURE_HEADER = "X-KILOCODE-FEATURE";
6+const KILOCODE_FEATURE_DEFAULT = "openclaw";
7+const KILOCODE_FEATURE_ENV_VAR = "KILOCODE_FEATURE";
8+9+type ThinkLevel = NonNullable<ProviderWrapStreamFnContext["thinkingLevel"]>;
10+type ProviderStreamFn = NonNullable<ProviderWrapStreamFnContext["streamFn"]>;
11+type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
12+13+function resolveKilocodeAppHeaders(): Record<string, string> {
14+const feature = process.env[KILOCODE_FEATURE_ENV_VAR]?.trim() || KILOCODE_FEATURE_DEFAULT;
15+return { [KILOCODE_FEATURE_HEADER]: feature };
16+}
17+18+function mapThinkingLevelToReasoningEffort(thinkingLevel: ThinkLevel): ReasoningEffort {
19+if (thinkingLevel === "off") {
20+return "none";
21+}
22+if (thinkingLevel === "adaptive") {
23+return "medium";
24+}
25+if (thinkingLevel === "max") {
26+return "xhigh";
27+}
28+return thinkingLevel;
29+}
30+31+function normalizeKilocodeReasoningPayload(
32+payloadObj: Record<string, unknown>,
33+thinkingLevel?: ThinkLevel,
34+): void {
35+delete payloadObj.reasoning_effort;
36+if (!thinkingLevel || thinkingLevel === "off") {
37+return;
38+}
39+40+const existingReasoning = payloadObj.reasoning;
41+if (
42+existingReasoning &&
43+typeof existingReasoning === "object" &&
44+!Array.isArray(existingReasoning)
45+) {
46+const reasoningObj = existingReasoning as Record<string, unknown>;
47+if (!("max_tokens" in reasoningObj) && !("effort" in reasoningObj)) {
48+reasoningObj.effort = mapThinkingLevelToReasoningEffort(thinkingLevel);
49+}
50+} else if (!existingReasoning) {
51+payloadObj.reasoning = {
52+effort: mapThinkingLevelToReasoningEffort(thinkingLevel),
53+};
54+}
55+}
56+57+function normalizeKilocodeStopPayload(payloadObj: Record<string, unknown>): void {
58+if (typeof payloadObj.stop === "string") {
59+payloadObj.stop = [payloadObj.stop];
60+}
61+}
62+63+function asRecord(value: unknown): Record<string, unknown> | undefined {
64+return value && typeof value === "object" && !Array.isArray(value)
65+ ? (value as Record<string, unknown>)
66+ : undefined;
67+}
68+69+function normalizeKilocodeStopAfterCaller(
70+value: unknown,
71+fallbackPayload: Record<string, unknown> | undefined,
72+): unknown {
73+const replacementPayload = asRecord(value);
74+if (replacementPayload) {
75+normalizeKilocodeStopPayload(replacementPayload);
76+return value;
77+}
78+if (fallbackPayload) {
79+normalizeKilocodeStopPayload(fallbackPayload);
80+}
81+return value;
82+}
83+84+function isProxyReasoningUnsupported(modelId: string): boolean {
85+const trimmed = normalizeOptionalLowercaseString(modelId);
86+const slashIndex = trimmed?.indexOf("/") ?? -1;
87+return slashIndex > 0 && trimmed?.slice(0, slashIndex) === "x-ai";
88+}
89+90+function resolveKilocodeThinkingLevel(ctx: ProviderWrapStreamFnContext): ThinkLevel | undefined {
91+if (ctx.modelId === "kilo/auto" || isProxyReasoningUnsupported(ctx.modelId)) {
92+return undefined;
93+}
94+return ctx.thinkingLevel;
95+}
96+97+export function createKilocodeStreamWrapper(
98+baseStreamFn: ProviderWrapStreamFnContext["streamFn"],
99+thinkingLevel?: ThinkLevel,
100+): ProviderWrapStreamFnContext["streamFn"] {
101+if (!baseStreamFn) {
102+return undefined;
103+}
104+const underlying = baseStreamFn;
105+return (model, context, options) => {
106+const originalOnPayload = options?.onPayload;
107+const headers = resolveProviderRequestHeaders({
108+provider: typeof model.provider === "string" ? model.provider : "kilocode",
109+api: model.api,
110+baseUrl: typeof model.baseUrl === "string" ? model.baseUrl : undefined,
111+capability: "llm",
112+transport: "stream",
113+callerHeaders: options?.headers,
114+defaultHeaders: resolveKilocodeAppHeaders(),
115+precedence: "defaults-win",
116+});
117+return underlying(model, context, {
118+ ...options,
119+ headers,
120+onPayload(payload, payloadModel) {
121+const payloadObj = asRecord(payload);
122+if (payloadObj) {
123+// Keep Kilo thinking defaults overrideable by later caller/config payload hooks.
124+normalizeKilocodeReasoningPayload(payloadObj, thinkingLevel);
125+}
126+127+const result = originalOnPayload?.(payload, payloadModel);
128+if (result && typeof (result as Promise<unknown>).then === "function") {
129+return Promise.resolve(result).then((resolved) =>
130+normalizeKilocodeStopAfterCaller(resolved, payloadObj),
131+);
132+}
133+return normalizeKilocodeStopAfterCaller(result, payloadObj);
134+},
135+});
136+};
137+}
138+139+export function wrapKilocodeProviderStream(
140+ctx: ProviderWrapStreamFnContext,
141+): ProviderStreamFn | undefined {
142+if (normalizeOptionalLowercaseString(ctx.provider) !== "kilocode") {
143+return undefined;
144+}
145+return createKilocodeStreamWrapper(ctx.streamFn, resolveKilocodeThinkingLevel(ctx));
146+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。