@bitkyc08/opencodex 2.79.0 → 2.81.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 +3 -3
- package/gui/dist/assets/App-7yhoys8a.js +54 -0
- package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
- package/gui/dist/assets/index-RsoaAxXY.js +94 -0
- package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
- package/gui/dist/index.html +1 -1
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +7 -2
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/cursor/live-transport.ts +5 -3
- package/src/adapters/cursor/native-exec-fs.ts +12 -9
- package/src/adapters/cursor/native-exec.ts +13 -5
- package/src/adapters/cursor/protobuf-request.ts +13 -7
- package/src/adapters/cursor/request-builder.ts +23 -10
- package/src/adapters/cursor/tool-definitions.ts +9 -6
- package/src/adapters/cursor/tool-guidance.ts +29 -8
- package/src/adapters/cursor/tool-naming.ts +40 -7
- package/src/adapters/cursor/tool-wording.ts +46 -0
- package/src/adapters/cursor.ts +3 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/devin.ts +25 -11
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -4
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +10 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-integrations.ts +16 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +11 -5
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +32 -3
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +1 -0
- package/src/codex/autostart-health.ts +3 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +15 -7
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +5 -3
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +15 -9
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/journal.ts +36 -5
- package/src/codex/shim-overlay.ts +9 -1
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +6 -3
- package/src/combos/types.ts +1 -1
- package/src/config/diagnostics.ts +27 -16
- package/src/config/load-degrade.ts +34 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +7 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +347 -199
- package/src/generated/model-metadata.ts +5 -4
- package/src/images/loop.ts +2 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +8 -2
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +236 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +51 -17
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +90 -173
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +67 -9
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/derive.ts +2 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +22 -32
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/spill-store.ts +5 -1
- package/src/responses/state/metrics.ts +87 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-queue.ts +1 -0
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +50 -66
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/chat-native.ts +45 -25
- package/src/server/claude-messages.ts +16 -2
- package/src/server/gui-static.ts +38 -4
- package/src/server/index/spend-ledger-lifecycle.ts +10 -4
- package/src/server/index.ts +16 -16
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +30 -6
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/config-routes.ts +27 -7
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +86 -41
- package/src/server/messages-native-scope.ts +4 -1
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +84 -25
- package/src/server/request-log.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +29 -17
- package/src/server/responses/adapter-dispatch.ts +47 -17
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/combo-requested-effort.ts +14 -0
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-combo-native.ts +4 -1
- package/src/server/responses/core-combo.ts +21 -18
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +3 -1
- package/src/server/responses/core-replay.ts +5 -0
- package/src/server/responses/passthrough-delivery.ts +7 -3
- package/src/server/responses/passthrough-dispatch.ts +21 -8
- package/src/server/responses/request-prepare.ts +37 -5
- package/src/server/responses/request-send-budget.ts +74 -37
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/run-turn-execution.ts +11 -3
- package/src/server/responses/sidecar-execution.ts +96 -14
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/workflow-refusal.ts +30 -0
- package/src/service/desktop-startup.ts +100 -3
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +12 -28
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types.ts +4 -0
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/header-deadline.ts +67 -0
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -10
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-BOUa-ek3.js +0 -54
- package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
- package/gui/dist/assets/index-DwHNDF4s.js +0 -94
- package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
import { jsonResponse } from "../auth-cors";
|
|
15
15
|
import type { ManagementContext } from "./context";
|
|
16
16
|
import { getValidAccessSnapshotForAccount } from "../../oauth";
|
|
17
|
-
import {
|
|
17
|
+
import { configuredAnthropicInstance, isAnthropicInstanceId, type AnthropicInstanceId } from "../../providers/anthropic-instance";
|
|
18
|
+
import { captureAnthropicPhysicalSendOwnership, anthropicPhysicalSendOwnershipIsCurrent } from "../../oauth/anthropic-send-ownership";
|
|
19
|
+
import { anthropicCooldownRecoveryFor } from "../../providers/quota/anthropic-cooldown-recovery";
|
|
20
|
+
import { captureProviderAccountQuotaEpoch } from "../../providers/quota/account-cache";
|
|
21
|
+
import { captureOAuthAccountSelection, getAccountSet, getAccountCredentialWithStatus, listAccounts } from "../../oauth/store";
|
|
18
22
|
import {
|
|
19
23
|
ANTHROPIC_RESET_GRANT_ID_RE,
|
|
20
24
|
AnthropicResetGrantError,
|
|
@@ -28,19 +32,20 @@ import {
|
|
|
28
32
|
AnthropicResetLedgerError,
|
|
29
33
|
anthropicOrgDigest,
|
|
30
34
|
anthropicResetOperationExists,
|
|
35
|
+
anthropicResetJournalPathForInstance,
|
|
31
36
|
beginAnthropicResetOperation,
|
|
32
37
|
pendingAnthropicResetOperation,
|
|
33
38
|
releaseAnthropicResetLease,
|
|
34
39
|
settleAnthropicResetOperation,
|
|
35
40
|
} from "../../providers/anthropic-reset-grant-ledger";
|
|
36
41
|
|
|
37
|
-
const PROVIDER = "anthropic";
|
|
38
42
|
const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
39
43
|
|
|
40
44
|
const MESSAGES = {
|
|
41
45
|
method_not_allowed: "Method not allowed",
|
|
42
46
|
session_required: "Spending a reset requires a dashboard session.",
|
|
43
47
|
invalid_json: "Request body must be JSON.",
|
|
48
|
+
invalid_provider: "No matching builtin Anthropic OAuth provider.",
|
|
44
49
|
invalid_grant_id: "grantId is missing or malformed.",
|
|
45
50
|
invalid_operation_id: "operationId must be a UUIDv4.",
|
|
46
51
|
no_account: "No matching Anthropic OAuth account.",
|
|
@@ -60,6 +65,9 @@ const MESSAGES = {
|
|
|
60
65
|
type ErrorCode = keyof typeof MESSAGES;
|
|
61
66
|
|
|
62
67
|
export interface AnthropicResetGrantRouteDeps {
|
|
68
|
+
provider?: AnthropicInstanceId;
|
|
69
|
+
isCurrent?: () => boolean;
|
|
70
|
+
forProvider?: (provider: AnthropicInstanceId) => AnthropicResetGrantRouteDeps;
|
|
63
71
|
fetchFn?: typeof globalThis.fetch;
|
|
64
72
|
journalPath?: string;
|
|
65
73
|
now?: () => number;
|
|
@@ -68,25 +76,64 @@ export interface AnthropicResetGrantRouteDeps {
|
|
|
68
76
|
accessTokenFor: (accountId: string) => Promise<string>;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
function defaultDepsFor(ctx: ManagementContext, provider: AnthropicInstanceId): AnthropicResetGrantRouteDeps | null {
|
|
80
|
+
// One source: the live management config. A keeps its legacy reach without a providers row;
|
|
81
|
+
// B requires its configured builtin row. A fresh file load here would refuse on any drift.
|
|
82
|
+
const live = ctx.config;
|
|
83
|
+
const target = live.providers?.[provider];
|
|
84
|
+
if (target?.disabled || target && target.authMode !== "oauth" || configuredAnthropicInstance(live, provider) !== provider) return null;
|
|
85
|
+
const identity = JSON.stringify(target ?? null);
|
|
86
|
+
let owner: ReturnType<typeof captureAnthropicPhysicalSendOwnership> = null;
|
|
87
|
+
const targetCurrent = () => configuredAnthropicInstance(live, provider) === provider
|
|
88
|
+
&& JSON.stringify(live.providers?.[provider] ?? null) === identity;
|
|
89
|
+
return {
|
|
90
|
+
// Only the journal path: the ledger's numeric `now` option is not the deps clock function.
|
|
91
|
+
provider, journalPath: anthropicResetJournalPathForInstance(provider),
|
|
92
|
+
listAccountIds: () => listAccounts(provider).map(account => account.id),
|
|
93
|
+
activeAccountId: () => captureOAuthAccountSelection(provider)?.accountId ?? null,
|
|
94
|
+
isCurrent: () => {
|
|
95
|
+
if (!targetCurrent() || !owner || !anthropicPhysicalSendOwnershipIsCurrent(owner)) return false;
|
|
96
|
+
const row = getAccountCredentialWithStatus(provider, owner.accountId);
|
|
97
|
+
return !!row && !row.paused && !row.needsReauth;
|
|
98
|
+
},
|
|
99
|
+
accessTokenFor: async accountId => {
|
|
100
|
+
const initial = getAccountSet(provider)?.accounts.find(row => row.id === accountId);
|
|
101
|
+
const recovery = anthropicCooldownRecoveryFor(provider);
|
|
102
|
+
const incarnation = recovery.reserveAnthropicAccountIncarnation(accountId);
|
|
103
|
+
const epoch = captureProviderAccountQuotaEpoch(provider);
|
|
104
|
+
const snapshot = await getValidAccessSnapshotForAccount(provider, accountId, { requireUsableAccount: true });
|
|
105
|
+
const current = getAccountSet(provider)?.accounts.find(row => row.id === accountId);
|
|
106
|
+
if (!initial || !current || initial.loginId !== current.loginId || initial.addedAt !== current.addedAt
|
|
107
|
+
|| recovery.anthropicAccountIncarnation(accountId) !== incarnation || captureProviderAccountQuotaEpoch(provider) !== epoch) throw new Error("reset account replaced");
|
|
108
|
+
owner = captureAnthropicPhysicalSendOwnership(snapshot);
|
|
109
|
+
if (!targetCurrent() || !owner) throw new Error("reset owner changed");
|
|
110
|
+
return snapshot.accessToken;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function selectDeps(ctx: ManagementContext, requested: unknown, deps?: AnthropicResetGrantRouteDeps): AnthropicResetGrantRouteDeps | null {
|
|
116
|
+
const provider = requested === undefined || requested === null ? "anthropic" : requested;
|
|
117
|
+
if (!isAnthropicInstanceId(provider)) return null;
|
|
118
|
+
const selected = deps?.forProvider?.(provider) ?? deps;
|
|
119
|
+
// Legacy injected dependencies are A-only; B must explicitly bind its own journal.
|
|
120
|
+
if (selected) return (selected.provider ?? "anthropic") === provider
|
|
121
|
+
? { journalPath: anthropicResetJournalPathForInstance(provider), ...selected, provider } : null;
|
|
122
|
+
return defaultDepsFor(ctx, provider);
|
|
123
|
+
}
|
|
77
124
|
|
|
78
125
|
function fail(ctx: ManagementContext, status: number, code: ErrorCode, extra: Record<string, unknown> = {}): Response {
|
|
79
126
|
return jsonResponse({ error: { code, message: MESSAGES[code], ...extra } }, status, ctx.req, ctx.config);
|
|
80
127
|
}
|
|
81
128
|
|
|
82
|
-
function ledgerFailure(ctx: ManagementContext, error: unknown): Response {
|
|
83
|
-
if (error instanceof AnthropicResetLedgerError && error.code === "busy") return fail(ctx, 503, "ledger_busy");
|
|
84
|
-
return fail(ctx, 503, "ledger_unavailable");
|
|
129
|
+
function ledgerFailure(ctx: ManagementContext, error: unknown, extra: Record<string, unknown> = {}): Response {
|
|
130
|
+
if (error instanceof AnthropicResetLedgerError && error.code === "busy") return fail(ctx, 503, "ledger_busy", extra);
|
|
131
|
+
return fail(ctx, 503, "ledger_unavailable", extra);
|
|
85
132
|
}
|
|
86
133
|
|
|
87
|
-
function readFailure(ctx: ManagementContext, error: unknown): Response {
|
|
88
|
-
if (error instanceof AnthropicResetGrantError && error.code === "auth") return fail(ctx, 401, "auth_failed");
|
|
89
|
-
return fail(ctx, 502, "upstream_unavailable");
|
|
134
|
+
function readFailure(ctx: ManagementContext, error: unknown, extra: Record<string, unknown> = {}): Response {
|
|
135
|
+
if (error instanceof AnthropicResetGrantError && error.code === "auth") return fail(ctx, 401, "auth_failed", extra);
|
|
136
|
+
return fail(ctx, 502, "upstream_unavailable", extra);
|
|
90
137
|
}
|
|
91
138
|
|
|
92
139
|
function resolveAccountId(deps: AnthropicResetGrantRouteDeps, requested: unknown): string | null {
|
|
@@ -107,16 +154,19 @@ async function tokenFor(deps: AnthropicResetGrantRouteDeps, accountId: string):
|
|
|
107
154
|
}
|
|
108
155
|
|
|
109
156
|
async function handleRead(ctx: ManagementContext, deps: AnthropicResetGrantRouteDeps): Promise<Response> {
|
|
157
|
+
const receipt = { provider: deps.provider };
|
|
110
158
|
const accountId = resolveAccountId(deps, ctx.url.searchParams.get("accountId") ?? undefined);
|
|
111
|
-
if (!accountId) return fail(ctx, 400, "no_account");
|
|
159
|
+
if (!accountId) return fail(ctx, 400, "no_account", receipt);
|
|
112
160
|
const accessToken = await tokenFor(deps, accountId);
|
|
113
|
-
if (!accessToken) return fail(ctx, 401, "auth_failed");
|
|
161
|
+
if (!accessToken) return fail(ctx, 401, "auth_failed", receipt);
|
|
162
|
+
if (deps.isCurrent?.() === false) return fail(ctx, 401, "auth_failed", receipt);
|
|
114
163
|
let status;
|
|
115
164
|
try {
|
|
116
165
|
status = await fetchAnthropicResetGrantStatus({ accessToken, fetchFn: deps.fetchFn });
|
|
117
166
|
} catch (error) {
|
|
118
|
-
return readFailure(ctx, error);
|
|
167
|
+
return readFailure(ctx, error, receipt);
|
|
119
168
|
}
|
|
169
|
+
if (deps.isCurrent?.() === false) return fail(ctx, 401, "auth_failed", receipt);
|
|
120
170
|
let pendingOperation = null;
|
|
121
171
|
let journalAvailable = true;
|
|
122
172
|
try {
|
|
@@ -124,10 +174,10 @@ async function handleRead(ctx: ManagementContext, deps: AnthropicResetGrantRoute
|
|
|
124
174
|
} catch {
|
|
125
175
|
journalAvailable = false;
|
|
126
176
|
}
|
|
127
|
-
return jsonResponse({ accountId, ...status, pendingOperation, journalAvailable }, 200, ctx.req, ctx.config);
|
|
177
|
+
return jsonResponse({ provider: deps.provider, accountId, ...status, pendingOperation, journalAvailable }, 200, ctx.req, ctx.config);
|
|
128
178
|
}
|
|
129
179
|
|
|
130
|
-
async function handleConsume(ctx: ManagementContext,
|
|
180
|
+
async function handleConsume(ctx: ManagementContext, injected?: AnthropicResetGrantRouteDeps): Promise<Response> {
|
|
131
181
|
if (ctx.principal !== "gui-session") return fail(ctx, 403, "session_required");
|
|
132
182
|
let body: Record<string, unknown>;
|
|
133
183
|
try {
|
|
@@ -137,21 +187,27 @@ async function handleConsume(ctx: ManagementContext, deps: AnthropicResetGrantRo
|
|
|
137
187
|
} catch {
|
|
138
188
|
return fail(ctx, 400, "invalid_json");
|
|
139
189
|
}
|
|
190
|
+
const deps = selectDeps(ctx, body.provider, injected);
|
|
191
|
+
if (!deps) return fail(ctx, 400, "invalid_provider");
|
|
192
|
+
const provider = deps.provider;
|
|
193
|
+
const operationReceipt = { provider };
|
|
194
|
+
const scopedFail = (status: number, code: ErrorCode, extra: Record<string, unknown> = {}) => fail(ctx, status, code, { ...operationReceipt, ...extra });
|
|
140
195
|
const { grantId, operationId } = body;
|
|
141
|
-
if (typeof grantId !== "string" || !ANTHROPIC_RESET_GRANT_ID_RE.test(grantId)) return
|
|
142
|
-
if (typeof operationId !== "string" || !UUID_V4_RE.test(operationId)) return
|
|
143
|
-
if (typeof body.accountId !== "string") return
|
|
196
|
+
if (typeof grantId !== "string" || !ANTHROPIC_RESET_GRANT_ID_RE.test(grantId)) return scopedFail(400, "invalid_grant_id");
|
|
197
|
+
if (typeof operationId !== "string" || !UUID_V4_RE.test(operationId)) return scopedFail(400, "invalid_operation_id");
|
|
198
|
+
if (typeof body.accountId !== "string") return scopedFail(400, "no_account");
|
|
144
199
|
const accountId = resolveAccountId(deps, body.accountId);
|
|
145
|
-
if (!accountId) return
|
|
200
|
+
if (!accountId) return scopedFail(400, "no_account");
|
|
146
201
|
|
|
147
202
|
const accessToken = await tokenFor(deps, accountId);
|
|
148
|
-
if (!accessToken) return
|
|
203
|
+
if (!accessToken || deps.isCurrent?.() === false) return scopedFail(401, "auth_failed");
|
|
149
204
|
let organizationUuid: string;
|
|
150
205
|
try {
|
|
151
206
|
organizationUuid = await fetchAnthropicOrganizationUuid({ accessToken, fetchFn: deps.fetchFn });
|
|
152
207
|
} catch (error) {
|
|
153
|
-
return readFailure(ctx, error);
|
|
208
|
+
return readFailure(ctx, error, operationReceipt);
|
|
154
209
|
}
|
|
210
|
+
if (deps.isCurrent?.() === false) return scopedFail(401, "auth_failed");
|
|
155
211
|
const ledger = { journalPath: deps.journalPath, now: deps.now?.() };
|
|
156
212
|
|
|
157
213
|
// A new operation must pass the spend gate on a fresh read. A same-id retry of
|
|
@@ -161,39 +217,40 @@ async function handleConsume(ctx: ManagementContext, deps: AnthropicResetGrantRo
|
|
|
161
217
|
try {
|
|
162
218
|
known = anthropicResetOperationExists(operationId, ledger);
|
|
163
219
|
} catch (error) {
|
|
164
|
-
return ledgerFailure(ctx, error);
|
|
220
|
+
return ledgerFailure(ctx, error, operationReceipt);
|
|
165
221
|
}
|
|
166
222
|
if (!known) {
|
|
167
223
|
let status;
|
|
168
224
|
try {
|
|
169
225
|
status = await fetchAnthropicResetGrantStatus({ accessToken, fetchFn: deps.fetchFn });
|
|
170
226
|
} catch (error) {
|
|
171
|
-
return readFailure(ctx, error);
|
|
227
|
+
return readFailure(ctx, error, operationReceipt);
|
|
172
228
|
}
|
|
173
229
|
const blocker = anthropicResetGrantBlocker(status, grantId);
|
|
174
|
-
if (blocker) return
|
|
230
|
+
if (blocker) return scopedFail(409, "grant_not_usable", { reason: blocker });
|
|
175
231
|
}
|
|
176
232
|
|
|
177
233
|
let begin;
|
|
234
|
+
if (deps.isCurrent?.() === false) return scopedFail(401, "auth_failed");
|
|
178
235
|
try {
|
|
179
236
|
begin = beginAnthropicResetOperation(
|
|
180
237
|
{ operationId, accountId, grantId, orgDigest: anthropicOrgDigest(organizationUuid) },
|
|
181
238
|
{ journalPath: deps.journalPath, now: deps.now?.() },
|
|
182
239
|
);
|
|
183
240
|
} catch (error) {
|
|
184
|
-
return ledgerFailure(ctx, error);
|
|
241
|
+
return ledgerFailure(ctx, error, operationReceipt);
|
|
185
242
|
}
|
|
186
243
|
switch (begin.kind) {
|
|
187
244
|
case "replay":
|
|
188
245
|
return jsonResponse(
|
|
189
|
-
{ code: begin.code, replayed: true, resetsLeft: begin.resetsLeft, accountId, grantId, operationId },
|
|
246
|
+
{ provider, code: begin.code, replayed: true, resetsLeft: begin.resetsLeft, accountId, grantId, operationId },
|
|
190
247
|
200, ctx.req, ctx.config,
|
|
191
248
|
);
|
|
192
|
-
case "identity-mismatch": return
|
|
193
|
-
case "in-flight": return
|
|
194
|
-
case "expired": return
|
|
195
|
-
case "unresolved-prior": return
|
|
196
|
-
case "capacity": return
|
|
249
|
+
case "identity-mismatch": return scopedFail(409, "operation_identity_mismatch");
|
|
250
|
+
case "in-flight": return scopedFail(409, "in_flight");
|
|
251
|
+
case "expired": return scopedFail(409, "unknown_outcome_expired");
|
|
252
|
+
case "unresolved-prior": return scopedFail(409, "unresolved_prior_operation", { pendingOperationId: begin.operationId });
|
|
253
|
+
case "capacity": return scopedFail(503, "ledger_capacity");
|
|
197
254
|
case "execute": break;
|
|
198
255
|
}
|
|
199
256
|
|
|
@@ -206,7 +263,7 @@ async function handleConsume(ctx: ManagementContext, deps: AnthropicResetGrantRo
|
|
|
206
263
|
try {
|
|
207
264
|
releaseAnthropicResetLease(operationId, { journalPath: deps.journalPath, now: deps.now?.() });
|
|
208
265
|
} catch { /* the lease expires on its own */ }
|
|
209
|
-
return
|
|
266
|
+
return scopedFail(502, "unknown_outcome", { operationId, grantId, accountId });
|
|
210
267
|
}
|
|
211
268
|
let settled;
|
|
212
269
|
try {
|
|
@@ -216,13 +273,14 @@ async function handleConsume(ctx: ManagementContext, deps: AnthropicResetGrantRo
|
|
|
216
273
|
);
|
|
217
274
|
} catch {
|
|
218
275
|
// Fail closed: the caller must not read this as a durable result.
|
|
219
|
-
return
|
|
276
|
+
return scopedFail(500, "journal_write_failed", { operationId, grantId, accountId });
|
|
220
277
|
}
|
|
221
278
|
// Report what the journal holds. An overlapping same-id attempt may have
|
|
222
279
|
// settled first; its answer is the canonical one.
|
|
223
280
|
const replayed = settled.code !== answer.code || settled.resetsLeft !== answer.resetsLeft;
|
|
224
281
|
return jsonResponse(
|
|
225
282
|
{
|
|
283
|
+
provider,
|
|
226
284
|
code: settled.code,
|
|
227
285
|
replayed,
|
|
228
286
|
resetsLeft: settled.resetsLeft,
|
|
@@ -237,12 +295,13 @@ async function handleConsume(ctx: ManagementContext, deps: AnthropicResetGrantRo
|
|
|
237
295
|
|
|
238
296
|
export async function handleAnthropicResetGrantRoutes(
|
|
239
297
|
ctx: ManagementContext,
|
|
240
|
-
deps
|
|
298
|
+
deps?: AnthropicResetGrantRouteDeps,
|
|
241
299
|
): Promise<Response | null> {
|
|
242
300
|
const { pathname } = ctx.url;
|
|
243
301
|
if (pathname === "/api/anthropic/reset-grants") {
|
|
244
302
|
if (ctx.req.method !== "GET") return fail(ctx, 405, "method_not_allowed");
|
|
245
|
-
|
|
303
|
+
const selected = selectDeps(ctx, ctx.url.searchParams.get("provider"), deps);
|
|
304
|
+
return selected ? handleRead(ctx, selected) : fail(ctx, 400, "invalid_provider");
|
|
246
305
|
}
|
|
247
306
|
if (pathname === "/api/anthropic/reset-grants/consume") {
|
|
248
307
|
if (ctx.req.method !== "POST") return fail(ctx, 405, "method_not_allowed");
|
|
@@ -4,6 +4,8 @@ import { getMainAccountExternalUsageWarning } from "../../codex/main-account-ext
|
|
|
4
4
|
import { getObservedMainQuotaIdentityKey } from "../../codex/main-account-cache";
|
|
5
5
|
import { compactionRoutingSchema, memoryModelsSchema } from "../../config/schema/leaf-validators";
|
|
6
6
|
import { compactionRecoverySchema } from "../../config/schema/compaction-recovery";
|
|
7
|
+
import { anthropicSidecarPatchError } from "../../config/schema/anthropic-account-pool";
|
|
8
|
+
import { isAnthropicInstanceId } from "../../providers/anthropic-instance-id";
|
|
7
9
|
import { captureConfigTopLevelRollback } from "../../config/rebase-provenance";
|
|
8
10
|
import type { IntegrationClientId } from "../../integrations/registry";
|
|
9
11
|
import { randomUUID } from "node:crypto";
|
|
@@ -85,7 +87,6 @@ import { getProviderRegistryEntry } from "../../providers/registry";
|
|
|
85
87
|
import { VISION_REASONING_EFFORTS, isVisionReasoningEffort } from "../../reasoning-effort";
|
|
86
88
|
import { normalizeVisionReasoningForModel } from "../../vision/reasoning";
|
|
87
89
|
import {
|
|
88
|
-
findAnthropicVisionProvider,
|
|
89
90
|
isValidVisionTimeoutMs,
|
|
90
91
|
MAX_VISION_TIMEOUT_MS,
|
|
91
92
|
MIN_VISION_TIMEOUT_MS,
|
|
@@ -105,6 +106,7 @@ import {
|
|
|
105
106
|
webSearchModelIsRejected,
|
|
106
107
|
webSearchModelOptionsFrom,
|
|
107
108
|
webSearchModelRejection,
|
|
109
|
+
sidecarAnthropicPoolOptions, sidecarOptionsAuth, sidecarSettingsAfterPatch,
|
|
108
110
|
} from "./web-search-sidecar-options";
|
|
109
111
|
import { validateXaiSearchOptions } from "../../web-search/xai-executor";
|
|
110
112
|
import { getDebugLogEntries } from "../../lib/debug-log-buffer";
|
|
@@ -150,7 +152,9 @@ async function sidecarVisionResponseSettings(config: OcxConfig): Promise<{
|
|
|
150
152
|
const vs = config.visionSidecar ?? {};
|
|
151
153
|
// Match the runtime's one selected Anthropic executor for both backend fallback
|
|
152
154
|
// and catalog reachability; resolving it once prevents the two projections drifting.
|
|
153
|
-
const
|
|
155
|
+
const auth = sidecarOptionsAuth(config, vs.anthropicInstance);
|
|
156
|
+
const anthropicSidecar = auth.isAnthropicAuth && auth.anthropicProviderName && auth.anthropicProvider
|
|
157
|
+
? { providerName: auth.anthropicProviderName, provider: auth.anthropicProvider, config } : undefined;
|
|
154
158
|
// The routed backend reports its own namespaced model verbatim: it is the
|
|
155
159
|
// dispatched value, and collapsing it through the legacy resolver would
|
|
156
160
|
// display a describer the runtime is not using (roadmap 190).
|
|
@@ -276,7 +280,7 @@ export async function syncEnabledClientIntegrations(
|
|
|
276
280
|
},
|
|
277
281
|
config,
|
|
278
282
|
port,
|
|
279
|
-
}, ["mcode", "pi", "aside", "raycast", "omo", "cline", "droid", "opencode", "kilo"]));
|
|
283
|
+
}, ["mcode", "pi", "aside", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo"]));
|
|
280
284
|
|
|
281
285
|
return out;
|
|
282
286
|
}
|
|
@@ -290,6 +294,8 @@ function publicVisionSidecarSettings(
|
|
|
290
294
|
enabled: vs.enabled !== false,
|
|
291
295
|
model: vision.model,
|
|
292
296
|
backend: vs.backend,
|
|
297
|
+
...(vs.anthropicInstance ? { anthropicInstance: vs.anthropicInstance } : {}),
|
|
298
|
+
anthropicPool: sidecarAnthropicPoolOptions(config, { ...vs, backend: vs.backend ?? (vision.models.find(row => row.value === vision.model)?.backend) }),
|
|
293
299
|
reasoning: vision.reasoning,
|
|
294
300
|
maxDescriptionsPerTurn: resolveMaxDescriptionsPerTurn(vs.maxDescriptionsPerTurn),
|
|
295
301
|
timeoutMs: resolveVisionTimeoutMs(vs.timeoutMs),
|
|
@@ -882,6 +888,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
882
888
|
enabled: ws.enabled !== false,
|
|
883
889
|
model: ws.model ?? "gpt-5.6-luna",
|
|
884
890
|
backend: ws.backend,
|
|
891
|
+
...(ws.anthropicInstance ? { anthropicInstance: ws.anthropicInstance } : {}),
|
|
892
|
+
anthropicPool: sidecarAnthropicPoolOptions(config, ws),
|
|
885
893
|
streamRoutedModelOutput: ws.streamRoutedModelOutput === true,
|
|
886
894
|
...(ws.xSearch ? { xSearch: ws.xSearch } : {}),
|
|
887
895
|
},
|
|
@@ -902,10 +910,11 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
902
910
|
if (raw.webSearch !== undefined && !isPlainRecord(raw.webSearch)) return jsonResponse({ error: "webSearch must be an object" }, 400);
|
|
903
911
|
if (raw.vision !== undefined && !isPlainRecord(raw.vision)) return jsonResponse({ error: "vision must be an object" }, 400);
|
|
904
912
|
const body = raw as {
|
|
905
|
-
webSearch?: { enabled?: unknown; model?: unknown; backend?: unknown; reasoning?: unknown; streamRoutedModelOutput?: unknown; exaApiKey?: unknown; xSearch?: unknown };
|
|
913
|
+
webSearch?: { enabled?: unknown; model?: unknown; backend?: unknown; anthropicInstance?: unknown; reasoning?: unknown; streamRoutedModelOutput?: unknown; exaApiKey?: unknown; xSearch?: unknown };
|
|
906
914
|
vision?: {
|
|
907
915
|
model?: unknown;
|
|
908
916
|
backend?: unknown;
|
|
917
|
+
anthropicInstance?: unknown;
|
|
909
918
|
reasoning?: unknown;
|
|
910
919
|
maxDescriptionsPerTurn?: unknown;
|
|
911
920
|
enabled?: unknown;
|
|
@@ -913,6 +922,11 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
913
922
|
};
|
|
914
923
|
};
|
|
915
924
|
const WEB_SEARCH_BACKENDS_UNION = ["openai", "anthropic", "xai", "gemini", "exa"] as const;
|
|
925
|
+
const instanceError = anthropicSidecarPatchError(config, {
|
|
926
|
+
...(body.webSearch ? { webSearchSidecar: body.webSearch } : {}),
|
|
927
|
+
...(body.vision ? { visionSidecar: body.vision } : {}),
|
|
928
|
+
});
|
|
929
|
+
if (instanceError) return jsonResponse({ error: instanceError }, 400);
|
|
916
930
|
if (body.webSearch && body.webSearch.backend !== undefined && body.webSearch.backend !== null
|
|
917
931
|
&& !WEB_SEARCH_BACKENDS_UNION.includes(body.webSearch.backend as never)) {
|
|
918
932
|
return jsonResponse({ error: "webSearch.backend must be openai, anthropic, xai, gemini, exa, or null" }, 400);
|
|
@@ -956,7 +970,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
956
970
|
// rejection body, so a 400 cannot cost two provider fetches.
|
|
957
971
|
if (body.vision && typeof body.vision.model === "string" && body.vision.model !== "") {
|
|
958
972
|
const requested = body.vision.model;
|
|
959
|
-
const candidates = await visionCandidateRows(config);
|
|
973
|
+
const candidates = await visionCandidateRows({ ...config, visionSidecar: sidecarSettingsAfterPatch(config.visionSidecar, body.vision) });
|
|
960
974
|
const hint = body.vision.backend === "anthropic" || body.vision.backend === "openai"
|
|
961
975
|
|| body.vision.backend === "routed"
|
|
962
976
|
? body.vision.backend
|
|
@@ -994,7 +1008,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
994
1008
|
// only case that owes a config.toml rewrite.
|
|
995
1009
|
const webSearchEnabledBefore = config.webSearchSidecar?.enabled !== false;
|
|
996
1010
|
if (body.webSearch) {
|
|
997
|
-
const pairTouched = body.webSearch.model !== undefined || body.webSearch.backend !== undefined;
|
|
1011
|
+
const pairTouched = body.webSearch.model !== undefined || body.webSearch.backend !== undefined || body.webSearch.anthropicInstance !== undefined;
|
|
998
1012
|
// Validate against the backend the caller SUBMITTED, across the whole
|
|
999
1013
|
// union — not just openai/anthropic (#2457). The union check above has
|
|
1000
1014
|
// already refused unknown literals, so a surviving string is a member;
|
|
@@ -1013,12 +1027,14 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
1013
1027
|
? body.webSearch.model || undefined
|
|
1014
1028
|
: config.webSearchSidecar?.model;
|
|
1015
1029
|
if (pairTouched && effectiveModel) {
|
|
1016
|
-
const candidates = await webSearchCandidateRows(config);
|
|
1030
|
+
const candidates = await webSearchCandidateRows({ ...config, webSearchSidecar: sidecarSettingsAfterPatch(config.webSearchSidecar, body.webSearch) });
|
|
1017
1031
|
if (webSearchModelIsRejected(effectiveBackend, effectiveModel, candidates)) {
|
|
1018
1032
|
return jsonResponse(webSearchModelRejection("webSearch.model", effectiveBackend, effectiveModel, candidates), 400);
|
|
1019
1033
|
}
|
|
1020
1034
|
}
|
|
1021
1035
|
const webSearchCandidate = { ...config.webSearchSidecar };
|
|
1036
|
+
if (body.webSearch.anthropicInstance === null) delete webSearchCandidate.anthropicInstance;
|
|
1037
|
+
else if (isAnthropicInstanceId(body.webSearch.anthropicInstance)) webSearchCandidate.anthropicInstance = body.webSearch.anthropicInstance;
|
|
1022
1038
|
if (typeof body.webSearch.model === "string") {
|
|
1023
1039
|
if (body.webSearch.model === "") delete webSearchCandidate.model;
|
|
1024
1040
|
else webSearchCandidate.model = body.webSearch.model;
|
|
@@ -1098,6 +1114,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
1098
1114
|
}
|
|
1099
1115
|
if (body.vision) {
|
|
1100
1116
|
config.visionSidecar = { ...config.visionSidecar };
|
|
1117
|
+
if (body.vision.anthropicInstance === null) delete config.visionSidecar.anthropicInstance;
|
|
1118
|
+
else if (isAnthropicInstanceId(body.vision.anthropicInstance)) config.visionSidecar.anthropicInstance = body.vision.anthropicInstance;
|
|
1101
1119
|
if (typeof body.vision.model === "string") {
|
|
1102
1120
|
if (body.vision.model === "") delete config.visionSidecar.model;
|
|
1103
1121
|
else config.visionSidecar.model = body.vision.model;
|
|
@@ -1143,6 +1161,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
1143
1161
|
enabled: ws.enabled !== false,
|
|
1144
1162
|
model: ws.model ?? "gpt-5.6-luna",
|
|
1145
1163
|
backend: ws.backend,
|
|
1164
|
+
...(ws.anthropicInstance ? { anthropicInstance: ws.anthropicInstance } : {}),
|
|
1165
|
+
anthropicPool: sidecarAnthropicPoolOptions(config, ws),
|
|
1146
1166
|
streamRoutedModelOutput: ws.streamRoutedModelOutput === true,
|
|
1147
1167
|
...(ws.xSearch ? { xSearch: ws.xSearch } : {}),
|
|
1148
1168
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsonResponse } from "../auth-cors";
|
|
2
|
+
import { jevDecisionEndpointUrl } from "../../combos/jev-decision-contract";
|
|
2
3
|
import type { ManagementContext } from "./context";
|
|
3
4
|
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
4
5
|
import { isPlainRecord } from "./shared";
|
|
@@ -29,7 +30,7 @@ function decisionServiceIssue(
|
|
|
29
30
|
provider: DecisionRow,
|
|
30
31
|
isSystemOneEndpoint: (url: string) => boolean,
|
|
31
32
|
): { url: string; model: string; issue?: "disabled" | "endpoint" | "model" } {
|
|
32
|
-
const url = typeof provider.baseUrl === "string" ? provider.baseUrl
|
|
33
|
+
const url = typeof provider.baseUrl === "string" ? jevDecisionEndpointUrl(provider.baseUrl) : "";
|
|
33
34
|
const model = provider.defaultModel?.trim() || provider.models?.[0]?.trim() || "";
|
|
34
35
|
const issue = provider.disabled === true
|
|
35
36
|
? "disabled"
|
|
@@ -318,6 +318,25 @@ function invalidClientResponse(ctx: ManagementContext): Response {
|
|
|
318
318
|
}, 400, ctx.req, ctx.config);
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
/** Bind an explicitly scoped restore before profile delegation or mutation. */
|
|
322
|
+
function restoreClientScopeResponse(ctx: ManagementContext, opId: string, expectedClientId: unknown, pathClientId?: IntegrationClientId): Response | null {
|
|
323
|
+
if (pathClientId !== undefined) {
|
|
324
|
+
if (expectedClientId !== undefined && (typeof expectedClientId !== "string" || !isIntegrationClientId(expectedClientId))) return invalidClientResponse(ctx);
|
|
325
|
+
if (expectedClientId !== undefined && expectedClientId !== pathClientId) return jsonResponse({ error: "restore identity does not match the client path", code: "integration_client_mismatch" }, 409, ctx.req, ctx.config);
|
|
326
|
+
expectedClientId = pathClientId;
|
|
327
|
+
}
|
|
328
|
+
if (expectedClientId === undefined) return null;
|
|
329
|
+
if (typeof expectedClientId !== "string" || !isIntegrationClientId(expectedClientId)) return invalidClientResponse(ctx);
|
|
330
|
+
try {
|
|
331
|
+
const operation = integrationStore().findOperation(opId);
|
|
332
|
+
if (!operation) return jsonResponse({ error: "integration operation not found", code: "integration_operation_not_found", opId }, 404, ctx.req, ctx.config);
|
|
333
|
+
if (operation.clientId !== expectedClientId) return jsonResponse({ error: "integration operation belongs to a different client", code: "integration_client_mismatch" }, 409, ctx.req, ctx.config);
|
|
334
|
+
return null;
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return internalErrorResponse(error, ctx);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
321
340
|
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
322
341
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
323
342
|
&& Object.values(value).every(item => typeof item === "string");
|
|
@@ -775,7 +794,7 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
775
794
|
}
|
|
776
795
|
}
|
|
777
796
|
|
|
778
|
-
if (url.pathname === "/api/client-integrations/restore/preview") {
|
|
797
|
+
if (url.pathname === "/api/client-integrations/restore/preview" || url.pathname === "/api/client-integrations/commandcode/restore/preview") {
|
|
779
798
|
if (req.method !== "POST") return null;
|
|
780
799
|
const parsed = await readJsonBody(ctx);
|
|
781
800
|
if (parsed instanceof Response) return parsed;
|
|
@@ -786,6 +805,8 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
786
805
|
return jsonResponse({ error: "confirmDrift must be a boolean", code: "invalid_confirm_drift" }, 400, req, ctx.config);
|
|
787
806
|
}
|
|
788
807
|
const opId = parsed.opId.trim();
|
|
808
|
+
const scopeResponse = restoreClientScopeResponse(ctx, opId, parsed.expectedClientId, url.pathname === "/api/client-integrations/commandcode/restore/preview" ? "commandcode" : undefined);
|
|
809
|
+
if (scopeResponse) return scopeResponse;
|
|
789
810
|
try {
|
|
790
811
|
const store = integrationStore();
|
|
791
812
|
const operation = store.findOperation(opId);
|
|
@@ -819,7 +840,7 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
819
840
|
}
|
|
820
841
|
}
|
|
821
842
|
|
|
822
|
-
if (url.pathname === "/api/client-integrations/restore") {
|
|
843
|
+
if (url.pathname === "/api/client-integrations/restore" || url.pathname === "/api/client-integrations/commandcode/restore") {
|
|
823
844
|
if (req.method !== "POST") return null;
|
|
824
845
|
const parsed = await readJsonBody(ctx);
|
|
825
846
|
if (parsed instanceof Response) return parsed;
|
|
@@ -837,6 +858,8 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
837
858
|
}
|
|
838
859
|
|
|
839
860
|
const opId = parsed.opId.trim();
|
|
861
|
+
const scopeResponse = restoreClientScopeResponse(ctx, opId, parsed.expectedClientId, url.pathname === "/api/client-integrations/commandcode/restore" ? "commandcode" : undefined);
|
|
862
|
+
if (scopeResponse) return scopeResponse;
|
|
840
863
|
const confirmDrift = parsed.confirmDrift ?? false;
|
|
841
864
|
const restoreBinding = planBindingOf(parsed);
|
|
842
865
|
if (restoreBinding === "half") return halfBoundResponse(ctx);
|