File size: 7,454 Bytes
c75f885
 
89ef4db
 
c75f885
 
 
 
 
 
 
89ef4db
 
 
 
 
 
 
 
 
c75f885
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89ef4db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c1bf102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c75f885
89ef4db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c75f885
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89ef4db
 
 
 
 
 
 
 
 
 
 
 
c75f885
89ef4db
c75f885
 
 
 
 
 
 
 
 
 
 
 
 
 
c1bf102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import { homedir, platform } from "node:os";
import path from "node:path";
import { tool } from "@opencode-ai/plugin";

const TASK_KINDS = ["auto", "website", "business_document", "business_suite", "app", "code_project", "repo_patch", "coding"];
const defaultRuntimeScript = () =>
  path.join(homedir(), ".config", "opencode", "kaiju-coder-7-runtime", "scripts", "run_kaiju_router.py");

const resolveSafeFile = (directory, filePath) => {
  const base = path.resolve(directory || process.cwd());
  const resolved = path.resolve(base, filePath);
  if (resolved !== base && !resolved.startsWith(`${base}${path.sep}`)) {
    throw new Error(`Refusing to write outside the OpenCode directory: ${filePath}`);
  }
  return resolved;
};

const runProcess = (command, args, options = {}) =>
  new Promise((resolve, reject) => {
    const child = spawn(command, args, {
      ...options,
      env: {
        ...process.env,
        ...(options.env || {}),
      },
    });
    let stdout = "";
    let stderr = "";
    child.stdout?.on("data", (chunk) => {
      stdout += chunk.toString();
    });
    child.stderr?.on("data", (chunk) => {
      stderr += chunk.toString();
    });
    child.on("error", reject);
    child.on("close", (code) => {
      if (code === 0) {
        resolve({ stdout, stderr });
        return;
      }
      reject(new Error([`Command failed with exit ${code}: ${command}`, stdout, stderr].filter(Boolean).join("\n")));
    });
  });

const shouldOpenArtifact = (text) =>
  /\b(open it|open the (?:html|file|site|website|page)|launch it|show it)\b/i.test(text || "");

const extractArtifactPath = (stdout) => {
  const artifact = stdout.match(/^Artifact:\s*(.+)$/m);
  if (artifact) {
    return artifact[1].trim();
  }
  const project = stdout.match(/^Project\/repo:\s*(.+)$/m);
  return project ? project[1].trim() : "";
};

const openArtifact = async (artifactPath) => {
  if (!artifactPath || !existsSync(artifactPath)) {
    return "";
  }
  const system = platform();
  if (system === "darwin") {
    await runProcess("open", [artifactPath]);
  } else if (system === "win32") {
    await runProcess("cmd", ["/c", "start", "", artifactPath]);
  } else {
    await runProcess("xdg-open", [artifactPath]);
  }
  return `Opened artifact: ${artifactPath}`;
};

export const KaijuCoder7NoAutocontinuePlugin = async () => {
  const isKaijuCoder7 = (input) => {
    const payload = JSON.stringify({
      agent: input?.agent,
      model: input?.model,
      provider: input?.provider,
      session: input?.session,
    });

    return (
      payload.includes("kaiju-coder-7") ||
      payload.includes("Kaiju Coder 7") ||
      payload.includes("kaiju/kaiju-coder-7")
    );
  };

  return {
    tool: {
      kaiju_write_file: tool({
        description:
          "Write one exact small file in the current OpenCode directory. Use for exact one-file creation requests, not large generated websites or projects.",
        args: {
          file_path: tool.schema.string().describe("Relative path to write inside the current OpenCode directory."),
          content: tool.schema.string().describe("Exact file content to write."),
        },
        async execute(args, context) {
          const target = resolveSafeFile(context.directory, args.file_path);
          await mkdir(path.dirname(target), { recursive: true });
          await writeFile(target, args.content, "utf8");
          return {
            output: `Wrote file: ${target}`,
            metadata: { file_path: target, bytes: Buffer.byteLength(args.content, "utf8") },
          };
        },
      }),

      kaiju_artifact: tool({
        description:
          "Create fast Kaiju Coder 7 websites, owner packs, apps, business documents, or code artifacts with the packaged local router. Use this instead of bash/write for large generated artifact tasks.",
        args: {
          prompt: tool.schema.string().describe("The full user request to build."),
          out_dir: tool.schema
            .string()
            .optional()
            .describe("Absolute output directory. For Desktop requests, use /Users/<user>/Desktop/<clear-folder-name>."),
          kind: tool.schema
            .enum(TASK_KINDS)
            .optional()
            .describe("Artifact type. Use website for sites and business_suite for owner operating packs."),
          no_planner: tool.schema
            .boolean()
            .optional()
            .describe("Use deterministic rendering without a second model-planning call. Defaults to true."),
        },
        async execute(args, context) {
          const runtimeScript = process.env.KAIJU_ROUTER_SCRIPT || defaultRuntimeScript();
          if (!existsSync(runtimeScript)) {
            throw new Error(`Kaiju router runtime is missing: ${runtimeScript}`);
          }
          const outDir =
            args.out_dir ||
            path.join(homedir(), "Desktop", "Kaiju-Coder-7-Artifacts");
          const kind = args.kind || "auto";
          const noPlanner = args.no_planner !== false;
          const procArgs = [runtimeScript, "--kind", kind, "--out-dir", outDir, "--prompt", args.prompt];
          if (noPlanner) {
            procArgs.splice(1, 0, "--no-planner");
          }

          context.metadata({
            title: `Kaiju artifact: ${kind}`,
            metadata: { out_dir: outDir, kind, no_planner: noPlanner },
          });

          const result = await runProcess(process.env.KAIJU_PYTHON || "python3", procArgs, {
            cwd: context.directory || process.cwd(),
          });
          let output = result.stdout.trim() || "Kaiju artifact command completed.";
          if (shouldOpenArtifact(args.prompt)) {
            const artifactPath = extractArtifactPath(output);
            try {
              const opened = await openArtifact(artifactPath);
              output += opened
                ? `\n${opened}`
                : "\nOpen requested, but no generated artifact path was available to open.";
            } catch (error) {
              output += `\nOpen requested, but could not open artifact: ${error.message}`;
            }
          }
          return {
            output,
            metadata: { stderr: result.stderr.trim(), out_dir: outDir, kind, no_planner: noPlanner },
          };
        },
      }),
    },

    async "chat.params"(input, output) {
      if (!isKaijuCoder7(input)) {
        return;
      }
      output.temperature = 0;
      output.maxOutputTokens = Math.min(output.maxOutputTokens || 768, 768);
    },

    async "experimental.compaction.autocontinue"(input, output) {
      if (isKaijuCoder7(input)) {
        output.enabled = false;
      }
    },

    async "experimental.session.compacting"(input, output) {
      if (!isKaijuCoder7(input)) {
        return;
      }

      output.context.push(
        [
          "For Kaiju Coder 7 sessions, summarize only facts proven by tool output.",
          "Do not say a file was created unless a write/edit/bash/read result confirms it exists.",
          "If work is incomplete or unverified, mark it incomplete instead of complete.",
          "Never convert a maximum-step, compaction, or length-limit stop into a successful completion claim.",
        ].join("\n"),
      );
    },
  };
};

export default KaijuCoder7NoAutocontinuePlugin;