@bitkyc08/opencodex 2.56.0 → 2.57.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/bin/ocx.mjs +10 -0
- package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
- package/gui/dist/assets/{index-D4zuyIxQ.js → index-Cz7CLdif.js} +21 -21
- package/gui/dist/index.html +2 -2
- package/package.json +3 -3
- package/src/adapters/codebuddy/adapter.ts +2 -1
- package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
- package/src/adapters/command-code.ts +1 -1
- package/src/adapters/cursor/envelope-echo.ts +8 -2
- package/src/adapters/google.ts +7 -7
- package/src/adapters/kiro/payload.ts +17 -3
- package/src/adapters/kiro/reasoning.ts +70 -7
- package/src/adapters/kiro/stream.ts +8 -2
- package/src/adapters/kiro/wire.ts +2 -1
- package/src/adapters/kiro-events.ts +21 -13
- package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
- package/src/adapters/openai-chat/tool-schema.ts +25 -7
- package/src/adapters/openai-chat.ts +8 -8
- package/src/adapters/openai-responses/passthrough.ts +32 -1
- package/src/bridge/errors.ts +26 -2
- package/src/bridge/response-json.ts +7 -1
- package/src/bridge/sse.ts +19 -1
- package/src/claude/desktop-profile.ts +66 -9
- package/src/claude/outbound.ts +18 -0
- package/src/cli/account-main.ts +1 -1
- package/src/cli/capabilities.ts +2 -2
- package/src/cli/combo.ts +10 -1
- package/src/cli/index.ts +48 -5
- package/src/cli/registry.ts +2 -1
- package/src/cli/system-command.ts +4 -4
- package/src/clients/config-export.ts +7 -3
- package/src/codex/account-label.ts +14 -3
- package/src/codex/account-store.ts +113 -26
- package/src/codex/account-usability.ts +21 -0
- package/src/codex/auth-api/login-flow.ts +14 -2
- package/src/codex/auth-api/reset-credit-service.ts +11 -2
- package/src/codex/auth-context.ts +157 -7
- package/src/codex/catalog/aggregation.ts +80 -1
- package/src/codex/catalog/model-visibility.ts +1 -0
- package/src/codex/catalog/remote.ts +30 -0
- package/src/codex/catalog/retained-sync.ts +9 -1
- package/src/codex/catalog/routed-gather.ts +38 -1
- package/src/codex/cli-install-provenance.ts +7 -1
- package/src/codex/convergence.ts +7 -2
- package/src/codex/desktop-app/types.ts +11 -2
- package/src/codex/desktop-app/windows.ts +5 -5
- package/src/codex/inject/restore.ts +29 -2
- package/src/codex/inject.ts +9 -9
- package/src/codex/model-entitlements.ts +152 -15
- package/src/codex/pool-refresh-backoff.ts +12 -3
- package/src/codex/quota-rejection.ts +104 -15
- package/src/codex/routing/cache-affinity.ts +70 -0
- package/src/codex/routing/cooldown-math.ts +10 -0
- package/src/codex/routing/selection.ts +79 -2
- package/src/codex/routing/thread-affinity.ts +50 -2
- package/src/codex/routing/transient-hold-dispatch.ts +141 -0
- package/src/codex/routing.ts +29 -49
- package/src/codex/warmup.ts +1 -1
- package/src/combos/failover.ts +85 -0
- package/src/combos/request.ts +17 -10
- package/src/combos/types.ts +23 -2
- package/src/config/pending-teardown.ts +31 -0
- package/src/generated/compatibility-version.json +163 -135
- package/src/images/loop.ts +1 -1
- package/src/lib/errors.ts +17 -0
- package/src/lib/request-execution-budget.ts +147 -21
- package/src/lib/spend-reservation-ledger.ts +18 -0
- package/src/lib/state-store-registrations.ts +6 -2
- package/src/lib/test-home-guard.ts +85 -1
- package/src/lib/upstream-retry.ts +77 -10
- package/src/lib/windows-elevation.ts +76 -14
- package/src/oauth/index.ts +2 -2
- package/src/oauth/key-providers.ts +2 -2
- package/src/providers/kiro-models.ts +4 -3
- package/src/providers/label.ts +19 -1
- package/src/providers/model-discovery.ts +16 -0
- package/src/providers/registry/entries-core.ts +7 -0
- package/src/providers/registry/entries-extended.ts +9 -0
- package/src/providers/registry/model-seeds.ts +4 -0
- package/src/responses/reasoning-envelope.ts +6 -3
- package/src/routing/identity-domains.ts +21 -14
- package/src/routing/probe-lease.ts +103 -1
- package/src/server/chat-completions.ts +3 -1
- package/src/server/chat-native.ts +37 -9
- package/src/server/index/live-sideband.ts +37 -1
- package/src/server/index/websocket-handler.ts +6 -2
- package/src/server/index.ts +5 -5
- package/src/server/inspection-tee.ts +107 -0
- package/src/server/live.ts +46 -1
- package/src/server/management/combo-routes.ts +10 -1
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +14 -19
- package/src/server/request-log.ts +127 -3
- package/src/server/response-log-body.ts +153 -0
- package/src/server/responses/account-change-state.ts +74 -0
- package/src/server/responses/adapter-continuation.ts +33 -7
- package/src/server/responses/adapter-delivery.ts +5 -11
- package/src/server/responses/adapter-dispatch.ts +84 -13
- package/src/server/responses/codex-ws-wire.ts +5 -0
- package/src/server/responses/collaboration.ts +74 -4
- package/src/server/responses/combo-session-recall.ts +68 -8
- package/src/server/responses/compact.ts +54 -13
- package/src/server/responses/core-auth.ts +2 -0
- package/src/server/responses/core-codex-account.ts +51 -3
- package/src/server/responses/core-combo.ts +103 -23
- package/src/server/responses/core-errors.ts +18 -0
- package/src/server/responses/core-replay.ts +105 -32
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/encrypted-payload.ts +0 -1
- package/src/server/responses/input-admission.ts +126 -6
- package/src/server/responses/passthrough-delivery.ts +19 -6
- package/src/server/responses/passthrough-dispatch.ts +28 -10
- package/src/server/responses/passthrough-error.ts +38 -2
- package/src/server/responses/request-prepare.ts +132 -22
- package/src/server/responses/request-send-budget.ts +97 -2
- package/src/server/responses/request-spend.ts +147 -0
- package/src/server/responses/request-transport.ts +62 -3
- package/src/server/responses/run-turn-execution.ts +59 -31
- package/src/server/responses/sidecar-execution.ts +7 -13
- package/src/server/responses/terminal-guard.ts +65 -4
- package/src/server/responses-undeclared-tool-guard.ts +9 -5
- package/src/service/windows-ops.ts +210 -16
- package/src/service/windows-scheduler.ts +28 -21
- package/src/service.ts +1 -1
- package/src/types/config.ts +4 -1
- package/src/types/request.ts +8 -5
- package/src/types/tools.ts +24 -0
- package/src/types.ts +2 -0
- package/src/update/index.ts +10 -0
- package/src/update/stop-contract.d.mts +1 -0
- package/src/update/stop-contract.mjs +19 -0
- package/src/update/stop-decision.d.mts +1 -1
- package/src/update/stop-decision.mjs +12 -3
- package/src/usage/log.ts +1 -1
- package/src/vision/anthropic-describe.ts +1 -1
- package/src/vision/describe.ts +5 -5
- package/src/web-search/anthropic-executor.ts +1 -1
- package/src/web-search/exa-executor.ts +1 -1
- package/src/web-search/executor.ts +1 -1
- package/src/web-search/gemini-executor.ts +1 -1
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/ollama-executor.ts +1 -1
- package/src/web-search/parse.ts +67 -14
- package/src/web-search/passthrough-bridge.ts +64 -31
- package/src/web-search/xai-executor.ts +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { namespacedToolName, type OcxParsedRequest, type OcxTool } from "../../types";
|
|
3
|
+
import { frameAgentRouterMessages } from "../agentrouter";
|
|
4
|
+
|
|
5
|
+
const MAX_CHAT_TOOL_NAME_LENGTH = 64;
|
|
6
|
+
const ALIAS_HINT_CHARS = 16;
|
|
7
|
+
const RESERVED_ALIAS_PATTERN = /^ocx_[a-zA-Z0-9_-]{16}_[a-zA-Z0-9_-]{43}$/;
|
|
8
|
+
type ToolIdentity = Readonly<Pick<OcxTool, "namespace" | "name">>;
|
|
9
|
+
|
|
10
|
+
export interface OpenAIChatToolNameRegistry {
|
|
11
|
+
alias(tool: ToolIdentity): string;
|
|
12
|
+
aliasWireName(wireName: string): string;
|
|
13
|
+
restore(wireName: string): string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface OpenAIChatToolNameScope {
|
|
17
|
+
messages(parsed: OcxParsedRequest, baseUrl: string, messages: readonly unknown[]): unknown;
|
|
18
|
+
registry(): OpenAIChatToolNameRegistry;
|
|
19
|
+
restore(wireName: string): string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function identityKey(tool: ToolIdentity): string {
|
|
23
|
+
return JSON.stringify([tool.namespace ?? null, tool.name]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function boundedAlias(tool: ToolIdentity, wireName: string): string {
|
|
27
|
+
const hint = wireName
|
|
28
|
+
.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
29
|
+
.slice(-ALIAS_HINT_CHARS)
|
|
30
|
+
.padStart(ALIAS_HINT_CHARS, "_");
|
|
31
|
+
const key = identityKey(tool);
|
|
32
|
+
const digest = createHash("sha256")
|
|
33
|
+
.update(key)
|
|
34
|
+
.digest("base64url");
|
|
35
|
+
return `ocx_${hint}_${digest}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Catalog declarations plus structured calls retained in replay history. */
|
|
39
|
+
export function openAIChatToolNameIdentities(parsed: OcxParsedRequest): ToolIdentity[] {
|
|
40
|
+
const identities: ToolIdentity[] = [...(parsed.context.tools ?? [])];
|
|
41
|
+
for (const message of parsed.context.messages) {
|
|
42
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
43
|
+
for (const part of message.content) {
|
|
44
|
+
if (part.type !== "toolCall") continue;
|
|
45
|
+
identities.push({
|
|
46
|
+
name: part.name,
|
|
47
|
+
...(part.namespace === undefined ? {} : { namespace: part.namespace }),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return identities;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* One collision domain for a translated Chat Completions request.
|
|
56
|
+
*
|
|
57
|
+
* Namespaced names whose flattened spelling exceeds Chat Completions' 64-character function-name
|
|
58
|
+
* bound are rewritten. Ordinary names and bare names pass through byte-for-byte unless they occupy
|
|
59
|
+
* the reserved alias spelling; those are re-aliased so no declaration can shadow another identity's
|
|
60
|
+
* deterministic alias. Distinct identities sharing one flattened spelling each keep an identity
|
|
61
|
+
* alias, while replay rewriting leaves that ambiguous spelling untouched. Echoed aliases restore to
|
|
62
|
+
* the original flattened name consumed by the Responses bridge's existing namespace map.
|
|
63
|
+
*/
|
|
64
|
+
export function createOpenAIChatToolNameRegistry(
|
|
65
|
+
tools: readonly ToolIdentity[] | undefined,
|
|
66
|
+
): OpenAIChatToolNameRegistry {
|
|
67
|
+
const identities = new Map<string, ToolIdentity>();
|
|
68
|
+
for (const tool of tools ?? []) identities.set(identityKey(tool), tool);
|
|
69
|
+
const sortedIdentities = [...identities.entries()]
|
|
70
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
71
|
+
|
|
72
|
+
const aliasesByIdentity = new Map<string, string>();
|
|
73
|
+
const aliasesByWireName = new Map<string, string>();
|
|
74
|
+
const originalsByAlias = new Map<string, string>();
|
|
75
|
+
const wireOwners = new Map<string, string | null>();
|
|
76
|
+
for (const [key, tool] of sortedIdentities) {
|
|
77
|
+
const wireName = namespacedToolName(tool.namespace, tool.name);
|
|
78
|
+
const owner = wireOwners.get(wireName);
|
|
79
|
+
if (owner === undefined) wireOwners.set(wireName, key);
|
|
80
|
+
else if (owner !== key) wireOwners.set(wireName, null);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const aliasOwners = new Map<string, string>();
|
|
84
|
+
const wireClaims = new Map<string, { key: string; alias: string } | null>();
|
|
85
|
+
for (const [key, tool] of sortedIdentities) {
|
|
86
|
+
const wireName = namespacedToolName(tool.namespace, tool.name);
|
|
87
|
+
const candidate = (tool.namespace !== undefined && wireName.length > MAX_CHAT_TOOL_NAME_LENGTH)
|
|
88
|
+
|| RESERVED_ALIAS_PATTERN.test(wireName)
|
|
89
|
+
|| wireOwners.get(wireName) === null
|
|
90
|
+
? boundedAlias(tool, wireName)
|
|
91
|
+
: wireName;
|
|
92
|
+
// A full SHA-256 collision is not safely attributable. Keep the later identity's native
|
|
93
|
+
// spelling instead of failing the request or stealing the first identity's restore entry.
|
|
94
|
+
const alias = aliasOwners.has(candidate) ? wireName : candidate;
|
|
95
|
+
aliasesByIdentity.set(key, alias);
|
|
96
|
+
if (!aliasOwners.has(alias)) aliasOwners.set(alias, key);
|
|
97
|
+
if (alias !== wireName) originalsByAlias.set(alias, wireName);
|
|
98
|
+
|
|
99
|
+
const claim = wireClaims.get(wireName);
|
|
100
|
+
if (claim === undefined) wireClaims.set(wireName, { key, alias });
|
|
101
|
+
else if (claim !== null && claim.key !== key) wireClaims.set(wireName, null);
|
|
102
|
+
}
|
|
103
|
+
for (const [wireName, claim] of wireClaims) {
|
|
104
|
+
if (claim !== null) aliasesByWireName.set(wireName, claim.alias);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
alias(tool: ToolIdentity): string {
|
|
109
|
+
const key = identityKey(tool);
|
|
110
|
+
const known = aliasesByIdentity.get(key);
|
|
111
|
+
if (known !== undefined) return known;
|
|
112
|
+
return namespacedToolName(tool.namespace, tool.name);
|
|
113
|
+
},
|
|
114
|
+
aliasWireName(wireName: string): string {
|
|
115
|
+
return aliasesByWireName.get(wireName) ?? wireName;
|
|
116
|
+
},
|
|
117
|
+
restore(wireName: string): string {
|
|
118
|
+
return originalsByAlias.get(wireName) ?? wireName;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function restoreOpenAIChatToolName(
|
|
124
|
+
registry: OpenAIChatToolNameRegistry,
|
|
125
|
+
wireName: string,
|
|
126
|
+
): string {
|
|
127
|
+
return registry.restore(wireName);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
131
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Rewrite replayed assistant tool calls after the ordinary message converter has flattened them. */
|
|
135
|
+
export function aliasOpenAIChatMessageToolNames(
|
|
136
|
+
messages: readonly unknown[],
|
|
137
|
+
registry: OpenAIChatToolNameRegistry,
|
|
138
|
+
): unknown[] {
|
|
139
|
+
return messages.map(message => {
|
|
140
|
+
if (!isRecord(message) || !Array.isArray(message.tool_calls)) return message;
|
|
141
|
+
let changed = false;
|
|
142
|
+
const toolCalls = message.tool_calls.map(toolCall => {
|
|
143
|
+
if (!isRecord(toolCall) || !isRecord(toolCall.function)
|
|
144
|
+
|| typeof toolCall.function.name !== "string") return toolCall;
|
|
145
|
+
const name = registry.aliasWireName(toolCall.function.name);
|
|
146
|
+
if (name === toolCall.function.name) return toolCall;
|
|
147
|
+
changed = true;
|
|
148
|
+
return { ...toolCall, function: { ...toolCall.function, name } };
|
|
149
|
+
});
|
|
150
|
+
return changed ? { ...message, tool_calls: toolCalls } : message;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function withOpenAIChatToolNames<T>(
|
|
155
|
+
build: (scope: OpenAIChatToolNameScope) => T,
|
|
156
|
+
): T {
|
|
157
|
+
let registry = createOpenAIChatToolNameRegistry(undefined);
|
|
158
|
+
return build({
|
|
159
|
+
messages(parsed, baseUrl, messages): unknown {
|
|
160
|
+
registry = createOpenAIChatToolNameRegistry(openAIChatToolNameIdentities(parsed));
|
|
161
|
+
return frameAgentRouterMessages(baseUrl, aliasOpenAIChatMessageToolNames(messages, registry));
|
|
162
|
+
},
|
|
163
|
+
registry: () => registry,
|
|
164
|
+
restore: wireName => restoreOpenAIChatToolName(registry, wireName),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isNativeOpenAIChatTarget } from "./wire";
|
|
2
|
+
import { createOpenAIChatToolNameRegistry, type OpenAIChatToolNameRegistry } from "./tool-name-registry";
|
|
2
3
|
import { isXaiSchemaTarget, lookupLocalJsonPointer, normalizeXaiToolParameters } from "../xai-tool-schema";
|
|
3
4
|
import { stripResponsesOnlyEncryptedMarker, stripUnicodePropertyPatterns } from "../responses-tool-schema";
|
|
4
|
-
import { isAllowedToolChoice,
|
|
5
|
+
import { isAllowedToolChoice, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../../types";
|
|
5
6
|
import type { OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
6
7
|
|
|
7
8
|
const ZEN_SCHEMA_MAP_KEYS = new Set(["properties", "$defs", "definitions"]);
|
|
@@ -409,7 +410,11 @@ function normalizeMoonshotToolParameters(parameters: unknown): Record<string, un
|
|
|
409
410
|
return isXaiObjectSchema(normalized) ? normalized : rooted;
|
|
410
411
|
}
|
|
411
412
|
|
|
412
|
-
export function toolsToChatFormat(
|
|
413
|
+
export function toolsToChatFormat(
|
|
414
|
+
parsed: OcxParsedRequest,
|
|
415
|
+
provider: OcxProviderConfig,
|
|
416
|
+
registry: OpenAIChatToolNameRegistry = createOpenAIChatToolNameRegistry(parsed.context.tools),
|
|
417
|
+
): unknown[] | undefined {
|
|
413
418
|
if (!parsed.context.tools || parsed.context.tools.length === 0) return undefined;
|
|
414
419
|
const tools = parsed.context.tools.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools));
|
|
415
420
|
if (tools.length === 0) return undefined;
|
|
@@ -427,7 +432,7 @@ export function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProvide
|
|
|
427
432
|
return [{
|
|
428
433
|
type: "function",
|
|
429
434
|
function: {
|
|
430
|
-
name:
|
|
435
|
+
name: registry.alias(t),
|
|
431
436
|
...(t.description ? { description: t.description } : {}),
|
|
432
437
|
parameters,
|
|
433
438
|
...(t.strict !== undefined ? { strict: t.strict } : {}),
|
|
@@ -437,8 +442,12 @@ export function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProvide
|
|
|
437
442
|
return formatted.length > 0 ? formatted : undefined;
|
|
438
443
|
}
|
|
439
444
|
|
|
440
|
-
export function toolsToChatFormatForProvider(
|
|
441
|
-
|
|
445
|
+
export function toolsToChatFormatForProvider(
|
|
446
|
+
parsed: OcxParsedRequest,
|
|
447
|
+
provider: OcxProviderConfig,
|
|
448
|
+
registry: OpenAIChatToolNameRegistry = createOpenAIChatToolNameRegistry(parsed.context.tools),
|
|
449
|
+
): unknown[] | undefined {
|
|
450
|
+
const base = toolsToChatFormat(parsed, provider, registry);
|
|
442
451
|
const azureChat = isAzureOpenAiChatTarget(provider);
|
|
443
452
|
const zenChat = shouldSanitizeZenToolParameters(provider);
|
|
444
453
|
if (!base || (!zenChat && !azureChat)) return base;
|
|
@@ -463,15 +472,24 @@ export function toolChoiceToChatFormat(
|
|
|
463
472
|
tc: OcxParsedRequest["options"]["toolChoice"],
|
|
464
473
|
tools: OcxParsedRequest["context"]["tools"],
|
|
465
474
|
provider: OcxProviderConfig,
|
|
475
|
+
registry: OpenAIChatToolNameRegistry = createOpenAIChatToolNameRegistry(tools),
|
|
466
476
|
): unknown {
|
|
467
477
|
if (!tc) return undefined;
|
|
468
478
|
if (isAllowedToolChoice(tc)) {
|
|
469
479
|
if (tc.mode === "required" && tc.allowedTools.length === 1 && isNativeOpenAIChatTarget(provider)) {
|
|
470
|
-
return {
|
|
480
|
+
return {
|
|
481
|
+
type: "function",
|
|
482
|
+
function: { name: registry.aliasWireName(resolveToolChoiceWireName(tools, tc.allowedTools[0])) },
|
|
483
|
+
};
|
|
471
484
|
}
|
|
472
485
|
return tc.mode === "required" ? "required" : "auto";
|
|
473
486
|
}
|
|
474
487
|
if (tc === "auto" || tc === "none" || tc === "required") return tc;
|
|
475
|
-
if ("name" in tc)
|
|
488
|
+
if ("name" in tc) {
|
|
489
|
+
return {
|
|
490
|
+
type: "function",
|
|
491
|
+
function: { name: registry.aliasWireName(resolveToolChoiceWireName(tools, tc.name)) },
|
|
492
|
+
};
|
|
493
|
+
}
|
|
476
494
|
return undefined;
|
|
477
495
|
}
|
|
@@ -6,7 +6,6 @@ import { mapReasoningEffort, modelRecordValue } from "../reasoning-effort";
|
|
|
6
6
|
import { debugProviderDiagnostic } from "../lib/debug";
|
|
7
7
|
import { sseFieldValue } from "../lib/sse-decoder";
|
|
8
8
|
import { isDebugEnabled } from "../lib/debug-settings";
|
|
9
|
-
import { frameAgentRouterMessages } from "./agentrouter";
|
|
10
9
|
import { openRouterProviderPayload, resolveOpenRouterRouting } from "../providers/openrouter-routing";
|
|
11
10
|
import { resolveVercelGatewayRouting, vercelGatewayProviderPayload } from "../providers/vercel-gateway-routing";
|
|
12
11
|
import { fastPolicyForModel } from "../providers/service-tier";
|
|
@@ -39,6 +38,7 @@ import {
|
|
|
39
38
|
upstreamErrorEvent,
|
|
40
39
|
} from "./openai-chat/errors";
|
|
41
40
|
import { messagesToChatFormat } from "./openai-chat/messages";
|
|
41
|
+
import { withOpenAIChatToolNames } from "./openai-chat/tool-name-registry";
|
|
42
42
|
import { isNativeOpenAIChatTarget, openAIChatTransport, stripBracketedModelSuffix } from "./openai-chat/wire";
|
|
43
43
|
import { toolChoiceToChatFormat, toolsToChatFormatForProvider } from "./openai-chat/tool-schema";
|
|
44
44
|
|
|
@@ -88,7 +88,7 @@ function canSerializeOpenAIChatServiceTier(
|
|
|
88
88
|
|
|
89
89
|
export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
90
90
|
let lastRequestedModelId: string | undefined;
|
|
91
|
-
return {
|
|
91
|
+
return withOpenAIChatToolNames(toolNames => ({
|
|
92
92
|
name: "openai-chat",
|
|
93
93
|
|
|
94
94
|
formatErrorBody: formatOpenAIChatErrorBody,
|
|
@@ -96,10 +96,10 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
96
96
|
buildRequest(parsed: OcxParsedRequest, incoming?: IncomingMeta) {
|
|
97
97
|
lastRequestedModelId = parsed.modelId;
|
|
98
98
|
const { url, headers, hasCredential } = openAIChatTransport(provider);
|
|
99
|
-
const messages =
|
|
99
|
+
const messages = toolNames.messages(parsed, provider.baseUrl, messagesToChatFormat(parsed, provider));
|
|
100
100
|
const finish = (): AdapterRequest => {
|
|
101
|
-
const tools = toolsToChatFormatForProvider(parsed, provider);
|
|
102
|
-
const toolChoice = toolChoiceToChatFormat(parsed.options.toolChoice, parsed.context.tools, provider);
|
|
101
|
+
const tools = toolsToChatFormatForProvider(parsed, provider, toolNames.registry());
|
|
102
|
+
const toolChoice = toolChoiceToChatFormat(parsed.options.toolChoice, parsed.context.tools, provider, toolNames.registry());
|
|
103
103
|
|
|
104
104
|
const body: Record<string, unknown> = {
|
|
105
105
|
model: provider.modelSuffixBracketStrip ? stripBracketedModelSuffix(parsed.modelId) : parsed.modelId,
|
|
@@ -365,7 +365,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
365
365
|
return "terminate";
|
|
366
366
|
}
|
|
367
367
|
if (!call.id) call.id = `call_${++toolCallSeq}`;
|
|
368
|
-
yield { type: "tool_call_start", id: call.id, name: call.name };
|
|
368
|
+
yield { type: "tool_call_start", id: call.id, name: toolNames.restore(call.name) };
|
|
369
369
|
if (call.args.length > 0) yield { type: "tool_call_delta", arguments: call.args };
|
|
370
370
|
yield { type: "tool_call_end" };
|
|
371
371
|
}
|
|
@@ -801,7 +801,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
801
801
|
logInvalidToolCalls("response", rawToolCalls);
|
|
802
802
|
return [invalidToolCallsEvent(rawToolCalls, "response", usage)];
|
|
803
803
|
}
|
|
804
|
-
events.push({ type: "tool_call_start", id, name });
|
|
804
|
+
events.push({ type: "tool_call_start", id, name: toolNames.restore(name) });
|
|
805
805
|
events.push({ type: "tool_call_delta", arguments: args });
|
|
806
806
|
events.push({ type: "tool_call_end" });
|
|
807
807
|
}
|
|
@@ -818,5 +818,5 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
818
818
|
budget.releaseRetained(responseBytes, { kind: "retained_collectors" });
|
|
819
819
|
}
|
|
820
820
|
},
|
|
821
|
-
};
|
|
821
|
+
}));
|
|
822
822
|
}
|
|
@@ -41,6 +41,19 @@ import { applyTierDecisionToResponsesBody, normalizeCanonicalForwardContinuation
|
|
|
41
41
|
import { normalizeImageGenClientTools, preferConfiguredHostedTools } from "./image-gen";
|
|
42
42
|
import { stripMuseSparkUnsupportedWebSearchFields, stripOpenAiOnlyWebSearchFields } from "./web-search";
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Identifies DeepSeek's strict Responses replay contract: tool-bearing continuations need
|
|
46
|
+
* plaintext reasoning and cannot consume opaque reasoning state. The two existing flags are
|
|
47
|
+
* current evidence for that one provider contract, not equivalent capabilities: preservation
|
|
48
|
+
* keeps plaintext reasoning on the wire, while adjacency marks its strict tool-history shape.
|
|
49
|
+
* The moment a second provider needs this behavior, replace this derivation with an explicit
|
|
50
|
+
* registry capability rather than extending the inference.
|
|
51
|
+
*/
|
|
52
|
+
export function requiresPlaintextReasoningReplay(provider: OcxProviderConfig): boolean {
|
|
53
|
+
return provider.preserveResponsesReasoningContent === true
|
|
54
|
+
&& provider.requiresAdjacentResponsesToolResults === true;
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
// Headers relayed verbatim from the caller in OAuth-passthrough ("forward") mode.
|
|
45
58
|
// Exported so the web-search sidecar reuses the exact same forwarded-auth set for its ChatGPT call.
|
|
46
59
|
export const FORWARD_HEADERS = [
|
|
@@ -64,6 +77,16 @@ export const FORWARD_HEADERS = [
|
|
|
64
77
|
CODEX_RESPONSES_LITE_HEADER,
|
|
65
78
|
];
|
|
66
79
|
|
|
80
|
+
/** Preserve the caller fingerprint unless the provider explicitly owns that header. */
|
|
81
|
+
function applyCallerUserAgentFallback(
|
|
82
|
+
headers: Record<string, string>,
|
|
83
|
+
incoming: IncomingMeta,
|
|
84
|
+
): void {
|
|
85
|
+
if (Object.keys(headers).some(name => name.toLowerCase() === "user-agent")) return;
|
|
86
|
+
const userAgent = incoming.headers.get("user-agent");
|
|
87
|
+
if (userAgent) headers["User-Agent"] = userAgent;
|
|
88
|
+
}
|
|
89
|
+
|
|
67
90
|
/** Replace every `input_image` part under a routed-compaction body with a short marker. */
|
|
68
91
|
function stripInputImagesDeep(value: unknown): unknown {
|
|
69
92
|
if (Array.isArray(value)) return value.map(stripInputImagesDeep);
|
|
@@ -221,6 +244,10 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
221
244
|
if (provider.apiKey) headers["Authorization"] = `Bearer ${provider.apiKey}`;
|
|
222
245
|
if (provider.headers) Object.assign(headers, provider.headers);
|
|
223
246
|
}
|
|
247
|
+
// Some Responses-compatible gateways select their Codex compatibility path from the real
|
|
248
|
+
// client fingerprint. This is a single non-credential fallback, not broader caller-header
|
|
249
|
+
// forwarding. Static provider headers remain authoritative in either auth mode.
|
|
250
|
+
applyCallerUserAgentFallback(headers, incoming);
|
|
224
251
|
|
|
225
252
|
const forward = provider.authMode === "forward";
|
|
226
253
|
let convertedRoutedCustomToolNames: Set<string> | undefined;
|
|
@@ -357,6 +384,10 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
357
384
|
}
|
|
358
385
|
}
|
|
359
386
|
const threadServingIdentityChanged = parsed._stripReasoningEncryptedContent === true;
|
|
387
|
+
// Providers with the strict plaintext tool-continuation contract cannot consume any
|
|
388
|
+
// encrypted reasoning blob, including one whose provenance is unknown. Combo routing
|
|
389
|
+
// separately refuses a proven cross-route replay when no plaintext exists; this final
|
|
390
|
+
// serializer guard ensures the foreign opaque state is never forwarded regardless.
|
|
360
391
|
const sanitizedBody = normalizeToolSchemas(
|
|
361
392
|
stripItemIdsWhenUnstored(
|
|
362
393
|
stripInvalidItemIds(
|
|
@@ -370,7 +401,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
370
401
|
{
|
|
371
402
|
preserveRawReasoningContent: provider.preserveResponsesReasoningContent === true,
|
|
372
403
|
dropNullContentChannel: !isOpenAiOperatedResponsesDestination(provider),
|
|
373
|
-
stripEncryptedContent: threadServingIdentityChanged,
|
|
404
|
+
stripEncryptedContent: threadServingIdentityChanged || requiresPlaintextReasoningReplay(provider),
|
|
374
405
|
},
|
|
375
406
|
),
|
|
376
407
|
provider,
|
package/src/bridge/errors.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isNonReplayableUpstreamCode,
|
|
3
|
+
isReplayRefusalCode,
|
|
4
|
+
markReplayRefusalResponse,
|
|
5
|
+
markResponseNonReplayable,
|
|
6
|
+
REPLAY_REFUSED_STATUS,
|
|
7
|
+
} from "../lib/upstream-retry";
|
|
1
8
|
import {
|
|
2
9
|
adapterFailureFromMessage,
|
|
3
10
|
classifyError,
|
|
@@ -18,17 +25,34 @@ export function formatErrorResponse(
|
|
|
18
25
|
error.code = CYBER_POLICY_ERROR_CODE;
|
|
19
26
|
error.type = cyberPolicyErrorType(type);
|
|
20
27
|
}
|
|
21
|
-
|
|
28
|
+
// Only the allowlisted transport verdicts survive this formatter. Do not forward
|
|
29
|
+
// arbitrary provider codes, and preserve the existing cyber-policy precedence.
|
|
30
|
+
const replayBlocked = error.code !== CYBER_POLICY_ERROR_CODE
|
|
31
|
+
&& isNonReplayableUpstreamCode(options?.code);
|
|
32
|
+
if (replayBlocked) error.code = options!.code!;
|
|
33
|
+
// The replay refusal owns its status as well as its code. A combo or adapter formatter
|
|
34
|
+
// reaches here holding the upstream-shaped status it was about to report, and inheriting
|
|
35
|
+
// that would hand the client a 5xx it is configured to retry four times.
|
|
36
|
+
const finalStatus = error.code === CYBER_POLICY_ERROR_CODE
|
|
37
|
+
? 400
|
|
38
|
+
: isReplayRefusalCode(error.code) ? REPLAY_REFUSED_STATUS : status;
|
|
22
39
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
23
40
|
const retryAfter = options?.retryAfter?.trim();
|
|
24
41
|
if (error.code !== CYBER_POLICY_ERROR_CODE
|
|
42
|
+
&& !replayBlocked
|
|
25
43
|
&& retryAfter
|
|
26
44
|
&& retryAfter.length > 0
|
|
27
45
|
&& retryAfter.length <= 128) {
|
|
28
46
|
headers.set("Retry-After", retryAfter);
|
|
29
47
|
}
|
|
30
|
-
|
|
48
|
+
const response = new Response(JSON.stringify({ error }), {
|
|
31
49
|
status: finalStatus,
|
|
32
50
|
headers,
|
|
33
51
|
});
|
|
52
|
+
if (replayBlocked) markResponseNonReplayable(response);
|
|
53
|
+
// Re-wrapping is where the refusal loses its provenance: combo failure consumption parses
|
|
54
|
+
// the JSON and builds a new Response, and the code alone does not tell a later quota
|
|
55
|
+
// recorder that no upstream produced this status. Carry the narrower marker across too.
|
|
56
|
+
if (replayBlocked && isReplayRefusalCode(error.code)) markReplayRefusalResponse(response);
|
|
57
|
+
return response;
|
|
34
58
|
}
|
|
@@ -68,6 +68,8 @@ function buildResponseJSONWithBudget(
|
|
|
68
68
|
toolNsMap?: Map<string, { namespace: string; name: string; freeform?: true }>;
|
|
69
69
|
/** Request-visible tool names. When present, an upstream call outside this set fails closed. */
|
|
70
70
|
declaredToolNames?: ReadonlySet<string>;
|
|
71
|
+
/** See `bridgeToResponsesSSE`: enforcement is separate from normalization (#4735). */
|
|
72
|
+
enforceDeclaredToolNames?: boolean;
|
|
71
73
|
/** Declared parameter schema per tool name; repairs integral-float integer args (#1611). */
|
|
72
74
|
toolParameterSchemas?: ReadonlyMap<string, Record<string, unknown>>;
|
|
73
75
|
freeformToolNames?: Set<string>;
|
|
@@ -432,7 +434,11 @@ function buildResponseJSONWithBudget(
|
|
|
432
434
|
}
|
|
433
435
|
flushToolCall();
|
|
434
436
|
const effectiveName = normalizeDeclaredToolName(e.name, options?.declaredToolNames);
|
|
435
|
-
if (
|
|
437
|
+
if (
|
|
438
|
+
options?.declaredToolNames
|
|
439
|
+
&& options.enforceDeclaredToolNames !== false
|
|
440
|
+
&& !options.declaredToolNames.has(effectiveName)
|
|
441
|
+
) {
|
|
436
442
|
errorEvent = {
|
|
437
443
|
type: "error",
|
|
438
444
|
message: `routed provider emitted undeclared client tool "${effectiveName}"; only request-declared tools may be called`,
|
package/src/bridge/sse.ts
CHANGED
|
@@ -88,6 +88,20 @@ export function bridgeToResponsesSSE(
|
|
|
88
88
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
89
89
|
/** Request-visible tool names. When present, an upstream call outside this set fails closed. */
|
|
90
90
|
declaredToolNames?: ReadonlySet<string>;
|
|
91
|
+
/**
|
|
92
|
+
* Whether `declaredToolNames` is an authorization boundary this proxy enforces, or only the
|
|
93
|
+
* catalog used to normalize provider-invented names back to declared ones.
|
|
94
|
+
*
|
|
95
|
+
* Defaults to enforcing. The chat and Anthropic inbound wires set it false: those specs make
|
|
96
|
+
* the server relay a tool call and leave execution or refusal to the client's own runner, and
|
|
97
|
+
* harnesses on them legitimately defer part of their catalog (#4735).
|
|
98
|
+
*
|
|
99
|
+
* It is a separate flag rather than simply withholding `declaredToolNames`, because the set
|
|
100
|
+
* also drives `normalizeDeclaredToolName` and `declaresCodeModeExec`. Passing `undefined`
|
|
101
|
+
* turns those off too, so a provider that invents `default.lookup` for a declared `lookup`
|
|
102
|
+
* would reach the client under the invented name instead of the normalized one.
|
|
103
|
+
*/
|
|
104
|
+
enforceDeclaredToolNames?: boolean;
|
|
91
105
|
/** Declared parameter schema per tool name; repairs integral-float integer args (#1611). */
|
|
92
106
|
toolParameterSchemas?: ReadonlyMap<string, Record<string, unknown>>;
|
|
93
107
|
/**
|
|
@@ -1008,7 +1022,11 @@ export function bridgeToResponsesSSE(
|
|
|
1008
1022
|
: undefined;
|
|
1009
1023
|
const mapped = toolNsMap?.get(effectiveName);
|
|
1010
1024
|
const realName = mapped?.name ?? effectiveName;
|
|
1011
|
-
if (
|
|
1025
|
+
if (
|
|
1026
|
+
options?.declaredToolNames
|
|
1027
|
+
&& options.enforceDeclaredToolNames !== false
|
|
1028
|
+
&& !options.declaredToolNames.has(effectiveName)
|
|
1029
|
+
) {
|
|
1012
1030
|
const failure = responseError(
|
|
1013
1031
|
502,
|
|
1014
1032
|
"upstream_error",
|
|
@@ -22,8 +22,34 @@ export interface RenderedDesktopModel extends DesktopProfileModel {
|
|
|
22
22
|
supports1m: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// Managed-namespace date aliases run 2026-2035. The original 2026-only
|
|
26
|
+
// (365 slots) design failed with "all 365 encoded date slots are occupied"
|
|
27
|
+
// once a catalog exceeded 365 routes (stale assignments are retained by
|
|
28
|
+
// design, so the set only grows). Years before 2026 stay rejected: dated
|
|
29
|
+
// ids like `claude-opus-4-8-20250201` are real model snapshot ids, not
|
|
30
|
+
// managed aliases, and the inbound decoder relies on that distinction.
|
|
31
|
+
// Every emitted suffix stays 8 digits so modelMap date-stripping keeps
|
|
32
|
+
// working.
|
|
33
|
+
const DATE_ALIAS = /^claude-opus-4-8-(202[6-9]\d{4}|203[0-5]\d{4})$/;
|
|
34
|
+
const LEGACY_YEAR = 2026;
|
|
35
|
+
const LEGACY_DAY_COUNT = 365;
|
|
36
|
+
const ALIAS_FIRST_YEAR = 2026;
|
|
37
|
+
const ALIAS_LAST_YEAR = 2035;
|
|
38
|
+
|
|
39
|
+
function isLeapYear(year: number): boolean {
|
|
40
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function daysInAliasYear(year: number): number {
|
|
44
|
+
return isLeapYear(year) ? 366 : 365;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const TOTAL_ALIAS_SLOTS = (() => {
|
|
48
|
+
let total = 0;
|
|
49
|
+
for (let year = ALIAS_FIRST_YEAR; year <= ALIAS_LAST_YEAR; year += 1) total += daysInAliasYear(year);
|
|
50
|
+
return total;
|
|
51
|
+
})();
|
|
52
|
+
export const ALIAS_YEAR_RANGE = { first: ALIAS_FIRST_YEAR, last: ALIAS_LAST_YEAR } as const;
|
|
27
53
|
|
|
28
54
|
export class DesktopProfileError extends Error {
|
|
29
55
|
constructor(message: string, readonly path = "profile") {
|
|
@@ -119,7 +145,7 @@ export function parseDesktopProfile(value: unknown): DesktopProfile {
|
|
|
119
145
|
if (isRealAnthropicRoute(route)) {
|
|
120
146
|
if (raw.alias !== routeModelId(route)) throw new DesktopProfileError("real Anthropic routes must keep their exact model id", `profile.assignments.${route}.alias`);
|
|
121
147
|
} else if (!validDateAlias(raw.alias)) {
|
|
122
|
-
throw new DesktopProfileError("must be a valid claude-opus-4-8-
|
|
148
|
+
throw new DesktopProfileError("must be a valid claude-opus-4-8-YYYYMMDD alias", `profile.assignments.${route}.alias`);
|
|
123
149
|
}
|
|
124
150
|
if (aliases.has(raw.alias)) throw new DesktopProfileError(`duplicate alias "${raw.alias}"`, `profile.assignments.${route}.alias`);
|
|
125
151
|
aliases.add(raw.alias);
|
|
@@ -144,26 +170,57 @@ export function parseDesktopProfile(value: unknown): DesktopProfile {
|
|
|
144
170
|
return { version: 1, assignments, defaults, ...appliedMarkers(value) };
|
|
145
171
|
}
|
|
146
172
|
|
|
147
|
-
function
|
|
148
|
-
const date = new Date(Date.UTC(
|
|
173
|
+
function formatSlotDate(year: number, dayOfYear: number): string {
|
|
174
|
+
const date = new Date(Date.UTC(year, 0, dayOfYear));
|
|
149
175
|
const y = date.getUTCFullYear();
|
|
150
176
|
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
151
177
|
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
152
178
|
return `claude-opus-4-8-${y}${m}${d}`;
|
|
153
179
|
}
|
|
154
180
|
|
|
181
|
+
// Legacy 2026 ring, byte-identical to the original allocator: the same route
|
|
182
|
+
// must keep resolving to the same 2026 alias it always had, and a probe over
|
|
183
|
+
// a nearly-full 2026 set must land on the same free date as before.
|
|
184
|
+
function legacyDayAlias(dayIndex: number): string {
|
|
185
|
+
return formatSlotDate(LEGACY_YEAR, dayIndex + 1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Overflow ring for catalogs past 365 routes (2027-2035). Probed only after
|
|
189
|
+
// every legacy slot is taken, so existing profiles never shift into it.
|
|
190
|
+
const OVERFLOW_FIRST_YEAR = LEGACY_YEAR + 1;
|
|
191
|
+
const OVERFLOW_SLOT_COUNT = TOTAL_ALIAS_SLOTS - LEGACY_DAY_COUNT;
|
|
192
|
+
|
|
193
|
+
function overflowSlotAlias(slotIndex: number): string {
|
|
194
|
+
let remaining = ((slotIndex % OVERFLOW_SLOT_COUNT) + OVERFLOW_SLOT_COUNT) % OVERFLOW_SLOT_COUNT;
|
|
195
|
+
for (let year = OVERFLOW_FIRST_YEAR; year <= ALIAS_LAST_YEAR; year += 1) {
|
|
196
|
+
const days = daysInAliasYear(year);
|
|
197
|
+
if (remaining < days) return formatSlotDate(year, remaining + 1);
|
|
198
|
+
remaining -= days;
|
|
199
|
+
}
|
|
200
|
+
throw new DesktopProfileError("slot index out of range", "profile.assignments");
|
|
201
|
+
}
|
|
202
|
+
|
|
155
203
|
function routeStartDay(route: string): number {
|
|
156
|
-
return createHash("sha256").update(route).digest().readUInt32BE(0) %
|
|
204
|
+
return createHash("sha256").update(route).digest().readUInt32BE(0) % LEGACY_DAY_COUNT;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function routeOverflowStart(route: string): number {
|
|
208
|
+
return createHash("sha256").update(route).digest().readUInt32BE(4) % OVERFLOW_SLOT_COUNT;
|
|
157
209
|
}
|
|
158
210
|
|
|
159
211
|
function allocateAlias(route: string, used: Set<string>): string {
|
|
160
212
|
if (isRealAnthropicRoute(route)) return routeModelId(route);
|
|
161
213
|
const start = routeStartDay(route);
|
|
162
|
-
for (let offset = 0; offset <
|
|
163
|
-
const alias =
|
|
214
|
+
for (let offset = 0; offset < LEGACY_DAY_COUNT; offset += 1) {
|
|
215
|
+
const alias = legacyDayAlias((start + offset) % LEGACY_DAY_COUNT);
|
|
216
|
+
if (!used.has(alias)) return alias;
|
|
217
|
+
}
|
|
218
|
+
const overflowStart = routeOverflowStart(route);
|
|
219
|
+
for (let offset = 0; offset < OVERFLOW_SLOT_COUNT; offset += 1) {
|
|
220
|
+
const alias = overflowSlotAlias((overflowStart + offset) % OVERFLOW_SLOT_COUNT);
|
|
164
221
|
if (!used.has(alias)) return alias;
|
|
165
222
|
}
|
|
166
|
-
throw new DesktopProfileError(
|
|
223
|
+
throw new DesktopProfileError(`all ${TOTAL_ALIAS_SLOTS} encoded date slots are occupied`, `profile.assignments.${route}.alias`);
|
|
167
224
|
}
|
|
168
225
|
|
|
169
226
|
export function reconcileDesktopProfile(
|
package/src/claude/outbound.ts
CHANGED
|
@@ -212,6 +212,8 @@ interface OpenBlock {
|
|
|
212
212
|
argsBuf?: string;
|
|
213
213
|
argsBufBytes?: number;
|
|
214
214
|
webSearchArgsEmitted?: boolean;
|
|
215
|
+
/** True once ordinary function-call arguments were emitted to Anthropic SSE. */
|
|
216
|
+
toolArgsEmitted?: boolean;
|
|
215
217
|
callId?: string;
|
|
216
218
|
/** Last fixed-size reasoning identity (item + summary/content index) seen by this block. */
|
|
217
219
|
reasoningPartKey?: string;
|
|
@@ -488,6 +490,7 @@ export function responsesSseToAnthropicSse(
|
|
|
488
490
|
argsBuf: "",
|
|
489
491
|
argsBufBytes: 0,
|
|
490
492
|
webSearchArgsEmitted: false,
|
|
493
|
+
toolArgsEmitted: false,
|
|
491
494
|
};
|
|
492
495
|
break;
|
|
493
496
|
}
|
|
@@ -518,6 +521,14 @@ export function responsesSseToAnthropicSse(
|
|
|
518
521
|
type: "content_block_delta", index: open.index,
|
|
519
522
|
delta: { type: "input_json_delta", partial_json: data.delta },
|
|
520
523
|
});
|
|
524
|
+
open.toolArgsEmitted = true;
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
case "response.function_call_arguments.done": {
|
|
528
|
+
if (!open || open.kind !== "tool_use" || open.bufferWebSearchArgs || open.toolArgsEmitted) break;
|
|
529
|
+
if (typeof data.arguments !== "string" || data.arguments.length === 0) break;
|
|
530
|
+
emit("content_block_delta", { type: "content_block_delta", index: open.index, delta: { type: "input_json_delta", partial_json: data.arguments } });
|
|
531
|
+
open.toolArgsEmitted = true;
|
|
521
532
|
break;
|
|
522
533
|
}
|
|
523
534
|
case "response.output_item.done": {
|
|
@@ -565,6 +576,13 @@ export function responsesSseToAnthropicSse(
|
|
|
565
576
|
delta: { type: "input_json_delta", partial_json: JSON.stringify(sanitizeWebSearchInput(parsed)) },
|
|
566
577
|
});
|
|
567
578
|
open.webSearchArgsEmitted = true;
|
|
579
|
+
} else if (!open.bufferWebSearchArgs && !open.toolArgsEmitted
|
|
580
|
+
&& typeof item.arguments === "string" && item.arguments.length > 0) {
|
|
581
|
+
emit("content_block_delta", {
|
|
582
|
+
type: "content_block_delta", index: open.index,
|
|
583
|
+
delta: { type: "input_json_delta", partial_json: item.arguments },
|
|
584
|
+
});
|
|
585
|
+
open.toolArgsEmitted = true;
|
|
568
586
|
}
|
|
569
587
|
closeOpenBlock();
|
|
570
588
|
}
|
package/src/cli/account-main.ts
CHANGED
|
@@ -245,7 +245,7 @@ export async function cmdNativeMainAccount(args: string[], deps: AccountDeps): P
|
|
|
245
245
|
}
|
|
246
246
|
if (noWait) {
|
|
247
247
|
printStatus({ flowId: startFlowId, ...pending });
|
|
248
|
-
console.log("follow up: ocx account main reauth status --flow " + startFlowId);
|
|
248
|
+
if (!wantsJson) console.log("follow up: ocx account main reauth status --flow " + startFlowId);
|
|
249
249
|
return 0;
|
|
250
250
|
}
|
|
251
251
|
// Blocking wait bounded by the service flow expiry (15-minute grant + margin).
|
package/src/cli/capabilities.ts
CHANGED
|
@@ -710,10 +710,10 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
710
710
|
},
|
|
711
711
|
{
|
|
712
712
|
command: ["system", "codex-restart"],
|
|
713
|
-
summary: "Restart the Codex app-
|
|
713
|
+
summary: "Restart the Codex desktop app and app-servers.",
|
|
714
714
|
routes: [{ method: "POST", path: "/api/system/codex-restart" }],
|
|
715
715
|
flags: [
|
|
716
|
-
{ name: "--yes", value: "boolean", summary: "Required:
|
|
716
|
+
{ name: "--yes", value: "boolean", summary: "Required: fully quits and relaunches the operator's Codex desktop app and restarts its app-servers." },
|
|
717
717
|
{ name: "--json", value: "boolean", summary: "Emit the restart result as JSON." },
|
|
718
718
|
],
|
|
719
719
|
mutates: true,
|