@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
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import type { AdapterRequest, ProviderAdapter } from "../base";
|
|
3
4
|
import { mapReasoningEffort } from "../../reasoning-effort";
|
|
4
|
-
import { buildSystemPrompt } from "../coding-agent/protocol";
|
|
5
|
-
import {
|
|
5
|
+
import { buildSystemPrompt, MAX_TOOL_BLOCK_STARTS } from "../coding-agent/protocol";
|
|
6
|
+
import { buildCodingAgentToolCatalog, CodingAgentToolCatalogError } from "../coding-agent/tool-catalog";
|
|
7
|
+
import { baseScopedEnv, runCodingAgentTurn, type CodingAgentDeps, type CodingAgentToolBridgeInput } from "../coding-agent/turn";
|
|
6
8
|
import { QODER_PROFILES, type QoderProfile } from "./profiles";
|
|
7
9
|
import { QoderScaffoldFilter, QODER_SCAFFOLD_ERROR_CODE, qoderScaffoldErrorMessage } from "./scaffold-guard";
|
|
8
10
|
|
|
9
11
|
export type QoderAdapterDeps = CodingAgentDeps;
|
|
10
12
|
|
|
13
|
+
const QODER_MCP_SERVER_NAME = "opencodex";
|
|
14
|
+
const QODER_MCP_SERVER_PATH = fileURLToPath(new URL("../coding-agent/mcp-server.ts", import.meta.url));
|
|
15
|
+
|
|
11
16
|
export function buildQoderChildEnv(profile: QoderProfile, apiKey: string, systemPrompt?: string): Record<string, string> {
|
|
12
17
|
const promptEnv = profile.region === "cn" ? "QODERCN_APPEND_SYSTEM_PROMPT" : "QODER_APPEND_SYSTEM_PROMPT";
|
|
13
18
|
return { ...baseScopedEnv(), NO_COLOR: "1", [profile.tokenEnv]: apiKey,
|
|
14
19
|
...(systemPrompt ? { [promptEnv]: systemPrompt } : {}) };
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
/**
|
|
22
|
+
/** Disable Qoder's built-in tools; only a request-scoped MCP catalog can be exposed. */
|
|
18
23
|
export function buildQoderArgs(parsed: OcxParsedRequest, provider: OcxProviderConfig): string[] {
|
|
19
24
|
const args = [
|
|
20
25
|
"-p",
|
|
@@ -35,8 +40,8 @@ export function buildQoderArgs(parsed: OcxParsedRequest, provider: OcxProviderCo
|
|
|
35
40
|
/**
|
|
36
41
|
* Wrap the turn's outbound channel with the scaffolding guard (#4190).
|
|
37
42
|
*
|
|
38
|
-
* The vendor CLI can put its own agent layer into the text channel despite
|
|
39
|
-
*
|
|
43
|
+
* The vendor CLI can put its own agent layer into the text channel despite built-in tools
|
|
44
|
+
* being disabled, and the shared stream-json parser forwards a text delta
|
|
40
45
|
* without inspecting it. This is the last point that is still qoder-specific, so the guard
|
|
41
46
|
* sits here rather than in the parser every coding-agent CLI shares.
|
|
42
47
|
*
|
|
@@ -123,7 +128,36 @@ export function createQoderAdapter(provider: OcxProviderConfig, deps: QoderAdapt
|
|
|
123
128
|
});
|
|
124
129
|
return;
|
|
125
130
|
}
|
|
126
|
-
|
|
131
|
+
let catalog: ReturnType<typeof buildCodingAgentToolCatalog>;
|
|
132
|
+
try {
|
|
133
|
+
catalog = buildCodingAgentToolCatalog(parsed, QODER_MCP_SERVER_NAME);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (!(error instanceof CodingAgentToolCatalogError)) throw error;
|
|
136
|
+
emit({
|
|
137
|
+
type: "error",
|
|
138
|
+
message: "Invalid Qoder tool catalog.",
|
|
139
|
+
status: 400,
|
|
140
|
+
errorType: "invalid_request_error",
|
|
141
|
+
code: "tool_catalog_invalid",
|
|
142
|
+
retryable: false,
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const toolBridge: CodingAgentToolBridgeInput | undefined = catalog.tools.length > 0 || catalog.requireToolCall
|
|
147
|
+
? {
|
|
148
|
+
serverName: QODER_MCP_SERVER_NAME,
|
|
149
|
+
serverModulePath: QODER_MCP_SERVER_PATH,
|
|
150
|
+
tools: catalog.tools,
|
|
151
|
+
emittedNameMap: catalog.emittedNameMap,
|
|
152
|
+
maxTurnToolCalls: MAX_TOOL_BLOCK_STARTS,
|
|
153
|
+
projectHistoryToolNames: true,
|
|
154
|
+
completeAssistantToolUse: true,
|
|
155
|
+
toolTurnCompletionSignal: "assistant_tool_use_stop",
|
|
156
|
+
allowedToolsFlag: "--allowed-tools",
|
|
157
|
+
standaloneEntrypoint: "__qoder-mcp",
|
|
158
|
+
requireToolCall: catalog.requireToolCall,
|
|
159
|
+
}
|
|
160
|
+
: undefined;
|
|
127
161
|
// Keep the folded prompt out of argv and never inherit an ambient vendor prompt.
|
|
128
162
|
const system = buildSystemPrompt(parsed);
|
|
129
163
|
await runCodingAgentTurn({
|
|
@@ -135,6 +169,7 @@ export function createQoderAdapter(provider: OcxProviderConfig, deps: QoderAdapt
|
|
|
135
169
|
buildArgs: (_profile, req, prov) => buildQoderArgs(req, prov),
|
|
136
170
|
buildEnv: (profile, apiKey) => buildQoderChildEnv(profile as QoderProfile, apiKey, system),
|
|
137
171
|
deps,
|
|
172
|
+
...(toolBridge ? { toolBridge } : {}),
|
|
138
173
|
});
|
|
139
174
|
},
|
|
140
175
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vendor-scaffolding guard for the qoder route (#4190).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The vendor CLI does not always honour that. It has been observed emitting its own agent
|
|
4
|
+
* Even with built-in tools disabled and strict MCP isolation, Codex keeps tool execution
|
|
5
|
+
* ownership. The vendor CLI does not always honour that. It has been observed emitting its own agent
|
|
7
6
|
* layer into the assistant text channel — an MCP lazy-loading `<system-reminder>` block
|
|
8
7
|
* listing the local machine's configured MCP servers, and framework tool-call markup with a
|
|
9
8
|
* mismatched closer. Both reached the client verbatim, because the shared stream-json parser
|
package/src/adapters/zed.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createAnthropicAdapter } from "./anthropic";
|
|
|
4
4
|
import { createGoogleAdapter } from "./google";
|
|
5
5
|
import { createOpenAIChatAdapter } from "./openai-chat";
|
|
6
6
|
import { createResponsesPassthroughAdapter } from "./openai-responses";
|
|
7
|
+
import { createAdapterPhysicalSend } from "./physical-send";
|
|
7
8
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
8
9
|
import { createTranslatorBudget, releaseTranslatedEvent, retainTranslatedEvent, type TranslatorBudget } from "../lib/translator-budget";
|
|
9
10
|
import { releaseCompactionCiphertextLease } from "../responses/compaction";
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
resolveZedModels,
|
|
14
15
|
scrubZedCredentials,
|
|
15
16
|
zedLlmFetch,
|
|
17
|
+
ZED_CLOUD_BASE_URL,
|
|
16
18
|
ZED_HEADERS,
|
|
17
19
|
type ZedCredentials,
|
|
18
20
|
} from "../providers/zed";
|
|
@@ -340,8 +342,21 @@ export function createZedAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
|
340
342
|
async fetchResponse(request, ctx) {
|
|
341
343
|
if (!credentials) throw new Error("Zed request credentials were not initialized");
|
|
342
344
|
const fetchFn = ctx?.executor ?? globalThis.fetch;
|
|
345
|
+
const send = createAdapterPhysicalSend(ctx);
|
|
346
|
+
let completionAttempts = 0;
|
|
343
347
|
return zedLlmFetch(credentials, "/completions", {
|
|
344
|
-
fetchFn,
|
|
348
|
+
fetchFn: (async (input, init) => {
|
|
349
|
+
const url = input instanceof Request ? input.url : new URL(String(input)).href;
|
|
350
|
+
// Token exchange/account lookup are support traffic; both completion attempts
|
|
351
|
+
// share admission and physical ordinals, including the provider's 401 replay.
|
|
352
|
+
if (url === `${ZED_CLOUD_BASE_URL}/client/users/me`
|
|
353
|
+
|| url === `${ZED_CLOUD_BASE_URL}/client/llm_tokens`) return fetchFn(input, init);
|
|
354
|
+
const recovery = completionAttempts++ > 0;
|
|
355
|
+
return send({ url, sendClass: recovery ? "auth-recovery" : "initial",
|
|
356
|
+
...(recovery ? { recovery: "oauth-401" } : {}),
|
|
357
|
+
dispatch: executor => executor(input, init),
|
|
358
|
+
});
|
|
359
|
+
}) as typeof globalThis.fetch,
|
|
345
360
|
signal: ctx?.abortSignal,
|
|
346
361
|
baseUrl: provider.baseUrl,
|
|
347
362
|
fetchInit: {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* occasional TaskCreate nudges) into system text that becomes Responses
|
|
4
4
|
* `instructions`. That churn breaks Muse/Go prefix cache on the instructions
|
|
5
5
|
* prefix even when tools stay stable. Strip dynamics from instructions;
|
|
6
|
-
* surface the latest notice on `input` instead.
|
|
6
|
+
* drop token footers and surface the latest TaskCreate notice on `input` instead.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* canonical notice at the end of instructions is
|
|
8
|
+
* Peeling is identified by harness shape: only a trailing, unfenced,
|
|
9
|
+
* canonical notice at the end of instructions is removed. An unmatched fence
|
|
10
10
|
* opener covers through EOF. No match → the original string is returned
|
|
11
11
|
* byte-for-byte. The matcher is content identity only; `translateAnthropicRequest`
|
|
12
12
|
* requires `claudeCode.stabilizePromptCache: true` before this helper runs. Claude Code
|
|
@@ -86,7 +86,6 @@ export function stabilizeClaudeInstructionsForPromptCache(
|
|
|
86
86
|
const ranges = fencedRanges(instructions);
|
|
87
87
|
let fenceIndex = ranges.length - 1;
|
|
88
88
|
let end = instructions.length;
|
|
89
|
-
let latestTotal: string | null = null;
|
|
90
89
|
let latestNudge: string | null = null;
|
|
91
90
|
let peeled = false;
|
|
92
91
|
|
|
@@ -110,7 +109,6 @@ export function stabilizeClaudeInstructionsForPromptCache(
|
|
|
110
109
|
|
|
111
110
|
while (fenceIndex >= 0 && ranges[fenceIndex]!.start > contentStart) fenceIndex--;
|
|
112
111
|
if (fenceIndex >= 0 && contentStart < ranges[fenceIndex]!.end) break;
|
|
113
|
-
if (total && latestTotal === null) latestTotal = notice;
|
|
114
112
|
if (nudge && latestNudge === null) latestNudge = notice;
|
|
115
113
|
peeled = true;
|
|
116
114
|
|
|
@@ -123,8 +121,5 @@ export function stabilizeClaudeInstructionsForPromptCache(
|
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
if (!peeled) return { instructions, dynamicNotice: null };
|
|
126
|
-
|
|
127
|
-
if (latestTotal) noticeParts.push(latestTotal);
|
|
128
|
-
if (latestNudge) noticeParts.push(latestNudge);
|
|
129
|
-
return { instructions: instructions.slice(0, end), dynamicNotice: noticeParts.join("\n\n") };
|
|
124
|
+
return { instructions: instructions.slice(0, end), dynamicNotice: latestNudge };
|
|
130
125
|
}
|
package/src/claude/inbound.ts
CHANGED
|
@@ -72,6 +72,15 @@ function toolResultOutput(block: Rec): string | Rec[] {
|
|
|
72
72
|
if (!isRec(item)) continue;
|
|
73
73
|
if (item.type === "text" && typeof item.text === "string") {
|
|
74
74
|
out.push({ type: "input_text", text: item.text });
|
|
75
|
+
} else if (item.type === "tool_reference" && typeof item.tool_name === "string" && item.tool_name.length > 0) {
|
|
76
|
+
// Responses has no tool_reference result carrier. Preserve ToolSearch's loaded-name
|
|
77
|
+
// evidence as result text so a reference-only reply does not become empty (#6647).
|
|
78
|
+
// This describes client output; it must not create tools or widen authorization.
|
|
79
|
+
// Text-only tool output is later joined without separators, so the marker carries its
|
|
80
|
+
// own line breaks to keep each loaded name distinct from its neighbours.
|
|
81
|
+
const prev = out[out.length - 1];
|
|
82
|
+
const lead = prev?.type === "input_text" && typeof prev.text === "string" && !prev.text.endsWith("\n") ? "\n" : "";
|
|
83
|
+
out.push({ type: "input_text", text: `${lead}Tool loaded: ${item.tool_name}\n` });
|
|
75
84
|
} else if (item.type === "image") {
|
|
76
85
|
const img = imageBlockToInputImage(item);
|
|
77
86
|
if (img) out.push(img);
|
|
@@ -436,6 +445,7 @@ function translateAnthropicRequest(
|
|
|
436
445
|
// a matching suffix or metadata.user_id.
|
|
437
446
|
const stabilized = stabilizeClaudeInstructionsForPromptCache(joinedSystem);
|
|
438
447
|
if (stabilized.instructions) body.instructions = stabilized.instructions;
|
|
448
|
+
// Token footers are dropped; append nothing if no other notice remains.
|
|
439
449
|
if (stabilized.dynamicNotice) {
|
|
440
450
|
input.push({
|
|
441
451
|
type: "message",
|
|
@@ -35,6 +35,7 @@ export const DESKTOP_PICKER_ID_SUGGESTIONS: readonly string[] = [
|
|
|
35
35
|
"claude-sonnet-5-5",
|
|
36
36
|
"claude-sonnet-5",
|
|
37
37
|
"claude-fable-5-1",
|
|
38
|
+
"claude-haiku-5-5", // 261008 preemptive suggestion; Desktop owns the live picker.
|
|
38
39
|
"claude-haiku-4-5",
|
|
39
40
|
"claude-opus-5",
|
|
40
41
|
"claude-fable-5",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* HTTP/1.1 server, unchanged. Upstream is one HTTP/1.1 request per client request on both paths.
|
|
12
12
|
* Only the bounded bootstrap response is held; other bodies and upgraded sockets relay as streams.
|
|
13
13
|
*/
|
|
14
|
-
import { createSecureServer } from "node:http2";
|
|
14
|
+
import { constants as h2Constants, createSecureServer } from "node:http2";
|
|
15
15
|
import type { Http2ServerRequest, Http2ServerResponse, ServerHttp2Session } from "node:http2";
|
|
16
16
|
import { createServer, request as httpsRequest } from "node:https";
|
|
17
17
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
@@ -184,9 +184,35 @@ export async function startPickerListener(options: PickerListenerOptions): Promi
|
|
|
184
184
|
// reason phrase differs, and sendHead branches on it.
|
|
185
185
|
const res = relayRes as ServerResponse;
|
|
186
186
|
const h2 = req.httpVersionMajor === 2;
|
|
187
|
+
// Bun's h2 compat response emits finish only after both stream halves close.
|
|
188
|
+
const responseWritable = h2 ? (req as Http2ServerRequest).stream : res;
|
|
189
|
+
// Framing, not method, determines whether a peer can keep sending a body. A bodyless h2
|
|
190
|
+
// request carries END_STREAM on its headers; HTTP/1.1 needs length or chunked framing.
|
|
191
|
+
const uploading = h2 ? !(req as Http2ServerRequest).stream.endAfterHeaders
|
|
192
|
+
: req.headers["transfer-encoding"] !== undefined || Number(req.headers["content-length"] ?? 0) > 0;
|
|
193
|
+
const closeInput = (completeReply = false) => {
|
|
194
|
+
if (h2) {
|
|
195
|
+
const stream = (req as Http2ServerRequest).stream;
|
|
196
|
+
if (!stream.closed && !stream.destroyed) {
|
|
197
|
+
stream.close(completeReply ? h2Constants.NGHTTP2_NO_ERROR : h2Constants.NGHTTP2_CANCEL);
|
|
198
|
+
}
|
|
199
|
+
} else if (!req.destroyed) {
|
|
200
|
+
if (completeReply) {
|
|
201
|
+
// finish is not peer receipt: destroy can truncate bytes still queued in TLS/TCP.
|
|
202
|
+
// Discard further input and flush a graceful FIN without waiting for body completion.
|
|
203
|
+
req.resume();
|
|
204
|
+
req.socket.end();
|
|
205
|
+
} else req.destroy();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
187
208
|
// Refusals answer with an empty response and a fixed log line that carries no request data.
|
|
188
209
|
const refuse = (status: 400 | 503) => {
|
|
189
|
-
|
|
210
|
+
// Do not leave a refused, still-uploading stream behind after delivering its empty reply.
|
|
211
|
+
if (uploading && !req.complete) {
|
|
212
|
+
req.once("error", () => res.destroy());
|
|
213
|
+
responseWritable.once("finish", () => closeInput(true));
|
|
214
|
+
}
|
|
215
|
+
res.writeHead(status, { "Content-Length": "0", ...(!h2 && uploading && !req.complete ? { Connection: "close" } : {}) });
|
|
190
216
|
res.end();
|
|
191
217
|
options.log?.(`picker request refused ${status}`);
|
|
192
218
|
};
|
|
@@ -209,12 +235,16 @@ export async function startPickerListener(options: PickerListenerOptions): Promi
|
|
|
209
235
|
const fail = () => {
|
|
210
236
|
if (clientGone) return;
|
|
211
237
|
if (res.headersSent) res.destroy();
|
|
212
|
-
else {
|
|
238
|
+
else {
|
|
239
|
+
res.writeHead(502, { "Content-Length": "0", ...(!h2 && uploading && !req.complete ? { Connection: "close" } : {}) });
|
|
240
|
+
res.end(); log(502);
|
|
241
|
+
}
|
|
213
242
|
};
|
|
214
243
|
const omit = bootstrap ? new Set(["accept-encoding"]) : new Set<string>();
|
|
215
244
|
const headers = upstreamRequestHeaders(req, omit);
|
|
216
245
|
if (bootstrap) headers.push("Accept-Encoding", narrowBootstrapAcceptEncoding());
|
|
217
246
|
let upReq: ReturnType<typeof httpsRequest>;
|
|
247
|
+
let upstreamResponded = false;
|
|
218
248
|
try {
|
|
219
249
|
// The HTTP/1.1 client validates the method, path and header values synchronously.
|
|
220
250
|
upReq = httpsRequest({
|
|
@@ -222,13 +252,17 @@ export async function startPickerListener(options: PickerListenerOptions): Promi
|
|
|
222
252
|
ca: upstream.ca, rejectUnauthorized: true, agent: false,
|
|
223
253
|
method, path: req.url, headers, maxHeaderSize: PICKER_MAX_HEADER_BYTES,
|
|
224
254
|
}, upRes => {
|
|
255
|
+
upstreamResponded = true;
|
|
225
256
|
const status = upRes.statusCode ?? 502;
|
|
226
257
|
const originalHeaders = filteredHeaders(upRes.rawHeaders);
|
|
227
258
|
const sendHead = (raw: string[]) => {
|
|
228
259
|
if (res.headersSent) return;
|
|
229
260
|
// HTTP/2 has no reason phrase; its compat writeHead takes the same flat raw header array.
|
|
230
261
|
if (h2) (relayRes as Http2ServerResponse).writeHead(status, raw as unknown as Record<string, string>);
|
|
231
|
-
else
|
|
262
|
+
else {
|
|
263
|
+
if (uploading && !req.complete) raw.push("Connection", "close");
|
|
264
|
+
res.writeHead(status, upRes.statusMessage, raw);
|
|
265
|
+
}
|
|
232
266
|
log(status);
|
|
233
267
|
};
|
|
234
268
|
upRes.on("error", fail);
|
|
@@ -293,6 +327,39 @@ export async function startPickerListener(options: PickerListenerOptions): Promi
|
|
|
293
327
|
// Bun's compat response skips its close event for a HEAD reset before end(); the stream's own
|
|
294
328
|
// close always fires. Destroying an upstream request that already completed is a no-op.
|
|
295
329
|
if (h2) (req as Http2ServerRequest).stream.once("close", onClientClose);
|
|
330
|
+
if (uploading) {
|
|
331
|
+
let uploadFinished = false;
|
|
332
|
+
const finishUpload = () => {
|
|
333
|
+
if (uploadFinished) return;
|
|
334
|
+
uploadFinished = true;
|
|
335
|
+
req.off("end", finishUpload);
|
|
336
|
+
req.off("close", finishUpload);
|
|
337
|
+
upReq.off("close", onUpstreamClose);
|
|
338
|
+
responseWritable.off("finish", onResponseFinish);
|
|
339
|
+
res.off("close", stopUnfinishedUpload);
|
|
340
|
+
if (h2) (req as Http2ServerRequest).stream.off("close", finishUpload);
|
|
341
|
+
};
|
|
342
|
+
// Upstream close can precede draining a successful reply to a slow downstream.
|
|
343
|
+
// Stop relaying input only once that reply's writable finishes or closes.
|
|
344
|
+
const stopUnfinishedUpload = (completeReply = false) => {
|
|
345
|
+
if (!uploadFinished) {
|
|
346
|
+
req.unpipe(upReq);
|
|
347
|
+
closeInput(completeReply);
|
|
348
|
+
finishUpload();
|
|
349
|
+
upReq.destroy();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const onResponseFinish = () => stopUnfinishedUpload(true);
|
|
353
|
+
const onUpstreamClose = () => {
|
|
354
|
+
if (!upstreamResponded && !res.writableEnded) stopUnfinishedUpload();
|
|
355
|
+
};
|
|
356
|
+
req.once("end", finishUpload);
|
|
357
|
+
req.once("close", finishUpload);
|
|
358
|
+
upReq.once("close", onUpstreamClose);
|
|
359
|
+
responseWritable.once("finish", onResponseFinish);
|
|
360
|
+
res.once("close", stopUnfinishedUpload);
|
|
361
|
+
if (h2) (req as Http2ServerRequest).stream.once("close", finishUpload);
|
|
362
|
+
}
|
|
296
363
|
req.pipe(upReq);
|
|
297
364
|
};
|
|
298
365
|
server.on("request", relay);
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
1
2
|
import { apiError, apiJson, proxyUnreachable, resolveBaseUrl, type AccountDeps } from "./account-api";
|
|
2
3
|
|
|
4
|
+
/** A 200 that names another pool is refused locally; there is no upstream status to forward. */
|
|
5
|
+
export function anthropicProviderIdentityError(json: Record<string, unknown>, provider: AnthropicInstanceId, label: string): number | undefined {
|
|
6
|
+
return json.provider === provider ? undefined : apiError({}, `invalid ${label} identity`, 400);
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
/** A separate account selector prevents accidentally changing the whole Anthropic pool. */
|
|
4
|
-
export async function cmdAnthropicAccountThreshold(args: string[], action: string, wantsJson: boolean, deps: AccountDeps): Promise<number> {
|
|
10
|
+
export async function cmdAnthropicAccountThreshold(args: string[], action: string, wantsJson: boolean, deps: AccountDeps, provider: AnthropicInstanceId = "anthropic"): Promise<number> {
|
|
5
11
|
const selector = args.indexOf("--account");
|
|
6
12
|
const accountId = selector >= 0 ? args[selector + 1] : undefined;
|
|
7
13
|
if (selector >= 0) args.splice(selector, 2);
|
|
@@ -15,8 +21,8 @@ export async function cmdAnthropicAccountThreshold(args: string[], action: strin
|
|
|
15
21
|
const base = await resolveBaseUrl(deps);
|
|
16
22
|
if (!base) return proxyUnreachable();
|
|
17
23
|
const response = action === "status"
|
|
18
|
-
? await apiJson(deps, base, "GET",
|
|
19
|
-
: await apiJson(deps, base, "PUT", "/api/oauth/accounts/auto-switch", { provider
|
|
24
|
+
? await apiJson(deps, base, "GET", `/api/oauth/accounts?provider=${provider}`)
|
|
25
|
+
: await apiJson(deps, base, "PUT", "/api/oauth/accounts/auto-switch", { provider, accountId, threshold });
|
|
20
26
|
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
21
27
|
if (response.status !== 200) return apiError(response.json, "failed to update account threshold", response.status);
|
|
22
28
|
if (!response.json || typeof response.json !== "object" || Array.isArray(response.json)) return apiError({}, "invalid account threshold response", 400);
|
|
@@ -28,7 +34,10 @@ export async function cmdAnthropicAccountThreshold(args: string[], action: strin
|
|
|
28
34
|
const validPercent = (value: unknown) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 100;
|
|
29
35
|
if ((result.autoSwitchThresholdOverride !== null && !validPercent(result.autoSwitchThresholdOverride))
|
|
30
36
|
|| !validPercent(result.effectiveAutoSwitchThreshold)) return apiError({}, "invalid account threshold response", 400);
|
|
31
|
-
const
|
|
37
|
+
const responseProvider = action === "status" ? response.json.provider : result.provider;
|
|
38
|
+
if ((provider === "anthropic2" || responseProvider !== undefined) && responseProvider !== provider) return apiError({}, "invalid account threshold identity", 400);
|
|
39
|
+
if (action !== "status" && (provider === "anthropic2" || result.accountId !== undefined) && result.accountId !== accountId) return apiError({}, "invalid account threshold identity", 400);
|
|
40
|
+
const payload = { provider, accountId, autoSwitchThresholdOverride: result.autoSwitchThresholdOverride,
|
|
32
41
|
effectiveAutoSwitchThreshold: result.effectiveAutoSwitchThreshold };
|
|
33
42
|
if (wantsJson) console.log(JSON.stringify(payload, null, 2));
|
|
34
43
|
else console.log(`auto-switch: ${payload.autoSwitchThresholdOverride === null ? "inherited" : "custom"} (${payload.effectiveAutoSwitchThreshold === 0 ? "usage-based switching disabled" : `${payload.effectiveAutoSwitchThreshold}%`})`);
|
|
@@ -36,6 +45,6 @@ export async function cmdAnthropicAccountThreshold(args: string[], action: strin
|
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
function invalid(): number {
|
|
39
|
-
console.error("Usage: ocx account auto-switch anthropic <status|inherit|on|off|threshold <0-100>> --account <id> [--json]");
|
|
48
|
+
console.error("Usage: ocx account auto-switch <anthropic|anthropic2> <status|inherit|on|off|threshold <0-100>> --account <id> [--json]");
|
|
40
49
|
return 2;
|
|
41
50
|
}
|
package/src/cli/account-api.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { configuredAnthropicInstance, isAnthropicOAuthInstance } from "../providers/anthropic-instance";
|
|
1
2
|
import { parseQuotaFailureCode, type QuotaFailureCode, type ProviderQuota, type AccountQuotaMode } from "../providers/quota-types";
|
|
2
3
|
/**
|
|
3
4
|
* Data-access layer for `ocx account` (issue #180) — live-proxy HTTP client and
|
|
@@ -110,6 +111,7 @@ export function classifyAccount(config: OcxConfig, name: string): ClassifyResult
|
|
|
110
111
|
if (provider && !provider.authMode && (provider.apiKey || (provider.apiKeyPool?.length ?? 0) > 0)) {
|
|
111
112
|
return { type: "api-key" };
|
|
112
113
|
}
|
|
114
|
+
if (name === "anthropic2" && !configuredAnthropicInstance(config, name)) return { error: "Anthropic pool instance is not configured" };
|
|
113
115
|
if (isPublicOAuthProvider(name)) return { type: "oauth" };
|
|
114
116
|
if (provider) return { type: "api-key" };
|
|
115
117
|
return { error: `unknown provider "${name}"` };
|
|
@@ -408,7 +410,7 @@ async function fetchOAuthRows(
|
|
|
408
410
|
needsReauth: a.needsReauth,
|
|
409
411
|
...projectAccountHealth(a, name, a.id),
|
|
410
412
|
...(a.paused === true ? { paused: true } : {}),
|
|
411
|
-
...(name
|
|
413
|
+
...(isAnthropicOAuthInstance(name) && Object.hasOwn(a, "autoSwitchThresholdOverride") ? { autoSwitchThresholdOverride: a.autoSwitchThresholdOverride } : {}),
|
|
412
414
|
...(a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
|
|
413
415
|
...(name === "kiro" && typeof a.autoSelectable === "boolean"
|
|
414
416
|
? { autoSelectable: a.autoSelectable } : {}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { isAnthropicOAuthInstance } from "../providers/anthropic-instance";
|
|
1
2
|
import { loadConfig } from "../config";
|
|
2
|
-
import { cmdAnthropicAccountThreshold } from "./account-anthropic-threshold";
|
|
3
|
+
import { anthropicProviderIdentityError, cmdAnthropicAccountThreshold } from "./account-anthropic-threshold";
|
|
3
4
|
import { isReservedCodexAccountWord, reportCodexAccountTargetError, resolveCodexAccountTarget } from "./account-target";
|
|
4
5
|
import { hasPassiveAccountQuota } from "../providers/quota";
|
|
5
6
|
import { closeSync, openSync, readSync, readFileSync, statSync } from "node:fs";
|
|
@@ -41,7 +42,7 @@ const AUTO_NOTE = "auto (no pin — lowest-usage account is selected per request
|
|
|
41
42
|
const EXTENDED_USAGE = `Usage:
|
|
42
43
|
ocx account refresh <provider> [--json]
|
|
43
44
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
44
|
-
ocx account auto-switch anthropic <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
|
|
45
|
+
ocx account auto-switch <anthropic|anthropic2> <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
|
|
45
46
|
ocx account alias <provider> <id|alias|main> <display-name|-> [--json]
|
|
46
47
|
ocx account priority <provider> <id|alias|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
|
|
47
48
|
ocx account pause <provider> <id|alias|main> [--json]
|
|
@@ -49,7 +50,7 @@ const EXTENDED_USAGE = `Usage:
|
|
|
49
50
|
ocx account pause-exhausted <provider> [--json]
|
|
50
51
|
ocx account strategy <provider> [<quota|round-robin|fill-first|least-loaded|reset-first>] [--json]
|
|
51
52
|
ocx account sticky <provider> [<1-100>] [--json]
|
|
52
|
-
ocx account routes anthropic [--file <json-file>|--clear] [--json]
|
|
53
|
+
ocx account routes <anthropic|anthropic2> [--file <json-file>|--clear] [--json]
|
|
53
54
|
ocx account remove <provider> <id|alias|main> --yes [--json]
|
|
54
55
|
ocx account clear-cooldown <provider> <id|alias|main> [--json]
|
|
55
56
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
@@ -360,8 +361,8 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise<
|
|
|
360
361
|
const classified = configAndType(deps, name);
|
|
361
362
|
// Anthropic keeps its threshold on its own pool contract; generic OAuth providers (#695)
|
|
362
363
|
// and the Codex pool are accepted here.
|
|
363
|
-
if (!("error" in classified) && classified.type === "oauth" && name
|
|
364
|
-
if ("error" in classified || classified.type === "api-key" || name
|
|
364
|
+
if (!("error" in classified) && classified.type === "oauth" && isAnthropicOAuthInstance(name)) return cmdAnthropicAccountThreshold(args, action, wantsJson, deps, name);
|
|
365
|
+
if ("error" in classified || classified.type === "api-key" || isAnthropicOAuthInstance(name)) {
|
|
365
366
|
return usage("Error: auto-switch only applies to the openai Codex account pool or a generic OAuth provider pool");
|
|
366
367
|
}
|
|
367
368
|
const genericPool = classified.type === "oauth";
|
|
@@ -638,7 +639,7 @@ export async function cmdClearCooldown(args: string[], deps: AccountDeps): Promi
|
|
|
638
639
|
if (!name || !requestedId || args.length) return usage();
|
|
639
640
|
const classified = configAndType(deps, name);
|
|
640
641
|
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
641
|
-
if (classified.type !== "codex" && !(classified.type === "oauth" && name
|
|
642
|
+
if (classified.type !== "codex" && !(classified.type === "oauth" && isAnthropicOAuthInstance(name))) {
|
|
642
643
|
return usage(`Error: ${name} has no operator-clearable account cooldown`);
|
|
643
644
|
}
|
|
644
645
|
const baseUrl = await resolveBaseUrl(deps);
|
|
@@ -1108,7 +1109,8 @@ export async function cmdRoutes(args: string[], deps: AccountDeps): Promise<numb
|
|
|
1108
1109
|
const wantsJson = flag(args, "--json");
|
|
1109
1110
|
const file = flagValue(args, "--file");
|
|
1110
1111
|
const clear = flag(args, "--clear");
|
|
1111
|
-
|
|
1112
|
+
const provider = args.shift();
|
|
1113
|
+
if (!isAnthropicOAuthInstance(provider) || args.length || (file.found && (!file.value || clear))) return usage();
|
|
1112
1114
|
const baseUrl = await resolveBaseUrl(deps);
|
|
1113
1115
|
if (!baseUrl) return proxyUnreachable();
|
|
1114
1116
|
let routes: unknown;
|
|
@@ -1123,10 +1125,12 @@ export async function cmdRoutes(args: string[], deps: AccountDeps): Promise<numb
|
|
|
1123
1125
|
}
|
|
1124
1126
|
const writing = file.found || clear;
|
|
1125
1127
|
const response = await apiJson(deps, baseUrl, writing ? "PUT" : "GET",
|
|
1126
|
-
writing ? "/api/pool/settings" :
|
|
1127
|
-
writing ? { provider
|
|
1128
|
+
writing ? "/api/pool/settings" : `/api/pool/settings?provider=${provider}`,
|
|
1129
|
+
writing ? { provider, routes: clear ? null : routes } : undefined);
|
|
1128
1130
|
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
1129
1131
|
if (response.status !== 200) return apiError(response.json, "failed to manage Anthropic routes", response.status);
|
|
1132
|
+
const identityError = anthropicProviderIdentityError(response.json, provider, "Anthropic routes");
|
|
1133
|
+
if (identityError !== undefined) return identityError;
|
|
1130
1134
|
if (wantsJson) console.log(JSON.stringify(response.json, null, 2));
|
|
1131
1135
|
else console.log(JSON.stringify(response.json.routes ?? [], null, 2));
|
|
1132
1136
|
return 0;
|
|
@@ -51,7 +51,7 @@ const grant = z.object({ id: grantId, label: text, resetsTotal: z.number().int()
|
|
|
51
51
|
percentUsed: z.object({ five_hour: threshold.optional(), seven_day: threshold.optional(),
|
|
52
52
|
seven_day_overage_included: threshold.optional() }),
|
|
53
53
|
}).refine(value => value.resetsLeft <= value.resetsTotal);
|
|
54
|
-
export const grantsSchema = z.object({ accountId: text.min(1), eligible: z.boolean(),
|
|
54
|
+
export const grantsSchema = z.object({ provider: z.enum(["anthropic", "anthropic2"]).optional(), accountId: text.min(1), eligible: z.boolean(),
|
|
55
55
|
ineligibleReason: z.enum(["config_off", "tier", "seat", "mobile", "surface", "cli_version", "no_grant", "tenure", "other_experiment", "unavailable", "unknown"]).nullable(),
|
|
56
56
|
atLimit: z.boolean(), grants: z.array(grant).refine(values => new Set(values.map(value => value.id)).size === values.length), nextGrantId: grantId.nullable(), weeklyResetsAt: date, cooldownUntil: date,
|
|
57
57
|
pendingOperation: z.object({ operationId: z.string().uuid(), grantId, createdAt: z.number().finite(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
1
2
|
import { MAIN_CODEX_ACCOUNT_ID } from "../codex/account-id";
|
|
2
3
|
import { isValidProviderName } from "../config/provider-name";
|
|
3
4
|
import { resolveCodexAccountTargetFromRows } from "./account-target";
|
|
@@ -13,7 +14,7 @@ const USAGE = "Usage: ocx account pool <provider> [--enabled on|off] [--threshol
|
|
|
13
14
|
+ " ocx account auto-switch openai <status|on|off|inherit|threshold N> --account ID [--json]\n"
|
|
14
15
|
+ " ocx account credits openai <ID on|off|--all on|off> [--json]\n"
|
|
15
16
|
+ " ocx account quota-activation openai ID --window <fiveHour|weekly> <on|off> [--json]\n"
|
|
16
|
-
+ " ocx account anthropic-reset-grants [ID] [--json]";
|
|
17
|
+
+ " ocx account anthropic-reset-grants [ID] [--provider anthropic|anthropic2] [--json]";
|
|
17
18
|
const READ_ERRORS = { no_account: "No matching Anthropic OAuth account.", auth_failed: "Sign in to this Anthropic account again.",
|
|
18
19
|
upstream_unavailable: "Anthropic did not return the reset-grant status.", ledger_unavailable: "The reset journal is unavailable.",
|
|
19
20
|
ledger_busy: "The reset journal is busy. No grant was consumed." };
|
|
@@ -67,14 +68,14 @@ async function pool(args: string[], json: boolean, deps: RuntimeApiDeps): Promis
|
|
|
67
68
|
patch.strategy = strategy;
|
|
68
69
|
}
|
|
69
70
|
if (window !== undefined) {
|
|
70
|
-
if (provider
|
|
71
|
+
if (!isAnthropicInstanceId(provider) || !["five-hour", "weekly", "max-utilization"].includes(window)) usage();
|
|
71
72
|
patch.quotaWindow = window;
|
|
72
73
|
}
|
|
73
74
|
done(args);
|
|
74
75
|
const pinned = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
|
|
75
76
|
const before = parseDto(poolSchema, await request(`/api/pool/settings?provider=${encodeURIComponent(provider)}`, pinned));
|
|
76
77
|
requireEqual(before.provider, provider);
|
|
77
|
-
requireEqual(before.kind, provider === "openai" ? "codex" : provider
|
|
78
|
+
requireEqual(before.kind, provider === "openai" ? "codex" : isAnthropicInstanceId(provider) ? "anthropic" : "generic");
|
|
78
79
|
if (Object.keys(patch).some(key => !(before.supported as string[]).includes(key))
|
|
79
80
|
|| (before.kind === "codex" && enabled !== undefined)) throw new CliUsageError("The selected pool does not support a requested field.");
|
|
80
81
|
if (!Object.keys(patch).length) return output(before, json);
|
|
@@ -157,10 +158,18 @@ export async function handleAccountPolicyCommand(sub: Subcommand, args: string[]
|
|
|
157
158
|
if (rest.includes("--json")) usage();
|
|
158
159
|
if (sub === "pool") return pool(rest, json, deps);
|
|
159
160
|
if (sub !== "anthropic-reset-grants") return codex(sub, rest, json, deps);
|
|
161
|
+
const selectedProvider = option(rest, "--provider");
|
|
162
|
+
const provider = selectedProvider ?? "anthropic";
|
|
163
|
+
if (!isAnthropicInstanceId(provider)) usage();
|
|
160
164
|
const id = rest.length ? selector(rest.shift()) : undefined;
|
|
161
165
|
done(rest);
|
|
162
166
|
const pinned = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
|
|
163
|
-
|
|
167
|
+
// Omission preserves legacy A request syntax; explicit calls verify the echoed instance.
|
|
168
|
+
const query = new URLSearchParams();
|
|
169
|
+
if (id) query.set("accountId", id);
|
|
170
|
+
if (selectedProvider !== undefined) query.set("provider", provider);
|
|
171
|
+
const result = parseDto(grantsSchema, await request(`/api/anthropic/reset-grants${query.size ? `?${query}` : ""}`, pinned));
|
|
172
|
+
if (selectedProvider !== undefined || result.provider !== undefined) requireEqual(result.provider, provider);
|
|
164
173
|
if (id) requireEqual(result.accountId, id);
|
|
165
174
|
return output(result, json, "Reset-grant status read; no grant was consumed.");
|
|
166
175
|
}, READ_ERRORS);
|
package/src/cli/account.ts
CHANGED
|
@@ -52,7 +52,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
52
52
|
ocx account clear <provider> [--json]
|
|
53
53
|
ocx account refresh <provider> [--json]
|
|
54
54
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
55
|
-
ocx account auto-switch anthropic <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
|
|
55
|
+
ocx account auto-switch <anthropic|anthropic2> <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
|
|
56
56
|
ocx account alias <provider> <account-or-key-id|alias> <display-name|-> [--json]
|
|
57
57
|
ocx account priority <provider> <account-id|alias|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
|
|
58
58
|
ocx account pause <provider> <account-id|alias|main> [--json]
|
|
@@ -60,7 +60,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
60
60
|
ocx account pause-exhausted <provider> [--json]
|
|
61
61
|
ocx account strategy <provider> [<quota|round-robin|fill-first|least-loaded|reset-first>] [--json]
|
|
62
62
|
ocx account sticky <provider> [<1-100>] [--json]
|
|
63
|
-
ocx account routes anthropic [--file <json-file>|--clear] [--json]
|
|
63
|
+
ocx account routes <anthropic|anthropic2> [--file <json-file>|--clear] [--json]
|
|
64
64
|
ocx account remove <provider> <account-or-key-id|alias|main> --yes [--json]
|
|
65
65
|
ocx account clear-cooldown <provider> <account-id|alias|main> [--json]
|
|
66
66
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
@@ -70,7 +70,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
70
70
|
ocx account pool <provider> [--enabled on|off] [--threshold N] [--strategy NAME] [--sticky N] [--quota-window W] [--json]
|
|
71
71
|
ocx account credits openai <ID on|off|--all on|off> [--json]
|
|
72
72
|
ocx account quota-activation openai ID --window fiveHour|weekly <on|off> [--json]
|
|
73
|
-
ocx account anthropic-reset-grants [ID] [--json]
|
|
73
|
+
ocx account anthropic-reset-grants [ID] [--provider anthropic|anthropic2] [--json]
|
|
74
74
|
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
75
75
|
ocx account cancel <provider> [--flow <flow-id>] [--json] (--flow required for codex)
|
|
76
76
|
ocx account reset-credits <account-id|main> [--consume --yes] [--json]
|
|
@@ -184,10 +184,10 @@ export const ACCOUNT_CAPABILITIES: readonly Capability[] = [
|
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
command: ["account","anthropic-reset-grants"],
|
|
187
|
-
usage: "ocx account anthropic-reset-grants [account-id] [--json]",
|
|
187
|
+
usage: "ocx account anthropic-reset-grants [account-id] [--provider anthropic|anthropic2] [--json]",
|
|
188
188
|
summary: "Read Anthropic reset-grant eligibility and pending status without consuming a grant.",
|
|
189
189
|
routes: [{"method":"GET","path":"/api/anthropic/reset-grants"}],
|
|
190
|
-
flags: [{"name":"--json","value":"boolean","summary":"Emit the validated task result as one JSON document."}],
|
|
190
|
+
flags: [{ name: "--provider", value: "string", summary: "Anthropic OAuth instance: anthropic or anthropic2; omission keeps the legacy Anthropic pool." }, {"name":"--json","value":"boolean","summary":"Emit the validated task result as one JSON document."}],
|
|
191
191
|
mutates: false,
|
|
192
192
|
json: "envelope",
|
|
193
193
|
details: ["Optional identity is the exact Anthropic account ID. Omission delegates active/fallback account choice to the server, not the Codex alias resolver.","This GET can refresh access/status information upstream. Empty grants, unavailable status, pending operation and unavailable journal remain distinct. Consume remains a GUI-session-only human handoff; no session acquisition or retry is performed."],
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import type { Capability } from "./capability-types";
|
|
2
2
|
|
|
3
3
|
export const AGENT_ROUTING_CAPABILITIES: readonly Capability[] = [
|
|
4
|
+
{
|
|
5
|
+
command: ["message", "sessions"],
|
|
6
|
+
summary: "Discover already-loaded local Codex sessions without reading conversation history.",
|
|
7
|
+
routes: [], flags: [{ name: "--json", value: "boolean", summary: "Emit a versioned local session directory." }],
|
|
8
|
+
mutates: false, json: "envelope",
|
|
9
|
+
bannerLines: ["ocx message <sub> Loaded local Codex sessions and queued peer messages (sessions|send)"],
|
|
10
|
+
details: ["Linux/macOS Unix socket only. Uses effective CODEX_HOME; starts no daemon and returns no partial directory."],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
command: ["message", "send"],
|
|
14
|
+
summary: "Submit one correlated peer message to an exact loaded local Codex destination.",
|
|
15
|
+
routes: [], mutates: true, json: "envelope",
|
|
16
|
+
flags: [
|
|
17
|
+
{ name: "--thread", value: "string", summary: "Exact loaded UUID; choose this or --name." },
|
|
18
|
+
{ name: "--name", value: "string", summary: "Unique exact loaded name; choose this or --thread." },
|
|
19
|
+
{ name: "--stdin", value: "boolean", required: true, summary: "Read at most 16 KiB of UTF-8 message text." },
|
|
20
|
+
{ name: "--kind", value: "string", summary: "request (default), response or notification." },
|
|
21
|
+
{ name: "--in-reply-to", value: "string", summary: "Request message UUID; required only for a response." },
|
|
22
|
+
{ name: "--json", value: "boolean", summary: "Emit a receipt with not_sent, queued or unknown status." },
|
|
23
|
+
],
|
|
24
|
+
details: ["Requires daemon support for experimental thread/queue/add. Sender context comes from CODEX_THREAD_ID, not an authentication claim.",
|
|
25
|
+
"queued means submitted, not processed. unknown must not be replayed; no automatic retry, daemon start or thread resume.",
|
|
26
|
+
"Exit 0: queued; 1: not sent; 3: unknown; 64: invalid usage. No remote/Claude transport or skill installation."],
|
|
27
|
+
},
|
|
4
28
|
{
|
|
5
29
|
command: ["agent", "status"],
|
|
6
30
|
usage: "ocx agent status [--json]",
|