From cd825e5993b71b4aeafdfeaa96e82d3be71fb6a0 Mon Sep 17 00:00:00 2001 From: leecoder Date: Tue, 11 Aug 2026 09:24:41 +0900 Subject: [PATCH 1/4] feat(models): add per-model context window size lookup table - Add MODEL_CONTEXT_WINDOWS map with explicit sizes per model - getContextWindowSize() checks map first, falls back to isLongContextModel - Add test coverage for context window size resolution --- src/__tests__/models.test.ts | 18 ++++++++++++++++++ src/plugin/models.ts | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/__tests__/models.test.ts diff --git a/src/__tests__/models.test.ts b/src/__tests__/models.test.ts new file mode 100644 index 0000000..a01c262 --- /dev/null +++ b/src/__tests__/models.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, test } from 'bun:test' +import { getContextWindowSize } from '../plugin/models.js' + +describe('getContextWindowSize', () => { + test('uses the configured long context size for standard model aliases', () => { + expect(getContextWindowSize('claude-sonnet-4-6')).toBe(1_000_000) + expect(getContextWindowSize('claude-opus-4-6')).toBe(1_000_000) + }) + + test('preserves the standard context size for standard models', () => { + expect(getContextWindowSize('claude-sonnet-4-5')).toBe(200_000) + expect(getContextWindowSize('deepseek-3.2')).toBe(128_000) + }) + + test('supports explicit 1m model aliases', () => { + expect(getContextWindowSize('claude-sonnet-4-6-1m')).toBe(1_000_000) + }) +}) diff --git a/src/plugin/models.ts b/src/plugin/models.ts index c9aac7a..63590d6 100644 --- a/src/plugin/models.ts +++ b/src/plugin/models.ts @@ -1,5 +1,24 @@ import { MODEL_MAPPING, SUPPORTED_MODELS, isLongContextModel } from '../constants' +const MODEL_CONTEXT_WINDOWS = { + auto: 200_000, + 'claude-sonnet-4': 200_000, + 'claude-sonnet-4-5': 200_000, + 'claude-sonnet-4-6': 1_000_000, + 'claude-sonnet-5': 1_000_000, + 'claude-haiku-4-5': 200_000, + 'claude-opus-4-5': 200_000, + 'claude-opus-4-6': 1_000_000, + 'claude-opus-4-7': 1_000_000, + 'claude-opus-4-8': 1_000_000, + 'claude-opus-4-8-thinking': 1_000_000, + 'deepseek-3.2': 128_000, + 'glm-5': 200_000, + 'minimax-m2.5': 200_000, + 'minimax-m2.1': 200_000, + 'qwen3-coder-next': 256_000 +} as const + export function resolveKiroModel(model: string): string { const resolved = MODEL_MAPPING[model] if (!resolved) { @@ -9,5 +28,8 @@ export function resolveKiroModel(model: string): string { } export function getContextWindowSize(model: string): number { + if (model in MODEL_CONTEXT_WINDOWS) { + return MODEL_CONTEXT_WINDOWS[model as keyof typeof MODEL_CONTEXT_WINDOWS] + } return isLongContextModel(model) ? 1000000 : 200000 } From 2ce59b4f82946dc08361a0395240f7e095f09c2a Mon Sep 17 00:00:00 2001 From: leecoder Date: Tue, 11 Aug 2026 09:50:42 +0900 Subject: [PATCH 2/4] feat(models): discover context windows from Kiro catalog Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/__tests__/models.test.ts | 46 +++++++++---- src/plugin/models.ts | 127 ++++++++++++++++++++++++++++------- 2 files changed, 138 insertions(+), 35 deletions(-) diff --git a/src/__tests__/models.test.ts b/src/__tests__/models.test.ts index a01c262..b8f83e8 100644 --- a/src/__tests__/models.test.ts +++ b/src/__tests__/models.test.ts @@ -1,18 +1,40 @@ import { describe, expect, test } from 'bun:test' -import { getContextWindowSize } from '../plugin/models.js' +import { + clearContextWindowCatalog, + getContextWindowSize, + refreshContextWindowSizes +} from '../plugin/models.js' +import type { KiroAuthDetails } from '../plugin/types.js' -describe('getContextWindowSize', () => { - test('uses the configured long context size for standard model aliases', () => { - expect(getContextWindowSize('claude-sonnet-4-6')).toBe(1_000_000) - expect(getContextWindowSize('claude-opus-4-6')).toBe(1_000_000) - }) +const auth: KiroAuthDetails = { + access: 'test-token', + refresh: 'test-refresh', + expires: Date.now() + 60_000, + authMethod: 'idc', + region: 'us-east-1' +} - test('preserves the standard context size for standard models', () => { - expect(getContextWindowSize('claude-sonnet-4-5')).toBe(200_000) - expect(getContextWindowSize('deepseek-3.2')).toBe(128_000) - }) +describe('getContextWindowSize', () => { + test('uses maxInputTokens from the live model catalog for aliases', async () => { + const originalFetch = globalThis.fetch + globalThis.fetch = (async () => + new Response( + JSON.stringify({ + models: [ + { modelId: 'claude-sonnet-4.6', tokenLimits: { maxInputTokens: 1_000_000 } }, + { modelId: 'deepseek-3.2', tokenLimits: { maxInputTokens: 164_000 } } + ] + }), + { status: 200 } + )) as typeof fetch - test('supports explicit 1m model aliases', () => { - expect(getContextWindowSize('claude-sonnet-4-6-1m')).toBe(1_000_000) + try { + await refreshContextWindowSizes(auth) + expect(getContextWindowSize('claude-sonnet-4-6')).toBe(1_000_000) + expect(getContextWindowSize('deepseek-3.2')).toBe(164_000) + } finally { + globalThis.fetch = originalFetch + clearContextWindowCatalog() + } }) }) diff --git a/src/plugin/models.ts b/src/plugin/models.ts index 63590d6..2043946 100644 --- a/src/plugin/models.ts +++ b/src/plugin/models.ts @@ -1,23 +1,28 @@ -import { MODEL_MAPPING, SUPPORTED_MODELS, isLongContextModel } from '../constants' - -const MODEL_CONTEXT_WINDOWS = { - auto: 200_000, - 'claude-sonnet-4': 200_000, - 'claude-sonnet-4-5': 200_000, - 'claude-sonnet-4-6': 1_000_000, - 'claude-sonnet-5': 1_000_000, - 'claude-haiku-4-5': 200_000, - 'claude-opus-4-5': 200_000, - 'claude-opus-4-6': 1_000_000, - 'claude-opus-4-7': 1_000_000, - 'claude-opus-4-8': 1_000_000, - 'claude-opus-4-8-thinking': 1_000_000, - 'deepseek-3.2': 128_000, - 'glm-5': 200_000, - 'minimax-m2.5': 200_000, - 'minimax-m2.1': 200_000, - 'qwen3-coder-next': 256_000 -} as const +import { + extractRegionFromArn, + isLongContextModel, + MODEL_MAPPING, + SUPPORTED_MODELS +} from '../constants' +import type { KiroAuthDetails } from './types' + +const MODEL_CATALOG_TTL_MS = 5 * 60 * 1000 +const DEFAULT_CONTEXT_WINDOW = 200_000 + +type ModelCatalogResponse = { + models?: Array<{ + modelId?: string + tokenLimits?: { maxInputTokens?: number } + }> +} + +type CatalogCacheEntry = { + expiresAt: number + contextWindows: Map +} + +const catalogCache = new Map() +let activeContextWindows = new Map() export function resolveKiroModel(model: string): string { const resolved = MODEL_MAPPING[model] @@ -28,8 +33,84 @@ export function resolveKiroModel(model: string): string { } export function getContextWindowSize(model: string): number { - if (model in MODEL_CONTEXT_WINDOWS) { - return MODEL_CONTEXT_WINDOWS[model as keyof typeof MODEL_CONTEXT_WINDOWS] + return ( + activeContextWindows.get(model) ?? + (isLongContextModel(model) ? 1_000_000 : DEFAULT_CONTEXT_WINDOW) + ) +} + +function getCatalogRegion(auth: KiroAuthDetails): string { + return extractRegionFromArn(auth.profileArn) ?? auth.region ?? 'us-east-1' +} + +function applyCatalog(contextWindows: Map): void { + activeContextWindows = new Map(contextWindows) +} + +function parseModelCatalog(data: ModelCatalogResponse): Map { + const contextWindows = new Map() + + for (const model of data.models ?? []) { + const modelId = model.modelId + const maxInputTokens = model.tokenLimits?.maxInputTokens + if ( + !modelId || + typeof maxInputTokens !== 'number' || + !Number.isInteger(maxInputTokens) || + maxInputTokens <= 0 + ) + continue + + contextWindows.set(modelId, maxInputTokens) + for (const [alias, resolved] of Object.entries(MODEL_MAPPING)) { + if (resolved === modelId) contextWindows.set(alias, maxInputTokens) + } } - return isLongContextModel(model) ? 1000000 : 200000 + + return contextWindows +} + +export async function refreshContextWindowSizes(auth: KiroAuthDetails): Promise { + const cacheKey = `${auth.access}:${getCatalogRegion(auth)}` + const cached = catalogCache.get(cacheKey) + if (cached && cached.expiresAt > Date.now()) { + applyCatalog(cached.contextWindows) + return + } + + const regions = [getCatalogRegion(auth)] + if (!regions.includes('us-east-1')) regions.push('us-east-1') + + for (const region of regions) { + const endpoint = new URL(`https://q.${region}.amazonaws.com/ListAvailableModels`) + endpoint.searchParams.set('origin', 'AI_EDITOR') + + try { + const response = await fetch(endpoint, { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${auth.access}` + }, + signal: AbortSignal.timeout(5_000) + }) + if (!response.ok) continue + + const contextWindows = parseModelCatalog((await response.json()) as ModelCatalogResponse) + if (contextWindows.size === 0) continue + + catalogCache.set(cacheKey, { + expiresAt: Date.now() + MODEL_CATALOG_TTL_MS, + contextWindows + }) + applyCatalog(contextWindows) + return + } catch { + continue + } + } +} + +export function clearContextWindowCatalog(): void { + catalogCache.clear() + activeContextWindows = new Map() } From 683344c8ba76641e67aeef3107de1ad34c5254c0 Mon Sep 17 00:00:00 2001 From: leecoder Date: Tue, 11 Aug 2026 09:50:54 +0900 Subject: [PATCH 3/4] feat(request): refresh model context catalog per account Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/core/request/request-handler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/request/request-handler.ts b/src/core/request/request-handler.ts index e7745b7..5bf3f67 100644 --- a/src/core/request/request-handler.ts +++ b/src/core/request/request-handler.ts @@ -4,6 +4,7 @@ import type { AccountManager } from '../../plugin/accounts' import type { KiroConfig } from '../../plugin/config' import { isPermanentError } from '../../plugin/health' import * as logger from '../../plugin/logger' +import { refreshContextWindowSizes } from '../../plugin/models' import { transformToSdkRequest } from '../../plugin/request' import { createSdkClient } from '../../plugin/sdk-client' import { syncFromKiroCli } from '../../plugin/sync/kiro-cli' @@ -132,6 +133,7 @@ export class RequestHandler { continue } + await refreshContextWindowSizes(auth) const sdkPrep = this.prepareSdkRequest(init?.body, model, auth, think, budget, showToast) const apiTimestamp = this.config.enable_log_api_request ? logger.getTimestamp() : null From fb8f8c60906d0457565bd51569487eeea5d63dae Mon Sep 17 00:00:00 2001 From: leecoder Date: Tue, 11 Aug 2026 09:52:47 +0900 Subject: [PATCH 4/4] fix(models): clear stale catalog between accounts Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/plugin/models.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugin/models.ts b/src/plugin/models.ts index 2043946..67d7524 100644 --- a/src/plugin/models.ts +++ b/src/plugin/models.ts @@ -77,6 +77,7 @@ export async function refreshContextWindowSizes(auth: KiroAuthDetails): Promise< applyCatalog(cached.contextWindows) return } + activeContextWindows = new Map() const regions = [getCatalogRegion(auth)] if (!regions.includes('us-east-1')) regions.push('us-east-1')