@bitkyc08/opencodex 2.7.33 → 2.7.34
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.
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +21 -10
- package/README.ru.md +1 -1
- package/README.zh-CN.md +1 -1
- package/gui/dist/assets/index-BkmJJgg6.js +52 -0
- package/gui/dist/assets/index-Sg-7L_oZ.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +13 -6
- package/src/adapters/cursor/discovery.ts +39 -4
- package/src/adapters/cursor/exec-policy.ts +11 -13
- package/src/adapters/cursor/live-transport.ts +22 -4
- package/src/adapters/cursor/protobuf-events.ts +140 -8
- package/src/adapters/cursor/protobuf-request.ts +15 -0
- package/src/adapters/cursor/request-builder.ts +10 -5
- package/src/adapters/cursor/transport.ts +3 -2
- package/src/adapters/cursor/types.ts +14 -0
- package/src/adapters/kiro-constants.ts +12 -0
- package/src/adapters/kiro-errors.ts +111 -2
- package/src/adapters/kiro-events.ts +154 -35
- package/src/adapters/kiro-retry.ts +116 -32
- package/src/adapters/kiro-tools.ts +30 -20
- package/src/adapters/kiro-wire.ts +47 -6
- package/src/adapters/kiro.ts +891 -228
- package/src/adapters/openai-chat.ts +12 -5
- package/src/adapters/openai-responses.ts +7 -2
- package/src/bridge.ts +109 -26
- package/src/claude/outbound.ts +27 -4
- package/src/cli/index.ts +1 -1
- package/src/codex/catalog.ts +375 -33
- package/src/combos/index.ts +3 -0
- package/src/combos/request.ts +4 -4
- package/src/combos/resolve.ts +2 -2
- package/src/combos/types.ts +104 -2
- package/src/config.ts +70 -1
- package/src/lib/eventstream-decoder.ts +9 -0
- package/src/oauth/index.ts +3 -1
- package/src/oauth/kiro-credentials.ts +48 -20
- package/src/oauth/login-cli.ts +2 -0
- package/src/providers/derive.ts +8 -0
- package/src/providers/kiro-models.ts +2 -2
- package/src/providers/openai-sidecar.ts +28 -1
- package/src/providers/registry.ts +39 -2
- package/src/responses/parser.ts +22 -10
- package/src/responses/schema.ts +1 -0
- package/src/responses/state.ts +50 -10
- package/src/router.ts +15 -3
- package/src/server/auth-cors.ts +7 -0
- package/src/server/claude-messages.ts +6 -0
- package/src/server/index.ts +6 -3
- package/src/server/management-api.ts +187 -43
- package/src/server/ports.ts +4 -2
- package/src/server/request-log.ts +3 -2
- package/src/server/responses-item-id-repair.ts +281 -0
- package/src/server/responses.ts +274 -73
- package/src/types.ts +109 -16
- package/src/update/job.ts +81 -1
- package/src/vision/describe.ts +2 -1
- package/src/web-search/executor.ts +2 -1
- package/src/web-search/loop.ts +9 -1
- package/src/web-search/progress-stream.ts +12 -10
- package/gui/dist/assets/index-D6Fcl4yM.css +0 -1
- package/gui/dist/assets/index-d63HMU0x.js +0 -52
package/src/combos/request.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { OcxComboDefaultEffort, OcxComboTarget } from "../types";
|
|
2
|
-
import {
|
|
1
|
+
import type { OcxComboDefaultEffort, OcxComboTarget, OcxConfig } from "../types";
|
|
2
|
+
import { resolveComboId } from "./types";
|
|
3
3
|
|
|
4
4
|
const warnedUnsupportedDefaults = new Set<string>();
|
|
5
5
|
|
|
@@ -7,11 +7,11 @@ export function resetComboEffortWarningStateForTests(): void {
|
|
|
7
7
|
warnedUnsupportedDefaults.clear();
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function comboIdFromRawBody(body: unknown): string | null {
|
|
10
|
+
export function comboIdFromRawBody(body: unknown, config: OcxConfig): string | null {
|
|
11
11
|
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
12
12
|
const model = (body as { model?: unknown }).model;
|
|
13
13
|
if (typeof model !== "string") return null;
|
|
14
|
-
return
|
|
14
|
+
return resolveComboId(config, model);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function concreteComboRequestBody(
|
package/src/combos/resolve.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { OcxComboTarget, OcxConfig } from "../types";
|
|
2
2
|
import { coolComboTarget, isComboTargetInCooldown } from "./failover";
|
|
3
|
-
import { getCombo,
|
|
3
|
+
import { getCombo, resolveComboId, targetKey } from "./types";
|
|
4
4
|
import type { NormalizedComboConfig } from "./types";
|
|
5
5
|
|
|
6
6
|
export interface ComboPick {
|
|
@@ -162,7 +162,7 @@ export function clearComboSelectionState(comboId?: string): void {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
export function tryPickComboModel(config: OcxConfig, modelId: string): ComboPick | null {
|
|
165
|
-
const comboId =
|
|
165
|
+
const comboId = resolveComboId(config, modelId);
|
|
166
166
|
if (!comboId) return null;
|
|
167
167
|
if (!getCombo(config, comboId)) throw new UnknownComboError(comboId);
|
|
168
168
|
const picked = pickComboTarget(config, comboId);
|
package/src/combos/types.ts
CHANGED
|
@@ -9,6 +9,18 @@ import type {
|
|
|
9
9
|
|
|
10
10
|
export const COMBO_NAMESPACE = "combo";
|
|
11
11
|
const COMBO_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
12
|
+
/**
|
|
13
|
+
* Public alias shape: one optional "/" segment, each segment id-shaped. Bare aliases
|
|
14
|
+
* (no "/") are the masquerade case — the combo answers to a mandated model id with no
|
|
15
|
+
* `combo/` prefix. Codex-facing slugs tolerate at most one "/", so deeper paths reject.
|
|
16
|
+
*/
|
|
17
|
+
const COMBO_ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,63})?$/;
|
|
18
|
+
/**
|
|
19
|
+
* Bare aliases in the OpenAI native family (gpt-*, o1-*, o3-*, o4-*, codex-*) are
|
|
20
|
+
* rejected: they collide with native catalog rows and the canonical-OpenAI routing
|
|
21
|
+
* branch, which cannot be shadowed honestly.
|
|
22
|
+
*/
|
|
23
|
+
const NATIVE_OPENAI_FAMILY_PATTERN = /^(?:gpt-|o1-|o3-|o4-|codex-)/;
|
|
12
24
|
|
|
13
25
|
export interface ComboValidationIssue {
|
|
14
26
|
path: Array<string | number>;
|
|
@@ -19,6 +31,8 @@ export interface NormalizedComboConfig {
|
|
|
19
31
|
strategy: OcxComboStrategy;
|
|
20
32
|
stickyLimit: number;
|
|
21
33
|
defaultEffort: OcxComboDefaultEffort | null;
|
|
34
|
+
/** Trimmed public alias, or null when the combo keeps the default `combo/<id>` slug. */
|
|
35
|
+
alias: string | null;
|
|
22
36
|
targets: Array<Required<OcxComboTarget>>;
|
|
23
37
|
}
|
|
24
38
|
|
|
@@ -37,11 +51,88 @@ export function comboModelId(id: string): string {
|
|
|
37
51
|
return `${COMBO_NAMESPACE}/${id}`;
|
|
38
52
|
}
|
|
39
53
|
|
|
54
|
+
/** Public model id clients request: the alias when set, else the default `combo/<id>`. */
|
|
55
|
+
export function comboPublicModelId(id: string, combo: { alias?: string | null }): string {
|
|
56
|
+
const alias = typeof combo.alias === "string" ? combo.alias.trim() : "";
|
|
57
|
+
return alias || comboModelId(id);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a client-requested model id to a combo config key. The canonical `combo/<id>`
|
|
62
|
+
* form wins first (back-compat); otherwise an exact alias match across configured combos.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveComboId(
|
|
65
|
+
config: { combos?: Record<string, OcxComboConfig> },
|
|
66
|
+
modelId: string,
|
|
67
|
+
): string | null {
|
|
68
|
+
const direct = parseComboModelId(modelId);
|
|
69
|
+
if (direct) return direct;
|
|
70
|
+
const combos = config.combos;
|
|
71
|
+
if (!combos) return null;
|
|
72
|
+
for (const [id, raw] of Object.entries(combos)) {
|
|
73
|
+
if (!raw || typeof raw !== "object") continue;
|
|
74
|
+
const alias = typeof raw.alias === "string" ? raw.alias.trim() : "";
|
|
75
|
+
if (alias && alias === modelId) return id;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cross-combo alias checks that need the full combos map (uniqueness). Kept separate
|
|
82
|
+
* from `comboConfigIssues` so config-file validation and the management API share it.
|
|
83
|
+
*/
|
|
84
|
+
export function comboAliasIssues(
|
|
85
|
+
id: string,
|
|
86
|
+
alias: string,
|
|
87
|
+
combos: Record<string, OcxComboConfig> | undefined,
|
|
88
|
+
options: { excludeComboId?: string } = {},
|
|
89
|
+
): ComboValidationIssue[] {
|
|
90
|
+
const issues: ComboValidationIssue[] = [];
|
|
91
|
+
if (!COMBO_ALIAS_PATTERN.test(alias)) {
|
|
92
|
+
issues.push({
|
|
93
|
+
path: ["alias"],
|
|
94
|
+
message: "alias must use letters, numbers, dot, underscore, or hyphen, with at most one \"/\" segment",
|
|
95
|
+
});
|
|
96
|
+
return issues;
|
|
97
|
+
}
|
|
98
|
+
if (alias === COMBO_NAMESPACE || alias.startsWith(`${COMBO_NAMESPACE}/`)) {
|
|
99
|
+
issues.push({
|
|
100
|
+
path: ["alias"],
|
|
101
|
+
message: `alias must not use the reserved "${COMBO_NAMESPACE}/" namespace`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (!alias.includes("/") && NATIVE_OPENAI_FAMILY_PATTERN.test(alias)) {
|
|
105
|
+
issues.push({
|
|
106
|
+
path: ["alias"],
|
|
107
|
+
message: "bare aliases in the OpenAI native family (gpt-*, o1-*, o3-*, o4-*, codex-*) are not allowed",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
for (const [otherId, other] of Object.entries(combos ?? {})) {
|
|
111
|
+
if (otherId === id || otherId === options.excludeComboId) continue;
|
|
112
|
+
const otherAlias = typeof other?.alias === "string" ? other.alias.trim() : "";
|
|
113
|
+
if (otherAlias && otherAlias === alias) {
|
|
114
|
+
issues.push({
|
|
115
|
+
path: ["alias"],
|
|
116
|
+
message: `alias "${alias}" is already used by combo "${otherId}"`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return issues;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface ComboValidationOptions {
|
|
124
|
+
requireEnabledTarget?: boolean;
|
|
125
|
+
/** Full combos map for alias uniqueness checks; omitted during early config load. */
|
|
126
|
+
combos?: Record<string, OcxComboConfig>;
|
|
127
|
+
/** Combo being renamed — its stored alias is excluded from uniqueness checks. */
|
|
128
|
+
excludeComboId?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
40
131
|
export function comboConfigIssues(
|
|
41
132
|
id: string,
|
|
42
133
|
raw: unknown,
|
|
43
134
|
providers: Record<string, OcxProviderConfig>,
|
|
44
|
-
options:
|
|
135
|
+
options: ComboValidationOptions = {},
|
|
45
136
|
): ComboValidationIssue[] {
|
|
46
137
|
const issues: ComboValidationIssue[] = [];
|
|
47
138
|
if (!isValidComboId(id)) {
|
|
@@ -88,6 +179,15 @@ export function comboConfigIssues(
|
|
|
88
179
|
});
|
|
89
180
|
}
|
|
90
181
|
|
|
182
|
+
if (body.alias !== undefined) {
|
|
183
|
+
if (typeof body.alias !== "string") {
|
|
184
|
+
issues.push({ path: ["alias"], message: "alias must be a string" });
|
|
185
|
+
} else {
|
|
186
|
+
const alias = body.alias.trim();
|
|
187
|
+
if (alias) issues.push(...comboAliasIssues(id, alias, options.combos, options));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
91
191
|
if (!Array.isArray(body.targets) || body.targets.length === 0) {
|
|
92
192
|
issues.push({ path: ["targets"], message: "targets must be a non-empty array" });
|
|
93
193
|
return issues;
|
|
@@ -155,16 +255,18 @@ export function comboConfigError(
|
|
|
155
255
|
id: string,
|
|
156
256
|
raw: unknown,
|
|
157
257
|
providers: Record<string, OcxProviderConfig>,
|
|
158
|
-
options:
|
|
258
|
+
options: ComboValidationOptions = {},
|
|
159
259
|
): string | null {
|
|
160
260
|
return comboConfigIssues(id, raw, providers, options)[0]?.message ?? null;
|
|
161
261
|
}
|
|
162
262
|
|
|
163
263
|
export function normalizeComboConfig(raw: OcxComboConfig): NormalizedComboConfig {
|
|
264
|
+
const alias = typeof raw.alias === "string" ? raw.alias.trim() : "";
|
|
164
265
|
return {
|
|
165
266
|
strategy: raw.strategy ?? "failover",
|
|
166
267
|
stickyLimit: raw.stickyLimit ?? 1,
|
|
167
268
|
defaultEffort: raw.defaultEffort ?? null,
|
|
269
|
+
alias: alias || null,
|
|
168
270
|
targets: raw.targets.map(target => ({
|
|
169
271
|
provider: target.provider.trim(),
|
|
170
272
|
model: target.model.trim(),
|
package/src/config.ts
CHANGED
|
@@ -329,8 +329,14 @@ const warnedConfigFallbacks = new Set<string>();
|
|
|
329
329
|
const providerConfigSchema = z.object({
|
|
330
330
|
adapter: z.string().min(1),
|
|
331
331
|
baseUrl: z.string().min(1),
|
|
332
|
+
responsesPath: z.string().min(1).optional(),
|
|
332
333
|
allowPrivateNetwork: z.boolean().optional(),
|
|
333
334
|
codexAccountMode: z.enum(["pool", "direct"]).optional(),
|
|
335
|
+
responsesItemIdRepair: z.object({
|
|
336
|
+
message: z.array(z.string().min(1)).optional(),
|
|
337
|
+
reasoning: z.array(z.string().min(1)).optional(),
|
|
338
|
+
repairMissingTerminalIds: z.boolean().optional(),
|
|
339
|
+
}).strict().optional(),
|
|
334
340
|
}).passthrough();
|
|
335
341
|
|
|
336
342
|
const RESERVED_PROVIDER_NAMES = new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -369,6 +375,18 @@ export function providerBaseUrlConfigError(baseUrl: string): string | null {
|
|
|
369
375
|
return null;
|
|
370
376
|
}
|
|
371
377
|
|
|
378
|
+
function providerResponsesPathConfigError(responsesPath: string | undefined): string | null {
|
|
379
|
+
if (responsesPath === undefined) return null;
|
|
380
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(responsesPath) || responsesPath.includes("://")) {
|
|
381
|
+
return "responsesPath must be a relative path without a URL scheme";
|
|
382
|
+
}
|
|
383
|
+
if (!responsesPath.startsWith("/")) return "responsesPath must start with /";
|
|
384
|
+
if (responsesPath.includes("?") || responsesPath.includes("#")) {
|
|
385
|
+
return "responsesPath must not include query strings or fragments";
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
372
390
|
export function providerHeadersConfigError(headers: unknown): string | null {
|
|
373
391
|
if (headers === undefined) return null;
|
|
374
392
|
if (!headers || typeof headers !== "object" || Array.isArray(headers)) return "headers must be an object";
|
|
@@ -396,6 +414,14 @@ export function positiveIntegerRecordConfigError(value: unknown, field: string):
|
|
|
396
414
|
return null;
|
|
397
415
|
}
|
|
398
416
|
|
|
417
|
+
export function positiveIntegerConfigError(value: unknown, field: string): string | null {
|
|
418
|
+
if (value === undefined) return null;
|
|
419
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
420
|
+
return `${field} must be a positive finite integer`;
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
|
|
399
425
|
const configSchema = z.object({
|
|
400
426
|
port: z.number().int().min(0).max(65535).default(10100),
|
|
401
427
|
providers: z.record(z.string(), providerConfigSchema),
|
|
@@ -403,6 +429,7 @@ const configSchema = z.object({
|
|
|
403
429
|
openaiProviderTierVersion: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
404
430
|
providerContextCaps: z.record(z.string(), z.number().int().positive()).optional(),
|
|
405
431
|
contextCapValue: z.number().int().positive().optional(),
|
|
432
|
+
multiAgentGuidanceEnabled: z.boolean().optional(),
|
|
406
433
|
}).passthrough().superRefine((config, ctx) => {
|
|
407
434
|
for (const name of Object.keys(config.providers)) {
|
|
408
435
|
if (!isValidProviderName(name)) {
|
|
@@ -451,6 +478,14 @@ const configSchema = z.object({
|
|
|
451
478
|
});
|
|
452
479
|
}
|
|
453
480
|
}
|
|
481
|
+
const responsesPathError = providerResponsesPathConfigError(provider.responsesPath);
|
|
482
|
+
if (responsesPathError) {
|
|
483
|
+
ctx.addIssue({
|
|
484
|
+
code: "custom",
|
|
485
|
+
path: ["providers", name, "responsesPath"],
|
|
486
|
+
message: responsesPathError,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
454
489
|
const headersError = providerHeadersConfigError((provider as { headers?: unknown }).headers);
|
|
455
490
|
if (headersError) {
|
|
456
491
|
ctx.addIssue({
|
|
@@ -470,6 +505,28 @@ const configSchema = z.object({
|
|
|
470
505
|
message: maxInputError,
|
|
471
506
|
});
|
|
472
507
|
}
|
|
508
|
+
const defaultMaxOutputError = positiveIntegerConfigError(
|
|
509
|
+
(provider as { defaultMaxOutputTokens?: unknown }).defaultMaxOutputTokens,
|
|
510
|
+
"defaultMaxOutputTokens",
|
|
511
|
+
);
|
|
512
|
+
if (defaultMaxOutputError) {
|
|
513
|
+
ctx.addIssue({
|
|
514
|
+
code: "custom",
|
|
515
|
+
path: ["providers", name, "defaultMaxOutputTokens"],
|
|
516
|
+
message: defaultMaxOutputError,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
const maxOutputError = positiveIntegerRecordConfigError(
|
|
520
|
+
(provider as { modelMaxOutputTokens?: unknown }).modelMaxOutputTokens,
|
|
521
|
+
"modelMaxOutputTokens",
|
|
522
|
+
);
|
|
523
|
+
if (maxOutputError) {
|
|
524
|
+
ctx.addIssue({
|
|
525
|
+
code: "custom",
|
|
526
|
+
path: ["providers", name, "modelMaxOutputTokens"],
|
|
527
|
+
message: maxOutputError,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
473
530
|
if (Object.hasOwn(provider, "codexAccountMode") && provider.codexAccountMode !== undefined) {
|
|
474
531
|
// Persisted account mode is valid ONLY on the canonical built-in `openai` forward provider.
|
|
475
532
|
// Old openai-multi rows stay parseable (they never carry a mode) so startup can migrate them.
|
|
@@ -500,7 +557,12 @@ const configSchema = z.object({
|
|
|
500
557
|
ctx.addIssue({ code: "custom", path: ["combos"], message: "combos must be an object" });
|
|
501
558
|
} else {
|
|
502
559
|
for (const [id, raw] of Object.entries(combos as Record<string, unknown>)) {
|
|
503
|
-
|
|
560
|
+
// Pass the full map so cross-combo rules (alias uniqueness) apply at load time
|
|
561
|
+
// too, not just via the management API; each combo is excluded from its own check.
|
|
562
|
+
for (const issue of comboConfigIssues(id, raw, config.providers, {
|
|
563
|
+
combos: combos as Record<string, import("./types").OcxComboConfig>,
|
|
564
|
+
excludeComboId: id,
|
|
565
|
+
})) {
|
|
504
566
|
ctx.addIssue({
|
|
505
567
|
code: "custom",
|
|
506
568
|
path: ["combos", id, ...issue.path],
|
|
@@ -688,6 +750,12 @@ export function codexAutoStartEnabled(config: Pick<OcxConfig, "codexAutoStart">)
|
|
|
688
750
|
return config.codexAutoStart !== false;
|
|
689
751
|
}
|
|
690
752
|
|
|
753
|
+
export function multiAgentGuidanceEnabled(
|
|
754
|
+
config: Pick<OcxConfig, "multiAgentGuidanceEnabled">,
|
|
755
|
+
): boolean {
|
|
756
|
+
return config.multiAgentGuidanceEnabled !== false;
|
|
757
|
+
}
|
|
758
|
+
|
|
691
759
|
export function getDefaultConfig(): OcxConfig {
|
|
692
760
|
// Fresh-install default: works out of the box with Codex's ChatGPT OAuth (no API key).
|
|
693
761
|
// gpt-* requests forward the caller's incoming OAuth headers to the ChatGPT backend.
|
|
@@ -708,6 +776,7 @@ export function getDefaultConfig(): OcxConfig {
|
|
|
708
776
|
},
|
|
709
777
|
defaultProvider: "openai",
|
|
710
778
|
subagentModels: [...DEFAULT_SUBAGENT_MODELS],
|
|
779
|
+
multiAgentGuidanceEnabled: true,
|
|
711
780
|
websockets: false,
|
|
712
781
|
codexAutoStart: true,
|
|
713
782
|
};
|
|
@@ -24,6 +24,7 @@ const MESSAGE_CRC_LEN = 4;
|
|
|
24
24
|
const HEADER_BLOCK_OFFSET = PRELUDE_LEN + PRELUDE_CRC_LEN;
|
|
25
25
|
const MIN_MESSAGE_LEN = HEADER_BLOCK_OFFSET + MESSAGE_CRC_LEN;
|
|
26
26
|
const MAX_MESSAGE_LEN = 16 * 1024 * 1024;
|
|
27
|
+
const MAX_HEADERS_LEN = 128 * 1024;
|
|
27
28
|
|
|
28
29
|
export interface EventStreamMessage {
|
|
29
30
|
/** Header casing is preserved verbatim (e.g. `:event-type`, `:message-type`). */
|
|
@@ -62,6 +63,7 @@ export function decodeMessage(frame: Uint8Array): EventStreamMessage {
|
|
|
62
63
|
const preludeCrc = view.getUint32(8, false);
|
|
63
64
|
const computedPreludeCrc = crc32(frame.subarray(0, PRELUDE_LEN));
|
|
64
65
|
if (computedPreludeCrc !== preludeCrc) throw new Error("eventstream: prelude CRC mismatch");
|
|
66
|
+
if (headersLen > MAX_HEADERS_LEN) throw new Error(`eventstream: headers length ${headersLen} exceeds maximum`);
|
|
65
67
|
if (headersLen > total - MIN_MESSAGE_LEN) throw new Error("eventstream: headers length exceeds frame payload");
|
|
66
68
|
const msgCrc = view.getUint32(total - MESSAGE_CRC_LEN, false);
|
|
67
69
|
const computedMsgCrc = crc32(frame.subarray(0, total - MESSAGE_CRC_LEN));
|
|
@@ -185,6 +187,13 @@ export async function* decodeEventStream(source: ReadableStream<Uint8Array>): As
|
|
|
185
187
|
const total = dv.getUint32(0, false);
|
|
186
188
|
if (total < MIN_MESSAGE_LEN) throw new Error(`eventstream: total length ${total} below minimum`);
|
|
187
189
|
if (total > MAX_MESSAGE_LEN) throw new Error(`eventstream: total length ${total} exceeds maximum`);
|
|
190
|
+
if (buf.length - offset >= HEADER_BLOCK_OFFSET) {
|
|
191
|
+
const headersLen = dv.getUint32(4, false);
|
|
192
|
+
const preludeCrc = dv.getUint32(8, false);
|
|
193
|
+
const computedPreludeCrc = crc32(buf.subarray(offset, offset + PRELUDE_LEN));
|
|
194
|
+
if (computedPreludeCrc !== preludeCrc) throw new Error("eventstream: prelude CRC mismatch");
|
|
195
|
+
if (headersLen > MAX_HEADERS_LEN) throw new Error(`eventstream: headers length ${headersLen} exceeds maximum`);
|
|
196
|
+
}
|
|
188
197
|
if (buf.length - offset < total) break;
|
|
189
198
|
const frame = buf.subarray(offset, offset + total);
|
|
190
199
|
yield decodeMessage(frame);
|
package/src/oauth/index.ts
CHANGED
|
@@ -219,7 +219,7 @@ export async function getValidAccessTokenSnapshot(provider: string): Promise<OAu
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
/** Providers whose upstream-401 replay path may force a snapshot refresh. */
|
|
222
|
-
const FORCE_REFRESH_PROVIDERS = new Set(["xai", "github-copilot"]);
|
|
222
|
+
const FORCE_REFRESH_PROVIDERS = new Set(["xai", "github-copilot", "kiro"]);
|
|
223
223
|
|
|
224
224
|
export async function forceRefreshOAuthAccessSnapshot(
|
|
225
225
|
rejected: OAuthAccessSnapshot,
|
|
@@ -478,6 +478,8 @@ const OAUTH_RECONCILE_FIELDS: (keyof OcxProviderConfig)[] = [
|
|
|
478
478
|
"models",
|
|
479
479
|
"contextWindow",
|
|
480
480
|
"modelContextWindows",
|
|
481
|
+
"defaultMaxOutputTokens",
|
|
482
|
+
"modelMaxOutputTokens",
|
|
481
483
|
"modelInputModalities",
|
|
482
484
|
"noReasoningModels",
|
|
483
485
|
"noVisionModels",
|
|
@@ -6,8 +6,17 @@ import { Database } from "bun:sqlite";
|
|
|
6
6
|
const DEFAULT_EXPIRES_MS = 3600_000;
|
|
7
7
|
const KIRO_REGION_PATTERN = /^[a-z]{2}(?:-[a-z]+)+-\d$/;
|
|
8
8
|
const CLIENT_ID_HASH_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
9
|
-
const TOKEN_KEYS = [
|
|
10
|
-
|
|
9
|
+
const TOKEN_KEYS = [
|
|
10
|
+
"kirocli:odic:token",
|
|
11
|
+
"kirocli:oidc:token",
|
|
12
|
+
"kirocli:social:token",
|
|
13
|
+
"codewhisperer:odic:token",
|
|
14
|
+
];
|
|
15
|
+
const REGISTRATION_KEYS = [
|
|
16
|
+
"kirocli:odic:device-registration",
|
|
17
|
+
"kirocli:oidc:device-registration",
|
|
18
|
+
"codewhisperer:odic:device-registration",
|
|
19
|
+
];
|
|
11
20
|
|
|
12
21
|
export type KiroAuthType = "kiro_desktop" | "aws_sso_oidc";
|
|
13
22
|
export type KiroCredentialSource = "json" | "sqlite";
|
|
@@ -17,6 +26,8 @@ export type KiroDiagnosticStatus =
|
|
|
17
26
|
| "schema_mismatch"
|
|
18
27
|
| "invalid_json"
|
|
19
28
|
| "token_missing"
|
|
29
|
+
| "token_ambiguous"
|
|
30
|
+
| "token_key_missing"
|
|
20
31
|
| "token_found"
|
|
21
32
|
| "registration_found";
|
|
22
33
|
|
|
@@ -99,15 +110,27 @@ function jsonCredentialPaths(): string[] {
|
|
|
99
110
|
|
|
100
111
|
function sqliteEntries(): Array<{ location: KiroImportDiagnostic["location"]; path: string }> {
|
|
101
112
|
const home = userHome();
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
104
|
-
|
|
113
|
+
const configured = process.env.KIROCLI_DB_PATH?.trim() || process.env.KIRO_CLI_DB_FILE?.trim();
|
|
114
|
+
if (configured) return [{ location: "kiro-cli-db-env", path: expandPath(configured) }];
|
|
115
|
+
return [
|
|
105
116
|
{ location: "kiro-cli-data", path: join(home, "Library", "Application Support", "kiro-cli", "data.sqlite3") },
|
|
106
117
|
{ location: "kiro-cli-linux-data", path: join(home, ".local", "share", "kiro-cli", "data.sqlite3") },
|
|
107
118
|
{ location: "amazon-q-data", path: join(home, ".local", "share", "amazon-q", "data.sqlite3") },
|
|
108
119
|
{ location: "kiro-sso-cache", path: join(home, ".kiro", "sso", "cache.db") },
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function selectTokenRow(db: Database): { value: string } | null | "ambiguous" | "selected_missing" {
|
|
124
|
+
const rows = db.query("SELECT key, value FROM auth_kv WHERE key LIKE ? ORDER BY key ASC").all("%:token") as Array<{ key: string; value: string }>;
|
|
125
|
+
const selectedKey = process.env.KIROCLI_TOKEN_KEY?.trim();
|
|
126
|
+
if (selectedKey) return rows.find(row => row.key === selectedKey) ?? "selected_missing";
|
|
127
|
+
for (const preferred of TOKEN_KEYS) {
|
|
128
|
+
const row = rows.find(candidate => candidate.key === preferred);
|
|
129
|
+
if (row) return row;
|
|
130
|
+
}
|
|
131
|
+
if (rows.length === 0) return null;
|
|
132
|
+
if (rows.length === 1) return rows[0];
|
|
133
|
+
return "ambiguous";
|
|
111
134
|
}
|
|
112
135
|
|
|
113
136
|
function credentialFromJson(data: JsonObject, source: KiroCredentialSource): ImportedKiroCredential | undefined {
|
|
@@ -202,22 +225,26 @@ function readSqliteCredentials(diagnostics: KiroImportDiagnostic[]): ImportedKir
|
|
|
202
225
|
continue;
|
|
203
226
|
}
|
|
204
227
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
if (stringField(tokenData, "access_token", "accessToken")) break;
|
|
228
|
+
const row = selectTokenRow(db);
|
|
229
|
+
if (row === "ambiguous") {
|
|
230
|
+
diagnostics.push({ location, status: "token_ambiguous" });
|
|
231
|
+
throw new Error("Kiro CLI credential database contains multiple tokens; set KIROCLI_TOKEN_KEY to select one");
|
|
232
|
+
}
|
|
233
|
+
if (row === "selected_missing") {
|
|
234
|
+
diagnostics.push({ location, status: "token_key_missing" });
|
|
235
|
+
throw new Error("The KIROCLI_TOKEN_KEY selection was not found in the Kiro CLI credential database");
|
|
216
236
|
}
|
|
217
|
-
if (!
|
|
237
|
+
if (!row) {
|
|
218
238
|
diagnostics.push({ location, status: "token_missing" });
|
|
219
239
|
continue;
|
|
220
240
|
}
|
|
241
|
+
let tokenData: JsonObject;
|
|
242
|
+
try {
|
|
243
|
+
tokenData = JSON.parse(row.value) as JsonObject;
|
|
244
|
+
} catch {
|
|
245
|
+
diagnostics.push({ location, status: "invalid_json" });
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
221
248
|
let registrationData: JsonObject = {};
|
|
222
249
|
for (const key of REGISTRATION_KEYS) {
|
|
223
250
|
const row = db.query("SELECT value FROM auth_kv WHERE key = ?").get(key) as { value: string } | null;
|
|
@@ -235,7 +262,8 @@ function readSqliteCredentials(diagnostics: KiroImportDiagnostic[]): ImportedKir
|
|
|
235
262
|
const credential = credentialFromJson(merged, "sqlite");
|
|
236
263
|
diagnostics.push({ location, status: credential ? "token_found" : "token_missing" });
|
|
237
264
|
if (credential) return credential;
|
|
238
|
-
} catch {
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error instanceof Error && error.message.includes("KIROCLI_TOKEN_KEY")) throw error;
|
|
239
267
|
diagnostics.push({ location, status: "schema_mismatch" });
|
|
240
268
|
} finally {
|
|
241
269
|
db.close();
|
package/src/oauth/login-cli.ts
CHANGED
|
@@ -72,6 +72,8 @@ export function providerConfigFromKeyLoginProvider(def: KeyLoginProvider, key: s
|
|
|
72
72
|
...(def.contextWindow !== undefined ? { contextWindow: def.contextWindow } : {}),
|
|
73
73
|
...(def.modelContextWindows ? { modelContextWindows: { ...def.modelContextWindows } } : {}),
|
|
74
74
|
...(def.modelMaxInputTokens ? { modelMaxInputTokens: { ...def.modelMaxInputTokens } } : {}),
|
|
75
|
+
...(def.defaultMaxOutputTokens !== undefined ? { defaultMaxOutputTokens: def.defaultMaxOutputTokens } : {}),
|
|
76
|
+
...(def.modelMaxOutputTokens ? { modelMaxOutputTokens: { ...def.modelMaxOutputTokens } } : {}),
|
|
75
77
|
...(def.modelInputModalities ? { modelInputModalities: cloneRecordOfArrays(def.modelInputModalities) } : {}),
|
|
76
78
|
...(def.reasoningEfforts ? { reasoningEfforts: [...def.reasoningEfforts] } : {}),
|
|
77
79
|
...(def.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(def.modelReasoningEfforts) } : {}),
|
package/src/providers/derive.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface DerivedKeyLoginProvider {
|
|
|
13
13
|
modelContextWindows?: Record<string, number>;
|
|
14
14
|
modelInputModalities?: Record<string, string[]>;
|
|
15
15
|
modelMaxInputTokens?: Record<string, number>;
|
|
16
|
+
defaultMaxOutputTokens?: number;
|
|
17
|
+
modelMaxOutputTokens?: Record<string, number>;
|
|
16
18
|
reasoningEfforts?: string[];
|
|
17
19
|
modelReasoningEfforts?: Record<string, string[]>;
|
|
18
20
|
modelDefaultReasoningEfforts?: Record<string, string>;
|
|
@@ -96,6 +98,8 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
96
98
|
...(entry.modelContextWindows ? { modelContextWindows: { ...entry.modelContextWindows } } : {}),
|
|
97
99
|
...(entry.modelInputModalities ? { modelInputModalities: cloneRecordOfArrays(entry.modelInputModalities) } : {}),
|
|
98
100
|
...(entry.modelMaxInputTokens ? { modelMaxInputTokens: { ...entry.modelMaxInputTokens } } : {}),
|
|
101
|
+
...(entry.defaultMaxOutputTokens !== undefined ? { defaultMaxOutputTokens: entry.defaultMaxOutputTokens } : {}),
|
|
102
|
+
...(entry.modelMaxOutputTokens ? { modelMaxOutputTokens: { ...entry.modelMaxOutputTokens } } : {}),
|
|
99
103
|
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
100
104
|
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(entry.modelReasoningEfforts) } : {}),
|
|
101
105
|
...(entry.modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts: { ...entry.modelDefaultReasoningEfforts } } : {}),
|
|
@@ -135,6 +139,8 @@ export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> {
|
|
|
135
139
|
...(entry.modelContextWindows ? { modelContextWindows: { ...entry.modelContextWindows } } : {}),
|
|
136
140
|
...(entry.modelInputModalities ? { modelInputModalities: cloneRecordOfArrays(entry.modelInputModalities) } : {}),
|
|
137
141
|
...(entry.modelMaxInputTokens ? { modelMaxInputTokens: { ...entry.modelMaxInputTokens } } : {}),
|
|
142
|
+
...(entry.defaultMaxOutputTokens !== undefined ? { defaultMaxOutputTokens: entry.defaultMaxOutputTokens } : {}),
|
|
143
|
+
...(entry.modelMaxOutputTokens ? { modelMaxOutputTokens: { ...entry.modelMaxOutputTokens } } : {}),
|
|
138
144
|
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
139
145
|
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(entry.modelReasoningEfforts) } : {}),
|
|
140
146
|
...(entry.modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts: { ...entry.modelDefaultReasoningEfforts } } : {}),
|
|
@@ -203,6 +209,8 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
203
209
|
if (prov.contextWindow === undefined && seed.contextWindow !== undefined) prov.contextWindow = seed.contextWindow;
|
|
204
210
|
if (!prov.modelContextWindows && seed.modelContextWindows) prov.modelContextWindows = { ...seed.modelContextWindows };
|
|
205
211
|
if (!prov.modelInputModalities && seed.modelInputModalities) prov.modelInputModalities = cloneRecordOfArrays(seed.modelInputModalities);
|
|
212
|
+
if (prov.defaultMaxOutputTokens === undefined && seed.defaultMaxOutputTokens !== undefined) prov.defaultMaxOutputTokens = seed.defaultMaxOutputTokens;
|
|
213
|
+
if (!prov.modelMaxOutputTokens && seed.modelMaxOutputTokens) prov.modelMaxOutputTokens = { ...seed.modelMaxOutputTokens };
|
|
206
214
|
if (!prov.reasoningEfforts && seed.reasoningEfforts) prov.reasoningEfforts = [...seed.reasoningEfforts];
|
|
207
215
|
if (!prov.modelReasoningEfforts && seed.modelReasoningEfforts) prov.modelReasoningEfforts = cloneRecordOfArrays(seed.modelReasoningEfforts);
|
|
208
216
|
if (!prov.modelDefaultReasoningEfforts && seed.modelDefaultReasoningEfforts) prov.modelDefaultReasoningEfforts = { ...seed.modelDefaultReasoningEfforts };
|
|
@@ -45,8 +45,8 @@ export const KIRO_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
|
45
45
|
|
|
46
46
|
const KIRO_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
48
|
+
// gpt-5.6-sol sends these values through Kiro's verified native reasoning field. Other models map
|
|
49
|
+
// them to bounded thinking instructions until their native effort support is verified.
|
|
50
50
|
export const KIRO_MODEL_REASONING_EFFORTS: Record<string, string[]> = Object.fromEntries(
|
|
51
51
|
KIRO_MODELS.map(id => [id, KIRO_REASONING_EFFORTS]),
|
|
52
52
|
);
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type CodexAuthContext,
|
|
8
8
|
} from "../codex/auth-context";
|
|
9
9
|
import { recordCodexUpstreamOutcome, type CodexUpstreamOutcome } from "../codex/routing";
|
|
10
|
+
import { extractAccountId } from "../oauth/chatgpt";
|
|
10
11
|
import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../server/auth-cors";
|
|
11
12
|
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
|
|
12
13
|
import {
|
|
@@ -47,6 +48,23 @@ export function listOpenAiForwardSidecarCandidates(config: OcxConfig): OpenAiFor
|
|
|
47
48
|
}];
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
function directSidecarHeaders(
|
|
52
|
+
incomingHeaders: Headers,
|
|
53
|
+
): Headers | undefined {
|
|
54
|
+
const bearer = incomingHeaders.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
|
|
55
|
+
if (!bearer) return undefined;
|
|
56
|
+
const derivedAccountId = extractAccountId(undefined, bearer);
|
|
57
|
+
if (!derivedAccountId) return undefined;
|
|
58
|
+
const requestedAccountId = incomingHeaders.get("chatgpt-account-id")?.trim();
|
|
59
|
+
// JWT payloads are decoded locally but not signature-verified. Requiring the caller's
|
|
60
|
+
// explicit account header, and checking it against the token claim, makes forwarding an
|
|
61
|
+
// intentional ChatGPT-auth operation instead of silently reclassifying any JWT-shaped
|
|
62
|
+
// provider credential as a Codex bearer.
|
|
63
|
+
if (!requestedAccountId || requestedAccountId !== derivedAccountId) return undefined;
|
|
64
|
+
const selected = headersForCodexAuthContext(incomingHeaders, { kind: "main", accountId: null });
|
|
65
|
+
return selected;
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
export async function resolveFirstUsableOpenAiSidecar(
|
|
51
69
|
candidates: readonly OpenAiForwardSidecarCandidate[],
|
|
52
70
|
incomingHeaders: Headers,
|
|
@@ -60,7 +78,16 @@ export async function resolveFirstUsableOpenAiSidecar(
|
|
|
60
78
|
callerBearerMayBeForwarded = false;
|
|
61
79
|
}
|
|
62
80
|
for (const candidate of candidates) {
|
|
63
|
-
if (candidate.accountMode === "direct"
|
|
81
|
+
if (candidate.accountMode === "direct") {
|
|
82
|
+
if (!callerBearerMayBeForwarded || !hasCallerCodexBearer(incomingHeaders)) continue;
|
|
83
|
+
const headers = directSidecarHeaders(incomingHeaders);
|
|
84
|
+
if (!headers) continue;
|
|
85
|
+
return {
|
|
86
|
+
...candidate,
|
|
87
|
+
authContext: { kind: "main", accountId: null },
|
|
88
|
+
headers,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
64
91
|
const authContext = await resolveCodexAuthContext(incomingHeaders, config, candidate.accountMode);
|
|
65
92
|
if (!isCodexAuthContextUsable(authContext, config)) continue;
|
|
66
93
|
return {
|