mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-01 15:12:18 +01:00
Add the pinned Kimi Code runtime, native instruction and model admission, scoped credentials, and pending-candidate recovery contracts. Retain current master release attestations and provider-pack compatibility. Co-Authored-By: Paperclip <noreply@paperclip.ing>
161 lines
12 KiB
JavaScript
161 lines
12 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import { pathToFileURL } from "node:url";
|
|
import { KIMI_INSTRUCTION_RUNTIME_SOURCE } from "./kimi-instructions-runtime.mjs";
|
|
|
|
export const KIMI_RUNTIME_PATCH = Object.freeze({
|
|
version: "paperclip-kimi-2.3.3-instructions-v6",
|
|
vendorSha256: "f18b711e22947d473175126d0c2da437d641187963a3bc300d66e183060703e3",
|
|
patchedSha256: "79980aa95c54f325a05cbdf883f74443705db28f3e412a1b5ae22929d939eb66",
|
|
});
|
|
|
|
const sha256 = value => createHash("sha256").update(value).digest("hex");
|
|
|
|
export function replaceKimiPatchAnchor(source, before, after) {
|
|
if (source.split(before).length !== 2) throw new Error("Kimi runtime patch requires exactly one source anchor");
|
|
return source.replace(before, () => after);
|
|
}
|
|
|
|
export function patchKimiRuntimeSource(input) {
|
|
if (sha256(input) !== KIMI_RUNTIME_PATCH.vendorSha256) throw new Error("Kimi runtime patch input digest mismatch");
|
|
let source = input;
|
|
const replace = (before, after) => { source = replaceKimiPatchAnchor(source, before, after); };
|
|
replace("#!/usr/bin/env node\n", `#!/usr/bin/env node
|
|
import { createHash as paperclipKimiCreateHash } from "node:crypto";
|
|
let paperclipKimiInstructionSnapshot;
|
|
function paperclipKimiInstructionState() {
|
|
if (paperclipKimiInstructionSnapshot) return paperclipKimiInstructionSnapshot;
|
|
const raw = process.env.PAPERCLIP_KIMI_INSTRUCTIONS;
|
|
delete process.env.PAPERCLIP_KIMI_INSTRUCTIONS;
|
|
return paperclipKimiInstructionSnapshot = (${KIMI_INSTRUCTION_RUNTIME_SOURCE})(raw, {
|
|
createHash: paperclipKimiCreateHash, byteLength: value => Buffer.byteLength(value, "utf8")
|
|
});
|
|
}
|
|
function paperclipKimiChildEnvironment(overrides) {
|
|
const environment = { ...process.env, ...overrides };
|
|
delete environment.KIMI_API_KEY;
|
|
delete environment.PAPERCLIP_KIMI_INSTRUCTIONS;
|
|
return environment;
|
|
}
|
|
`);
|
|
replace('function buildEnv(overrides) {\n\tif (overrides === void 0) return;\n\treturn {\n\t\t...process.env,\n\t\t...overrides\n\t};\n}',
|
|
'function buildEnv(overrides) {\n\treturn paperclipKimiChildEnvironment(overrides);\n}');
|
|
replace('env: globalThis.process.env', 'env: paperclipKimiChildEnvironment()');
|
|
replace('\t\tbindTerminalCapability(terminal) {\n\t\t\tthis._terminalEnabled = terminal === true;\n\t\t}',
|
|
'\t\tbindTerminalCapability(terminal) {\n\t\t\tthis._terminalEnabled = false;\n\t\t}');
|
|
replace("\t\tget systemPrompt() {\n\t\t\treturn this.profileState.systemPrompt;\n\t\t}",
|
|
"\t\tget systemPrompt() {\n\t\t\treturn paperclipKimiInstructionState().append(this.profileState.systemPrompt);\n\t\t}");
|
|
replace("async function runAcpServerWithStream(stream, opts = {}) {\n",
|
|
"async function runAcpServerWithStream(stream, opts = {}) {\n\tpaperclipKimiInstructionState();\n");
|
|
replace('\t\tmerged() {\n\t\t\treturn {\n\t\t\t\t...Object.fromEntries(this.pluginServers),\n\t\t\t\t...Object.fromEntries(this.fileServers)\n\t\t\t};\n\t\t}',
|
|
'\t\tmerged() {\n\t\t\treturn {};\n\t\t}');
|
|
replace('\tawait pushFirstExisting$1(roots, USER_GENERIC_DIRS$1, osHomeDir, "user");', '');
|
|
replace('async function projectRoots(workDir, options = {}) {\n', 'async function projectRoots(workDir, options = {}) {\n\treturn [];\n');
|
|
replace('async function projectAgentRoots(fs, workDir, warn) {\n', 'async function projectAgentRoots(fs, workDir, warn) {\n\treturn [];\n');
|
|
replace('async function userAgentRoots(fs, homeDir, osHomeDir, warn) {\n', 'async function userAgentRoots(fs, homeDir, osHomeDir, warn) {\n\treturn [];\n');
|
|
replace('this.byEvent = indexHooks([...configured ?? [], ...pluginHooks]);', 'this.byEvent = new Map();');
|
|
replace('\t\tasync evaluate(context) {\n\t\t\tfor (const policy of this.policies) {',
|
|
'\t\tasync evaluate(context) {\n\t\t\treturn { policyName: "paperclip-runner", result: { kind: "ask" } };\n\t\t\tfor (const policy of this.policies) {');
|
|
replace('if (req.display.kind !== "plan_review") return CANONICAL_OPTIONS;',
|
|
'if (req.display.kind !== "plan_review") return CANONICAL_OPTIONS.filter(option => option.kind !== "allow_always");');
|
|
replace('\t\t\t\ttoolName: name,\n\t\t\t\taction,\n\t\t\t\tdisplay\n\t\t\t};\n\t\t\tconst approvalContext = {',
|
|
'\t\t\t\ttoolName: name,\n\t\t\t\ttoolInput: context.args,\n\t\t\t\taction,\n\t\t\t\tdisplay\n\t\t\t};\n\t\t\tconst approvalContext = {');
|
|
replace('\t\ttitle: req.toolName,\n\t\tcontent\n', '\t\ttitle: req.toolName,\n\t\tkind: inferToolKind(req.toolName),\n\t\trawInput: req.toolInput,\n\t\tcontent\n');
|
|
const bindRuntime = '\t\t\thandle.accessor.get(IAgentLifecycleService).handleOf(agentContext.agentId).accessor.get(IAgentRuntimeBindingService).switch(runtimeId);';
|
|
replace(bindRuntime, `${bindRuntime}
|
|
\t\t\tconst paperclipProfile = handle.accessor.get(IAgentLifecycleService).handleOf(agentContext.agentId).accessor.get(IAgentProfileService);
|
|
\t\t\treturn paperclipKimiInstructionState().receipt(sessionId, paperclipProfile.getSystemPrompt());`);
|
|
replace("\t\t\tawait this.bindSessionRuntime?.(sessionId);\n",
|
|
"\t\t\tconst paperclipReceipt = await this.bindSessionRuntime?.(sessionId);\n\t\t\tif (!paperclipReceipt || paperclipReceipt.sessionId !== sessionId) throw new Error(\"Kimi native instruction receipt is missing\");\n\t\t\tthis.paperclipInstructionReceipts ??= new Map();\n\t\t\tthis.paperclipInstructionReceipts.set(sessionId, paperclipReceipt);\n");
|
|
replace("\t\t\t\t...await this.activateSession(meta.id)\n",
|
|
"\t\t\t\t...await this.activateSession(meta.id),\n\t\t\t\t_meta: { paperclipKimiInstructions: this.paperclipInstructionReceipts.get(meta.id) }\n");
|
|
const lifecycleState = "\t\t\t\tconfigOptions: await acpSession.configOptions(),\n\t\t\t\tmodes: acpSession.modeState()\n\t\t\t};\n\t\t}\n\t\tasync ";
|
|
replace(`${lifecycleState}resumeSession(params)`,
|
|
`${lifecycleState.replace("modes: acpSession.modeState()", "modes: acpSession.modeState(),\n\t\t\t\t_meta: { paperclipKimiInstructions: this.paperclipInstructionReceipts.get(params.sessionId) }")}resumeSession(params)`);
|
|
replace(`${lifecycleState}listSessions(params)`,
|
|
`${lifecycleState.replace("modes: acpSession.modeState()", "modes: acpSession.modeState(),\n\t\t\t\t_meta: { paperclipKimiInstructions: this.paperclipInstructionReceipts.get(params.sessionId) }")}listSessions(params)`);
|
|
replace('\t\t\t\tdriver.resolve({ stopReason: turnEndReasonToStopReason(event.reason, error) });',
|
|
'\t\t\t\tif (event.reason === "failed") {\n\t\t\t\t\tdriver.reject(RequestError$1.internalError({ schema: "paperclip.kimi.provider-error.v1", kind: "provider_failed" }, "Kimi provider turn failed"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdriver.resolve({ stopReason: turnEndReasonToStopReason(event.reason, error) });');
|
|
const stepSchema = 'object({ type: string(), turnId: number$1().int().nonnegative(), step: number$1().int().positive(), stepId: string().min(1).max(256).optional(), finishReason: string().max(32).optional(), reason: string().max(32).optional() })';
|
|
replace('\tagentEvents = {\n\t\t"turn.started": {', `\tagentEvents = {
|
|
\t\t"turn.step.started": { kind: "stream", name: "events", type: "turn.step.started", schema: ${stepSchema} },
|
|
\t\t"turn.step.completed": { kind: "stream", name: "events", type: "turn.step.completed", schema: ${stepSchema} },
|
|
\t\t"turn.step.interrupted": { kind: "stream", name: "events", type: "turn.step.interrupted", schema: ${stepSchema} },
|
|
\t\t"turn.started": {`);
|
|
replace('\t\t\tthis.subscriptions.push(events.on("assistant.delta", (event) => {', `\t\t\tthis.subscriptions.push(
|
|
\t\t\t\tevents.on("turn.step.started", event => this.dispatchTurnEvent(event.turnId, () => this.paperclipKimiStep(event, "start"))),
|
|
\t\t\t\tevents.on("turn.step.completed", event => this.dispatchTurnEvent(event.turnId, () => this.paperclipKimiStep(event, "end"))),
|
|
\t\t\t\tevents.on("turn.step.interrupted", event => this.dispatchTurnEvent(event.turnId, () => this.paperclipKimiStep(event, "interrupted"))),
|
|
\t\t\t\tevents.on("assistant.delta", (event) => {`);
|
|
replace('\t\tonAssistantDelta(event) {\n\t\t\tif (this.driverFor(event.turnId) === void 0) return;\n\t\t\tthis.emit(assistantDeltaToSessionUpdate(this.sessionId, event));\n\t\t}', `\t\tpaperclipKimiMessageFailure(driver) {
|
|
\t\t\tthis.settleDriver(driver, () => driver.reject(RequestError$1.internalError({ schema: "paperclip.kimi.provider-error.v1", kind: "message_boundary_invalid" }, "Kimi native message boundary is invalid")));
|
|
\t\t}
|
|
\t\tpaperclipKimiChunk(driver, text, kind, thought = false) {
|
|
\t\t\tthis.emit({ sessionId: this.sessionId, update: {
|
|
\t\t\t\tsessionUpdate: thought ? "agent_thought_chunk" : "agent_message_chunk",
|
|
\t\t\t\tmessageId: driver.paperclipKimiMessage.id, content: { type: "text", text },
|
|
\t\t\t\t_meta: { origin: "kimi-native-assistant", source: "kimi-turn-step-v1", kind }
|
|
\t\t\t} });
|
|
\t\t}
|
|
\t\tpaperclipKimiStep(event, phase) {
|
|
\t\t\tconst driver = this.driverFor(event.turnId);
|
|
\t\t\tif (driver === void 0) return;
|
|
\t\t\tconst current = driver.paperclipKimiMessage;
|
|
\t\t\tif (!Number.isSafeInteger(event.step) || event.step < 1 || !Number.isSafeInteger(event.turnId) || event.turnId < 0) return this.paperclipKimiMessageFailure(driver);
|
|
\t\t\tif (phase === "start") {
|
|
\t\t\t\tif (current?.active || typeof event.stepId !== "string" || !/^[A-Za-z0-9_-]{1,256}$/.test(event.stepId) || current && event.step <= current.step) return this.paperclipKimiMessageFailure(driver);
|
|
\t\t\t\tdriver.paperclipKimiMessageIds ??= new Set();
|
|
\t\t\t\tconst id = "kimi-message-" + paperclipKimiCreateHash("sha256").update(JSON.stringify([this.sessionId, event.turnId, event.stepId])).digest("hex");
|
|
\t\t\t\tif (driver.paperclipKimiMessageIds.size >= 4096 || driver.paperclipKimiMessageIds.has(id)) return this.paperclipKimiMessageFailure(driver);
|
|
\t\t\t\tdriver.paperclipKimiMessageIds.add(id);
|
|
\t\t\t\tdriver.paperclipKimiMessage = { id, step: event.step, stepId: event.stepId, active: true };
|
|
\t\t\t\tthis.paperclipKimiChunk(driver, "", "start");
|
|
\t\t\t\treturn;
|
|
\t\t\t}
|
|
\t\t\tif (!current || current.step !== event.step || event.stepId !== void 0 && event.stepId !== current.stepId) return this.paperclipKimiMessageFailure(driver);
|
|
\t\t\tconst reason = phase === "interrupted" ? event.reason : event.finishReason;
|
|
\t\t\tconst reasons = phase === "interrupted" ? ["aborted", "max_steps", "error"] : ["end_turn", "max_tokens", "tool_use", "filtered", "paused", "other"];
|
|
\t\t\tif (!reasons.includes(reason)) return this.paperclipKimiMessageFailure(driver);
|
|
\t\t\tif (!current.active) {
|
|
\t\t\t\tif (phase === "interrupted") return;
|
|
\t\t\t\treturn this.paperclipKimiMessageFailure(driver);
|
|
\t\t\t}
|
|
\t\t\tthis.paperclipKimiChunk(driver, "", "end:" + reason);
|
|
\t\t\tcurrent.active = false;
|
|
\t\t}
|
|
\t\tonAssistantDelta(event) {
|
|
\t\t\tconst driver = this.driverFor(event.turnId);
|
|
\t\t\tif (driver === void 0) return;
|
|
\t\t\tif (!driver.paperclipKimiMessage?.active || typeof event.delta !== "string") return this.paperclipKimiMessageFailure(driver);
|
|
\t\t\tthis.paperclipKimiChunk(driver, event.delta, "delta");
|
|
\t\t}`);
|
|
replace('\t\tonThinkingDelta(event) {\n\t\t\tif (this.driverFor(event.turnId) === void 0) return;\n\t\t\tthis.emit(thinkingDeltaToSessionUpdate(this.sessionId, event));\n\t\t}', `\t\tonThinkingDelta(event) {
|
|
\t\t\tconst driver = this.driverFor(event.turnId);
|
|
\t\t\tif (driver === void 0) return;
|
|
\t\t\tif (!driver.paperclipKimiMessage?.active || typeof event.delta !== "string") return this.paperclipKimiMessageFailure(driver);
|
|
\t\t\tthis.paperclipKimiChunk(driver, event.delta, "delta", true);
|
|
\t\t}`);
|
|
return source;
|
|
}
|
|
|
|
export async function patchKimiRuntime(path) {
|
|
const input = await readFile(path, "utf8");
|
|
const digest = sha256(input);
|
|
if (digest === KIMI_RUNTIME_PATCH.patchedSha256) return { ...KIMI_RUNTIME_PATCH };
|
|
const patched = patchKimiRuntimeSource(input);
|
|
if (sha256(patched) !== KIMI_RUNTIME_PATCH.patchedSha256) throw new Error("Kimi runtime patch output digest mismatch");
|
|
await writeFile(path, patched);
|
|
return { ...KIMI_RUNTIME_PATCH };
|
|
}
|
|
|
|
if (import.meta.url?.endsWith("/patch-kimi-runtime.mjs") && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
const path = process.argv[2];
|
|
if (!path || process.argv.length !== 3) throw new Error("Usage: node patch-kimi-runtime.mjs <dist/main.mjs>");
|
|
patchKimiRuntime(path).then(result => {
|
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
}).catch(error => {
|
|
process.stderr.write(`${error instanceof Error ? error.message : "Kimi runtime patch failed"}\n`);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|