Skip to content
Source
Sign inSign up

acpSupport.ts

151 lines5.7 KB

1import type * as EffectAcpSchema from "effect-acp/compat";
2import type { ProviderApprovalOption, RuntimeMode } from "@t3tools/contracts";
3import type { KiroSettings } from "../settings.ts";
4import type * as SecureRandom from "effect/crypto/SecureRandom";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import type * as Scope from "effect/Scope";
8import type * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
9import type * as EffectAcpErrors from "effect-acp/errors";
10​
11import {
12 acpPermissionDisposition,
13 type AcpPermissionDisposition,
14 type AcpRuntimePolicy,
15} from "@t3tools/provider-acp/server/clientPolicy";
16import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
17​
18/**
19 * Kiro's default engine is a v2 server this driver does not speak, so V3 is
20 * requested. `--auth-method=cli` keeps access tokens inside Kiro. V3 rejects the
21 * v2 launch flags; mode and model are set per session with `session/set_config_option`.
22 */
23const KIRO_ACP_ARGS = ["acp", "--agent-engine=v3", "--auth-method=cli"] as const;
24​
25/**
26 * Kiro's `autopilot` session option. Off (Supervised), Kiro also asks the user
27 * to review a turn's file changes before it ends. Sessions start "on", so T3
28 * sets it from the thread's runtime mode.
29 */
30export const KIRO_AUTOPILOT_CONFIG_ID = "autopilot";
31​
32/** Kiro's model session option (`configId: "model"` in the V3 migration guide). */
33export const KIRO_MODEL_CONFIG_ID = "model";
34​
35/**
36 * Kiro's reasoning effort session option (`configId: "effortLevel"` in the V3
37 * migration guide). Kiro advertises it only while a model with effort runs.
38 */
39export const KIRO_EFFORT_CONFIG_ID = "effortLevel";
40​
41/**
42 * Kiro's agent session option. Kiro V3 offers its agents (the bundled ones,
43 * `~/.kiro/agents`, and the workspace's `.kiro/agents`) as the choices of its
44 * `mode` select, and the thread's choice is stored under the same id.
45 */
46export const KIRO_AGENT_CONFIG_ID = "mode";
47​
48/**
49 * Kiro offers two permission postures, Supervised and Autopilot, which T3's
50 * Supervised and Full access select. Any other mode runs Supervised.
51 */
52export const KIRO_SUPPORTED_RUNTIME_MODES = [
53 "approval-required",
54 "full-access",
55] as const satisfies ReadonlyArray<RuntimeMode>;
56​
57/**
58 * Autopilot is on only for an unrestricted Full access thread. An explicit
59 * approval or sandbox override keeps Kiro Supervised, so its end-of-turn
60 * review still reaches the user and every tool prompt reaches T3's policy
61 * check (the same rule as Grok's `grokLaunchRuntimeMode`).
62 */
63export function kiroAutopilotValue(policy: {
64 readonly runtimeMode: RuntimeMode;
65 readonly approvalPolicy?: unknown;
66 readonly sandboxPolicy?: unknown;
67}): "on" | "off" {
68 return policy.runtimeMode === "full-access" &&
69 policy.approvalPolicy === undefined &&
70 policy.sandboxPolicy === undefined
71 ? "on"
72 : "off";
74​
75type KiroAcpRuntimeSettings = Pick<KiroSettings, "binaryPath">;
76​
77export function buildKiroAcpSpawnInput(
78 settings: KiroAcpRuntimeSettings | null | undefined,
79 cwd: string,
80 environment?: NodeJS.ProcessEnv,
81): AcpSessionRuntime.AcpSpawnInput {
82 return {
83 command: settings?.binaryPath || "kiro-cli",
84 args: [...KIRO_ACP_ARGS],
85 cwd,
86 ...(environment === undefined ? {} : { env: environment }),
87 };
89​
90/**
91 * Approval choices a Kiro permission request can honor; V3's options are
92 * authoritative. Its `allow_always` saves a consent rule for the workspace or
93 * wider, so it is offered, as "Always allow", only without a one-time choice.
94 */
95export function kiroApprovalOptions(
96 request: EffectAcpSchema.RequestPermissionRequest,
97): ReadonlyArray<ProviderApprovalOption> {
98 const has = (kind: EffectAcpSchema.PermissionOption["kind"]) =>
99 request.options.some((option) => option.kind === kind && option.optionId.trim().length > 0);
100 return [
101 ...(has("allow_once")
102 ? [{ decision: "accept", label: "Allow once" } as const]
103 : has("allow_always")
104 ? [{ decision: "acceptForSession", label: "Always allow" } as const]
105 : []),
106 ...(has("reject_once") ? [{ decision: "decline", label: "Deny" } as const] : []),
107 { decision: "cancel", label: "Cancel" },
108 ];
110​
111/**
112 * T3's answer to a Kiro permission request. A policy approval may only use
113 * Kiro's `allow_once`: its `allow_always` saves a consent rule for the whole
114 * workspace or wider, which would outlive the thread. When Kiro offers no
115 * one-time choice, the request goes to the user instead of persisting a grant.
116 */
117export function kiroPermissionDisposition(
118 policy: AcpRuntimePolicy,
119 request: EffectAcpSchema.RequestPermissionRequest,
120): AcpPermissionDisposition {
121 const disposition = acpPermissionDisposition(policy, request);
122 const offersOnce = request.options.some(
123 (option) => option.kind === "allow_once" && option.optionId.trim().length > 0,
124 );
125 return disposition === "allow" && !offersOnce ? "ask" : disposition;
127​
128export interface KiroAcpRuntimeInput extends Omit<
129 AcpSessionRuntime.AcpSessionRuntimeOptions,
130 "authMethodId" | "spawn"
131> {
132 readonly settings: KiroAcpRuntimeSettings | null | undefined;
133 readonly environment?: NodeJS.ProcessEnv;
135​
136export const makeKiroAcpRuntime = Effect.fn("KiroAcpSupport.makeKiroAcpRuntime")(function* (
137 input: KiroAcpRuntimeInput,
138): Effect.fn.Return<
139 AcpSessionRuntime.AcpSessionRuntime["Service"],
140 EffectAcpErrors.AcpError,
141 ChildProcessSpawner.ChildProcessSpawner | SecureRandom.SecureRandom | Scope.Scope
142> {
143 const { settings, environment, ...runtimeOptions } = input;
144 const context = yield* Layer.build(
145 AcpSessionRuntime.layer({
146 ...runtimeOptions,
147 spawn: buildKiroAcpSpawnInput(settings, input.cwd, environment),
148 }),
149 );
150 return yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe(Effect.provide(context));
151});