@subrouter/opencode 0.3.0 → 0.5.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.
@@ -1,9 +1,12 @@
1
+ import childProcess from 'node:child_process'
1
2
  import { createServer, type Server } from 'node:http'
2
3
  import { mkdtemp, rm } from 'node:fs/promises'
3
4
  import { tmpdir } from 'node:os'
4
5
  import path from 'node:path'
6
+ import util from 'node:util'
5
7
  import { afterEach, beforeEach, expect, test } from 'vitest'
6
8
  import type { Config, PluginInput } from '@opencode-ai/plugin'
9
+ import { createOpencodeClient } from '@opencode-ai/sdk'
7
10
  import {
8
11
  addAccount,
9
12
  adapters,
@@ -11,11 +14,19 @@ import {
11
14
  PROVIDER_DISPLAY_NAME,
12
15
  PROVIDER_IDS,
13
16
  savePreset,
14
- setSubrouterLog,
17
+ setLiveRoute,
15
18
  } from '@subrouter/cli'
16
19
  import { subrouterAuthPlugin, subrouterPlugin } from './index.ts'
17
- import { revealRoutedModel, rewritePoweredByModelLine } from './provider.ts'
18
-
20
+ import {
21
+ appendApplyPatchConstraint,
22
+ applyPatchApiId,
23
+ applyPatchConstraint,
24
+ revealRoutedModel,
25
+ rewritePoweredByModelLine,
26
+ shouldUseApplyPatch,
27
+ } from './provider.ts'
28
+
29
+ const execFile = util.promisify(childProcess.execFile)
19
30
  let home: string
20
31
  const openServers: Server[] = []
21
32
  const pluginInput = {} as PluginInput
@@ -27,8 +38,6 @@ beforeEach(async () => {
27
38
  })
28
39
 
29
40
  afterEach(async () => {
30
- setSubrouterLog(undefined)
31
- delete process.env.SUBROUTER_HOME
32
41
  delete process.env.SUBROUTER_OPENAI_ISSUER_URL
33
42
  delete process.env.SUBROUTER_MODELS_DEV_URL
34
43
  for (const server of openServers.splice(0)) {
@@ -49,13 +58,18 @@ async function startFakeModelsDev(
49
58
  string,
50
59
  {
51
60
  id: string
52
- modalities?: { output?: string[] }
53
- limit?: { context?: number; output?: number; input?: number }
54
- }
55
- >
56
- }
57
- > = {},
58
- ) {
61
+ attachment?: boolean
62
+ reasoning?: boolean
63
+ temperature?: boolean
64
+ tool_call?: boolean
65
+ modalities?: { input?: string[]; output?: string[] }
66
+ limit?: { context?: number; output?: number; input?: number }
67
+ reasoning_options?: Array<{ type: string; values?: Array<string | null> }>
68
+ }
69
+ >
70
+ }
71
+ > = {},
72
+ ) {
59
73
  const payload = {
60
74
  anthropic: { models: {} },
61
75
  openai: { models: {} },
@@ -126,6 +140,111 @@ function authMethod() {
126
140
  })
127
141
  }
128
142
 
143
+ test('plugin load and config do not write stdout or stderr', async () => {
144
+ const script = "import('./src/index.ts').then(async ({ subrouterPlugin }) => { const hooks = await subrouterPlugin({}); await hooks.config?.({}) })"
145
+ const result = await execFile(process.execPath, ['--no-warnings', '--import', 'tsx', '--eval', script], {
146
+ cwd: process.cwd(),
147
+ env: process.env,
148
+ })
149
+ expect(result).toEqual({ stdout: '', stderr: '' })
150
+ })
151
+
152
+ test('provider log callback forwards only to client.app.log', async () => {
153
+ const received = Promise.withResolvers<object>()
154
+ const server = createServer((req, res) => {
155
+ const chunks: Buffer[] = []
156
+ req.on('data', (chunk: Buffer) => {
157
+ chunks.push(chunk)
158
+ })
159
+ req.on('end', () => {
160
+ received.resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')) as object)
161
+ res.writeHead(200, { 'Content-Type': 'application/json' })
162
+ res.end('{}')
163
+ })
164
+ })
165
+ await new Promise<void>((resolve) => {
166
+ server.listen(0, '127.0.0.1', resolve)
167
+ })
168
+ openServers.push(server)
169
+ const address = server.address()
170
+ if (typeof address === 'string' || !address) throw new Error('failed to bind log server')
171
+ const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${address.port}` })
172
+ const hooks = await subrouterPlugin({ ...pluginInput, client })
173
+ const config: Config = {}
174
+ await hooks.config?.(config)
175
+ const log = config.provider?.subrouter?.options?.log
176
+ expect(log).toBeTypeOf('function')
177
+ if (typeof log !== 'function') throw new Error('expected provider log callback')
178
+ await log({
179
+ level: 'warn',
180
+ message: 'failover openai/gpt-5.5',
181
+ extra: { provider: 'openai', modelId: 'gpt-5.5' },
182
+ })
183
+ expect(await received.promise).toEqual({
184
+ service: 'subrouter',
185
+ level: 'warn',
186
+ message: 'failover openai/gpt-5.5',
187
+ extra: { provider: 'openai', modelId: 'gpt-5.5' },
188
+ })
189
+ })
190
+
191
+ test('cooldown fallback persists one ignored notice during the active run', async () => {
192
+ const requests: Array<{ path: string; body: Record<string, unknown> }> = []
193
+ const server = createServer((req, res) => {
194
+ const chunks: Buffer[] = []
195
+ req.on('data', (chunk: Buffer) => chunks.push(chunk))
196
+ req.on('end', () => {
197
+ requests.push({
198
+ path: req.url ?? '',
199
+ body: JSON.parse(Buffer.concat(chunks).toString('utf8')),
200
+ })
201
+ res.writeHead(200, { 'Content-Type': 'application/json' })
202
+ res.end('{}')
203
+ })
204
+ })
205
+ await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve))
206
+ openServers.push(server)
207
+ const address = server.address()
208
+ if (typeof address === 'string' || !address) throw new Error('failed to bind notification server')
209
+ const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${address.port}` })
210
+ const hooks = await subrouterPlugin({ ...pluginInput, client, directory: '/tmp/project' })
211
+ const config: Config = {}
212
+ await hooks.config?.(config)
213
+ const onCooldownFallback = config.provider?.subrouter?.options?.onCooldownFallback
214
+ expect(onCooldownFallback).toBeTypeOf('function')
215
+ if (typeof onCooldownFallback !== 'function') throw new Error('expected cooldown callback')
216
+ const notice = {
217
+ sessionID: 'session-1',
218
+ agent: 'build',
219
+ variant: 'high',
220
+ preset: 'work',
221
+ preferred: { provider: 'xai', modelId: 'grok-4.6', retryAfterMs: 250_000 },
222
+ active: { provider: 'openai', modelId: 'gpt-5.6-sol' },
223
+ } as const
224
+
225
+ await onCooldownFallback(notice)
226
+ await onCooldownFallback(notice)
227
+
228
+ expect(requests).toEqual([
229
+ {
230
+ path: '/session/session-1/message?directory=%2Ftmp%2Fproject',
231
+ body: {
232
+ noReply: true,
233
+ agent: 'build',
234
+ model: { providerID: 'subrouter', modelID: 'work' },
235
+ variant: 'high',
236
+ parts: [
237
+ {
238
+ type: 'text',
239
+ text: 'Subrouter: Using openai/gpt-5.6-sol because xai/grok-4.6 is rate limited.',
240
+ ignored: true,
241
+ },
242
+ ],
243
+ },
244
+ },
245
+ ])
246
+ })
247
+
129
248
  test('config hook registers the subrouter provider with preset models', async () => {
130
249
  await savePreset({ name: 'work', models: ['anthropic/claude-opus-4-6'] })
131
250
 
@@ -146,7 +265,7 @@ test('config hook registers the subrouter provider with preset models', async ()
146
265
  expect(provider.models.work.limit).toEqual({ context: 200_000, output: 64_000 })
147
266
  })
148
267
 
149
- test('preset model names show the first live candidate', async () => {
268
+ test('preset model names stay stable when the routed candidate changes', async () => {
150
269
  await addAccount({
151
270
  provider: 'anthropic',
152
271
  account: {
@@ -166,7 +285,7 @@ test('preset model names show the first live candidate', async () => {
166
285
  await hooks.config?.(config)
167
286
 
168
287
  expect(config.provider?.subrouter?.name).toBe(PROVIDER_DISPLAY_NAME)
169
- expect(config.provider?.subrouter?.models?.work?.name).toBe('work (claude-opus-4-6)')
288
+ expect(config.provider?.subrouter?.models?.work?.name).toBe('work')
170
289
  })
171
290
 
172
291
  test('preset model limits follow the first live candidate', async () => {
@@ -177,8 +296,8 @@ test('preset model limits follow the first live candidate', async () => {
177
296
  models: {
178
297
  [modelId]: {
179
298
  id: modelId,
180
- modalities: { output: ['text'] },
181
- limit: { context: 1_000_000, output: 128_000 },
299
+ modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
300
+ limit: { context: 1_000_000, input: 900_000, output: 128_000 },
182
301
  },
183
302
  },
184
303
  },
@@ -203,20 +322,133 @@ test('preset model limits follow the first live candidate', async () => {
203
322
 
204
323
  expect(config.provider?.subrouter?.models?.work?.limit).toEqual({
205
324
  context: 1_000_000,
325
+ input: 900_000,
206
326
  output: 128_000,
207
327
  })
208
- expect(config.provider?.subrouter?.models?.default?.limit).toEqual({
209
- context: 1_000_000,
210
- output: 128_000,
328
+ })
329
+
330
+ test('preset reasoning follows the first live candidate', async () => {
331
+ const modelId = adapters.openai.defaultModels[0]
332
+ if (!modelId) throw new Error('openai adapter has no default model')
333
+ process.env.SUBROUTER_MODELS_DEV_URL = await startFakeModelsDev({
334
+ openai: {
335
+ models: {
336
+ [modelId]: {
337
+ id: modelId,
338
+ reasoning: true,
339
+ modalities: { input: ['text'], output: ['text'] },
340
+ limit: { context: 200_000, output: 64_000 },
341
+ },
342
+ },
343
+ },
344
+ })
345
+ await addAccount({
346
+ provider: 'openai',
347
+ account: {
348
+ type: 'oauth',
349
+ refresh: 'refresh-1',
350
+ access: 'access-1',
351
+ expires: Date.now() + 60_000,
352
+ email: 'a@x.com',
353
+ addedAt: 1,
354
+ lastUsed: 1,
355
+ },
356
+ })
357
+ await savePreset({ name: 'work', models: [`openai/${modelId}`] })
358
+
359
+ const hooks = await subrouterPlugin(pluginInput)
360
+ const config: Config = {}
361
+ await hooks.config?.(config)
362
+
363
+ expect(config.provider?.subrouter?.models?.work?.reasoning).toBe(true)
364
+ })
365
+
366
+ test('preset variants follow the first live candidate', async () => {
367
+ const modelId = adapters.openai.defaultModels[0]
368
+ if (!modelId) throw new Error('openai adapter has no default model')
369
+ process.env.SUBROUTER_MODELS_DEV_URL = await startFakeModelsDev({
370
+ openai: {
371
+ models: {
372
+ [modelId]: {
373
+ id: modelId,
374
+ reasoning: true,
375
+ modalities: { input: ['text'], output: ['text'] },
376
+ reasoning_options: [{ type: 'effort', values: ['none', 'low', 'medium', 'high'] }],
377
+ },
378
+ },
379
+ },
380
+ })
381
+ await addAccount({
382
+ provider: 'openai',
383
+ account: {
384
+ type: 'oauth',
385
+ refresh: 'refresh-1',
386
+ access: 'access-1',
387
+ expires: Date.now() + 60_000,
388
+ email: 'a@x.com',
389
+ addedAt: 1,
390
+ lastUsed: 1,
391
+ },
392
+ })
393
+ await savePreset({ name: 'work', models: [`openai/${modelId}#high`] })
394
+
395
+ const hooks = await subrouterPlugin(pluginInput)
396
+ const config: Config = {}
397
+ await hooks.config?.(config)
398
+
399
+ expect(config.provider?.subrouter?.models?.work).toMatchObject({
400
+ variants: {
401
+ none: { reasoningEffort: 'none', reasoningSummary: 'auto' },
402
+ low: { reasoningEffort: 'low', reasoningSummary: 'auto' },
403
+ medium: { reasoningEffort: 'medium', reasoningSummary: 'auto' },
404
+ high: { reasoningEffort: 'high', reasoningSummary: 'auto' },
405
+ },
211
406
  })
212
407
  })
213
408
 
214
- test('preset models permit image and PDF attachments', async () => {
409
+ test('preset model input modalities are the union of usable candidates', async () => {
410
+ process.env.SUBROUTER_MODELS_DEV_URL = await startFakeModelsDev({
411
+ openai: {
412
+ models: {
413
+ 'gpt-pdf': {
414
+ id: 'gpt-pdf',
415
+ attachment: true,
416
+ modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
417
+ },
418
+ },
419
+ },
420
+ 'kimi-for-coding': {
421
+ models: {
422
+ 'kimi-image': {
423
+ id: 'kimi-image',
424
+ attachment: true,
425
+ modalities: { input: ['text', 'image'], output: ['text'] },
426
+ },
427
+ },
428
+ },
429
+ })
430
+ await addAccount({
431
+ provider: 'openai',
432
+ account: {
433
+ type: 'oauth',
434
+ access: 'access-1',
435
+ refresh: 'refresh-1',
436
+ expires: Date.now() + 60_000,
437
+ addedAt: 1,
438
+ lastUsed: 1,
439
+ },
440
+ })
441
+ await addAccount({
442
+ provider: 'kimi',
443
+ account: { type: 'api', key: 'kimi-key', addedAt: 1, lastUsed: 1 },
444
+ })
445
+ await savePreset({ name: 'work', models: ['openai/gpt-pdf', 'kimi/kimi-image'] })
446
+
215
447
  const hooks = await subrouterPlugin(pluginInput)
216
448
  const config: Config = {}
217
449
  await hooks.config?.(config)
218
450
 
219
- expect(config.provider?.subrouter?.models?.default).toMatchObject({
451
+ expect(config.provider?.subrouter?.models?.work).toMatchObject({
220
452
  attachment: true,
221
453
  modalities: {
222
454
  input: ['text', 'image', 'pdf'],
@@ -225,6 +457,156 @@ test('preset models permit image and PDF attachments', async () => {
225
457
  })
226
458
  })
227
459
 
460
+ test('xAI presets do not advertise inline PDF support that its SDK cannot encode', async () => {
461
+ process.env.SUBROUTER_MODELS_DEV_URL = await startFakeModelsDev({
462
+ xai: {
463
+ models: {
464
+ 'grok-pdf': {
465
+ id: 'grok-pdf',
466
+ attachment: true,
467
+ modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
468
+ },
469
+ },
470
+ },
471
+ })
472
+ await addAccount({
473
+ provider: 'xai',
474
+ account: {
475
+ type: 'oauth',
476
+ access: 'access-1',
477
+ refresh: 'refresh-1',
478
+ expires: Date.now() + 60_000,
479
+ addedAt: 1,
480
+ lastUsed: 1,
481
+ },
482
+ })
483
+ await savePreset({ name: 'work', models: ['xai/grok-pdf'] })
484
+
485
+ const hooks = await subrouterPlugin(pluginInput)
486
+ const config: Config = {}
487
+ await hooks.config?.(config)
488
+
489
+ expect(config.provider?.subrouter?.models?.work).toMatchObject({
490
+ attachment: true,
491
+ modalities: {
492
+ input: ['text', 'image'],
493
+ output: ['text'],
494
+ },
495
+ })
496
+ })
497
+
498
+ test('Anthropic-compatible coding plans do not advertise video input', async () => {
499
+ process.env.SUBROUTER_MODELS_DEV_URL = await startFakeModelsDev({
500
+ 'kimi-for-coding': {
501
+ models: {
502
+ 'kimi-video': {
503
+ id: 'kimi-video',
504
+ attachment: true,
505
+ modalities: { input: ['text', 'image', 'video'], output: ['text'] },
506
+ },
507
+ },
508
+ },
509
+ })
510
+ await addAccount({
511
+ provider: 'kimi',
512
+ account: { type: 'api', key: 'kimi-key', addedAt: 1, lastUsed: 1 },
513
+ })
514
+ await savePreset({ name: 'work', models: ['kimi/kimi-video'] })
515
+
516
+ const hooks = await subrouterPlugin(pluginInput)
517
+ const config: Config = {}
518
+ await hooks.config?.(config)
519
+
520
+ expect(config.provider?.subrouter?.models?.work).toMatchObject({
521
+ attachment: true,
522
+ modalities: {
523
+ input: ['text', 'image'],
524
+ output: ['text'],
525
+ },
526
+ })
527
+ })
528
+
529
+ test('OpenCode apply_patch matcher follows gpt- and skips gpt-4 and gpt-oss', () => {
530
+ expect(shouldUseApplyPatch('gpt-5.5')).toBe(true)
531
+ expect(shouldUseApplyPatch('gpt-5.4')).toBe(true)
532
+ expect(shouldUseApplyPatch('gpt-5.3-codex')).toBe(true)
533
+ expect(shouldUseApplyPatch('gpt-4.1')).toBe(false)
534
+ expect(shouldUseApplyPatch('gpt-oss-120b')).toBe(false)
535
+ expect(shouldUseApplyPatch('claude-opus-4-6')).toBe(false)
536
+ })
537
+
538
+ test('apply_patch api ids stay unique across presets that share a model', () => {
539
+ const taken = new Set<string>()
540
+ const first = applyPatchApiId({ preset: 'codex-a', modelId: 'gpt-5.5', taken })
541
+ taken.add(first)
542
+ const second = applyPatchApiId({ preset: 'codex-b', modelId: 'gpt-5.5', taken })
543
+ expect(first).toBe('gpt-5.5')
544
+ expect(second).toBe('gpt-5.5:codex-b')
545
+ expect(shouldUseApplyPatch(second)).toBe(true)
546
+ })
547
+
548
+ test('appendApplyPatchConstraint adds the OpenCode GPT line once', () => {
549
+ const system = ['You are powered by the model named gpt-5.5.']
550
+ appendApplyPatchConstraint({ system, modelId: 'gpt-5.5' })
551
+ appendApplyPatchConstraint({ system, modelId: 'gpt-5.5' })
552
+ expect(system).toEqual(['You are powered by the model named gpt-5.5.', applyPatchConstraint('gpt-5.5')])
553
+ })
554
+
555
+ test('appendApplyPatchConstraint skips non-GPT models', () => {
556
+ const system = ['You are powered by the model named claude-opus-4-6.']
557
+ appendApplyPatchConstraint({ system, modelId: 'claude-opus-4-6' })
558
+ expect(system).toEqual(['You are powered by the model named claude-opus-4-6.'])
559
+ })
560
+
561
+ test('openai live candidate spoofs a gpt api id so OpenCode prefers apply_patch', async () => {
562
+ await addAccount({
563
+ provider: 'anthropic',
564
+ account: {
565
+ type: 'oauth',
566
+ refresh: 'refresh-1',
567
+ access: 'access-1',
568
+ expires: Date.now() + 60_000,
569
+ email: 'a@x.com',
570
+ addedAt: 1,
571
+ lastUsed: 1,
572
+ },
573
+ })
574
+ await addAccount({
575
+ provider: 'openai',
576
+ account: {
577
+ type: 'oauth',
578
+ refresh: 'refresh-o',
579
+ access: 'access-o',
580
+ expires: Date.now() + 60_000,
581
+ email: 'o@x.com',
582
+ addedAt: 1,
583
+ lastUsed: 1,
584
+ },
585
+ })
586
+ await savePreset({ name: 'default', models: ['anthropic/claude-opus-4-6'] })
587
+ await savePreset({ name: 'openai-only', models: ['openai/gpt-5.5'] })
588
+ await savePreset({ name: 'codex-b', models: ['openai/gpt-5.5'] })
589
+
590
+ const hooks = await subrouterPlugin(pluginInput)
591
+ const config: Config = {}
592
+ await hooks.config?.(config)
593
+
594
+ expect(config.provider?.subrouter?.models?.work).toBeUndefined()
595
+ expect(config.provider?.subrouter?.models?.['openai-only']).toMatchObject({
596
+ name: 'openai-only',
597
+ id: 'gpt-5.5',
598
+ })
599
+ expect(config.provider?.subrouter?.models?.['codex-b']).toMatchObject({
600
+ name: 'codex-b',
601
+ id: 'gpt-5.5:codex-b',
602
+ })
603
+ expect(config.provider?.subrouter?.models?.default?.id).toBeUndefined()
604
+ expect(config.provider?.subrouter?.options?.presetByApiId).toEqual({
605
+ 'gpt-5.5': 'openai-only',
606
+ 'gpt-5.5:codex-b': 'codex-b',
607
+ })
608
+ })
609
+
228
610
  test('rewrites the OpenCode powered-by line to the routed candidate', () => {
229
611
  const system = [
230
612
  'You are powered by the model named build. The exact model ID is subrouter/build\n<env>\n Working directory: /tmp\n</env>',
@@ -263,6 +645,84 @@ test('system transform rewrites the powered-by line to the live candidate', asyn
263
645
  expect(system[0]).not.toContain('subrouter/build')
264
646
  })
265
647
 
648
+ test('system transform uses the in-flight session route, not the first free preset model', async () => {
649
+ await addAccount({
650
+ provider: 'anthropic',
651
+ account: {
652
+ type: 'oauth',
653
+ refresh: 'refresh-1',
654
+ access: 'access-1',
655
+ expires: Date.now() + 60_000,
656
+ email: 'a@x.com',
657
+ addedAt: 1,
658
+ lastUsed: 1,
659
+ },
660
+ })
661
+ await addAccount({
662
+ provider: 'opencode-go',
663
+ account: { type: 'api', key: 'zen-key', addedAt: 1, lastUsed: 1 },
664
+ })
665
+ await savePreset({
666
+ name: 'default',
667
+ models: ['anthropic/claude-fake', 'opencode-go/fake-model'],
668
+ })
669
+ await setLiveRoute({
670
+ sessionID: 'ses_1',
671
+ preset: 'default',
672
+ provider: 'opencode-go',
673
+ modelId: 'fake-model',
674
+ })
675
+
676
+ const system = [
677
+ 'You are powered by the model named default. The exact model ID is subrouter/default',
678
+ ]
679
+ await revealRoutedModel({
680
+ providerID: 'subrouter',
681
+ preset: 'default',
682
+ sessionID: 'ses_1',
683
+ system,
684
+ })
685
+
686
+ expect(system[0]).toContain('You are powered by the model named fake-model.')
687
+ expect(system[0]).toContain('The exact model ID is opencode-go/fake-model')
688
+ expect(system[0]).not.toContain('claude-fake')
689
+ })
690
+
691
+ test('system transform appends the apply_patch constraint for a live GPT route', async () => {
692
+ await addAccount({
693
+ provider: 'openai',
694
+ account: {
695
+ type: 'oauth',
696
+ refresh: 'refresh-o',
697
+ access: 'access-o',
698
+ expires: Date.now() + 60_000,
699
+ email: 'o@x.com',
700
+ addedAt: 1,
701
+ lastUsed: 1,
702
+ },
703
+ })
704
+ await savePreset({ name: 'openai-only', models: ['openai/gpt-5.5'] })
705
+ await setLiveRoute({
706
+ sessionID: 'ses_gpt',
707
+ preset: 'openai-only',
708
+ provider: 'openai',
709
+ modelId: 'gpt-5.5',
710
+ })
711
+
712
+ const system = [
713
+ 'You are powered by the model named openai-only. The exact model ID is subrouter/openai-only',
714
+ ]
715
+ await revealRoutedModel({
716
+ providerID: 'subrouter',
717
+ preset: 'openai-only',
718
+ sessionID: 'ses_gpt',
719
+ system,
720
+ })
721
+
722
+ expect(system[0]).toContain('You are powered by the model named gpt-5.5.')
723
+ expect(system).toContain(applyPatchConstraint('gpt-5.5'))
724
+ })
725
+
266
726
  test('system transform leaves other providers unchanged', async () => {
267
727
  const original =
268
728
  'You are powered by the model named claude-opus-4-6. The exact model ID is anthropic/claude-opus-4-6'