@bitkyc08/opencodex 2.84.0 → 2.86.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.
- package/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-BjlttVLy.js +58 -0
- package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DyR9yBee.js} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/turn.ts +22 -9
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/qoder/adapter.ts +40 -1
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +5 -0
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +1 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- package/src/cli/dispatch.ts +5 -1
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +2 -1
- package/src/cli/index.ts +13 -4
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/registry.ts +15 -3
- package/src/cli/restart-scope.ts +3 -1
- package/src/clients/config-export/contracts.ts +1 -0
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +17 -4
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -1
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +4 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +55 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +173 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- package/src/config/diagnostics.ts +2 -1
- package/src/config/live-reconcile.ts +13 -0
- package/src/config/load-degrade.ts +6 -1
- package/src/config/paths.ts +10 -3
- package/src/config/schema/config-schema.ts +17 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +451 -163
- package/src/generated/model-metadata.ts +1 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/index.ts +4 -2
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/registry/entries-core.ts +7 -6
- package/src/providers/registry/entries-extended.ts +19 -0
- package/src/responses/parser.ts +30 -19
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +4 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/serve-options.ts +2 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +22 -4
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/route-registry.ts +11 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +6 -0
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native.ts +5 -1
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +29 -2
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- package/src/server/responses/core-combo-failure.ts +10 -1
- package/src/server/responses/core-combo.ts +21 -0
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +46 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/passthrough-delivery.ts +16 -10
- package/src/server/responses/passthrough-dispatch.ts +14 -3
- package/src/server/responses/request-prepare.ts +68 -5
- package/src/server/responses/request-transport.ts +16 -0
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +78 -3
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- package/src/service/desktop-startup.ts +14 -0
- package/src/service/health-sentinel-main.ts +23 -4
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +63 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/expected-prices.ts +12 -0
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -30,6 +30,7 @@ export const SIBLING_REFUSED_MANAGEMENT_PATHS: readonly { readonly path: string;
|
|
|
30
30
|
{ path: "/api/client-integrations", children: true },
|
|
31
31
|
{ path: "/api/native-integrations", children: true },
|
|
32
32
|
{ path: "/api/claude-desktop", children: true },
|
|
33
|
+
{ path: "/api/codex/desktop-compatibility", children: true },
|
|
33
34
|
{ path: "/api/claude-code", children: false },
|
|
34
35
|
{ path: "/api/grok/apply", children: false },
|
|
35
36
|
{ path: "/api/grok/selection", children: false },
|
|
@@ -79,6 +79,9 @@ import { handleCodexPromptRoutes } from "./management/codex-prompt-routes";
|
|
|
79
79
|
import { handleIntegrationRoutes } from "./management/integration-routes";
|
|
80
80
|
import { handleNativeIntegrationRoutes } from "./management/native-integration-routes";
|
|
81
81
|
import { handleClaudeDesktopPickerRoutes } from "./management/claude-desktop-picker-routes";
|
|
82
|
+
import { handleDesktopCompatibilityRoutes } from "./management/desktop-compatibility-routes";
|
|
83
|
+
import { handleDesktopCompatibilityRuntimeRoutes } from "./management/desktop-compatibility-runtime-routes";
|
|
84
|
+
import { handleDesktopCompatibilitySettingsRoutes } from "./management/desktop-compatibility-settings-routes";
|
|
82
85
|
import { handleCursorIntegrationRoutes } from "./management/cursor-integration-routes";
|
|
83
86
|
import type { ManagementContext } from "./management/context";
|
|
84
87
|
import type { ManagementPrincipal, ManagementSessionControl } from "./management-auth";
|
|
@@ -360,6 +363,9 @@ export async function handleManagementAPI(
|
|
|
360
363
|
?? (await handleNativeIntegrationRoutes(ctx))
|
|
361
364
|
?? (await handleCursorIntegrationRoutes(ctx))
|
|
362
365
|
?? (await handleClaudeDesktopPickerRoutes(ctx))
|
|
366
|
+
?? (await handleDesktopCompatibilityRoutes(ctx))
|
|
367
|
+
?? (await handleDesktopCompatibilityRuntimeRoutes(ctx))
|
|
368
|
+
?? (await handleDesktopCompatibilitySettingsRoutes(ctx))
|
|
363
369
|
?? (await handleAgentSettingsRoutes(ctx))
|
|
364
370
|
?? (await handleCodexPromptRoutes(ctx))
|
|
365
371
|
?? (await handleOauthAccountRoutes(ctx))
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Antigravity direct lane for the Messages ingress (`protocols.rollout.antigravityDirect`).
|
|
3
|
+
*
|
|
4
|
+
* The Messages ingress translates before it routes: the route is decided on the translated body's
|
|
5
|
+
* resolved model. So the lane is planned ahead of translation on the settings-only body
|
|
6
|
+
* (`anthropicSettingsTranslation`), whose model is that same resolved model, against a preview of
|
|
7
|
+
* the route; the conversation is decoded straight into the IR only when that preview is an
|
|
8
|
+
* eligible Antigravity route. The ingress then routes the settings-only body as usual and keeps
|
|
9
|
+
* the lane only if the settled route is still the one the plan was made for; otherwise it builds
|
|
10
|
+
* the full bridge translation, as it would have without the switch.
|
|
11
|
+
*
|
|
12
|
+
* Refusals stay the bridge's by construction: a settings error, or an undecodable proxy envelope
|
|
13
|
+
* (which the bridge retries through the native projection), returns no plan and the ingress takes
|
|
14
|
+
* its ordinary translation path, which answers exactly as before. Any other decode error is one
|
|
15
|
+
* the translator's walk raises with the same message at the same point
|
|
16
|
+
* (`tests/responses/protocol-messages-ir-parity.test.ts`).
|
|
17
|
+
*/
|
|
18
|
+
import { anthropicSettingsTranslation, AnthropicRequestError, type ClaudeCacheKeySource } from "../claude/inbound";
|
|
19
|
+
import { messagesToResponsesTranslation } from "../protocols/codecs/messages";
|
|
20
|
+
import {
|
|
21
|
+
MALFORMED_REASONING_SIGNATURE,
|
|
22
|
+
messagesConversationToIr,
|
|
23
|
+
messagesRouteEvidence,
|
|
24
|
+
withDirectMessagesConversation,
|
|
25
|
+
type MessagesIrConversation,
|
|
26
|
+
} from "../protocols/codecs/messages-ir";
|
|
27
|
+
import { resolveProtocolSettings } from "../protocols/settings";
|
|
28
|
+
import type { TranslatorBudget, TranslatorBufferKind } from "../lib/translator-budget";
|
|
29
|
+
import type { PolicyRequestEvidence } from "../routing/evaluator";
|
|
30
|
+
import { previewRouteModel, type RouteResult } from "../router";
|
|
31
|
+
import type { OcxClaudeCodeConfig, OcxConfig } from "../types";
|
|
32
|
+
import { antigravityMessagesDeclineReason, type AntigravityDirectDeclineReason } from "./antigravity-direct-eligibility";
|
|
33
|
+
import { cacheDiagnosticsObserving } from "./request-log";
|
|
34
|
+
import type { DirectRequestOption } from "./responses/core-options";
|
|
35
|
+
|
|
36
|
+
type Rec = Record<string, unknown>;
|
|
37
|
+
|
|
38
|
+
export interface MessagesDirectPlan {
|
|
39
|
+
/** The concrete route the plan was made for. */
|
|
40
|
+
providerName: string;
|
|
41
|
+
modelId: string;
|
|
42
|
+
/** The settings-only Responses body the core receives, and its prompt-cache key provenance. */
|
|
43
|
+
settingsBody: Rec;
|
|
44
|
+
cacheKeySource: ClaudeCacheKeySource;
|
|
45
|
+
conversation: MessagesIrConversation;
|
|
46
|
+
/** What the core would have read from the bridge body for policy routing. */
|
|
47
|
+
evidence: PolicyRequestEvidence;
|
|
48
|
+
/**
|
|
49
|
+
* Release the retained charges the plan's decode made. The ingress calls it when it abandons
|
|
50
|
+
* the plan for the full translation, which charges the same reasoning copies again.
|
|
51
|
+
*/
|
|
52
|
+
releaseDecodeCharges(): void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The request budget, with the retained charges made through it recorded so a decode the ingress
|
|
57
|
+
* abandons can hand them back. Without that, the bridge translation that follows would be held to
|
|
58
|
+
* a budget already spent on a copy nobody keeps, and could answer 413 where the bridge alone fits.
|
|
59
|
+
*/
|
|
60
|
+
function recordingBudget(budget: TranslatorBudget): { budget: TranslatorBudget; release(): void } {
|
|
61
|
+
const charged = new Map<TranslatorBufferKind, number>();
|
|
62
|
+
const recording: TranslatorBudget = {
|
|
63
|
+
openCall: id => budget.openCall(id),
|
|
64
|
+
closeCall: id => budget.closeCall(id),
|
|
65
|
+
reserveTransient: (bytes, scope) => budget.reserveTransient(bytes, scope),
|
|
66
|
+
chargeRetained: (delta, scope) => {
|
|
67
|
+
budget.chargeRetained(delta, scope);
|
|
68
|
+
charged.set(scope.kind, (charged.get(scope.kind) ?? 0) + delta);
|
|
69
|
+
},
|
|
70
|
+
releaseRetained: (bytes, scope) => {
|
|
71
|
+
budget.releaseRetained(bytes, scope);
|
|
72
|
+
charged.set(scope.kind, (charged.get(scope.kind) ?? 0) - bytes);
|
|
73
|
+
},
|
|
74
|
+
observeAcceptedRequestCopy: bytes => budget.observeAcceptedRequestCopy(bytes),
|
|
75
|
+
observeExternallyCapped: (kind, bytes) => budget.observeExternallyCapped(kind, bytes),
|
|
76
|
+
snapshot: () => budget.snapshot(),
|
|
77
|
+
dispose: () => budget.dispose(),
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
budget: recording,
|
|
81
|
+
release: () => {
|
|
82
|
+
for (const [kind, bytes] of charged) if (bytes > 0) budget.releaseRetained(bytes, { kind });
|
|
83
|
+
charged.clear();
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Plan the direct lane for a Messages body, or return `undefined` to translate as before. Throws
|
|
90
|
+
* only what the translator's conversation walk throws for the same body.
|
|
91
|
+
*/
|
|
92
|
+
export function planMessagesAntigravityDirect(
|
|
93
|
+
body: unknown,
|
|
94
|
+
cc: OcxClaudeCodeConfig | undefined,
|
|
95
|
+
config: OcxConfig,
|
|
96
|
+
budget: TranslatorBudget,
|
|
97
|
+
): MessagesDirectPlan | undefined {
|
|
98
|
+
if (!resolveProtocolSettings(config).rollout.antigravityDirect) return undefined;
|
|
99
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return undefined;
|
|
100
|
+
let settings: ReturnType<typeof anthropicSettingsTranslation>;
|
|
101
|
+
let route: RouteResult;
|
|
102
|
+
try {
|
|
103
|
+
settings = anthropicSettingsTranslation(body, cc);
|
|
104
|
+
route = previewRouteModel(config, settings.body.model as string);
|
|
105
|
+
} catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const model = settings.body.model as string;
|
|
109
|
+
if (!messagesDirectRouteHolds(route, body as Rec, model, config)) return undefined;
|
|
110
|
+
const recorded = recordingBudget(budget);
|
|
111
|
+
let conversation: MessagesIrConversation;
|
|
112
|
+
try {
|
|
113
|
+
conversation = messagesConversationToIr(body, cc, recorded.budget, model);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err instanceof AnthropicRequestError && err.message === MALFORMED_REASONING_SIGNATURE) {
|
|
116
|
+
// The bridge retries this body through the native projection; give it the whole budget.
|
|
117
|
+
recorded.release();
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
providerName: route.providerName,
|
|
124
|
+
modelId: route.modelId,
|
|
125
|
+
settingsBody: settings.body,
|
|
126
|
+
cacheKeySource: settings.cacheKeySource,
|
|
127
|
+
conversation,
|
|
128
|
+
evidence: messagesRouteEvidence(body),
|
|
129
|
+
releaseDecodeCharges: recorded.release,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Whether a route may carry this body on the direct lane, judged as the ingress judges it. */
|
|
134
|
+
export function messagesDirectRouteHolds(
|
|
135
|
+
route: Pick<RouteResult, "providerName" | "modelId" | "provider" | "combo" | "routeKind">,
|
|
136
|
+
body: Rec,
|
|
137
|
+
model: string,
|
|
138
|
+
config: OcxConfig,
|
|
139
|
+
): boolean {
|
|
140
|
+
return antigravityMessagesDeclineReason(route, body, model, config, { cacheDiagnostics: cacheDiagnosticsObserving() }) === undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Whether the route the ingress settled is still the one `plan` was made for, and still eligible. */
|
|
144
|
+
export function messagesDirectPlanHolds(plan: MessagesDirectPlan, route: RouteResult, body: Rec, config: OcxConfig): boolean {
|
|
145
|
+
return route.providerName === plan.providerName && route.modelId === plan.modelId
|
|
146
|
+
&& messagesDirectRouteHolds(route, body, plan.settingsBody.model as string, config);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The lane's decline for a bridged request's trace, when it says something about this route: with
|
|
151
|
+
* the switch off or a non-Antigravity target the path already explains the bridge.
|
|
152
|
+
*/
|
|
153
|
+
export function messagesDirectTraceDecline(
|
|
154
|
+
route: RouteResult,
|
|
155
|
+
body: unknown,
|
|
156
|
+
model: string,
|
|
157
|
+
config: OcxConfig,
|
|
158
|
+
): AntigravityDirectDeclineReason | undefined {
|
|
159
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return undefined;
|
|
160
|
+
const reason = antigravityMessagesDeclineReason(route, body as Rec, model, config, { cacheDiagnostics: cacheDiagnosticsObserving() });
|
|
161
|
+
return reason === "rollout-disabled" || reason === "not-migrated" ? undefined : reason;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The core option for a planned request. The bridge conversation is translated at most once, and
|
|
166
|
+
* only if the core asks for it (a reroute, or an ambiguous-reset judgment).
|
|
167
|
+
*/
|
|
168
|
+
export function messagesDirectRequestOption(
|
|
169
|
+
plan: MessagesDirectPlan,
|
|
170
|
+
body: unknown,
|
|
171
|
+
cc: OcxClaudeCodeConfig | undefined,
|
|
172
|
+
budget: TranslatorBudget,
|
|
173
|
+
): DirectRequestOption {
|
|
174
|
+
let bridge: Rec | undefined;
|
|
175
|
+
return {
|
|
176
|
+
protocol: "messages",
|
|
177
|
+
providerName: plan.providerName,
|
|
178
|
+
modelId: plan.modelId,
|
|
179
|
+
evidence: plan.evidence,
|
|
180
|
+
decode: base => withDirectMessagesConversation(
|
|
181
|
+
base,
|
|
182
|
+
plan.conversation,
|
|
183
|
+
modelId => messagesConversationToIr(body, cc, budget, modelId),
|
|
184
|
+
),
|
|
185
|
+
bridgeConversation: () => {
|
|
186
|
+
bridge ??= messagesToResponsesTranslation(body, cc, budget).body;
|
|
187
|
+
return {
|
|
188
|
+
input: bridge.input,
|
|
189
|
+
...(bridge.instructions !== undefined ? { instructions: bridge.instructions } : {}),
|
|
190
|
+
...(bridge.tools !== undefined ? { tools: bridge.tools } : {}),
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
@@ -82,6 +82,7 @@ import { preservesPhysicalComboProvider, resolveComboId } from "../combos";
|
|
|
82
82
|
import { captureRouteStaticPolicy, routedProviderConfig, routeModel, type RouteResult } from "../router";
|
|
83
83
|
import { POLICY_NAMESPACE, resolvePolicyProfileId } from "../routing/profile";
|
|
84
84
|
import type { AnthropicClientIdentity } from "../adapters/anthropic/client-identity";
|
|
85
|
+
import { logDroppedAnthropicBetas } from "../adapters/anthropic/beta-allowlist";
|
|
85
86
|
import type { OcxConfig, OcxProviderConfig, OcxUsage } from "../types";
|
|
86
87
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
87
88
|
import {
|
|
@@ -469,7 +470,10 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
469
470
|
clientIdentity: options.clientIdentity,
|
|
470
471
|
});
|
|
471
472
|
if (built.strippedOpaqueState && rejectUnrepresentable) throw new NativeOpaqueStateRefusal();
|
|
472
|
-
if (built.droppedBetas)
|
|
473
|
+
if (built.droppedBetas) {
|
|
474
|
+
addProtocolEntryReason(logCtx, "anthropic-beta-dropped");
|
|
475
|
+
logDroppedAnthropicBetas({ droppedNames: built.droppedBetaNames, droppedUnrecognized: built.droppedUnrecognizedBetas });
|
|
476
|
+
}
|
|
473
477
|
if (built.strippedOpaqueState) addProtocolEntryReason(logCtx, "opaque-state-stripped");
|
|
474
478
|
return built;
|
|
475
479
|
};
|
|
@@ -96,6 +96,8 @@ import type {
|
|
|
96
96
|
|
|
97
97
|
const CACHE_DIAGNOSTIC_HOOK = Symbol.for("opencodex.cache-diagnostic.v1");
|
|
98
98
|
interface CacheDiagnosticHooks {
|
|
99
|
+
/** Whether inbound bodies are actually recorded; the hook is registered at import either way. */
|
|
100
|
+
enabled(): boolean;
|
|
99
101
|
observeInbound(body: unknown, headers: Headers, source: PromptCacheKeySource): CacheDiagnosticDraft;
|
|
100
102
|
rebind(body: unknown, draft: CacheDiagnosticDraft | undefined): void;
|
|
101
103
|
finalize(facts: CacheDiagnosticFinalFacts): void;
|
|
@@ -104,6 +106,11 @@ function cacheDiagnosticHooks(): CacheDiagnosticHooks | undefined {
|
|
|
104
106
|
return (globalThis as Record<symbol, CacheDiagnosticHooks | undefined>)[CACHE_DIAGNOSTIC_HOOK];
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
/** Whether the cache diagnostic recorder will record inbound bodies (`OPENCODEX_CACHE_DEBUG=1`). */
|
|
110
|
+
export function cacheDiagnosticsObserving(): boolean {
|
|
111
|
+
return cacheDiagnosticHooks()?.enabled() === true;
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
export interface RequestLogContext extends UpstreamErrorDiagnostics {
|
|
108
115
|
model: string;
|
|
109
116
|
provider: string;
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
isNonReplayableResponse,
|
|
31
31
|
prepareSameTarget429Wait,
|
|
32
32
|
} from "../../lib/upstream-retry";
|
|
33
|
+
import { unwrapUpstreamRetryEvidenceError } from "../../codex/auth-context";
|
|
34
|
+
import { AdmissionModelDeniedError, MODEL_NOT_ALLOWED_FOR_KEY } from "../admission-model-scope";
|
|
33
35
|
import { redactSecretString } from "../../lib/redact";
|
|
34
36
|
import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
35
37
|
import { bindRouteReasoningReplayScope } from "./core-replay";
|
|
@@ -146,6 +148,22 @@ export function createAdapterContinuations(
|
|
|
146
148
|
sendBudgetExhausted,
|
|
147
149
|
} = sendBudgetState;
|
|
148
150
|
|
|
151
|
+
const continuationFailureEvent = (error: unknown): Extract<AdapterEvent, { type: "error" }> => {
|
|
152
|
+
if (options.abortSignal?.aborted || upstream.signal.aborted) {
|
|
153
|
+
return { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
154
|
+
}
|
|
155
|
+
const refusal = unwrapUpstreamRetryEvidenceError(error);
|
|
156
|
+
if (refusal instanceof AdmissionModelDeniedError) {
|
|
157
|
+
return {
|
|
158
|
+
type: "error", message: refusal.message, status: 403,
|
|
159
|
+
errorType: MODEL_NOT_ALLOWED_FOR_KEY, code: MODEL_NOT_ALLOWED_FOR_KEY, retryable: false,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
type: "error",
|
|
164
|
+
message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}`,
|
|
165
|
+
};
|
|
166
|
+
};
|
|
149
167
|
|
|
150
168
|
// One bounded internal continuation re-ask for clean end_turn turns that announced an edit
|
|
151
169
|
// without emitting a tool call. Anthropic gets this by default; openai-chat providers opt in
|
|
@@ -304,12 +322,7 @@ export function createAdapterContinuations(
|
|
|
304
322
|
kiroRefusalPendingReplay = undefined;
|
|
305
323
|
kiroReplaySetupFailed = true;
|
|
306
324
|
} else {
|
|
307
|
-
|
|
308
|
-
yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
309
|
-
} else {
|
|
310
|
-
yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
|
|
311
|
-
}
|
|
312
|
-
return;
|
|
325
|
+
yield continuationFailureEvent(error); return;
|
|
313
326
|
}
|
|
314
327
|
}
|
|
315
328
|
|
|
@@ -360,12 +373,7 @@ export function createAdapterContinuations(
|
|
|
360
373
|
try {
|
|
361
374
|
response = await fetchContinuation("rate-limit-429");
|
|
362
375
|
} catch (error) {
|
|
363
|
-
|
|
364
|
-
yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
365
|
-
} else {
|
|
366
|
-
yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
|
|
367
|
-
}
|
|
368
|
-
return;
|
|
376
|
+
yield continuationFailureEvent(error); return;
|
|
369
377
|
}
|
|
370
378
|
}
|
|
371
379
|
|
|
@@ -381,9 +389,7 @@ export function createAdapterContinuations(
|
|
|
381
389
|
route.provider = rotated;
|
|
382
390
|
try { await transportState.resolveCopilotSelection(nextParsed); }
|
|
383
391
|
catch (error) {
|
|
384
|
-
yield
|
|
385
|
-
...(options.abortSignal?.aborted || upstream.signal.aborted ? { status: 499 } : {}) };
|
|
386
|
-
return;
|
|
392
|
+
yield continuationFailureEvent(error); return;
|
|
387
393
|
}
|
|
388
394
|
invalidateSameTargetRequest();
|
|
389
395
|
transportState.activeAdapter = resolveSelectionAdapter(
|
|
@@ -630,13 +636,17 @@ export function createAdapterContinuations(
|
|
|
630
636
|
nextContinuationRecoveryKind = "oauth-account-429";
|
|
631
637
|
continue;
|
|
632
638
|
}
|
|
633
|
-
} catch {
|
|
639
|
+
} catch (error) {
|
|
634
640
|
// Everything in this try runs before the replay: the send happens on the next
|
|
635
641
|
// iteration, after `continue`. A throw here therefore leaves a reservation that
|
|
636
642
|
// never dispatched, and holding it would refuse a later recovery in this same
|
|
637
643
|
// request for a send that never left the process.
|
|
638
644
|
hop.permit?.release();
|
|
639
|
-
|
|
645
|
+
if (unwrapUpstreamRetryEvidenceError(error) instanceof AdmissionModelDeniedError) {
|
|
646
|
+
yield continuationFailureEvent(error);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
// Other negotiation failures preserve the original provider refusal.
|
|
640
650
|
}
|
|
641
651
|
}
|
|
642
652
|
}
|
|
@@ -717,7 +727,7 @@ export function createAdapterContinuations(
|
|
|
717
727
|
|
|
718
728
|
const fetchGuardedEmptyCompletionRetry = (): AsyncIterable<AdapterEvent> => {
|
|
719
729
|
const retryEvents = fetchTerminalGuardContinuation(parsed, "empty-completion", true);
|
|
720
|
-
|
|
730
|
+
const terminalGuardedRetry = terminalGuardEnabled
|
|
721
731
|
? guardTerminalEventStream({
|
|
722
732
|
parsed,
|
|
723
733
|
firstEvents: retryEvents,
|
|
@@ -726,6 +736,13 @@ export function createAdapterContinuations(
|
|
|
726
736
|
continuation: next => fetchTerminalGuardContinuation(next, undefined, !parsed.stream),
|
|
727
737
|
})
|
|
728
738
|
: retryEvents;
|
|
739
|
+
return parsed._advisorGuard
|
|
740
|
+
? parsed._advisorGuard({
|
|
741
|
+
parsed,
|
|
742
|
+
firstEvents: terminalGuardedRetry,
|
|
743
|
+
continuation: fetchTerminalGuardContinuation,
|
|
744
|
+
})
|
|
745
|
+
: terminalGuardedRetry;
|
|
729
746
|
};
|
|
730
747
|
|
|
731
748
|
return {
|
|
@@ -113,16 +113,28 @@ export async function deliverAdapterResponse(
|
|
|
113
113
|
continuation: next => fetchTerminalGuardContinuation(next, undefined, !parsed.stream),
|
|
114
114
|
})
|
|
115
115
|
: initialEventStream;
|
|
116
|
+
// Optional advisor guard (registered per request by the sidecar planner; absent unless the
|
|
117
|
+
// user enabled the advisor): holds synthetic `advisor` tool calls, consults the configured
|
|
118
|
+
// expert model, and re-dispatches the worker. Sits inside the empty-completion guard so an
|
|
119
|
+
// advisor-only turn never reads as an empty completion, and outside the bridge so the
|
|
120
|
+
// synthetic call never reaches the client.
|
|
121
|
+
const advisorStream = parsed._advisorGuard
|
|
122
|
+
? parsed._advisorGuard({
|
|
123
|
+
parsed,
|
|
124
|
+
firstEvents: eventStream,
|
|
125
|
+
continuation: fetchTerminalGuardContinuation,
|
|
126
|
+
})
|
|
127
|
+
: eventStream;
|
|
116
128
|
// The empty-completion guard sits OUTSIDE the terminal guard: a completed
|
|
117
129
|
// turn with no text and no tool call is retried with the IDENTICAL request
|
|
118
130
|
// (fetchTerminalGuardContinuation(parsed) replays the cached byte-identical
|
|
119
131
|
// request — same body, same headers, same signal).
|
|
120
132
|
const guardedEventStream = emptyCompletionGuardEnabled
|
|
121
133
|
? guardEmptyCompletionEventStream({
|
|
122
|
-
firstEvents:
|
|
134
|
+
firstEvents: advisorStream,
|
|
123
135
|
continuation: fetchGuardedEmptyCompletionRetry,
|
|
124
136
|
})
|
|
125
|
-
:
|
|
137
|
+
: advisorStream;
|
|
126
138
|
const { toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, bareCustomToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
127
139
|
// One completion owner for both deliveries: the bridge calls it from its terminal, the
|
|
128
140
|
// direct client encoder from the fold of the same events.
|
|
@@ -240,6 +252,16 @@ export async function deliverAdapterResponse(
|
|
|
240
252
|
} else {
|
|
241
253
|
guardedEvents = initialEvents;
|
|
242
254
|
}
|
|
255
|
+
// Optional advisor guard — same semantics as the streaming branch above.
|
|
256
|
+
if (parsed._advisorGuard) {
|
|
257
|
+
const advisorEvents: AdapterEvent[] = [];
|
|
258
|
+
for await (const event of parsed._advisorGuard({
|
|
259
|
+
parsed,
|
|
260
|
+
firstEvents: (async function* () { yield* guardedEvents; })(),
|
|
261
|
+
continuation: fetchTerminalGuardContinuation,
|
|
262
|
+
})) advisorEvents.push(event);
|
|
263
|
+
guardedEvents = advisorEvents;
|
|
264
|
+
}
|
|
243
265
|
if (emptyCompletionGuardEnabled) {
|
|
244
266
|
events = [];
|
|
245
267
|
for await (const event of guardEmptyCompletionEventStream({
|
|
@@ -112,6 +112,7 @@ import { mapCodexAuthContextErrorToResponse } from "./codex-auth-error";
|
|
|
112
112
|
import { chargeWorkflowSends } from "../../lib/workflow-budget";
|
|
113
113
|
import { isAntigravityValidationRefusal } from "./antigravity-validation-refusal";
|
|
114
114
|
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
115
|
+
import { AdmissionModelDeniedError, admissionModelDeniedResponse } from "../admission-model-scope";
|
|
115
116
|
|
|
116
117
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
117
118
|
export async function prepareAdapterExchange(
|
|
@@ -225,8 +226,13 @@ export async function prepareAdapterExchange(
|
|
|
225
226
|
|
|
226
227
|
|
|
227
228
|
let selfContainedJudgment: boolean | undefined;
|
|
229
|
+
// On the Antigravity direct lane the raw body carries only settings; judge the body the bridge
|
|
230
|
+
// would have sent, built only if an ambiguous reset actually asks.
|
|
231
|
+
const directRequest = options.directRequest;
|
|
228
232
|
const requestIsSelfContained = (): boolean =>
|
|
229
|
-
selfContainedJudgment ??= selfContainedResponsesBody(
|
|
233
|
+
selfContainedJudgment ??= selfContainedResponsesBody(directRequest
|
|
234
|
+
? { ...(parsed._rawBody as Record<string, unknown>), ...directRequest.bridgeConversation() }
|
|
235
|
+
: parsed._rawBody);
|
|
230
236
|
const claimPreHeaderResend = (): boolean => authorizeResendForRecovery(
|
|
231
237
|
"pre-header", "connection-reset",
|
|
232
238
|
ambiguousResendAllowanceFor(route.provider, requestIsSelfContained, claimAmbiguousResend),
|
|
@@ -541,6 +547,10 @@ export async function prepareAdapterExchange(
|
|
|
541
547
|
return storageRefusal;
|
|
542
548
|
}
|
|
543
549
|
const refusal = err instanceof UpstreamRetryEvidenceError ? err.cause : err;
|
|
550
|
+
if (refusal instanceof AdmissionModelDeniedError) {
|
|
551
|
+
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
552
|
+
return admissionModelDeniedResponse(refusal);
|
|
553
|
+
}
|
|
544
554
|
const codexRefusal = mapCodexAuthContextErrorToResponse(unwrapUpstreamRetryEvidenceError(err), {
|
|
545
555
|
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
546
556
|
});
|
|
@@ -810,6 +820,13 @@ export async function prepareAdapterExchange(
|
|
|
810
820
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
811
821
|
return { failed: storageRefusal };
|
|
812
822
|
}
|
|
823
|
+
const scopeRefusal = unwrapUpstreamRetryEvidenceError(err);
|
|
824
|
+
if (!options.abortSignal?.aborted && scopeRefusal instanceof AdmissionModelDeniedError) {
|
|
825
|
+
cleanupUpstreamAbort();
|
|
826
|
+
upstream.abort();
|
|
827
|
+
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
828
|
+
return { failed: admissionModelDeniedResponse(scopeRefusal) };
|
|
829
|
+
}
|
|
813
830
|
const codexRefusal = !options.abortSignal?.aborted && mapCodexAuthContextErrorToResponse(unwrapUpstreamRetryEvidenceError(err), {
|
|
814
831
|
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
815
832
|
});
|
|
@@ -1539,7 +1556,17 @@ export async function prepareAdapterExchange(
|
|
|
1539
1556
|
// The hop's permit is already settled by the dispatch boundary above; continuing
|
|
1540
1557
|
// only skips the remaining arms, it does not abandon a reservation.
|
|
1541
1558
|
if (isNonReplayableResponse(upstreamResponse)) continue recovery;
|
|
1542
|
-
} catch {
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
// A local admission denial is terminal for the whole request: rotating to another
|
|
1561
|
+
// account cannot re-scope the key, so the refusal answers in place of the 429.
|
|
1562
|
+
const scopeRefusal = unwrapUpstreamRetryEvidenceError(err);
|
|
1563
|
+
if (!options.abortSignal?.aborted && scopeRefusal instanceof AdmissionModelDeniedError) {
|
|
1564
|
+
hop.permit?.release();
|
|
1565
|
+
cleanupUpstreamAbort();
|
|
1566
|
+
upstream.abort();
|
|
1567
|
+
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
1568
|
+
return admissionModelDeniedResponse(scopeRefusal);
|
|
1569
|
+
}
|
|
1543
1570
|
// A throw before the send — snapshot fetch, credential application, adapter
|
|
1544
1571
|
// resolution — must hand the reservation back. Without this the ladder charges the
|
|
1545
1572
|
// request for a send it never made, and a later recovery in the same request is
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Core-owned, config-scoped registration. An inactive install has no Advisor factory. */
|
|
2
|
+
import type { OcxConfig, OcxParsedRequest, OcxTool } from "../../types";
|
|
3
|
+
|
|
4
|
+
export interface AdvisorPlanInput {
|
|
5
|
+
config: OcxConfig;
|
|
6
|
+
workerIdentity: string;
|
|
7
|
+
workerModelId: string;
|
|
8
|
+
abortSignal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RegisteredAdvisorPlan {
|
|
12
|
+
tool: OcxTool;
|
|
13
|
+
preflightInject(parsed: OcxParsedRequest): Promise<boolean>;
|
|
14
|
+
attachGuard(parsed: OcxParsedRequest): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type AdvisorPlanFactory = (input: AdvisorPlanInput) => Promise<RegisteredAdvisorPlan | null>;
|
|
18
|
+
const factories = new WeakMap<OcxConfig, AdvisorPlanFactory>();
|
|
19
|
+
|
|
20
|
+
export function setAdvisorPlanFactory(config: OcxConfig, factory: AdvisorPlanFactory | null): void {
|
|
21
|
+
if (factory) factories.set(config, factory);
|
|
22
|
+
else factories.delete(config);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createRegisteredAdvisorPlan(input: AdvisorPlanInput): Promise<RegisteredAdvisorPlan | null> | null {
|
|
26
|
+
return factories.get(input.config)?.(input) ?? null;
|
|
27
|
+
}
|