@yansigit/opencodex 2.32.0 → 2.33.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.
- package/README.md +2 -2
- package/gui/dist/assets/index-CIDo4y4k.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +8 -5
- package/src/adapters/anthropic.ts +20 -6
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +3 -1
- package/src/adapters/command-code.ts +131 -17
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-transport.ts +4 -2
- package/src/adapters/cursor/native-exec-fs.ts +13 -12
- package/src/adapters/cursor/native-exec-network.ts +3 -5
- package/src/adapters/cursor/native-exec-policy.ts +47 -0
- package/src/adapters/cursor/native-exec-shell.ts +13 -25
- package/src/adapters/cursor/native-exec.ts +18 -10
- package/src/adapters/cursor/protobuf-events.ts +186 -9
- package/src/adapters/cursor/protobuf-request.ts +53 -18
- package/src/adapters/cursor/request-builder.ts +11 -3
- package/src/adapters/cursor/tool-definitions.ts +49 -2
- package/src/adapters/cursor/tool-result-normalize.ts +21 -8
- package/src/adapters/cursor/types.ts +11 -3
- package/src/adapters/cursor.ts +123 -0
- package/src/adapters/google-aistudio-parser.ts +49 -0
- package/src/adapters/google-antigravity-replay.ts +2 -2
- package/src/adapters/google-antigravity-wire.ts +7 -0
- package/src/adapters/google-errors.ts +6 -2
- package/src/adapters/google-http.ts +30 -7
- package/src/adapters/google-truncation.ts +5 -0
- package/src/adapters/google-wire-compiler.ts +38 -6
- package/src/adapters/google.ts +242 -28
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +112 -9
- package/src/chat/inbound.ts +15 -0
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +10 -3
- package/src/cli/provider.ts +6 -0
- package/src/cli/status.ts +23 -0
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -12
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +39 -12
- package/src/codex/catalog/parsing.ts +38 -27
- package/src/codex/catalog/provider-fetch.ts +232 -133
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/convergence.ts +5 -0
- package/src/codex/shim.ts +56 -3
- package/src/config/provider-validation.ts +37 -0
- package/src/config.ts +78 -2
- package/src/generated/compatibility-version.json +187 -119
- package/src/generated/model-metadata.ts +3 -0
- package/src/images/loop.ts +37 -6
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/provider-outbound.ts +45 -33
- package/src/lib/provider-tls-profile.ts +309 -0
- package/src/lib/proxy-env.ts +49 -0
- package/src/lib/redact.ts +10 -1
- package/src/oauth/aistudio-native-daemon.ts +62 -0
- package/src/oauth/aistudio-session-sync.ts +95 -0
- package/src/oauth/antigravity-routing.ts +282 -236
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/command-code.ts +5 -16
- package/src/oauth/google-aistudio-auth.ts +98 -0
- package/src/oauth/google-antigravity.ts +42 -5
- package/src/oauth/index.ts +15 -3
- package/src/oauth/key-providers.ts +8 -0
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/login-cli.ts +66 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +6 -0
- package/src/providers/antigravity-quota.ts +3 -1
- package/src/providers/api-keys.ts +2 -1
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/derive.ts +5 -1
- package/src/providers/key-failover.ts +5 -1
- package/src/providers/openai-tiers.ts +5 -0
- package/src/providers/provider-id-rewrite.ts +1 -0
- package/src/providers/quota.ts +148 -50
- package/src/providers/registry.ts +27 -4
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +11 -0
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/schema.ts +37 -0
- package/src/responses/state.ts +94 -4
- package/src/router.ts +11 -2
- package/src/routing/account-pool/cooldown.ts +8 -0
- package/src/routing/account-pool/index.ts +1 -0
- package/src/server/aistudio-ws-hub.ts +295 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +2 -0
- package/src/server/images.ts +19 -35
- package/src/server/index.ts +94 -0
- package/src/server/management/agent-settings-routes.ts +205 -15
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +31 -5
- package/src/server/management/logs-usage-routes.ts +11 -5
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/oauth-account-routes.ts +38 -7
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/port-reclaim.ts +19 -1
- package/src/server/request-log-conversation.ts +12 -0
- package/src/server/request-log.ts +23 -1
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/compact.ts +30 -1
- package/src/server/responses/core.ts +363 -156
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +18 -5
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-undeclared-tool-guard.ts +90 -8
- package/src/server/ws-bridge.ts +2 -1
- package/src/service.ts +1 -1
- package/src/smoke/fingerprint-cache.ts +133 -0
- package/src/smoke/live-scenarios.ts +33 -0
- package/src/smoke/runner.ts +119 -0
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +18 -1
- package/src/types/request.ts +30 -0
- package/src/types/tools.ts +51 -0
- package/src/types.ts +6 -0
- package/src/usage/command-code-manifest.ts +116 -0
- package/src/usage/cost.ts +2 -2
- package/src/usage/expected-prices.ts +83 -0
- package/src/usage/log.ts +2 -2
- package/src/usage/summary.ts +34 -12
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/index.ts +16 -8
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-BG43zwVe.js +0 -102
- package/gui/dist/assets/index-CiSI-jrP.css +0 -1
package/src/usage/summary.ts
CHANGED
|
@@ -94,6 +94,7 @@ export interface UsageProvider {
|
|
|
94
94
|
|
|
95
95
|
export interface UsageAccount {
|
|
96
96
|
accountLogLabel: string;
|
|
97
|
+
provider?: string;
|
|
97
98
|
ambiguous: boolean;
|
|
98
99
|
requests: number;
|
|
99
100
|
attemptCount: number;
|
|
@@ -684,12 +685,25 @@ function legacyCodexAccountLabel(provider: string): string | null {
|
|
|
684
685
|
return suffix ?? LEGACY_AMBIGUOUS_ACCOUNT_LABEL;
|
|
685
686
|
}
|
|
686
687
|
|
|
687
|
-
function accountLabelForAttribution(
|
|
688
|
-
|
|
689
|
-
|
|
688
|
+
export function accountLabelForAttribution(
|
|
689
|
+
provider: string,
|
|
690
|
+
explicit: unknown,
|
|
691
|
+
fallbackAccountByProvider?: Record<string, string>,
|
|
692
|
+
): string | null {
|
|
693
|
+
if (typeof explicit === "string" && explicit.trim().length > 0) return explicit.trim();
|
|
694
|
+
const legacy = legacyCodexAccountLabel(provider);
|
|
695
|
+
if (legacy) return legacy;
|
|
696
|
+
const base = baseProviderLabel(provider);
|
|
697
|
+
if (fallbackAccountByProvider && fallbackAccountByProvider[base]) {
|
|
698
|
+
return fallbackAccountByProvider[base];
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
690
701
|
}
|
|
691
702
|
|
|
692
|
-
function buildAccounts(
|
|
703
|
+
function buildAccounts(
|
|
704
|
+
entries: PersistedUsageEntry[],
|
|
705
|
+
fallbackAccountByProvider?: Record<string, string>,
|
|
706
|
+
): UsageAccount[] {
|
|
693
707
|
const byLabel = new Map<string, UsageAccount>();
|
|
694
708
|
const requestIds = new Map<string, Set<string>>();
|
|
695
709
|
|
|
@@ -702,12 +716,13 @@ function buildAccounts(entries: PersistedUsageEntry[]): UsageAccount[] {
|
|
|
702
716
|
totalTokens?: number;
|
|
703
717
|
estimate: ReturnType<typeof estimateRequestCost>;
|
|
704
718
|
}): void => {
|
|
705
|
-
const label = accountLabelForAttribution(input.provider, input.accountLogLabel);
|
|
719
|
+
const label = accountLabelForAttribution(input.provider, input.accountLogLabel, fallbackAccountByProvider);
|
|
706
720
|
if (!label) return;
|
|
707
721
|
let row = byLabel.get(label);
|
|
708
722
|
if (!row) {
|
|
709
723
|
row = {
|
|
710
724
|
accountLogLabel: label,
|
|
725
|
+
provider: baseProviderLabel(input.provider),
|
|
711
726
|
ambiguous: label === LEGACY_AMBIGUOUS_ACCOUNT_LABEL,
|
|
712
727
|
requests: 0,
|
|
713
728
|
attemptCount: 0,
|
|
@@ -808,6 +823,7 @@ export function summarizeUsage(
|
|
|
808
823
|
range: UsageRange,
|
|
809
824
|
now: number,
|
|
810
825
|
surface: UsageSurface = "all",
|
|
826
|
+
fallbackAccountByProvider?: Record<string, string>,
|
|
811
827
|
): UsageSummary {
|
|
812
828
|
const { since } = rangeWindow(range, now);
|
|
813
829
|
const filteredEntries = entries.filter(entry => {
|
|
@@ -837,7 +853,7 @@ export function summarizeUsage(
|
|
|
837
853
|
days: buildDayGrid(range, since, now, filteredEntries),
|
|
838
854
|
models: buildModels(filteredEntries, totals.totalTokens),
|
|
839
855
|
providers: buildProviders(filteredEntries, totals.totalTokens),
|
|
840
|
-
accounts: buildAccounts(filteredEntries),
|
|
856
|
+
accounts: buildAccounts(filteredEntries, fallbackAccountByProvider),
|
|
841
857
|
};
|
|
842
858
|
}
|
|
843
859
|
|
|
@@ -869,12 +885,14 @@ function normalizeFilterValue(input: string | null | undefined): string | null {
|
|
|
869
885
|
*/
|
|
870
886
|
export function projectUsageSummary<T extends UsageSummary>(
|
|
871
887
|
summary: T,
|
|
872
|
-
filter: { provider?: string | null; model?: string | null },
|
|
888
|
+
filter: { provider?: string | null; model?: string | null; account?: string | null },
|
|
873
889
|
entries?: PersistedUsageEntry[],
|
|
890
|
+
fallbackAccountByProvider?: Record<string, string>,
|
|
874
891
|
): T & { filter?: UsageFilterEcho } {
|
|
875
892
|
const provider = normalizeFilterValue(filter.provider);
|
|
876
893
|
const model = normalizeFilterValue(filter.model);
|
|
877
|
-
|
|
894
|
+
const account = normalizeFilterValue(filter.account);
|
|
895
|
+
if (provider === null && model === null && account === null) return summary;
|
|
878
896
|
|
|
879
897
|
// Re-summarise from the entries the summary was built from, rather than
|
|
880
898
|
// projecting over its rows.
|
|
@@ -891,9 +909,13 @@ export function projectUsageSummary<T extends UsageSummary>(
|
|
|
891
909
|
//
|
|
892
910
|
// The entries are already in hand on every path that filters, so the honest
|
|
893
911
|
// computation is also the simple one.
|
|
894
|
-
const matches = (rowProvider: string, rowModel: string): boolean => {
|
|
912
|
+
const matches = (rowProvider: string, rowModel: string, rowAccount?: string | null): boolean => {
|
|
895
913
|
if (provider !== null && baseProviderLabel(rowProvider).toLowerCase() !== provider) return false;
|
|
896
914
|
if (model !== null && rowModel.toLowerCase() !== model) return false;
|
|
915
|
+
if (account !== null && rowAccount !== undefined) {
|
|
916
|
+
const effectiveAccount = accountLabelForAttribution(rowProvider, rowAccount, fallbackAccountByProvider);
|
|
917
|
+
if (!effectiveAccount || effectiveAccount.toLowerCase() !== account) return false;
|
|
918
|
+
}
|
|
897
919
|
return true;
|
|
898
920
|
};
|
|
899
921
|
|
|
@@ -909,10 +931,10 @@ export function projectUsageSummary<T extends UsageSummary>(
|
|
|
909
931
|
const filtered: PersistedUsageEntry[] = [];
|
|
910
932
|
for (const entry of source) {
|
|
911
933
|
if (!entry.attempts?.length) {
|
|
912
|
-
if (matches(entry.provider, antigravityUsageModel(entry.provider, entry.model))) filtered.push(entry);
|
|
934
|
+
if (matches(entry.provider, antigravityUsageModel(entry.provider, entry.model), entry.accountLogLabel)) filtered.push(entry);
|
|
913
935
|
continue;
|
|
914
936
|
}
|
|
915
|
-
const attempts = entry.attempts.filter(a => matches(a.provider, antigravityUsageModel(a.provider, a.model)));
|
|
937
|
+
const attempts = entry.attempts.filter(a => matches(a.provider, antigravityUsageModel(a.provider, a.model), a.accountLogLabel ?? entry.accountLogLabel));
|
|
916
938
|
if (attempts.length === 0) continue;
|
|
917
939
|
// A combo is still counted once per participating model, so a filtered
|
|
918
940
|
// request count can exceed the number of distinct requests. That is the
|
|
@@ -921,7 +943,7 @@ export function projectUsageSummary<T extends UsageSummary>(
|
|
|
921
943
|
filtered.push({ ...entry, attempts });
|
|
922
944
|
}
|
|
923
945
|
|
|
924
|
-
const projected = summarizeUsage(filtered, summary.range, summary.generatedAt, summary.surface);
|
|
946
|
+
const projected = summarizeUsage(filtered, summary.range, summary.generatedAt, summary.surface, fallbackAccountByProvider);
|
|
925
947
|
// matched reflects usage inside the requested WINDOW, not anywhere in the
|
|
926
948
|
// log: summarizeUsage applies the range and surface predicates, and the CLI
|
|
927
949
|
// uses this flag to decide between a table and "no usage recorded".
|
|
@@ -14,13 +14,14 @@ import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
|
14
14
|
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
|
|
15
15
|
import { readBoundedResponseBytes } from "../lib/bounded-body";
|
|
16
16
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
17
|
-
import { redactSecretString } from "../lib/redact";
|
|
17
|
+
import { redactErrorMessage, redactSecretString } from "../lib/redact";
|
|
18
18
|
import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
|
|
19
19
|
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
20
20
|
import { getProviderRegistryEntry } from "../providers/registry";
|
|
21
21
|
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
|
|
22
22
|
import { appendSafeWebSearchSource } from "./sources";
|
|
23
23
|
import { BASE_INSTRUCTION, IMAGE_INSTRUCTION, type SidecarOutcome, type SidecarSettings } from "./executor";
|
|
24
|
+
import { providerFetch } from "../server/responses/fetch-helpers";
|
|
24
25
|
|
|
25
26
|
const CCA_FALLBACK_BASE = "https://daily-cloudcode-pa.googleapis.com";
|
|
26
27
|
|
|
@@ -31,7 +32,7 @@ function isRec(v: unknown): v is Record<string, unknown> {
|
|
|
31
32
|
export async function runGeminiWebSearch(
|
|
32
33
|
query: string,
|
|
33
34
|
providerName: string,
|
|
34
|
-
|
|
35
|
+
provider: OcxProviderConfig,
|
|
35
36
|
settings: SidecarSettings,
|
|
36
37
|
abortSignal?: AbortSignal,
|
|
37
38
|
): Promise<SidecarOutcome> {
|
|
@@ -68,9 +69,10 @@ export async function runGeminiWebSearch(
|
|
|
68
69
|
const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
|
|
69
70
|
const sidecarExit = sidecarEnter("web-search");
|
|
70
71
|
const t0 = Date.now();
|
|
72
|
+
const executor = providerFetch(provider, undefined, { providerName, modelId: settings.model });
|
|
71
73
|
try {
|
|
72
74
|
const res = await fetchWithResetRetry(
|
|
73
|
-
() =>
|
|
75
|
+
() => executor(`${base}/v1internal:generateContent`, {
|
|
74
76
|
method: "POST",
|
|
75
77
|
headers: {
|
|
76
78
|
"Content-Type": "application/json",
|
|
@@ -110,7 +112,7 @@ export async function runGeminiWebSearch(
|
|
|
110
112
|
} catch (e) {
|
|
111
113
|
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
112
114
|
console.warn(`[web-search] gemini sidecar ${kind} (${Date.now() - t0}ms)`);
|
|
113
|
-
return { text: "", sources: [], error:
|
|
115
|
+
return { text: "", sources: [], error: redactErrorMessage(e instanceof Error ? e.message : String(e)) };
|
|
114
116
|
} finally {
|
|
115
117
|
sidecarExit();
|
|
116
118
|
linkedSignal.cleanup();
|
package/src/web-search/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { validateXaiSearchOptions, type XaiSearchOptions } from "./xai-executor"
|
|
|
9
9
|
import type { OcxWebSearchSidecarConfig } from "../types";
|
|
10
10
|
import { DEFAULT_STALL_TIMEOUT_SEC } from "../stall-timeout";
|
|
11
11
|
import { buildWebSearchTool, extractHostedWebSearch, WEB_SEARCH_TOOL_NAME } from "./synthetic-tool";
|
|
12
|
+
import { estimateInputTokens } from "../server/responses/input-admission";
|
|
12
13
|
|
|
13
14
|
export { runWithWebSearch } from "./loop";
|
|
14
15
|
export { buildWebSearchTool, extractHostedWebSearch, WEB_SEARCH_TOOL_NAME };
|
|
@@ -44,13 +45,19 @@ const STALL_MARGIN_SEC = 30;
|
|
|
44
45
|
* deliberately permissive, so malformed values fall back locally without rejecting or rewriting
|
|
45
46
|
* the caller's config object.
|
|
46
47
|
*/
|
|
47
|
-
export function resolveRoutedModelStallTimeoutMs(value: unknown): number {
|
|
48
|
-
|
|
48
|
+
export function resolveRoutedModelStallTimeoutMs(value: unknown, estimatedInputTokens?: number): number {
|
|
49
|
+
if (typeof value === "number"
|
|
49
50
|
&& Number.isInteger(value)
|
|
50
51
|
&& value >= 1
|
|
51
|
-
&& value <= MAX_ROUTED_MODEL_STALL_TIMEOUT_MS
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
&& value <= MAX_ROUTED_MODEL_STALL_TIMEOUT_MS) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
const base = DEFAULT_ROUTED_MODEL_STALL_TIMEOUT_MS;
|
|
56
|
+
if (typeof estimatedInputTokens === "number" && Number.isFinite(estimatedInputTokens) && estimatedInputTokens > 100_000) {
|
|
57
|
+
const extraBlocks = Math.ceil((estimatedInputTokens - 100_000) / 100_000);
|
|
58
|
+
return Math.min(MAX_ROUTED_MODEL_STALL_TIMEOUT_MS, base + extraBlocks * 60_000);
|
|
59
|
+
}
|
|
60
|
+
return base;
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
function finiteCeil(value: number | undefined, fallback: number): number {
|
|
@@ -305,9 +312,10 @@ export function planWebSearch(
|
|
|
305
312
|
const cfg = config.webSearchSidecar ?? {};
|
|
306
313
|
if (cfg.enabled === false) return undefined;
|
|
307
314
|
const timeoutMs = cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
const
|
|
315
|
+
const safeModelId = typeof modelId === "string" ? modelId : "";
|
|
316
|
+
const estimatedInputTokens = estimateInputTokens(parsed, safeModelId);
|
|
317
|
+
const routedModelStallTimeoutMs = resolveRoutedModelStallTimeoutMs(cfg.routedModelStallTimeoutMs, estimatedInputTokens);
|
|
318
|
+
const connectTimeoutMs = config.connectTimeoutMs ?? Math.max(200_000, routedModelStallTimeoutMs);
|
|
311
319
|
// Shared auth state (#2188): presence only — backend PREFERENCE stays with
|
|
312
320
|
// resolveSidecarBackend's explicit-or-openai contract.
|
|
313
321
|
const auth = resolveSidecarAuth(config);
|
package/src/web-search/loop.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AdapterEvent, OcxMessage, OcxParsedRequest, OcxProviderConfig, Ocx
|
|
|
3
3
|
import { namespacedToolName, toolChoiceToolPredicate } from "../types";
|
|
4
4
|
import { cloneProviderOpaqueToolCallMetadata } from "../responses/provider-opaque-metadata";
|
|
5
5
|
import type { AttemptRecoveryKind } from "../usage/log";
|
|
6
|
-
import { bridgeToResponsesSSE } from "../bridge";
|
|
6
|
+
import { bridgeToResponsesSSE, diagnoseAdapterEvent, type BridgeDiagnosticContext } from "../bridge";
|
|
7
7
|
import { runWebSearch, type SidecarOutcome, type SidecarOutcomeRecorder, type SidecarSettings } from "./executor";
|
|
8
8
|
import { runAnthropicWebSearch } from "./anthropic-executor";
|
|
9
9
|
import { runXaiWebSearch, type XaiSearchOptions } from "./xai-executor";
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { formatWebSearchResults } from "./format-result";
|
|
24
24
|
import { parseStreamWithProgress, RoutedModelInactivityError, WebSearchStreamProtocolError } from "./progress-stream";
|
|
25
25
|
import { WEB_SEARCH_TOOL_NAME } from "./synthetic-tool";
|
|
26
|
+
import { OcxRequestValidationError } from "../lib/errors";
|
|
26
27
|
|
|
27
28
|
const SSE_HEADERS = {
|
|
28
29
|
"Content-Type": "text/event-stream",
|
|
@@ -230,8 +231,14 @@ function forcedAnswerNudge(): OcxMessage {
|
|
|
230
231
|
};
|
|
231
232
|
}
|
|
232
233
|
|
|
233
|
-
function jsonError(status: number, message: string): Response {
|
|
234
|
-
return new Response(JSON.stringify({
|
|
234
|
+
function jsonError(status: number, message: string, errorType = "upstream_error", code: string | null = null): Response {
|
|
235
|
+
return new Response(JSON.stringify({
|
|
236
|
+
error: {
|
|
237
|
+
message,
|
|
238
|
+
type: errorType,
|
|
239
|
+
code,
|
|
240
|
+
},
|
|
241
|
+
}), {
|
|
235
242
|
status,
|
|
236
243
|
headers: { "Content-Type": "application/json" },
|
|
237
244
|
});
|
|
@@ -240,7 +247,12 @@ function jsonError(status: number, message: string): Response {
|
|
|
240
247
|
/** Hard provider/parse failure inside an iteration. The eager first iteration converts it to a
|
|
241
248
|
* non-200 jsonError; later (already-streaming) iterations surface it as an in-stream error event. */
|
|
242
249
|
class LoopError extends Error {
|
|
243
|
-
constructor(
|
|
250
|
+
constructor(
|
|
251
|
+
readonly status: number,
|
|
252
|
+
message: string,
|
|
253
|
+
readonly errorType?: string,
|
|
254
|
+
readonly code?: string,
|
|
255
|
+
) {
|
|
244
256
|
super(message);
|
|
245
257
|
this.name = "LoopError";
|
|
246
258
|
}
|
|
@@ -301,6 +313,8 @@ export interface WebSearchLoopDeps {
|
|
|
301
313
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
302
314
|
/** Observe the exact adapter request selected for each routed-model iteration. */
|
|
303
315
|
onRequestBuilt?: (request: AdapterRequest) => void;
|
|
316
|
+
/** Validate the final adapter before every cached replay or request build. */
|
|
317
|
+
validateAdapter?: (parsed: OcxParsedRequest, adapter: ProviderAdapter) => void;
|
|
304
318
|
/** Called before each routed-model dispatch in the loop, for attempt telemetry. Same-target 429 replays pass the `rate-limit-429` recovery kind. */
|
|
305
319
|
onAttemptSend?: (recovery?: AttemptRecoveryKind) => void;
|
|
306
320
|
/**
|
|
@@ -314,6 +328,8 @@ export interface WebSearchLoopDeps {
|
|
|
314
328
|
onCompletedResponse?: (response: Record<string, unknown>) => void;
|
|
315
329
|
/** OAuth account identity forwarded to AdapterFetchContext for provider-local cooldown bookkeeping. */
|
|
316
330
|
accountId?: string;
|
|
331
|
+
/** Internal, opt-in structural stream diagnostics shared with the final bridge. */
|
|
332
|
+
diagnostic?: BridgeDiagnosticContext;
|
|
317
333
|
}
|
|
318
334
|
|
|
319
335
|
/**
|
|
@@ -422,6 +438,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
422
438
|
* header deadline. The caller owns same-target 429 replays and key rotation around it.
|
|
423
439
|
*/
|
|
424
440
|
const fetchOnce = async (requestAdapter: ProviderAdapter, recovery?: AttemptRecoveryKind): Promise<IterationResponse> => {
|
|
441
|
+
deps.validateAdapter?.(iterParsed, requestAdapter);
|
|
425
442
|
let request: AdapterRequest;
|
|
426
443
|
if (cachedRequest !== undefined && cachedAdapter === requestAdapter) {
|
|
427
444
|
request = cachedRequest;
|
|
@@ -553,6 +570,9 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
553
570
|
return prepared;
|
|
554
571
|
} catch (error) {
|
|
555
572
|
if (isTranslatorBudgetExceededError(error)) throw error;
|
|
573
|
+
if (error instanceof OcxRequestValidationError) {
|
|
574
|
+
throw new LoopError(error.status, error.message, "invalid_request_error", "invalid_request_error");
|
|
575
|
+
}
|
|
556
576
|
if (headerDeadline.didExpire()) {
|
|
557
577
|
throw new LoopError(504, `Provider response-header timeout after ${connectTimeoutMs}ms during web-search`);
|
|
558
578
|
}
|
|
@@ -595,6 +615,10 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
595
615
|
inactivityTimeoutMs: routedModelStallTimeoutMs,
|
|
596
616
|
translatorBudget,
|
|
597
617
|
})) {
|
|
618
|
+
if (deps.diagnostic) {
|
|
619
|
+
deps.diagnostic.adapterName = prepared.responseAdapter.name;
|
|
620
|
+
diagnoseAdapterEvent(deps.diagnostic, event);
|
|
621
|
+
}
|
|
598
622
|
if (event.type === "heartbeat") yield event;
|
|
599
623
|
// Kiro's explicit-completion protocol marks ordinary assistant text as commentary while
|
|
600
624
|
// it performs a bounded final-answer retry. That text is safe to surface immediately and
|
|
@@ -779,7 +803,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
779
803
|
firstPrepared = await prepareIterationDrained(false);
|
|
780
804
|
} catch (e) {
|
|
781
805
|
if (abortSignal) abortSignal.removeEventListener("abort", linkAbort);
|
|
782
|
-
if (e instanceof LoopError) return jsonError(e.status, e.message);
|
|
806
|
+
if (e instanceof LoopError) return jsonError(e.status, e.message, e.errorType, e.code ?? null);
|
|
783
807
|
throw e;
|
|
784
808
|
}
|
|
785
809
|
|
|
@@ -863,7 +887,18 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
863
887
|
message: "upstream translation buffer exceeded the safe limit",
|
|
864
888
|
};
|
|
865
889
|
} else {
|
|
866
|
-
|
|
890
|
+
const message = e instanceof LoopError ? e.message : (e instanceof Error ? e.message : String(e));
|
|
891
|
+
yield {
|
|
892
|
+
type: "error",
|
|
893
|
+
...(e instanceof LoopError ? {
|
|
894
|
+
status: e.status,
|
|
895
|
+
...(e.errorType !== undefined || e.status === 400
|
|
896
|
+
? { errorType: e.errorType ?? "upstream_error" }
|
|
897
|
+
: {}),
|
|
898
|
+
...(e.code !== undefined ? { code: e.code } : {}),
|
|
899
|
+
} : {}),
|
|
900
|
+
message,
|
|
901
|
+
};
|
|
867
902
|
}
|
|
868
903
|
return;
|
|
869
904
|
}
|
|
@@ -890,6 +925,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
890
925
|
...(deps.onFirstOutput ? { onFirstOutput: deps.onFirstOutput } : {}),
|
|
891
926
|
...(deps.onUsage ? { onUsage: deps.onUsage } : {}),
|
|
892
927
|
...(deps.onCompletedResponse ? { onCompletedResponse: deps.onCompletedResponse } : {}),
|
|
928
|
+
...(deps.diagnostic ? { diagnostic: deps.diagnostic } : {}),
|
|
893
929
|
},
|
|
894
930
|
);
|
|
895
931
|
return new Response(sse, { headers: SSE_HEADERS });
|