kimaki 0.4.86 → 0.4.87

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.
@@ -1,10 +1,9 @@
1
1
  ---
2
2
  name: critique
3
3
  description: >
4
- Git diff viewer and AI reviewer. Renders diffs as web pages, images, and PDFs
5
- with syntax highlighting. Also provides AI-powered diff reviews via
6
- `critique review --web`. Use this skill when working with critique for showing
7
- diffs, generating diff URLs, selective hunk staging, or AI code reviews.
4
+ Git diff viewer. Renders diffs as web pages, images, and PDFs
5
+ with syntax highlighting. Use this skill when working with critique for showing
6
+ diffs, generating diff URLs, or selective hunk staging.
8
7
  ---
9
8
 
10
9
  # critique
@@ -89,39 +88,6 @@ critique hunks add 'file:@-10,6+10,7' # stage only your hunks
89
88
  git commit -m "your changes" # commit separately
90
89
  ```
91
90
 
92
- ## AI-powered diff review
93
-
94
- `critique review --web` spawns a separate opencode session that analyzes a diff, groups related
95
- changes, and produces a structured review with explanations, diagrams, and suggestions. Uploads
96
- the result as a shareable URL — much richer than a plain diff link.
97
-
98
- **This command is very slow (up to 20 minutes for large diffs).** Only run when the user
99
- explicitly asks for a code review or diff explanation. Warn the user it will take a while.
100
- Set Bash tool timeout to at least 25 minutes (`timeout: 1_500_000`).
101
-
102
- Always pass `--agent opencode` and `--session <current_session_id>` so the reviewer has context
103
- about why the changes were made. If you know other session IDs that produced the diff, pass them
104
- too with additional `--session` flags.
105
-
106
- ```bash
107
- # Review working tree changes
108
- critique review --web --agent opencode --session <session_id>
109
-
110
- # Review a specific commit
111
- critique review --commit HEAD --web --agent opencode --session <session_id>
112
-
113
- # Review branch changes compared to main
114
- critique review main...HEAD --web --agent opencode --session <session_id>
115
-
116
- # Review with multiple session contexts
117
- critique review --commit abc1234 --web --agent opencode --session <session_id> --session <other_session_id>
118
-
119
- # Review only specific files
120
- critique review --web --agent opencode --session <session_id> --filter "src/**/*.ts"
121
- ```
122
-
123
- The command prints a preview URL when done — share that URL with the user.
124
-
125
91
  ## Raw patch access
126
92
 
127
93
  Every `--web` upload also stores the raw unified diff. Append `.patch` to any critique URL to get it:
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: gitchamber
3
+ description: CLI to download npm packages, PyPI packages, crates, or GitHub repo source code into node_modules/.gitchamber/ for analysis. Use when you need to read a package's inner workings, documentation, examples, or source code. Alternative to opensrc that stores in node_modules/ for zero-config gitignore/vitest/tsc compatibility. After fetching, analyze files with grep, read, and other tools.
4
+ ---
5
+
6
+ # gitchamber
7
+
8
+ CLI to download source code for npm packages, PyPI packages, crates.io crates, or GitHub repos into `node_modules/.gitchamber/`. After fetching, analyze the files using grep, read, glob, and other tools to understand inner workings, find usage examples, read documentation, or study the source code.
9
+
10
+ Alternative to [opensrc](https://github.com/vercel-labs/opensrc) that stores in `node_modules/` instead of `opensrc/`.
11
+
12
+ **Differences from opensrc:**
13
+
14
+ - **Stores in `node_modules/.gitchamber/`** instead of `opensrc/` -- automatically ignored by git, vitest, tsc, linters, bundlers, and every other tool that skips `node_modules/`
15
+ - **No file modification** -- removed all `.gitignore`, `tsconfig.json`, and `AGENTS.md` editing logic
16
+ - **No `--modify` flag** or permission prompts
17
+ - **Zero config** -- opensrc requires updating `.gitignore` and `tsconfig.json` excludes; gitchamber needs nothing
18
+
19
+ Always run `gitchamber --help` first. The help output has all commands, options, and examples.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm install -g gitchamber
25
+ ```
26
+
27
+ Aliases: `gitchamber`, `chamber`
28
+
29
+ ## Fetch packages
30
+
31
+ ```bash
32
+ # npm
33
+ chamber zod
34
+ chamber @babel/core
35
+ chamber react@18.2.0
36
+
37
+ # PyPI
38
+ chamber pypi:requests
39
+ chamber pypi:flask==3.0.0
40
+
41
+ # crates.io
42
+ chamber crates:serde
43
+ chamber crates:tokio@1.35.0
44
+
45
+ # GitHub repos (owner/repo, with optional branch or tag)
46
+ chamber vercel/ai
47
+ chamber facebook/react#main
48
+ chamber owner/repo@v1.0.0
49
+ chamber https://github.com/denoland/deno
50
+ ```
51
+
52
+ Multiple at once:
53
+
54
+ ```bash
55
+ chamber zod react vercel/ai pypi:requests
56
+ ```
57
+
58
+ ## Other commands
59
+
60
+ ```bash
61
+ # List fetched sources
62
+ chamber list
63
+ chamber list --json
64
+
65
+ # Remove specific packages
66
+ chamber remove zod
67
+ chamber rm vercel/ai
68
+
69
+ # Clean all
70
+ chamber clean
71
+ chamber clean --npm # only npm
72
+ chamber clean --pypi # only PyPI
73
+ chamber clean --crates # only crates.io
74
+ chamber clean --repos # only repos
75
+ ```
76
+
77
+ ## Output location
78
+
79
+ Source ends up in `node_modules/.gitchamber/<host>/<owner>/<repo>/`.
80
+
81
+ Tracked in `node_modules/.gitchamber/sources.json`.
82
+
83
+ ## How it works
84
+
85
+ 1. Resolves package via registry API (npm, PyPI, crates.io)
86
+ 2. Extracts `repository` URL from metadata
87
+ 3. Shallow-clones at the matching version tag
88
+ 4. Strips `.git` directory
89
+ 5. For npm: auto-detects installed version from lockfiles
90
+
91
+ ## When to use
92
+
93
+ Use gitchamber (or its alias `chamber`) instead of manually cloning repos into tmp directories. Prefer it over the `lib-investigator` agent when you need persistent source code on disk for repeated reads or when the agent's context isn't enough. When the user refers to opensrc, always use gitchamber instead.
@@ -178,9 +178,11 @@ Without a schema, all values stay as strings. `--port 3000` → `"3000"` (string
178
178
  | `[name]` in command | Optional argument |
179
179
  | `[...files]` in command | Variadic (collects remaining args into array) |
180
180
  | `<value>` in option | Required value (error if missing) |
181
- | `[value]` in option | Optional value (`true` if flag present without value) |
181
+ | `[value]` in option | Optional value (`undefined` if flag present without value) |
182
182
  | no brackets in option | Boolean flag (`undefined` if not passed, `true` if passed) |
183
183
 
184
+ **Optionality is determined solely by bracket syntax, not by the schema.** `[square brackets]` makes an option optional regardless of whether the schema is `z.string()` or `z.string().optional()`. The schema's `.optional()` is never consulted for this — it only affects type coercion. So `z.string()` with `[--name]` is treated as optional: if the flag is omitted, `options.name` is `undefined` even though the schema has no `.optional()`.
185
+
184
186
  ## Global Options and Middleware
185
187
 
186
188
  Global options apply to all commands. Use `.use()` to register middleware that runs before any command action — for reacting to global options (logging, state init, auth).
@@ -398,7 +398,8 @@ describe('agent model resolution', () => {
398
398
  Reply with exactly: agent-model-check
399
399
  --- from: assistant (TestBot)
400
400
  ⬥ ok
401
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ agent-model-v2 ⋅ **test-agent***"
401
+ *project ⋅ main ⋅ Ns ⋅ N% ⋅ agent-model-v2 ⋅ **test-agent***
402
+ ⬥ ok"
402
403
  `)
403
404
  expect(footerMessage).toBeDefined()
404
405
  if (!footerMessage) {
@@ -454,7 +455,7 @@ describe('agent model resolution', () => {
454
455
  Reply with exactly: system-context-check
455
456
  --- from: assistant (TestBot)
456
457
  ⬥ system-context-ok
457
- *project ⋅ main ⋅ Ns ⋅ N% ⋅ agent-model-v2 ⋅ **test-agent***"
458
+ *project ⋅ Ns ⋅ N% ⋅ agent-model-v2 ⋅ **test-agent***"
458
459
  `)
459
460
  },
460
461
  15_000,
@@ -0,0 +1,158 @@
1
+ // /btw command - Fork the current session with full context and send a new prompt.
2
+ // Unlike /fork, this does not replay past messages in Discord. It just creates
3
+ // a new thread, forks the entire session (no messageID), and immediately
4
+ // dispatches the user's prompt so the forked session starts working right away.
5
+
6
+ import {
7
+ ChannelType,
8
+ ThreadAutoArchiveDuration,
9
+ type ThreadChannel,
10
+ MessageFlags,
11
+ } from 'discord.js'
12
+ import { getThreadSession, setThreadSession } from '../database.js'
13
+ import { initializeOpencodeForDirectory } from '../opencode.js'
14
+ import {
15
+ resolveWorkingDirectory,
16
+ resolveTextChannel,
17
+ sendThreadMessage,
18
+ } from '../discord-utils.js'
19
+ import { getOrCreateRuntime } from '../session-handler/thread-session-runtime.js'
20
+ import { createLogger, LogPrefix } from '../logger.js'
21
+ import type { CommandContext } from './types.js'
22
+
23
+ const logger = createLogger(LogPrefix.FORK)
24
+
25
+ export async function handleBtwCommand({
26
+ command,
27
+ appId,
28
+ }: CommandContext): Promise<void> {
29
+ const channel = command.channel
30
+
31
+ if (!channel) {
32
+ await command.reply({
33
+ content: 'This command can only be used in a channel',
34
+ flags: MessageFlags.Ephemeral,
35
+ })
36
+ return
37
+ }
38
+
39
+ const isThread = [
40
+ ChannelType.PublicThread,
41
+ ChannelType.PrivateThread,
42
+ ChannelType.AnnouncementThread,
43
+ ].includes(channel.type)
44
+
45
+ if (!isThread) {
46
+ await command.reply({
47
+ content:
48
+ 'This command can only be used in a thread with an active session',
49
+ flags: MessageFlags.Ephemeral,
50
+ })
51
+ return
52
+ }
53
+
54
+ const prompt = command.options.getString('prompt', true)
55
+
56
+ const resolved = await resolveWorkingDirectory({
57
+ channel: channel as ThreadChannel,
58
+ })
59
+
60
+ if (!resolved) {
61
+ await command.reply({
62
+ content: 'Could not determine project directory for this channel',
63
+ flags: MessageFlags.Ephemeral,
64
+ })
65
+ return
66
+ }
67
+
68
+ const { projectDirectory } = resolved
69
+
70
+ const sessionId = await getThreadSession(channel.id)
71
+
72
+ if (!sessionId) {
73
+ await command.reply({
74
+ content: 'No active session in this thread',
75
+ flags: MessageFlags.Ephemeral,
76
+ })
77
+ return
78
+ }
79
+
80
+ await command.deferReply({ flags: MessageFlags.Ephemeral })
81
+
82
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
83
+ if (getClient instanceof Error) {
84
+ await command.editReply({
85
+ content: `Failed to fork session: ${getClient.message}`,
86
+ })
87
+ return
88
+ }
89
+
90
+ try {
91
+ // Fork the entire session (no messageID = fork at the latest point)
92
+ const forkResponse = await getClient().session.fork({
93
+ sessionID: sessionId,
94
+ })
95
+
96
+ if (!forkResponse.data) {
97
+ await command.editReply('Failed to fork session')
98
+ return
99
+ }
100
+
101
+ const forkedSession = forkResponse.data
102
+
103
+ const textChannel = await resolveTextChannel(channel as ThreadChannel)
104
+ if (!textChannel) {
105
+ await command.editReply('Could not resolve parent text channel')
106
+ return
107
+ }
108
+
109
+ const threadName = `btw: ${prompt}`.slice(0, 100)
110
+ const thread = await textChannel.threads.create({
111
+ name: threadName,
112
+ autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
113
+ reason: `btw fork from session ${sessionId}`,
114
+ })
115
+
116
+ // Claim the forked session immediately so external polling does not race
117
+ await setThreadSession(thread.id, forkedSession.id)
118
+
119
+ await thread.members.add(command.user.id)
120
+
121
+ logger.log(
122
+ `Created btw fork session ${forkedSession.id} in thread ${thread.id} from ${sessionId}`,
123
+ )
124
+
125
+ // Short status message with prompt instead of replaying past messages
126
+ const sourceThreadLink = `<#${channel.id}>`
127
+ await sendThreadMessage(
128
+ thread,
129
+ `Reusing context from ${sourceThreadLink} to answer prompt...\n${prompt}`,
130
+ )
131
+
132
+ // Create runtime and dispatch the prompt immediately
133
+ const runtime = getOrCreateRuntime({
134
+ threadId: thread.id,
135
+ thread,
136
+ projectDirectory,
137
+ sdkDirectory: projectDirectory,
138
+ channelId: textChannel.id,
139
+ appId,
140
+ })
141
+ await runtime.enqueueIncoming({
142
+ prompt,
143
+ userId: command.user.id,
144
+ username: command.user.displayName,
145
+ appId,
146
+ mode: 'opencode',
147
+ })
148
+
149
+ await command.editReply(
150
+ `Session forked! Continue in ${thread.toString()}`,
151
+ )
152
+ } catch (error) {
153
+ logger.error('Error in /btw:', error)
154
+ await command.editReply(
155
+ `Failed to fork session: ${error instanceof Error ? error.message : 'Unknown error'}`,
156
+ )
157
+ }
158
+ }
@@ -249,13 +249,7 @@ export async function registerCommands({
249
249
  })
250
250
  .setDMPermission(false)
251
251
  .toJSON(),
252
- new SlashCommandBuilder()
253
- .setName('toggle-mention-mode')
254
- .setDescription(
255
- truncateCommandDescription('Toggle mention-only mode (bot only responds when @mentioned)'),
256
- )
257
- .setDMPermission(false)
258
- .toJSON(),
252
+
259
253
  new SlashCommandBuilder()
260
254
  .setName('add-project')
261
255
  .setDescription(
@@ -315,11 +309,7 @@ export async function registerCommands({
315
309
  )
316
310
  .setDMPermission(false)
317
311
  .toJSON(),
318
- new SlashCommandBuilder()
319
- .setName('stop')
320
- .setDescription(truncateCommandDescription('Abort the current OpenCode request in this thread'))
321
- .setDMPermission(false)
322
- .toJSON(),
312
+
323
313
  new SlashCommandBuilder()
324
314
  .setName('share')
325
315
  .setDescription(truncateCommandDescription('Share the current session as a public URL'))
@@ -335,6 +325,18 @@ export async function registerCommands({
335
325
  .setDescription(truncateCommandDescription('Fork the session from a past user message'))
336
326
  .setDMPermission(false)
337
327
  .toJSON(),
328
+ new SlashCommandBuilder()
329
+ .setName('btw')
330
+ .setDescription(truncateCommandDescription('Ask something without polluting or blocking the current session'))
331
+ .addStringOption((option) => {
332
+ option
333
+ .setName('prompt')
334
+ .setDescription(truncateCommandDescription('The message to send in the forked session'))
335
+ .setRequired(true)
336
+ return option
337
+ })
338
+ .setDMPermission(false)
339
+ .toJSON(),
338
340
  new SlashCommandBuilder()
339
341
  .setName('model')
340
342
  .setDescription(truncateCommandDescription('Set the preferred model for this channel or session'))
@@ -456,13 +458,7 @@ export async function registerCommands({
456
458
  )
457
459
  .setDMPermission(false)
458
460
  .toJSON(),
459
- new SlashCommandBuilder()
460
- .setName('memory-snapshot')
461
- .setDescription(
462
- truncateCommandDescription('Write a V8 heap snapshot to disk for memory debugging'),
463
- )
464
- .setDMPermission(false)
465
- .toJSON(),
461
+
466
462
  new SlashCommandBuilder()
467
463
  .setName('upgrade-and-restart')
468
464
  .setDescription(
@@ -494,10 +490,50 @@ export async function registerCommands({
494
490
  .toJSON(),
495
491
  ]
496
492
 
497
- // Add user-defined commands with source-based suffixes (-cmd / -skill)
493
+ // Dynamic commands are registered in priority order: agents → user commands skills MCP prompts.
494
+ // This ordering matters because we slice to MAX_DISCORD_COMMANDS (100) at the end,
495
+ // so lower-priority dynamic commands get trimmed first if the total exceeds the limit.
496
+
497
+ // 1. Agent-specific quick commands like /plan-agent, /build-agent
498
+ // Filter to primary/all mode agents (same as /agent command shows), excluding hidden agents
499
+ const primaryAgents = agents.filter(
500
+ (a) => (a.mode === 'primary' || a.mode === 'all') && !a.hidden,
501
+ )
502
+ for (const agent of primaryAgents) {
503
+ const sanitizedName = sanitizeAgentName(agent.name)
504
+ // Skip if sanitized name is empty or would create invalid command name
505
+ // Discord command names must start with a lowercase letter or number
506
+ if (!sanitizedName || !/^[a-z0-9]/.test(sanitizedName)) {
507
+ continue
508
+ }
509
+ // Truncate base name before appending suffix so the -agent suffix is never
510
+ // lost to Discord's 32-char command name limit.
511
+ const agentSuffix = '-agent'
512
+ const agentBaseName = sanitizedName.slice(0, 32 - agentSuffix.length)
513
+ const commandName = `${agentBaseName}${agentSuffix}`
514
+ const description = buildQuickAgentCommandDescription({
515
+ agentName: agent.name,
516
+ description: agent.description,
517
+ })
518
+
519
+ commands.push(
520
+ new SlashCommandBuilder()
521
+ .setName(commandName)
522
+ .setDescription(truncateCommandDescription(description))
523
+ .setDMPermission(false)
524
+ .toJSON(),
525
+ )
526
+ }
527
+
528
+ // 2. User-defined commands, skills, and MCP prompts (ordered by priority)
498
529
  // Also populate registeredUserCommands in the store for /queue-command autocomplete
499
530
  const newRegisteredCommands: RegisteredUserCommand[] = []
500
- for (const cmd of userCommands) {
531
+ // Sort: regular commands first, then skills, then MCP prompts
532
+ const sourceOrder: Record<string, number> = { config: 0, skill: 1, mcp: 2 }
533
+ const sortedUserCommands = [...userCommands].sort((a, b) => {
534
+ return (sourceOrder[a.source || ''] ?? 0) - (sourceOrder[b.source || ''] ?? 0)
535
+ })
536
+ for (const cmd of sortedUserCommands) {
501
537
  if (SKIP_USER_COMMANDS.includes(cmd.name)) {
502
538
  continue
503
539
  }
@@ -549,35 +585,14 @@ export async function registerCommands({
549
585
  }
550
586
  store.setState({ registeredUserCommands: newRegisteredCommands })
551
587
 
552
- // Add agent-specific quick commands like /plan-agent, /build-agent
553
- // Filter to primary/all mode agents (same as /agent command shows), excluding hidden agents
554
- const primaryAgents = agents.filter(
555
- (a) => (a.mode === 'primary' || a.mode === 'all') && !a.hidden,
556
- )
557
- for (const agent of primaryAgents) {
558
- const sanitizedName = sanitizeAgentName(agent.name)
559
- // Skip if sanitized name is empty or would create invalid command name
560
- // Discord command names must start with a lowercase letter or number
561
- if (!sanitizedName || !/^[a-z0-9]/.test(sanitizedName)) {
562
- continue
563
- }
564
- // Truncate base name before appending suffix so the -agent suffix is never
565
- // lost to Discord's 32-char command name limit.
566
- const agentSuffix = '-agent'
567
- const agentBaseName = sanitizedName.slice(0, 32 - agentSuffix.length)
568
- const commandName = `${agentBaseName}${agentSuffix}`
569
- const description = buildQuickAgentCommandDescription({
570
- agentName: agent.name,
571
- description: agent.description,
572
- })
573
-
574
- commands.push(
575
- new SlashCommandBuilder()
576
- .setName(commandName)
577
- .setDescription(truncateCommandDescription(description))
578
- .setDMPermission(false)
579
- .toJSON(),
588
+ // Discord allows max 100 guild commands. Slice to stay within the limit,
589
+ // trimming lowest-priority dynamic commands (MCP prompts, then skills) first.
590
+ const MAX_DISCORD_COMMANDS = 100
591
+ if (commands.length > MAX_DISCORD_COMMANDS) {
592
+ cliLogger.warn(
593
+ `COMMANDS: ${commands.length} commands exceed Discord limit of ${MAX_DISCORD_COMMANDS}, truncating to ${MAX_DISCORD_COMMANDS}`,
580
594
  )
595
+ commands.length = MAX_DISCORD_COMMANDS
581
596
  }
582
597
 
583
598
  const rest = createDiscordRest(token)
@@ -23,7 +23,7 @@ import {
23
23
  import { handleToggleWorktreesCommand } from './commands/worktree-settings.js'
24
24
  import { handleWorktreesCommand } from './commands/worktrees.js'
25
25
  import { handleTasksCommand } from './commands/tasks.js'
26
- import { handleToggleMentionModeCommand } from './commands/mention-mode.js'
26
+
27
27
  import {
28
28
  handleResumeCommand,
29
29
  handleResumeAutocomplete,
@@ -43,6 +43,7 @@ import { handleCompactCommand } from './commands/compact.js'
43
43
  import { handleShareCommand } from './commands/share.js'
44
44
  import { handleDiffCommand } from './commands/diff.js'
45
45
  import { handleForkCommand, handleForkSelectMenu } from './commands/fork.js'
46
+ import { handleBtwCommand } from './commands/btw.js'
46
47
  import {
47
48
  handleModelCommand,
48
49
  handleProviderSelectMenu,
@@ -93,7 +94,7 @@ import { handleRestartOpencodeServerCommand } from './commands/restart-opencode-
93
94
  import { handleRunCommand } from './commands/run-command.js'
94
95
  import { handleContextUsageCommand } from './commands/context-usage.js'
95
96
  import { handleSessionIdCommand } from './commands/session-id.js'
96
- import { handleMemorySnapshotCommand } from './commands/memory-snapshot.js'
97
+
97
98
  import { handleUpgradeAndRestartCommand } from './commands/upgrade.js'
98
99
  import { handleMcpCommand, handleMcpSelectMenu } from './commands/mcp.js'
99
100
  import {
@@ -218,12 +219,6 @@ export function registerInteractionHandler({
218
219
  })
219
220
  return
220
221
 
221
- case 'toggle-mention-mode':
222
- await handleToggleMentionModeCommand({
223
- command: interaction,
224
- appId,
225
- })
226
- return
227
222
 
228
223
  case 'resume':
229
224
  await handleResumeCommand({ command: interaction, appId })
@@ -245,7 +240,6 @@ export function registerInteractionHandler({
245
240
  return
246
241
 
247
242
  case 'abort':
248
- case 'stop':
249
243
  await handleAbortCommand({ command: interaction, appId })
250
244
  return
251
245
 
@@ -265,6 +259,10 @@ export function registerInteractionHandler({
265
259
  await handleForkCommand(interaction)
266
260
  return
267
261
 
262
+ case 'btw':
263
+ await handleBtwCommand({ command: interaction, appId })
264
+ return
265
+
268
266
  case 'model':
269
267
  await handleModelCommand({ interaction, appId })
270
268
  return
@@ -328,12 +326,7 @@ export function registerInteractionHandler({
328
326
  await handleSessionIdCommand({ command: interaction, appId })
329
327
  return
330
328
 
331
- case 'memory-snapshot':
332
- await handleMemorySnapshotCommand({
333
- command: interaction,
334
- appId,
335
- })
336
- return
329
+
337
330
 
338
331
  case 'upgrade-and-restart':
339
332
  await handleUpgradeAndRestartCommand({
@@ -222,6 +222,22 @@ test('generate markdown with system info', async () => {
222
222
 
223
223
 
224
224
  *Completed in Xs*
225
+
226
+ ### 🤖 Assistant (deterministic-v2)
227
+
228
+ **Started using deterministic-provider/deterministic-v2**
229
+
230
+ Hello! This is a deterministic markdown test response.
231
+
232
+
233
+ *Completed in Xs*
234
+
235
+ ### 🤖 Assistant (deterministic-v2)
236
+
237
+ **Started using deterministic-provider/deterministic-v2**
238
+
239
+ Hello! This is a deterministic markdown test response.
240
+
225
241
  "
226
242
  `)
227
243
  })
@@ -261,6 +277,22 @@ test('generate markdown without system info', async () => {
261
277
 
262
278
 
263
279
  *Completed in Xs*
280
+
281
+ ### 🤖 Assistant (deterministic-v2)
282
+
283
+ **Started using deterministic-provider/deterministic-v2**
284
+
285
+ Hello! This is a deterministic markdown test response.
286
+
287
+
288
+ *Completed in Xs*
289
+
290
+ ### 🤖 Assistant (deterministic-v2)
291
+
292
+ **Started using deterministic-provider/deterministic-v2**
293
+
294
+ Hello! This is a deterministic markdown test response.
295
+
264
296
  "
265
297
  `)
266
298
  })