kimaki 0.4.94 → 0.4.95

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.
@@ -430,15 +430,25 @@ function buildAuthorizeHandler(mode) {
430
430
  function toClaudeCodeToolName(name) {
431
431
  return OPENCODE_TO_CLAUDE_CODE_TOOL_NAME[name.toLowerCase()] ?? name;
432
432
  }
433
- function sanitizeSystemText(text) {
434
- return text.replaceAll(OPENCODE_IDENTITY, CLAUDE_CODE_IDENTITY);
433
+ function sanitizeSystemText(text, onError) {
434
+ const startIdx = text.indexOf(OPENCODE_IDENTITY);
435
+ if (startIdx === -1)
436
+ return text;
437
+ const codeRefsMarker = '# Code References';
438
+ const endIdx = text.indexOf(codeRefsMarker, startIdx);
439
+ if (endIdx === -1) {
440
+ onError?.(`sanitizeSystemText: could not find '# Code References' after OpenCode identity`);
441
+ return text;
442
+ }
443
+ // Remove everything from the OpenCode identity up to (but not including) '# Code References'
444
+ return text.slice(0, startIdx) + text.slice(endIdx);
435
445
  }
436
- function prependClaudeCodeIdentity(system) {
446
+ function prependClaudeCodeIdentity(system, onError) {
437
447
  const identityBlock = { type: 'text', text: CLAUDE_CODE_IDENTITY };
438
448
  if (typeof system === 'undefined')
439
449
  return [identityBlock];
440
450
  if (typeof system === 'string') {
441
- const sanitized = sanitizeSystemText(system);
451
+ const sanitized = sanitizeSystemText(system, onError);
442
452
  if (sanitized === CLAUDE_CODE_IDENTITY)
443
453
  return [identityBlock];
444
454
  return [identityBlock, { type: 'text', text: sanitized }];
@@ -447,11 +457,11 @@ function prependClaudeCodeIdentity(system) {
447
457
  return [identityBlock, system];
448
458
  const sanitized = system.map((item) => {
449
459
  if (typeof item === 'string')
450
- return { type: 'text', text: sanitizeSystemText(item) };
460
+ return { type: 'text', text: sanitizeSystemText(item, onError) };
451
461
  if (item && typeof item === 'object' && item.type === 'text') {
452
462
  const text = item.text;
453
463
  if (typeof text === 'string') {
454
- return { ...item, text: sanitizeSystemText(text) };
464
+ return { ...item, text: sanitizeSystemText(text, onError) };
455
465
  }
456
466
  }
457
467
  return item;
@@ -465,7 +475,7 @@ function prependClaudeCodeIdentity(system) {
465
475
  }
466
476
  return [identityBlock, ...sanitized];
467
477
  }
468
- function rewriteRequestPayload(body) {
478
+ function rewriteRequestPayload(body, onError) {
469
479
  if (!body)
470
480
  return { body, modelId: undefined, reverseToolNameMap: new Map() };
471
481
  try {
@@ -486,7 +496,7 @@ function rewriteRequestPayload(body) {
486
496
  });
487
497
  }
488
498
  // Rename system prompt
489
- payload.system = prependClaudeCodeIdentity(payload.system);
499
+ payload.system = prependClaudeCodeIdentity(payload.system, onError);
490
500
  // Rename tool_choice
491
501
  if (payload.tool_choice &&
492
502
  typeof payload.tool_choice === 'object' &&
@@ -627,15 +637,6 @@ async function getFreshOAuth(getAuth, client) {
627
637
  // --- Plugin export ---
628
638
  const AnthropicAuthPlugin = async ({ client }) => {
629
639
  return {
630
- "experimental.chat.system.transform": async (input, output) => {
631
- if (input.model.providerID !== ('anthropic'))
632
- return;
633
- const opencodePromptPart = output.system.findIndex(x => x?.includes('https://github.com/anomalyco/opencode'));
634
- // Remove the OpenCode system prompt part if present
635
- if (opencodePromptPart !== -1) {
636
- output.system.splice(opencodePromptPart, 1);
637
- }
638
- },
639
640
  auth: {
640
641
  provider: 'anthropic',
641
642
  async loader(getAuth, provider) {
@@ -667,7 +668,11 @@ const AnthropicAuthPlugin = async ({ client }) => {
667
668
  .text()
668
669
  .catch(() => undefined)
669
670
  : undefined;
670
- const rewritten = rewriteRequestPayload(originalBody);
671
+ const rewritten = rewriteRequestPayload(originalBody, (msg) => {
672
+ client.tui.showToast({
673
+ body: { message: msg, variant: 'error' },
674
+ }).catch(() => { });
675
+ });
671
676
  const headers = new Headers(init?.headers);
672
677
  if (input instanceof Request) {
673
678
  input.headers.forEach((v, k) => {
@@ -1,8 +1,11 @@
1
1
  // Discord-specific utility functions.
2
2
  // Handles markdown splitting for Discord's 2000-char limit, code block escaping,
3
3
  // thread message sending, and channel metadata extraction from topic tags.
4
- import { ChannelType, GuildMember, MessageFlags, PermissionsBitField, } from 'discord.js';
5
- import { REST, Routes } from 'discord.js';
4
+ // Use namespace import for CJS interop discord.js is CJS and its named
5
+ // exports aren't detectable by all ESM loaders (e.g. tsx/esbuild) because
6
+ // discord.js uses tslib's __exportStar which is opaque to static analysis.
7
+ import * as discord from 'discord.js';
8
+ const { ChannelType, GuildMember, MessageFlags, PermissionsBitField, REST, Routes } = discord;
6
9
  import { discordApiUrl } from './discord-urls.js';
7
10
  import { Lexer } from 'marked';
8
11
  import { splitTablesFromMarkdown } from './format-tables.js';
package/dist/utils.js CHANGED
@@ -2,7 +2,11 @@
2
2
  // Includes Discord OAuth URL generation, array deduplication,
3
3
  // abort error detection, and date/time formatting helpers.
4
4
  import os from 'node:os';
5
- import { PermissionsBitField } from 'discord.js';
5
+ // Use namespace import for CJS interop discord.js is CJS and its named
6
+ // exports aren't detectable by all ESM loaders (e.g. tsx/esbuild) because
7
+ // discord.js uses tslib's __exportStar which is opaque to static analysis.
8
+ import * as discord from 'discord.js';
9
+ const { PermissionsBitField } = discord;
6
10
  import * as errore from 'errore';
7
11
  export function generateBotInstallUrl({ clientId, permissions = [
8
12
  PermissionsBitField.Flags.ViewChannel,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "kimaki",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.4.94",
5
+ "version": "0.4.95",
6
6
  "repository": "https://github.com/remorses/kimaki",
7
7
  "bin": "bin.js",
8
8
  "files": [
@@ -25,9 +25,9 @@
25
25
  "prisma": "7.4.2",
26
26
  "tsx": "^4.20.5",
27
27
  "undici": "^8.0.2",
28
+ "discord-digital-twin": "^0.1.0",
28
29
  "opencode-cached-provider": "^0.0.1",
29
30
  "db": "^0.0.0",
30
- "discord-digital-twin": "^0.1.0",
31
31
  "opencode-deterministic-provider": "^0.0.1"
32
32
  },
33
33
  "dependencies": {
@@ -65,9 +65,9 @@
65
65
  "zod": "^4.3.6",
66
66
  "zustand": "^5.0.11",
67
67
  "errore": "^0.14.1",
68
- "traforo": "^0.2.4",
69
68
  "opencode-injection-guard": "^0.2.1",
70
- "libsqlproxy": "^0.1.0"
69
+ "libsqlproxy": "^0.1.0",
70
+ "traforo": "^0.2.4"
71
71
  },
72
72
  "optionalDependencies": {
73
73
  "@snazzah/davey": "^0.1.10",
@@ -528,17 +528,26 @@ function toClaudeCodeToolName(name: string) {
528
528
  return OPENCODE_TO_CLAUDE_CODE_TOOL_NAME[name.toLowerCase()] ?? name
529
529
  }
530
530
 
531
- function sanitizeSystemText(text: string) {
532
- return text.replaceAll(OPENCODE_IDENTITY, CLAUDE_CODE_IDENTITY)
531
+ function sanitizeSystemText(text: string, onError?: (msg: string) => void) {
532
+ const startIdx = text.indexOf(OPENCODE_IDENTITY)
533
+ if (startIdx === -1) return text
534
+ const codeRefsMarker = '# Code References'
535
+ const endIdx = text.indexOf(codeRefsMarker, startIdx)
536
+ if (endIdx === -1) {
537
+ onError?.(`sanitizeSystemText: could not find '# Code References' after OpenCode identity`)
538
+ return text
539
+ }
540
+ // Remove everything from the OpenCode identity up to (but not including) '# Code References'
541
+ return text.slice(0, startIdx) + text.slice(endIdx)
533
542
  }
534
543
 
535
- function prependClaudeCodeIdentity(system: unknown) {
544
+ function prependClaudeCodeIdentity(system: unknown, onError?: (msg: string) => void) {
536
545
  const identityBlock = { type: 'text', text: CLAUDE_CODE_IDENTITY }
537
546
 
538
547
  if (typeof system === 'undefined') return [identityBlock]
539
548
 
540
549
  if (typeof system === 'string') {
541
- const sanitized = sanitizeSystemText(system)
550
+ const sanitized = sanitizeSystemText(system, onError)
542
551
  if (sanitized === CLAUDE_CODE_IDENTITY) return [identityBlock]
543
552
  return [identityBlock, { type: 'text', text: sanitized }]
544
553
  }
@@ -546,11 +555,11 @@ function prependClaudeCodeIdentity(system: unknown) {
546
555
  if (!Array.isArray(system)) return [identityBlock, system]
547
556
 
548
557
  const sanitized = system.map((item) => {
549
- if (typeof item === 'string') return { type: 'text', text: sanitizeSystemText(item) }
558
+ if (typeof item === 'string') return { type: 'text', text: sanitizeSystemText(item, onError) }
550
559
  if (item && typeof item === 'object' && (item as { type?: unknown }).type === 'text') {
551
560
  const text = (item as { text?: unknown }).text
552
561
  if (typeof text === 'string') {
553
- return { ...(item as Record<string, unknown>), text: sanitizeSystemText(text) }
562
+ return { ...(item as Record<string, unknown>), text: sanitizeSystemText(text, onError) }
554
563
  }
555
564
  }
556
565
  return item
@@ -568,7 +577,7 @@ function prependClaudeCodeIdentity(system: unknown) {
568
577
  return [identityBlock, ...sanitized]
569
578
  }
570
579
 
571
- function rewriteRequestPayload(body: string | undefined) {
580
+ function rewriteRequestPayload(body: string | undefined, onError?: (msg: string) => void) {
572
581
  if (!body) return { body, modelId: undefined, reverseToolNameMap: new Map<string, string>() }
573
582
 
574
583
  try {
@@ -589,7 +598,7 @@ function rewriteRequestPayload(body: string | undefined) {
589
598
  }
590
599
 
591
600
  // Rename system prompt
592
- payload.system = prependClaudeCodeIdentity(payload.system)
601
+ payload.system = prependClaudeCodeIdentity(payload.system, onError)
593
602
 
594
603
  // Rename tool_choice
595
604
  if (
@@ -743,14 +752,6 @@ async function getFreshOAuth(
743
752
 
744
753
  const AnthropicAuthPlugin: Plugin = async ({ client }) => {
745
754
  return {
746
- "experimental.chat.system.transform": async (input, output) => {
747
- if (input.model.providerID !== ('anthropic')) return
748
- const opencodePromptPart = output.system.findIndex(x => x?.includes('https://github.com/anomalyco/opencode'))
749
- // Remove the OpenCode system prompt part if present
750
- if (opencodePromptPart !== -1) {
751
- output.system.splice(opencodePromptPart, 1)
752
- }
753
- },
754
755
  auth: {
755
756
  provider: 'anthropic',
756
757
  async loader(
@@ -787,7 +788,11 @@ const AnthropicAuthPlugin: Plugin = async ({ client }) => {
787
788
  .catch(() => undefined)
788
789
  : undefined
789
790
 
790
- const rewritten = rewriteRequestPayload(originalBody)
791
+ const rewritten = rewriteRequestPayload(originalBody, (msg) => {
792
+ client.tui.showToast({
793
+ body: { message: msg, variant: 'error' },
794
+ }).catch(() => {})
795
+ })
791
796
  const headers = new Headers(init?.headers)
792
797
  if (input instanceof Request) {
793
798
  input.headers.forEach((v, k) => {
@@ -2,19 +2,21 @@
2
2
  // Handles markdown splitting for Discord's 2000-char limit, code block escaping,
3
3
  // thread message sending, and channel metadata extraction from topic tags.
4
4
 
5
- import {
6
- type APIInteractionGuildMember,
7
- type AutocompleteInteraction,
8
- ChannelType,
9
- GuildMember,
10
- MessageFlags,
11
- PermissionsBitField,
12
- type Guild,
13
- type Message,
14
- type TextChannel,
15
- type ThreadChannel,
5
+ // Use namespace import for CJS interop — discord.js is CJS and its named
6
+ // exports aren't detectable by all ESM loaders (e.g. tsx/esbuild) because
7
+ // discord.js uses tslib's __exportStar which is opaque to static analysis.
8
+ import * as discord from 'discord.js'
9
+ import type {
10
+ APIInteractionGuildMember,
11
+ AutocompleteInteraction,
12
+ GuildMember as GuildMemberType,
13
+ Guild,
14
+ Message,
15
+ REST as RESTType,
16
+ TextChannel,
17
+ ThreadChannel,
16
18
  } from 'discord.js'
17
- import { REST, Routes } from 'discord.js'
19
+ const { ChannelType, GuildMember, MessageFlags, PermissionsBitField, REST, Routes } = discord
18
20
  import type { OpencodeClient } from '@opencode-ai/sdk/v2'
19
21
  import { discordApiUrl } from './discord-urls.js'
20
22
  import { Lexer } from 'marked'
@@ -37,7 +39,7 @@ const discordLogger = createLogger(LogPrefix.DISCORD)
37
39
  * Returns false if member is null or has the "no-kimaki" role (overrides all).
38
40
  */
39
41
  export function hasKimakiBotPermission(
40
- member: GuildMember | APIInteractionGuildMember | null,
42
+ member: GuildMemberType | APIInteractionGuildMember | null,
41
43
  guild?: Guild | null,
42
44
  ): boolean {
43
45
  if (!member) {
@@ -61,7 +63,7 @@ export function hasKimakiBotPermission(
61
63
  }
62
64
 
63
65
  function hasRoleByName(
64
- member: GuildMember | APIInteractionGuildMember,
66
+ member: GuildMemberType | APIInteractionGuildMember,
65
67
  roleName: string,
66
68
  guild?: Guild | null,
67
69
  ): boolean {
@@ -89,7 +91,7 @@ function hasRoleByName(
89
91
  * Check if the member has the "no-kimaki" role that blocks bot access.
90
92
  * Separate from hasKimakiBotPermission so callers can show a specific error message.
91
93
  */
92
- export function hasNoKimakiRole(member: GuildMember | null): boolean {
94
+ export function hasNoKimakiRole(member: GuildMemberType | null): boolean {
93
95
  if (!member?.roles?.cache) {
94
96
  return false
95
97
  }
@@ -108,7 +110,7 @@ export async function reactToThread({
108
110
  channelId,
109
111
  emoji,
110
112
  }: {
111
- rest: REST
113
+ rest: RESTType
112
114
  threadId: string
113
115
  /** Parent channel ID where the thread starter message lives.
114
116
  * If not provided, fetches the thread info from Discord API to resolve it. */
@@ -169,7 +171,7 @@ export async function archiveThread({
169
171
  client,
170
172
  archiveDelay = 0,
171
173
  }: {
172
- rest: REST
174
+ rest: RESTType
173
175
  threadId: string
174
176
  parentChannelId?: string
175
177
  sessionId?: string
package/src/utils.ts CHANGED
@@ -3,7 +3,11 @@
3
3
  // abort error detection, and date/time formatting helpers.
4
4
 
5
5
  import os from 'node:os'
6
- import { PermissionsBitField } from 'discord.js'
6
+ // Use namespace import for CJS interop discord.js is CJS and its named
7
+ // exports aren't detectable by all ESM loaders (e.g. tsx/esbuild) because
8
+ // discord.js uses tslib's __exportStar which is opaque to static analysis.
9
+ import * as discord from 'discord.js'
10
+ const { PermissionsBitField } = discord
7
11
  import type { BotMode } from './database.js'
8
12
  import * as errore from 'errore'
9
13