@vincemakes/kiso-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * L2 — ModeProfile: a mode is a cross-layer behavior switch (mauri ADR-0011),
3
+ * not a kernel feature. The kernel knows ONE mode at a time and applies it
4
+ * structurally:
5
+ *
6
+ * - `visibleToolNames` — physical removal: the registry is subset() to these
7
+ * tools BEFORE the adapter is called. The model cannot call what it cannot
8
+ * see; no system-prompt overlay can make that guarantee.
9
+ * - `systemOverlay` — appended to the system prompt by the harness (the
10
+ * kernel does not compose prompts); kept here because it is part of the
11
+ * mode's contract.
12
+ * - `permissionDefault` — the decision onPreTool falls back to when no hook
13
+ * or store decides.
14
+ * - `compactionKeepExtra` / `stopPredicate` — reserved (ADR-0011); read by
15
+ * the harness when the semantics land.
16
+ */
17
+ import type { PermissionDecision } from "./permission.js";
18
+ export interface ModeProfile {
19
+ readonly name: string;
20
+ readonly systemOverlay?: string;
21
+ readonly visibleToolNames?: readonly string[];
22
+ readonly permissionDefault?: PermissionDecision;
23
+ readonly compactionKeepExtra?: readonly string[];
24
+ readonly stopPredicate?: string;
25
+ }
26
+ export declare function resolveModeProfile(modes: readonly ModeProfile[] | undefined, name: string | undefined): ModeProfile | undefined;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * L2 — ModeProfile: a mode is a cross-layer behavior switch (mauri ADR-0011),
3
+ * not a kernel feature. The kernel knows ONE mode at a time and applies it
4
+ * structurally:
5
+ *
6
+ * - `visibleToolNames` — physical removal: the registry is subset() to these
7
+ * tools BEFORE the adapter is called. The model cannot call what it cannot
8
+ * see; no system-prompt overlay can make that guarantee.
9
+ * - `systemOverlay` — appended to the system prompt by the harness (the
10
+ * kernel does not compose prompts); kept here because it is part of the
11
+ * mode's contract.
12
+ * - `permissionDefault` — the decision onPreTool falls back to when no hook
13
+ * or store decides.
14
+ * - `compactionKeepExtra` / `stopPredicate` — reserved (ADR-0011); read by
15
+ * the harness when the semantics land.
16
+ */
17
+ export function resolveModeProfile(modes, name) {
18
+ if (!name || !modes)
19
+ return undefined;
20
+ return modes.find((m) => m.name === name);
21
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * L2 — permission as a negotiation, not a gate (mauri ADR-0002).
3
+ *
4
+ * A permission decision is a dialog step with state and an upgrade path:
5
+ * allow one call, deny with a reason fed back to the model, or defer to a
6
+ * human. The kernel's contract is only the decision shape; where decisions
7
+ * are stored (accept-for-session) is harness territory (PermissionStore,
8
+ * M2). M1 treats `defer` as a deny with reason "awaiting user" — the model
9
+ * sees the refusal and can adjust; the human-in-the-loop wiring arrives
10
+ * with the harness.
11
+ */
12
+ export type PermissionDecision = {
13
+ readonly action: "allow";
14
+ } | {
15
+ readonly action: "deny";
16
+ readonly reason: string;
17
+ } | {
18
+ readonly action: "defer";
19
+ readonly reason?: string;
20
+ };
21
+ /** The denial a tool result carries when a call was refused pre-flight. */
22
+ export declare function denialResult(reason: string): {
23
+ content: string;
24
+ isError: boolean;
25
+ errorKind: "precondition";
26
+ tags: readonly ["denied"];
27
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * L2 — permission as a negotiation, not a gate (mauri ADR-0002).
3
+ *
4
+ * A permission decision is a dialog step with state and an upgrade path:
5
+ * allow one call, deny with a reason fed back to the model, or defer to a
6
+ * human. The kernel's contract is only the decision shape; where decisions
7
+ * are stored (accept-for-session) is harness territory (PermissionStore,
8
+ * M2). M1 treats `defer` as a deny with reason "awaiting user" — the model
9
+ * sees the refusal and can adjust; the human-in-the-loop wiring arrives
10
+ * with the harness.
11
+ */
12
+ /** The denial a tool result carries when a call was refused pre-flight. */
13
+ export function denialResult(reason) {
14
+ return {
15
+ content: `[Permission denied] ${reason}`,
16
+ isError: true,
17
+ errorKind: "precondition",
18
+ tags: ["denied"],
19
+ };
20
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * L2 — the reducer: messages are a PURE projection of the event log.
3
+ *
4
+ * ADR-0002 says the log is the single truth. These two functions are what
5
+ * make that claim enforceable: `projectMessages(log.all)` rebuilds the exact
6
+ * message array the loop hands to the adapter, and `messagesToEvents`
7
+ * encodes seed history into the log. Round-trip property:
8
+ * `projectMessages(messagesToEvents(m)) === m` — pinned by tests, and
9
+ * LOSSLESS (Area 6): `source`, `tags`, image content blocks, and assistant
10
+ * text-block boundaries survive the round trip.
11
+ *
12
+ * The loop no longer keeps a parallel `messages` array: every adapter call
13
+ * derives from the log, so there is one store and the replay of `seq` 0..N
14
+ * reproduces the run exactly.
15
+ *
16
+ * Events with no message shape (usage, stop, thinking, terminal, compacted's
17
+ * own record) are skipped by the projection; `compacted` REPLAYS the
18
+ * compaction by re-running microcompact at that point in the sequence —
19
+ * microcompact is deterministic and idempotent, so the replay equals the
20
+ * live run. See ADR-0002.
21
+ */
22
+ import type { Event } from "../protocol/events.js";
23
+ import type { EventInput } from "./event-log.js";
24
+ import type { AssistantBlock, Message, MessageSource } from "../protocol/messages.js";
25
+ /**
26
+ * Rebuild the message array from events. Deterministic and order-sensitive:
27
+ * replaying the same log always produces the same messages.
28
+ *
29
+ * Text block boundaries are preserved: `text_end` closes the current text
30
+ * block (an explicit boundary); `text_start` after a block opens a new one.
31
+ * A stream of deltas WITHOUT `text_end` (the adapters' common shape) closes
32
+ * at the next block/tool/result boundary.
33
+ */
34
+ export declare function projectMessages(events: readonly (Event | EventInput)[]): readonly Message[];
35
+ /**
36
+ * Encode seed messages into log events — LOSSLESSLY for the framework's own
37
+ * shapes (Area 6): one `text_delta` per text block (boundaries preserved),
38
+ * `source` on the first event of each message, `tags` on tool results,
39
+ * content blocks passed through. Nothing a legal Message can express is
40
+ * dropped.
41
+ */
42
+ export declare function messagesToEvents(messages: readonly Message[]): EventInput[];
43
+ export type { AssistantBlock, MessageSource };
@@ -0,0 +1,287 @@
1
+ /**
2
+ * L2 — the reducer: messages are a PURE projection of the event log.
3
+ *
4
+ * ADR-0002 says the log is the single truth. These two functions are what
5
+ * make that claim enforceable: `projectMessages(log.all)` rebuilds the exact
6
+ * message array the loop hands to the adapter, and `messagesToEvents`
7
+ * encodes seed history into the log. Round-trip property:
8
+ * `projectMessages(messagesToEvents(m)) === m` — pinned by tests, and
9
+ * LOSSLESS (Area 6): `source`, `tags`, image content blocks, and assistant
10
+ * text-block boundaries survive the round trip.
11
+ *
12
+ * The loop no longer keeps a parallel `messages` array: every adapter call
13
+ * derives from the log, so there is one store and the replay of `seq` 0..N
14
+ * reproduces the run exactly.
15
+ *
16
+ * Events with no message shape (usage, stop, thinking, terminal, compacted's
17
+ * own record) are skipped by the projection; `compacted` REPLAYS the
18
+ * compaction by re-running microcompact at that point in the sequence —
19
+ * microcompact is deterministic and idempotent, so the replay equals the
20
+ * live run. See ADR-0002.
21
+ */
22
+ /**
23
+ * Rebuild the message array from events. Deterministic and order-sensitive:
24
+ * replaying the same log always produces the same messages.
25
+ *
26
+ * Text block boundaries are preserved: `text_end` closes the current text
27
+ * block (an explicit boundary); `text_start` after a block opens a new one.
28
+ * A stream of deltas WITHOUT `text_end` (the adapters' common shape) closes
29
+ * at the next block/tool/result boundary.
30
+ */
31
+ export function projectMessages(events) {
32
+ const out = [];
33
+ let blocks = [];
34
+ let text = null;
35
+ let assistantSource;
36
+ const pushText = () => {
37
+ if (text !== null) {
38
+ blocks.push({ type: "text", text });
39
+ text = null;
40
+ }
41
+ };
42
+ const flushAssistant = () => {
43
+ pushText();
44
+ if (blocks.length === 0) {
45
+ assistantSource = undefined;
46
+ return;
47
+ }
48
+ out.push({
49
+ role: "assistant",
50
+ blocks: [...blocks],
51
+ ...(assistantSource !== undefined ? { source: assistantSource } : {}),
52
+ });
53
+ blocks = [];
54
+ assistantSource = undefined;
55
+ };
56
+ // C 组/六: vetoed/rewritten user inputs. Collect the replacement map
57
+ // first — the FINAL replacement for each input wins (later replacements
58
+ // never produce extra messages), and the replacement renders AT THE
59
+ // INPUT'S OWN POSITION: the original is skipped, the final non-null
60
+ // content speaks for it there, a null content is a true veto (nothing
61
+ // at that position).
62
+ const replaced = new Map();
63
+ for (const ev of events) {
64
+ if (ev.type === "user_input_replaced") {
65
+ replaced.set(ev.replaces, {
66
+ content: ev.content,
67
+ ...(ev.source !== undefined ? { source: ev.source } : {}),
68
+ });
69
+ }
70
+ }
71
+ let explicitAssistant = false;
72
+ for (const ev of events) {
73
+ switch (ev.type) {
74
+ case "user_input": {
75
+ // 六: the final replacement renders HERE, at the input's own
76
+ // position — the original is skipped, the replacement event
77
+ // itself produces nothing (a later replacement for the same
78
+ // input never becomes a second message).
79
+ if ("seq" in ev && typeof ev.seq === "number" && replaced.has(ev.seq)) {
80
+ const replacement = replaced.get(ev.seq);
81
+ flushAssistant();
82
+ if (replacement.content !== null) {
83
+ out.push({
84
+ role: "user",
85
+ content: replacement.content,
86
+ ...(replacement.source !== undefined ? { source: replacement.source } : {}),
87
+ });
88
+ }
89
+ break;
90
+ }
91
+ flushAssistant();
92
+ out.push({
93
+ role: "user",
94
+ content: ev.content,
95
+ ...(ev.source !== undefined ? { source: ev.source } : {}),
96
+ });
97
+ break;
98
+ }
99
+ case "user_input_replaced":
100
+ // The replacement already rendered at its input's position —
101
+ // this event carries no message of its own (六).
102
+ break;
103
+ case "assistant_start":
104
+ // D 组: an explicit message boundary — close any open message
105
+ // and begin a new one (adjacent assistants stay separate).
106
+ flushAssistant();
107
+ explicitAssistant = true;
108
+ if (ev.source !== undefined)
109
+ assistantSource = ev.source;
110
+ break;
111
+ case "assistant_end":
112
+ // Close the message; an EMPTY explicit message is preserved.
113
+ if (explicitAssistant && blocks.length === 0 && text === null) {
114
+ out.push({
115
+ role: "assistant",
116
+ blocks: [],
117
+ ...(assistantSource !== undefined ? { source: assistantSource } : {}),
118
+ });
119
+ assistantSource = undefined;
120
+ }
121
+ else {
122
+ flushAssistant();
123
+ }
124
+ explicitAssistant = false;
125
+ break;
126
+ case "text_start":
127
+ pushText(); // an explicit boundary: a new block begins
128
+ if (ev.source !== undefined)
129
+ assistantSource = ev.source;
130
+ break;
131
+ case "text_end":
132
+ pushText(); // an explicit boundary: the block closes
133
+ break;
134
+ case "text_delta":
135
+ text = (text ?? "") + ev.text;
136
+ break;
137
+ case "tool_call_start":
138
+ if (ev.source !== undefined)
139
+ assistantSource = ev.source;
140
+ break;
141
+ case "tool_call_input_delta":
142
+ break; // the parsed input arrives at tool_call_end
143
+ case "tool_call_end":
144
+ pushText();
145
+ blocks.push({
146
+ type: "tool_use",
147
+ callId: ev.callId,
148
+ name: ev.name,
149
+ input: ev.input ?? {},
150
+ });
151
+ break;
152
+ case "tool_result": {
153
+ flushAssistant();
154
+ const message = {
155
+ role: "tool",
156
+ callId: ev.callId,
157
+ content: ev.content,
158
+ isError: ev.isError,
159
+ ...(ev.source !== undefined ? { source: ev.source } : {}),
160
+ ...(ev.tags !== undefined ? { tags: ev.tags } : {}),
161
+ };
162
+ // 五: the originating event's seq rides on the message as a
163
+ // NON-ENUMERABLE correlation field — the stable identity
164
+ // compaction uses to name WHICH result it replaced. Deep
165
+ // equality with seed messages (which carry no such field)
166
+ // still holds; a spread drops it, which is fine because the
167
+ // projection re-derives everything fresh from the log.
168
+ if ("seq" in ev && typeof ev.seq === "number") {
169
+ Object.defineProperty(message, "eventSeq", { value: ev.seq, enumerable: false, configurable: true });
170
+ }
171
+ out.push(message);
172
+ break;
173
+ }
174
+ case "compacted": {
175
+ flushAssistant();
176
+ // Apply the EXACT persisted replacements — never re-run the
177
+ // compaction algorithm (a future version could differ). 五:
178
+ // v2 entries are keyed by the replaced tool-result EVENT's
179
+ // seq, so only the specific result is rewritten — never a
180
+ // same-callId sibling from another turn or run. 第四轮: v1
181
+ // entries (round-three sessions, no eventSeq) replay with v1
182
+ // semantics — every tool result with that callId is replaced,
183
+ // exactly as the old framework did.
184
+ const byEventSeq = new Map(ev.cleared.filter((c) => c.eventSeq !== undefined).map((c) => [c.eventSeq, c.content]));
185
+ const byCallId = new Map(ev.cleared.filter((c) => c.eventSeq === undefined).map((c) => [c.callId, c.content]));
186
+ const replaced = out.map((m) => {
187
+ if (m.role !== "tool")
188
+ return m;
189
+ if (m.eventSeq !== undefined && byEventSeq.has(m.eventSeq)) {
190
+ return { ...m, content: byEventSeq.get(m.eventSeq) };
191
+ }
192
+ if (byCallId.has(m.callId))
193
+ return { ...m, content: byCallId.get(m.callId) };
194
+ return m;
195
+ });
196
+ out.splice(0, out.length, ...replaced);
197
+ break;
198
+ }
199
+ case "thinking":
200
+ case "usage":
201
+ case "stop":
202
+ case "terminal":
203
+ case "tool_execution_started":
204
+ case "tool_execution_succeeded":
205
+ case "tool_execution_failed":
206
+ case "tool_execution_resolved":
207
+ case "permission_requested":
208
+ case "permission_decided":
209
+ case "permission_expired":
210
+ case "uncertain_pending":
211
+ flushAssistant();
212
+ break;
213
+ }
214
+ }
215
+ flushAssistant();
216
+ return out;
217
+ }
218
+ /**
219
+ * Encode seed messages into log events — LOSSLESSLY for the framework's own
220
+ * shapes (Area 6): one `text_delta` per text block (boundaries preserved),
221
+ * `source` on the first event of each message, `tags` on tool results,
222
+ * content blocks passed through. Nothing a legal Message can express is
223
+ * dropped.
224
+ */
225
+ export function messagesToEvents(messages) {
226
+ const out = [];
227
+ for (const msg of messages) {
228
+ switch (msg.role) {
229
+ case "user": {
230
+ out.push({
231
+ type: "user_input",
232
+ content: msg.content,
233
+ ...(msg.source !== undefined ? { source: msg.source } : {}),
234
+ });
235
+ break;
236
+ }
237
+ case "assistant": {
238
+ // D 组: an explicit assistant_start/assistant_end pair frames
239
+ // the message — adjacent and empty assistants round-trip.
240
+ out.push({
241
+ type: "assistant_start",
242
+ ...(msg.source !== undefined ? { source: msg.source } : {}),
243
+ });
244
+ for (const block of msg.blocks) {
245
+ if (block.type === "text") {
246
+ out.push({
247
+ type: "text_start",
248
+ ...(msg.source !== undefined ? { source: msg.source } : {}),
249
+ });
250
+ out.push({ type: "text_delta", text: block.text });
251
+ out.push({ type: "text_end" });
252
+ }
253
+ else {
254
+ out.push({
255
+ type: "tool_call_start",
256
+ callId: block.callId,
257
+ name: block.name,
258
+ ...(msg.source !== undefined ? { source: msg.source } : {}),
259
+ });
260
+ out.push({
261
+ type: "tool_call_end",
262
+ callId: block.callId,
263
+ name: block.name,
264
+ input: block.input,
265
+ });
266
+ }
267
+ }
268
+ out.push({ type: "assistant_end" });
269
+ break;
270
+ }
271
+ case "tool": {
272
+ // D1: the FULL content (blocks included) is preserved — never
273
+ // flattened to text.
274
+ out.push({
275
+ type: "tool_result",
276
+ callId: msg.callId,
277
+ content: msg.content,
278
+ isError: msg.isError,
279
+ ...(msg.source !== undefined ? { source: msg.source } : {}),
280
+ ...(msg.tags !== undefined ? { tags: msg.tags } : {}),
281
+ });
282
+ break;
283
+ }
284
+ }
285
+ }
286
+ return out;
287
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * L1 — the adapter contract.
3
+ *
4
+ * One interface every provider implements. `stream()` returns an
5
+ * ASYNC ITERABLE of events — never a settled promise of a list, and never a
6
+ * callback emitter. An async iterable is incremental (the consumer sees each
7
+ * event as it happens), interruptible (AbortSignal), and replayable (the
8
+ * events carry `seq`). The three properties that let a ReAct loop stream
9
+ * without buffering and without losing its place.
10
+ *
11
+ * WHY not a `Promise<Event[]>`: a buffered adapter is what made agno's
12
+ * streaming a second-class feature bolted onto a batch loop. The contract
13
+ * shape IS the architecture — see ADR-0001.
14
+ *
15
+ * Adapters translate provider wire events INTO `Event`. They never see tool
16
+ * handlers (only `ToolSpec` projections) — the kernel is the only thing that
17
+ * runs tools. Provider-private fields (thinking blocks, cache_control,
18
+ * reasoning_content) are digested HERE and never leak into the union.
19
+ */
20
+ import { type Event } from "./events.js";
21
+ import type { Message, ToolSpec } from "./messages.js";
22
+ /**
23
+ * What the kernel accepts as a cancellation signal: a REAL AbortSignal
24
+ * (the universal host type — every Node and browser host has one) or a
25
+ * minimal structural stub for tests and exotic hosts. `AbortSignal` itself
26
+ * is not structurally assignable to a hand-rolled interface (its listener
27
+ * and options shapes are richer), so the union is the honest contract:
28
+ * real signals pass without casts, stubs stay possible.
29
+ */
30
+ export type AbortSignalLike = AbortSignal | AbortSignalStub;
31
+ /** Minimal structural stand-in: `aborted` + `addEventListener` + `removeEventListener`. */
32
+ export interface AbortSignalStub {
33
+ readonly aborted: boolean;
34
+ addEventListener(type: string, listener: (this: AbortSignalStub, ev: unknown) => void, options?: {
35
+ once?: boolean;
36
+ }): void;
37
+ removeEventListener(type: string, listener: (this: AbortSignalStub, ev: unknown) => void, options?: {
38
+ once?: boolean;
39
+ }): void;
40
+ }
41
+ export interface StreamOptions {
42
+ readonly model: string;
43
+ readonly messages: readonly Message[];
44
+ /** Provider-level system prompt. The kernel never composes prompts. */
45
+ readonly systemPrompt?: string;
46
+ readonly tools?: readonly ToolSpec[];
47
+ readonly maxTokens?: number;
48
+ readonly temperature?: number;
49
+ readonly signal?: AbortSignalLike;
50
+ }
51
+ /**
52
+ * The NARROW event set an adapter may produce (五). Everything else in the
53
+ * union is kernel-owned — `terminal`, `tool_execution_*`, `permission_*`,
54
+ * `user_input`, `compacted`, `uncertain_pending`, `user_input_replaced`,
55
+ * `assistant_start`/`assistant_end` — and a provider that yields any of
56
+ * those is FORGING kernel state. The type narrows the adapter contract;
57
+ * the loop ALSO enforces it at runtime (JS and third-party adapters are
58
+ * not trusted — see kernel/loop.ts).
59
+ */
60
+ export type AdapterEvent = Extract<Event, {
61
+ type: "text_start" | "text_delta" | "text_end" | "tool_call_start" | "tool_call_input_delta" | "tool_call_end" | "thinking" | "usage" | "stop";
62
+ }>;
63
+ /** Runtime whitelist backing the narrowed type — the loop's trust gate. */
64
+ export declare const ADAPTER_EVENT_TYPES: ReadonlySet<string>;
65
+ /**
66
+ * 第五轮(P1-8): the trust gate validates STRUCTURE, not just the type name.
67
+ * A third-party adapter can emit a legal type with illegal fields (a stop
68
+ * without a reason, a usage with known:true and no token, an array tool
69
+ * input) — persisted, that would poison the next load. The gate reuses the
70
+ * same per-variant validator the store relies on (isKisoEvent), so there
71
+ * is exactly ONE set of rules: an invalid event is a forgery, never
72
+ * appended, and the turn ends with an invalid_request terminal.
73
+ */
74
+ export declare function isAdapterEvent(value: Event): value is AdapterEvent;
75
+ export interface Adapter {
76
+ stream(options: StreamOptions): AsyncIterable<AdapterEvent>;
77
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * L1 — the adapter contract.
3
+ *
4
+ * One interface every provider implements. `stream()` returns an
5
+ * ASYNC ITERABLE of events — never a settled promise of a list, and never a
6
+ * callback emitter. An async iterable is incremental (the consumer sees each
7
+ * event as it happens), interruptible (AbortSignal), and replayable (the
8
+ * events carry `seq`). The three properties that let a ReAct loop stream
9
+ * without buffering and without losing its place.
10
+ *
11
+ * WHY not a `Promise<Event[]>`: a buffered adapter is what made agno's
12
+ * streaming a second-class feature bolted onto a batch loop. The contract
13
+ * shape IS the architecture — see ADR-0001.
14
+ *
15
+ * Adapters translate provider wire events INTO `Event`. They never see tool
16
+ * handlers (only `ToolSpec` projections) — the kernel is the only thing that
17
+ * runs tools. Provider-private fields (thinking blocks, cache_control,
18
+ * reasoning_content) are digested HERE and never leak into the union.
19
+ */
20
+ import { isKisoEvent } from "./events.js";
21
+ /** Runtime whitelist backing the narrowed type — the loop's trust gate. */
22
+ export const ADAPTER_EVENT_TYPES = new Set([
23
+ "text_start",
24
+ "text_delta",
25
+ "text_end",
26
+ "tool_call_start",
27
+ "tool_call_input_delta",
28
+ "tool_call_end",
29
+ "thinking",
30
+ "usage",
31
+ "stop",
32
+ ]);
33
+ /**
34
+ * 第五轮(P1-8): the trust gate validates STRUCTURE, not just the type name.
35
+ * A third-party adapter can emit a legal type with illegal fields (a stop
36
+ * without a reason, a usage with known:true and no token, an array tool
37
+ * input) — persisted, that would poison the next load. The gate reuses the
38
+ * same per-variant validator the store relies on (isKisoEvent), so there
39
+ * is exactly ONE set of rules: an invalid event is a forgery, never
40
+ * appended, and the turn ends with an invalid_request terminal.
41
+ */
42
+ export function isAdapterEvent(value) {
43
+ return ADAPTER_EVENT_TYPES.has(value.type) && isKisoEvent(value);
44
+ }