@sunerpy/opencode-kiro-auth 0.12.0 → 0.13.1

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.
@@ -19,6 +19,9 @@ export declare const KIRO_CONSTANTS: {
19
19
  export declare const MODEL_MAPPING: Record<string, string>;
20
20
  export declare const SUPPORTED_MODELS: string[];
21
21
  export declare function isLongContextModel(model: string): boolean;
22
+ export declare const DEFAULT_MODEL_CONTEXT_LIMIT = 200000;
23
+ export declare const MODEL_CONTEXT_LIMITS: Record<string, number>;
24
+ export declare function getModelContextLimit(baseModel: string): number;
22
25
  export declare const KIRO_AUTH_SERVICE: {
23
26
  ENDPOINT: string;
24
27
  SSO_OIDC_ENDPOINT: string;
package/dist/constants.js CHANGED
@@ -81,7 +81,8 @@ export const MODEL_MAPPING = {
81
81
  'minimax-m2.5': 'minimax-m2.5',
82
82
  'minimax-m2.1': 'minimax-m2.1',
83
83
  'qwen3-coder-next': 'qwen3-coder-next',
84
- // OpenAI GPT 5.6 — probe-confirmed (200, us-east-1); other naming guesses 400. No effort variant.
84
+ // OpenAI GPT 5.6 — probe-confirmed (200, us-east-1); other naming guesses 400.
85
+ // Effort variants (-low..-max) are parsed by resolveModelVariant, not mapped here.
85
86
  'gpt-5.6-sol': 'gpt-5.6-sol',
86
87
  'gpt-5.6-terra': 'gpt-5.6-terra',
87
88
  'gpt-5.6-luna': 'gpt-5.6-luna',
@@ -97,6 +98,37 @@ const LONG_CONTEXT_MODELS = new Set(Object.keys(MODEL_MAPPING).filter((k) => k.i
97
98
  export function isLongContextModel(model) {
98
99
  return LONG_CONTEXT_MODELS.has(model);
99
100
  }
101
+ // SSOT for token accounting, keyed by BASE model id (effort/thinking suffix
102
+ // stripped). Values MUST equal the limit.context advertised in src/plugin.ts —
103
+ // enforced by context-window.test.ts. Drift makes OpenCode under-count usage and
104
+ // skip auto-compaction until Kiro hard-rejects with 400 "Input is too long."
105
+ export const DEFAULT_MODEL_CONTEXT_LIMIT = 200000;
106
+ export const MODEL_CONTEXT_LIMITS = {
107
+ auto: 200000,
108
+ 'claude-haiku-4-5': 200000,
109
+ 'claude-sonnet-4': 200000,
110
+ 'claude-sonnet-4-5': 200000,
111
+ 'claude-sonnet-4-5-1m': 1000000,
112
+ 'claude-sonnet-4-6': 1000000,
113
+ 'claude-sonnet-4-6-1m': 1000000,
114
+ 'claude-sonnet-5': 1000000,
115
+ 'claude-opus-4-5': 200000,
116
+ 'claude-opus-4-6': 1000000,
117
+ 'claude-opus-4-6-1m': 1000000,
118
+ 'claude-opus-4-7': 1000000,
119
+ 'claude-opus-4-8': 1000000,
120
+ 'deepseek-3.2': 128000,
121
+ 'glm-5': 200000,
122
+ 'minimax-m2.5': 200000,
123
+ 'minimax-m2.1': 200000,
124
+ 'qwen3-coder-next': 256000,
125
+ 'gpt-5.6-sol': 272000,
126
+ 'gpt-5.6-terra': 272000,
127
+ 'gpt-5.6-luna': 272000
128
+ };
129
+ export function getModelContextLimit(baseModel) {
130
+ return MODEL_CONTEXT_LIMITS[baseModel] ?? DEFAULT_MODEL_CONTEXT_LIMIT;
131
+ }
100
132
  export const KIRO_AUTH_SERVICE = {
101
133
  ENDPOINT: 'https://prod.{{region}}.auth.desktop.kiro.dev',
102
134
  SSO_OIDC_ENDPOINT: 'https://oidc.{{region}}.amazonaws.com',
@@ -3,6 +3,7 @@ import type { AccountManager } from '../../plugin/accounts.js';
3
3
  import type { ManagedAccount } from '../../plugin/types.js';
4
4
  import type { ForceRefreshResult } from '../auth/token-refresher.js';
5
5
  type ToastFunction = (message: string, variant: 'info' | 'warning' | 'success' | 'error') => void;
6
+ export declare function isKiroContextOverflowBody(text: string): boolean;
6
7
  export interface RequestContext {
7
8
  retry: number;
8
9
  forcedRefreshAccountIds?: Set<string>;
@@ -1,4 +1,11 @@
1
1
  import { isAccessTokenError, toDeadReason } from '../../plugin/health.js';
2
+ // Unambiguous Kiro size-overflow 400 signals only. The generic "Improperly
3
+ // formed request." is deliberately excluded: it has no size discriminator in
4
+ // the body, so matching it would wrongly reclassify unrelated 400s as overflow.
5
+ const KIRO_CONTEXT_OVERFLOW_PATTERNS = [/input is too long/i, /CONTENT_LENGTH_EXCEEDS_THRESHOLD/i];
6
+ export function isKiroContextOverflowBody(text) {
7
+ return KIRO_CONTEXT_OVERFLOW_PATTERNS.some((p) => p.test(text));
8
+ }
2
9
  export class ErrorHandler {
3
10
  config;
4
11
  accountManager;
@@ -7,7 +7,7 @@ import { syncFromKiroCli } from '../../plugin/sync/kiro-cli.js';
7
7
  import { AccountSelector } from '../account/account-selector.js';
8
8
  import { UsageTracker } from '../account/usage-tracker.js';
9
9
  import { TokenRefresher } from '../auth/token-refresher.js';
10
- import { ErrorHandler } from './error-handler.js';
10
+ import { ErrorHandler, isKiroContextOverflowBody } from './error-handler.js';
11
11
  import { ResponseHandler } from './response-handler.js';
12
12
  import { RetryStrategy } from './retry-strategy.js';
13
13
  const KIRO_API_PATTERN = /^(https?:\/\/)?q\.[a-z0-9-]+\.amazonaws\.com/;
@@ -155,12 +155,14 @@ export class RequestHandler {
155
155
  if (apiTimestamp) {
156
156
  this.logSdkError(sdkPrep, e, acc, apiTimestamp);
157
157
  }
158
- const mockResponse = new Response(JSON.stringify({ message: e.message, __type: e.name }), {
158
+ const errorBody = JSON.stringify({ message: e.message, __type: e.name });
159
+ const errorStatusText = e.name || 'Error';
160
+ const jsonHeaders = { 'Content-Type': 'application/json' };
161
+ const errorResult = await this.errorHandler.handle(e, new Response(errorBody, {
159
162
  status: httpStatus,
160
- statusText: e.name || 'Error',
161
- headers: { 'Content-Type': 'application/json' }
162
- });
163
- const errorResult = await this.errorHandler.handle(e, mockResponse, acc, handlerContext, showToast);
163
+ statusText: errorStatusText,
164
+ headers: jsonHeaders
165
+ }), acc, handlerContext, showToast);
164
166
  if (errorResult.shouldRetry) {
165
167
  if (errorResult.newContext) {
166
168
  handlerContext = errorResult.newContext;
@@ -170,7 +172,17 @@ export class RequestHandler {
170
172
  }
171
173
  continue;
172
174
  }
173
- throw new Error(`Kiro Error: ${httpStatus}`);
175
+ // Terminal, non-retryable HTTP error. Return a fresh Response carrying
176
+ // the real Kiro body so @ai-sdk/openai-compatible produces an
177
+ // APICallError with status+body (not a bare Error that OpenCode
178
+ // degrades to UnknownError). Remap size-overflow 400s to 413 so
179
+ // OpenCode classifies context_overflow and auto-compacts.
180
+ const terminalStatus = httpStatus === 400 && isKiroContextOverflowBody(e.message ?? '') ? 413 : httpStatus;
181
+ return new Response(errorBody, {
182
+ status: terminalStatus,
183
+ statusText: errorStatusText,
184
+ headers: jsonHeaders
185
+ });
174
186
  }
175
187
  const networkResult = await this.errorHandler.handleNetworkError(e, handlerContext, showToast);
176
188
  if (networkResult.shouldRetry) {
@@ -11,6 +11,15 @@ export declare function supportsEffort(kiroModel: string): boolean;
11
11
  * Check if a model supports xhigh effort level.
12
12
  */
13
13
  export declare function supportsXHighEffort(kiroModel: string): boolean;
14
+ /**
15
+ * Build the additionalModelRequestFields payload carrying the effort level.
16
+ *
17
+ * GPT and Claude take effort through different, mutually-exclusive wire fields
18
+ * (each rejects the other's with HTTP 400, probe-confirmed):
19
+ * - GPT (Mantle): `reasoning.effort`
20
+ * - Claude: `output_config.effort`
21
+ */
22
+ export declare function buildEffortRequestFields(kiroModel: string, effort: Effort): Record<string, unknown>;
14
23
  /**
15
24
  * Resolve effort level for a given model.
16
25
  * - Returns undefined if model doesn't support effort
@@ -2,11 +2,23 @@
2
2
  * Effort levels ordered from lowest to highest reasoning depth.
3
3
  */
4
4
  export const EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
5
+ /**
6
+ * OpenAI GPT models (Kiro proxies these via Mantle). They accept the effort enum
7
+ * through a DIFFERENT wire field than Claude — see buildEffortRequestFields.
8
+ * All five levels (incl. xhigh/max) are probe-confirmed: credit usage scales
9
+ * monotonically low<medium<high<xhigh<max (.omo/evidence/task-gpt56-effort-probe.txt).
10
+ */
11
+ const GPT_REASONING_MODELS = new Set(['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna']);
5
12
  /**
6
13
  * Models that support the 5-value effort enum (including xhigh).
7
14
  * These models support up to 128k thinking tokens with max effort.
8
15
  */
9
- const XHIGH_CAPABLE_MODELS = new Set(['claude-opus-4.7', 'claude-opus-4.8', 'claude-sonnet-5']);
16
+ const XHIGH_CAPABLE_MODELS = new Set([
17
+ 'claude-opus-4.7',
18
+ 'claude-opus-4.8',
19
+ 'claude-sonnet-5',
20
+ ...GPT_REASONING_MODELS
21
+ ]);
10
22
  /**
11
23
  * Models that support the 4-value effort enum (no xhigh).
12
24
  * xhigh requests on these models are clamped to max.
@@ -33,6 +45,20 @@ export function supportsEffort(kiroModel) {
33
45
  export function supportsXHighEffort(kiroModel) {
34
46
  return XHIGH_CAPABLE_MODELS.has(kiroModel);
35
47
  }
48
+ /**
49
+ * Build the additionalModelRequestFields payload carrying the effort level.
50
+ *
51
+ * GPT and Claude take effort through different, mutually-exclusive wire fields
52
+ * (each rejects the other's with HTTP 400, probe-confirmed):
53
+ * - GPT (Mantle): `reasoning.effort`
54
+ * - Claude: `output_config.effort`
55
+ */
56
+ export function buildEffortRequestFields(kiroModel, effort) {
57
+ if (GPT_REASONING_MODELS.has(kiroModel)) {
58
+ return { reasoning: { effort } };
59
+ }
60
+ return { output_config: { effort } };
61
+ }
36
62
  /**
37
63
  * Resolve effort level for a given model.
38
64
  * - Returns undefined if model doesn't support effort
@@ -20,4 +20,5 @@ export interface ResolvedModelVariant {
20
20
  * with `effort` left undefined.
21
21
  */
22
22
  export declare function resolveModelVariant(model: string): ResolvedModelVariant;
23
+ export declare function stripModelSuffix(model: string): string;
23
24
  export declare function getContextWindowSize(model: string): number;
@@ -1,4 +1,4 @@
1
- import { MODEL_MAPPING, SUPPORTED_MODELS, isLongContextModel } from '../constants.js';
1
+ import { MODEL_MAPPING, SUPPORTED_MODELS, getModelContextLimit } from '../constants.js';
2
2
  export function resolveKiroModel(model) {
3
3
  const resolved = MODEL_MAPPING[model];
4
4
  if (!resolved) {
@@ -10,7 +10,10 @@ const VARIANT_BASE_ALLOWLIST = new Set([
10
10
  'claude-opus-4-8',
11
11
  'claude-opus-4-7',
12
12
  'claude-sonnet-5',
13
- 'claude-sonnet-4-6'
13
+ 'claude-sonnet-4-6',
14
+ 'gpt-5.6-sol',
15
+ 'gpt-5.6-terra',
16
+ 'gpt-5.6-luna'
14
17
  ]);
15
18
  const EFFORT_SUFFIXES = ['low', 'medium', 'high', 'xhigh', 'max'];
16
19
  /**
@@ -40,6 +43,24 @@ export function resolveModelVariant(model) {
40
43
  }
41
44
  return { wireId: resolveKiroModel(model), effort: undefined };
42
45
  }
46
+ export function stripModelSuffix(model) {
47
+ for (const suffix of EFFORT_SUFFIXES) {
48
+ const marker = `-${suffix}`;
49
+ if (model.endsWith(marker)) {
50
+ const base = model.slice(0, -marker.length);
51
+ if (VARIANT_BASE_ALLOWLIST.has(base)) {
52
+ return base;
53
+ }
54
+ }
55
+ }
56
+ if (model.endsWith('-thinking')) {
57
+ const base = model.slice(0, -'-thinking'.length);
58
+ if (MODEL_MAPPING[base]) {
59
+ return base;
60
+ }
61
+ }
62
+ return model;
63
+ }
43
64
  export function getContextWindowSize(model) {
44
- return isLongContextModel(model) ? 1000000 : 200000;
65
+ return getModelContextLimit(stripModelSuffix(model));
45
66
  }
@@ -1,5 +1,6 @@
1
1
  import { CodeWhispererStreamingClient } from '@aws/codewhisperer-streaming-client';
2
2
  import { KIRO_CONSTANTS } from '../constants.js';
3
+ import { buildEffortRequestFields } from './effort.js';
3
4
  const clientCache = new Map();
4
5
  const KIRO_CLI_MAX_ATTEMPTS = 3;
5
6
  export function createSdkClient(auth, region, effort) {
@@ -22,19 +23,15 @@ export function createSdkClient(auth, region, effort) {
22
23
  args.request.headers['x-amzn-kiro-agent-mode'] = 'vibe';
23
24
  return next(args);
24
25
  }, { step: 'build', name: 'addKiroHeaders' });
25
- // Inject additionalModelRequestFields for effort-based thinking control
26
+ // Effort wire shape differs per family (GPT: reasoning.effort, Claude:
27
+ // output_config.effort; each 400s the other), so derive it from the body's modelId.
26
28
  if (effort) {
27
29
  client.middlewareStack.add((next) => async (args) => {
28
- // The SDK serializes input to args.input, we need to modify the body
29
- // before it's sent. The body is in args.request.body as a string.
30
30
  if (args.request?.body) {
31
31
  try {
32
32
  const body = JSON.parse(args.request.body);
33
- body.additionalModelRequestFields = {
34
- output_config: {
35
- effort
36
- }
37
- };
33
+ const wireModel = body?.conversationState?.currentMessage?.userInputMessage?.modelId;
34
+ body.additionalModelRequestFields = buildEffortRequestFields(wireModel, effort);
38
35
  args.request.body = JSON.stringify(body);
39
36
  }
40
37
  catch {
package/dist/plugin.js CHANGED
@@ -263,17 +263,92 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
263
263
  'gpt-5.6-sol': {
264
264
  name: 'GPT 5.6 Sol (2.4x)',
265
265
  limit: { context: 272000, output: 64000 },
266
- modalities: { input: ['text'], output: ['text'] }
266
+ modalities: { input: ['text', 'image'], output: ['text'] }
267
+ },
268
+ 'gpt-5.6-sol-low': {
269
+ name: 'GPT 5.6 Sol (low) (2.4x)',
270
+ limit: { context: 272000, output: 64000 },
271
+ modalities: { input: ['text', 'image'], output: ['text'] }
272
+ },
273
+ 'gpt-5.6-sol-medium': {
274
+ name: 'GPT 5.6 Sol (medium) (2.4x)',
275
+ limit: { context: 272000, output: 64000 },
276
+ modalities: { input: ['text', 'image'], output: ['text'] }
277
+ },
278
+ 'gpt-5.6-sol-high': {
279
+ name: 'GPT 5.6 Sol (high) (2.4x)',
280
+ limit: { context: 272000, output: 64000 },
281
+ modalities: { input: ['text', 'image'], output: ['text'] }
282
+ },
283
+ 'gpt-5.6-sol-xhigh': {
284
+ name: 'GPT 5.6 Sol (xhigh) (2.4x)',
285
+ limit: { context: 272000, output: 64000 },
286
+ modalities: { input: ['text', 'image'], output: ['text'] }
287
+ },
288
+ 'gpt-5.6-sol-max': {
289
+ name: 'GPT 5.6 Sol (max) (2.4x)',
290
+ limit: { context: 272000, output: 64000 },
291
+ modalities: { input: ['text', 'image'], output: ['text'] }
267
292
  },
268
293
  'gpt-5.6-terra': {
269
294
  name: 'GPT 5.6 Terra (1.2x)',
270
295
  limit: { context: 272000, output: 64000 },
271
- modalities: { input: ['text'], output: ['text'] }
296
+ modalities: { input: ['text', 'image'], output: ['text'] }
297
+ },
298
+ 'gpt-5.6-terra-low': {
299
+ name: 'GPT 5.6 Terra (low) (1.2x)',
300
+ limit: { context: 272000, output: 64000 },
301
+ modalities: { input: ['text', 'image'], output: ['text'] }
302
+ },
303
+ 'gpt-5.6-terra-medium': {
304
+ name: 'GPT 5.6 Terra (medium) (1.2x)',
305
+ limit: { context: 272000, output: 64000 },
306
+ modalities: { input: ['text', 'image'], output: ['text'] }
307
+ },
308
+ 'gpt-5.6-terra-high': {
309
+ name: 'GPT 5.6 Terra (high) (1.2x)',
310
+ limit: { context: 272000, output: 64000 },
311
+ modalities: { input: ['text', 'image'], output: ['text'] }
312
+ },
313
+ 'gpt-5.6-terra-xhigh': {
314
+ name: 'GPT 5.6 Terra (xhigh) (1.2x)',
315
+ limit: { context: 272000, output: 64000 },
316
+ modalities: { input: ['text', 'image'], output: ['text'] }
317
+ },
318
+ 'gpt-5.6-terra-max': {
319
+ name: 'GPT 5.6 Terra (max) (1.2x)',
320
+ limit: { context: 272000, output: 64000 },
321
+ modalities: { input: ['text', 'image'], output: ['text'] }
272
322
  },
273
323
  'gpt-5.6-luna': {
274
324
  name: 'GPT 5.6 Luna',
275
325
  limit: { context: 272000, output: 64000 },
276
- modalities: { input: ['text'], output: ['text'] }
326
+ modalities: { input: ['text', 'image'], output: ['text'] }
327
+ },
328
+ 'gpt-5.6-luna-low': {
329
+ name: 'GPT 5.6 Luna (low)',
330
+ limit: { context: 272000, output: 64000 },
331
+ modalities: { input: ['text', 'image'], output: ['text'] }
332
+ },
333
+ 'gpt-5.6-luna-medium': {
334
+ name: 'GPT 5.6 Luna (medium)',
335
+ limit: { context: 272000, output: 64000 },
336
+ modalities: { input: ['text', 'image'], output: ['text'] }
337
+ },
338
+ 'gpt-5.6-luna-high': {
339
+ name: 'GPT 5.6 Luna (high)',
340
+ limit: { context: 272000, output: 64000 },
341
+ modalities: { input: ['text', 'image'], output: ['text'] }
342
+ },
343
+ 'gpt-5.6-luna-xhigh': {
344
+ name: 'GPT 5.6 Luna (xhigh)',
345
+ limit: { context: 272000, output: 64000 },
346
+ modalities: { input: ['text', 'image'], output: ['text'] }
347
+ },
348
+ 'gpt-5.6-luna-max': {
349
+ name: 'GPT 5.6 Luna (max)',
350
+ limit: { context: 272000, output: 64000 },
351
+ modalities: { input: ['text', 'image'], output: ['text'] }
277
352
  }
278
353
  };
279
354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sunerpy/opencode-kiro-auth",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to Claude models",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",