@bitkyc08/opencodex 2.25.0 → 2.26.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 +59 -7
- package/gui/dist/assets/index-RL6b1bTV.js +102 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/base.ts +18 -0
- package/src/adapters/client-fingerprint.ts +0 -2
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/live-models.ts +117 -30
- package/src/adapters/cursor/live-transport.ts +137 -56
- package/src/adapters/cursor/native-exec-fs.ts +1 -1
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/protobuf-request.ts +89 -21
- package/src/adapters/cursor/tool-definitions.ts +18 -7
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +7 -0
- package/src/adapters/cursor.ts +2 -0
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google.ts +23 -3
- package/src/adapters/openai-chat.ts +38 -13
- package/src/adapters/openai-responses.ts +129 -9
- package/src/adapters/registry.ts +30 -1
- package/src/bridge.ts +50 -12
- package/src/chat/inbound.ts +1 -0
- package/src/claude/agents-inject.ts +3 -2
- package/src/cli/dispatch.ts +19 -6
- package/src/cli/help.ts +2 -1
- package/src/cli/integrations.ts +35 -0
- package/src/cli/minimax.ts +8 -2
- package/src/cli/registry.ts +13 -2
- package/src/clients/config-export.ts +120 -1
- package/src/codex/account-store.ts +17 -1
- package/src/codex/auth-api.ts +51 -17
- package/src/codex/catalog/effort.ts +8 -5
- package/src/codex/catalog/provider-fetch.ts +39 -27
- package/src/codex/catalog/sync.ts +30 -8
- package/src/codex/inject.ts +39 -13
- package/src/codex/main-account.ts +29 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +25 -0
- package/src/codex/prompt-journal.ts +6 -2
- package/src/codex/quota-rejection.ts +21 -7
- package/src/codex/refresh.ts +4 -2
- package/src/codex/sync.ts +106 -3
- package/src/codex/warmup.ts +187 -81
- package/src/config.ts +85 -46
- package/src/generated/compatibility-version.json +127 -83
- package/src/grok/inject.ts +1 -1
- package/src/images/loop.ts +1 -0
- package/src/integrations/registry.ts +7 -0
- package/src/lab/automation/config-persistence.ts +2 -2
- package/src/lab/automation/persistence.ts +2 -2
- package/src/lab/ledger/purge.ts +2 -2
- package/src/lab/subject/behavior-fingerprint.ts +2 -2
- package/src/lib/config-ownership.ts +5 -2
- package/src/lib/destination-policy.ts +18 -1
- package/src/lib/provider-outbound.ts +7 -0
- package/src/lib/redact.ts +11 -0
- package/src/lib/tool-argument-integers.ts +50 -6
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-retry.ts +2 -1
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/oauth/google-antigravity.ts +2 -2
- package/src/oauth/index.ts +60 -8
- package/src/providers/antigravity-models.ts +58 -4
- package/src/providers/command-code-efforts.ts +36 -9
- package/src/providers/context-cap.ts +9 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/fastwire.ts +453 -0
- package/src/providers/registry.ts +21 -1
- package/src/providers/service-tier.ts +173 -89
- package/src/responses/parser.ts +25 -16
- package/src/responses/reasoning-replay-cache.ts +30 -0
- package/src/responses/thought-signature-replay.ts +74 -4
- package/src/router.ts +6 -0
- package/src/routing/compatibility/behavior.ts +27 -16
- package/src/server/index.ts +10 -1
- package/src/server/management/oauth-account-routes.ts +10 -2
- package/src/server/management/shared.ts +1 -1
- package/src/server/management/system-routes.ts +15 -0
- package/src/server/request-log.ts +57 -3
- package/src/server/responses/agent-task-recovery.ts +6 -1
- package/src/server/responses/core.ts +209 -40
- package/src/server/responses/fetch-helpers.ts +15 -36
- package/src/server/responses/responses-field-backfill.ts +173 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +171 -0
- package/src/service.ts +38 -36
- package/src/storage/cleanup.ts +2 -2
- package/src/tray/windows.ts +3 -2
- package/src/types.ts +101 -14
- package/src/update/job.ts +9 -18
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +34 -5
- package/src/usage/log.ts +74 -4
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/web-search/anthropic-executor.ts +8 -6
- package/gui/dist/assets/index-DxJ7kXj9.js +0 -102
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { IncomingMeta, ProviderAdapter } from "./base";
|
|
3
|
-
import { namespacedToolName, type AdapterEvent, type OcxParsedRequest, type OcxProviderConfig, type OcxUsage } from "../types";
|
|
3
|
+
import { namespacedToolName, type AdapterEvent, type OcxParsedRequest, type OcxProviderConfig, type OcxUsage, type TierDecision } from "../types";
|
|
4
4
|
import { catalogModelSupportsReasoningSummaries } from "../codex/catalog";
|
|
5
5
|
import { COMPACT_PROMPT, decodeCompactionSummary, SUMMARY_PREFIX } from "../responses/compaction";
|
|
6
6
|
import { collectResponsesToolGroups } from "../responses/tool-groups";
|
|
@@ -12,6 +12,9 @@ import { modelRecordValue } from "../reasoning-effort";
|
|
|
12
12
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
13
13
|
import { rewriteRoutedCustomToolsForUpstream } from "../responses/custom-tool-compat";
|
|
14
14
|
import { openaiResponsesUrl } from "./openai-responses-url";
|
|
15
|
+
import {
|
|
16
|
+
createAdapterTierMetadata,
|
|
17
|
+
} from "../providers/fastwire";
|
|
15
18
|
|
|
16
19
|
// Headers relayed verbatim from the caller in OAuth-passthrough ("forward") mode.
|
|
17
20
|
// Exported so the web-search sidecar reuses the exact same forwarded-auth set for its ChatGPT call.
|
|
@@ -572,6 +575,15 @@ function toolOutputText(output: unknown): string {
|
|
|
572
575
|
* expansion misses (proxy restart, unrecorded prior turn), previous_response_id is stripped
|
|
573
576
|
* (the ChatGPT backend rejects it), so the delta may carry items that reference now-absent
|
|
574
577
|
* prior items and 400 upstream:
|
|
578
|
+
* - `function_call`/`local_shell_call`/`custom_tool_call` without their paired output item
|
|
579
|
+
* ("No tool output found for tool call <call_id>"). A stateless upstream cannot resolve
|
|
580
|
+
* the pair from its own storage, so a placeholder output is synthesized to keep the
|
|
581
|
+
* turn continuable without pretending the result was real. Synthetic outputs are
|
|
582
|
+
* emitted after the complete parallel call batch, in call order alongside any real
|
|
583
|
+
* outputs, so the adjacency normalizer can still recognize the batch as one
|
|
584
|
+
* reasoning-bearing assistant turn (#1477). Gated on
|
|
585
|
+
* `synthesizeMissingCallOutputs` (stateless AND non-forward wires); forward replay keeps
|
|
586
|
+
* fail-closed behavior.
|
|
575
587
|
* - `function_call_output`/`custom_tool_call_output` without their paired call item
|
|
576
588
|
* ("No tool call found for function call output with call_id ..."). Converted to user
|
|
577
589
|
* messages so the result text survives. `function_call_output` also pairs with
|
|
@@ -608,26 +620,38 @@ function backfillWebSearchQueries(body: unknown): unknown {
|
|
|
608
620
|
return changed ? { ...body, input } : body;
|
|
609
621
|
}
|
|
610
622
|
|
|
611
|
-
function repairOrphanedInputItems(body: unknown, dropReasoning: boolean): unknown {
|
|
623
|
+
function repairOrphanedInputItems(body: unknown, dropReasoning: boolean, synthesizeMissingCallOutputs = false): unknown {
|
|
612
624
|
if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
|
|
613
625
|
const input = body.input;
|
|
614
626
|
|
|
615
627
|
const functionCallIds = new Set<string>();
|
|
616
628
|
const customCallIds = new Set<string>();
|
|
629
|
+
const functionOutputIds = new Set<string>();
|
|
630
|
+
const customOutputIds = new Set<string>();
|
|
617
631
|
for (const item of input) {
|
|
618
632
|
if (!isPlainObject(item) || typeof item.call_id !== "string") continue;
|
|
619
633
|
if (item.type === "function_call" || item.type === "local_shell_call") functionCallIds.add(item.call_id);
|
|
620
634
|
else if (item.type === "custom_tool_call") customCallIds.add(item.call_id);
|
|
635
|
+
else if (item.type === "function_call_output") functionOutputIds.add(item.call_id);
|
|
636
|
+
else if (item.type === "custom_tool_call_output") customOutputIds.add(item.call_id);
|
|
621
637
|
}
|
|
622
638
|
|
|
623
639
|
let changed = false;
|
|
624
640
|
const repaired: unknown[] = [];
|
|
641
|
+
const syntheticKeys = new Set<string>();
|
|
642
|
+
const pendingSyntheticOutputs: unknown[] = [];
|
|
643
|
+
const flushPendingSyntheticOutputs = (): void => {
|
|
644
|
+
if (pendingSyntheticOutputs.length === 0) return;
|
|
645
|
+
repaired.push(...pendingSyntheticOutputs);
|
|
646
|
+
pendingSyntheticOutputs.length = 0;
|
|
647
|
+
};
|
|
625
648
|
for (const item of input) {
|
|
626
|
-
if (!isPlainObject(item)) { repaired.push(item); continue; }
|
|
649
|
+
if (!isPlainObject(item)) { flushPendingSyntheticOutputs(); repaired.push(item); continue; }
|
|
627
650
|
if (dropReasoning && item.type === "reasoning") { changed = true; continue; }
|
|
628
651
|
const isFnOutput = item.type === "function_call_output";
|
|
629
652
|
const isCustomOutput = item.type === "custom_tool_call_output";
|
|
630
653
|
if (isFnOutput || isCustomOutput) {
|
|
654
|
+
flushPendingSyntheticOutputs();
|
|
631
655
|
const callId = typeof item.call_id === "string" ? item.call_id : "";
|
|
632
656
|
const paired = isFnOutput ? functionCallIds.has(callId) : customCallIds.has(callId);
|
|
633
657
|
if (!paired) {
|
|
@@ -640,10 +664,83 @@ function repairOrphanedInputItems(body: unknown, dropReasoning: boolean): unknow
|
|
|
640
664
|
continue;
|
|
641
665
|
}
|
|
642
666
|
}
|
|
667
|
+
const isFnCall = item.type === "function_call" || item.type === "local_shell_call";
|
|
668
|
+
const isCustomCall = item.type === "custom_tool_call";
|
|
669
|
+
if (isFnCall || isCustomCall) {
|
|
670
|
+
repaired.push(item);
|
|
671
|
+
if (synthesizeMissingCallOutputs) {
|
|
672
|
+
const callId = typeof item.call_id === "string" ? item.call_id : "";
|
|
673
|
+
const hasOutput = isFnCall ? functionOutputIds.has(callId) : customOutputIds.has(callId);
|
|
674
|
+
if (!hasOutput && callId) {
|
|
675
|
+
changed = true;
|
|
676
|
+
const name = typeof item.name === "string" && item.name.length > 0 ? item.name : callId;
|
|
677
|
+
const text = `[ocx] no tool result was recorded for "${name}"; execution status unknown — do not treat this as success, failure, or user-provided input.`;
|
|
678
|
+
syntheticKeys.add(`${isFnCall ? "function" : "custom"}:${callId}`);
|
|
679
|
+
pendingSyntheticOutputs.push(isFnCall
|
|
680
|
+
? { type: "function_call_output", call_id: callId, output: text }
|
|
681
|
+
: { type: "custom_tool_call_output", call_id: callId, output: text });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
flushPendingSyntheticOutputs();
|
|
643
687
|
repaired.push(item);
|
|
644
688
|
}
|
|
689
|
+
flushPendingSyntheticOutputs();
|
|
690
|
+
|
|
691
|
+
const callKeyOf = (item: unknown): string | null => {
|
|
692
|
+
if (!isPlainObject(item) || typeof item.call_id !== "string") return null;
|
|
693
|
+
if (item.type === "function_call" || item.type === "local_shell_call") return `function:${item.call_id}`;
|
|
694
|
+
if (item.type === "custom_tool_call") return `custom:${item.call_id}`;
|
|
695
|
+
return null;
|
|
696
|
+
};
|
|
697
|
+
const outputKeyOf = (item: unknown): string | null => {
|
|
698
|
+
if (!isPlainObject(item) || typeof item.call_id !== "string") return null;
|
|
699
|
+
if (item.type === "function_call_output") return `function:${item.call_id}`;
|
|
700
|
+
if (item.type === "custom_tool_call_output") return `custom:${item.call_id}`;
|
|
701
|
+
return null;
|
|
702
|
+
};
|
|
703
|
+
const reorderBatchOutputs = (items: unknown[]): unknown[] => {
|
|
704
|
+
const ordered: unknown[] = [];
|
|
705
|
+
let index = 0;
|
|
706
|
+
while (index < items.length) {
|
|
707
|
+
const key = callKeyOf(items[index]);
|
|
708
|
+
if (key === null) { ordered.push(items[index]); index += 1; continue; }
|
|
709
|
+
const batch: unknown[] = [];
|
|
710
|
+
const batchKeys: string[] = [];
|
|
711
|
+
let cursor = index;
|
|
712
|
+
while (cursor < items.length) {
|
|
713
|
+
const nextKey = callKeyOf(items[cursor]);
|
|
714
|
+
if (nextKey === null) break;
|
|
715
|
+
batch.push(items[cursor]);
|
|
716
|
+
batchKeys.push(nextKey);
|
|
717
|
+
cursor += 1;
|
|
718
|
+
}
|
|
719
|
+
const hasSynthetic = batchKeys.some(batchKey => syntheticKeys.has(batchKey));
|
|
720
|
+
if (!hasSynthetic) {
|
|
721
|
+
ordered.push(...batch);
|
|
722
|
+
index = cursor;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const remainder: unknown[] = [];
|
|
726
|
+
const batchOutputs: Array<{ key: string; item: unknown }> = [];
|
|
727
|
+
for (let probe = cursor; probe < items.length; probe += 1) {
|
|
728
|
+
const outputKey = outputKeyOf(items[probe]);
|
|
729
|
+
if (outputKey !== null && batchKeys.includes(outputKey)) {
|
|
730
|
+
batchOutputs.push({ key: outputKey, item: items[probe] });
|
|
731
|
+
} else {
|
|
732
|
+
remainder.push(items[probe]);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
batchOutputs.sort((left, right) => batchKeys.indexOf(left.key) - batchKeys.indexOf(right.key));
|
|
736
|
+
ordered.push(...batch, ...batchOutputs.map(output => output.item));
|
|
737
|
+
ordered.push(...reorderBatchOutputs(remainder));
|
|
738
|
+
return ordered;
|
|
739
|
+
}
|
|
740
|
+
return ordered;
|
|
741
|
+
};
|
|
645
742
|
|
|
646
|
-
return changed ? { ...body, input: repaired } : body;
|
|
743
|
+
return changed ? { ...body, input: reorderBatchOutputs(repaired) } : body;
|
|
647
744
|
}
|
|
648
745
|
|
|
649
746
|
/**
|
|
@@ -764,6 +861,15 @@ function stripPreviousResponseId(body: unknown, strip: boolean): unknown {
|
|
|
764
861
|
return rest;
|
|
765
862
|
}
|
|
766
863
|
|
|
864
|
+
/** Apply the settled tier only to a fresh outbound object; `_rawBody` remains caller-owned. */
|
|
865
|
+
function applyTierDecisionToResponsesBody(body: unknown, decision: TierDecision | undefined): unknown {
|
|
866
|
+
if (!decision || decision.kind === "forward-caller" || !isPlainObject(body)) return body;
|
|
867
|
+
const next: Record<string, unknown> = { ...body };
|
|
868
|
+
if (decision.kind === "set") next.service_tier = decision.value;
|
|
869
|
+
else delete next.service_tier;
|
|
870
|
+
return next;
|
|
871
|
+
}
|
|
872
|
+
|
|
767
873
|
/**
|
|
768
874
|
* Drop request parameters a stateless Responses upstream cannot implement, and pin
|
|
769
875
|
* `store` false.
|
|
@@ -778,8 +884,8 @@ function stripPreviousResponseId(body: unknown, strip: boolean): unknown {
|
|
|
778
884
|
* `prompt` is a reference to a server-stored prompt template — the most stateful
|
|
779
885
|
* field in the accepted schema.
|
|
780
886
|
*
|
|
781
|
-
* `service_tier` is deliberately NOT dropped: the
|
|
782
|
-
*
|
|
887
|
+
* `service_tier` is deliberately NOT dropped: the final TierDecision is applied to a
|
|
888
|
+
* detached outbound body before this sanitizer chain, and silently deleting a configured knob is
|
|
783
889
|
* worse than forwarding a parameter the upstream ignores.
|
|
784
890
|
*
|
|
785
891
|
* MUST run before the composed sanitize chain below: `stripItemIdsWhenUnstored` keys
|
|
@@ -1367,6 +1473,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1367
1473
|
parsed._rawBody,
|
|
1368
1474
|
forward || parsed._previousResponseInputExpanded === true,
|
|
1369
1475
|
);
|
|
1476
|
+
// stripPreviousResponseId() intentionally returns its input on a no-op. Detach before the
|
|
1477
|
+
// tier write so a force-fast/default decision can never mutate parsed._rawBody.
|
|
1478
|
+
outBody = applyTierDecisionToResponsesBody(outBody, parsed.options?.tierDecision);
|
|
1370
1479
|
const stateless = provider.statelessResponses === true;
|
|
1371
1480
|
if (stateless) outBody = stripStatefulResponsesParams(outBody);
|
|
1372
1481
|
// A replay miss can leave a function_call_output whose paired function_call sat
|
|
@@ -1375,7 +1484,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1375
1484
|
// backend gets — dropping previous_response_id is not much use if the body that
|
|
1376
1485
|
// reaches the wire is unparseable.
|
|
1377
1486
|
if (forward || stateless) {
|
|
1378
|
-
outBody = repairOrphanedInputItems(outBody, unexpandedMiss);
|
|
1487
|
+
outBody = repairOrphanedInputItems(outBody, unexpandedMiss, stateless && !forward);
|
|
1379
1488
|
}
|
|
1380
1489
|
if (provider.requiresAdjacentResponsesToolResults === true) {
|
|
1381
1490
|
outBody = normalizeResponsesToolResultAdjacency(outBody);
|
|
@@ -1414,11 +1523,21 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1414
1523
|
convertedRoutedCustomToolNames = rewritten.names;
|
|
1415
1524
|
}
|
|
1416
1525
|
const sanitizedBody = normalizeToolSchemas(stripSparkCompatibility(stripUnsupportedReasoningParams(stripItemIdsWhenUnstored(stripInvalidItemIds(stripUnsupportedHostedTools(sanitizeReasoningInputContent(scrubOcxCompactionItems(outBody), { preserveRawReasoningContent: provider.preserveResponsesReasoningContent === true })))))));
|
|
1417
|
-
const
|
|
1526
|
+
const finalBody = stripDisabledReasoningSummaries(
|
|
1418
1527
|
normalizeConfiguredReasoningSummaryDelivery(sanitizedBody, provider, parsed.modelId),
|
|
1419
1528
|
provider,
|
|
1420
1529
|
parsed.modelId,
|
|
1421
|
-
)
|
|
1530
|
+
);
|
|
1531
|
+
const actualServiceTier = isPlainObject(finalBody) && typeof finalBody.service_tier === "string"
|
|
1532
|
+
? finalBody.service_tier
|
|
1533
|
+
: null;
|
|
1534
|
+
const tierLog = createAdapterTierMetadata(
|
|
1535
|
+
parsed.options?.tierObservation,
|
|
1536
|
+
parsed.options?.tierDecision,
|
|
1537
|
+
actualServiceTier === null ? null : "service-tier",
|
|
1538
|
+
actualServiceTier,
|
|
1539
|
+
);
|
|
1540
|
+
const body = JSON.stringify(finalBody);
|
|
1422
1541
|
const releaseBodyObservation = translatorBudget.observeExternallyCapped(
|
|
1423
1542
|
"passthrough_serialization",
|
|
1424
1543
|
new TextEncoder().encode(body).byteLength,
|
|
@@ -1430,6 +1549,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1430
1549
|
body,
|
|
1431
1550
|
releaseBodyObservation,
|
|
1432
1551
|
...(convertedRoutedCustomToolNames ? { convertedRoutedCustomToolNames } : {}),
|
|
1552
|
+
...(tierLog ? { tierLog } : {}),
|
|
1433
1553
|
};
|
|
1434
1554
|
},
|
|
1435
1555
|
|
package/src/adapters/registry.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { createMimoFreeAdapter } from "./mimo-free";
|
|
|
10
10
|
import { createOpenAIChatAdapter } from "./openai-chat";
|
|
11
11
|
import { createResponsesPassthroughAdapter } from "./openai-responses";
|
|
12
12
|
import type { OcxProviderConfig } from "../types";
|
|
13
|
+
import { createAdapterTierMetadata } from "../providers/fastwire";
|
|
13
14
|
|
|
14
15
|
export type AdapterCacheRetention = "none" | "short" | "long";
|
|
15
16
|
|
|
@@ -142,5 +143,33 @@ export function createRegisteredAdapter(
|
|
|
142
143
|
): ProviderAdapter {
|
|
143
144
|
const definition = getAdapterDefinition(provider.adapter);
|
|
144
145
|
if (!definition) throw new Error(`Unknown adapter: ${provider.adapter}`);
|
|
145
|
-
|
|
146
|
+
const adapter = definition.create(provider, context);
|
|
147
|
+
const buildRequest = adapter.buildRequest.bind(adapter);
|
|
148
|
+
adapter.buildRequest = (parsed, incoming) => {
|
|
149
|
+
const attachTierMetadata = (request: Awaited<ReturnType<ProviderAdapter["buildRequest"]>>) => {
|
|
150
|
+
// OpenAI-family adapters report the exact emitted field themselves. Other adapters
|
|
151
|
+
// still report an exact absence at this serialization boundary, which makes a routed
|
|
152
|
+
// Fast downgrade observable without asking core to infer an outbound body shape.
|
|
153
|
+
request.tierLog ??= createAdapterTierMetadata(
|
|
154
|
+
parsed.options.tierObservation,
|
|
155
|
+
parsed.options.tierDecision,
|
|
156
|
+
null,
|
|
157
|
+
null,
|
|
158
|
+
);
|
|
159
|
+
return request;
|
|
160
|
+
};
|
|
161
|
+
const request = buildRequest(parsed, incoming);
|
|
162
|
+
return request instanceof Promise
|
|
163
|
+
? request.then(attachTierMetadata)
|
|
164
|
+
: attachTierMetadata(request);
|
|
165
|
+
};
|
|
166
|
+
if (adapter.runTurn && !adapter.tierLogForRunTurn) {
|
|
167
|
+
adapter.tierLogForRunTurn = parsed => createAdapterTierMetadata(
|
|
168
|
+
parsed.options.tierObservation,
|
|
169
|
+
parsed.options.tierDecision,
|
|
170
|
+
null,
|
|
171
|
+
null,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return adapter;
|
|
146
175
|
}
|
package/src/bridge.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { rememberReasoningForCall } from "./responses/reasoning-replay-cache";
|
|
|
15
15
|
import {
|
|
16
16
|
rememberAndSerializeExtraContent,
|
|
17
17
|
rememberExtraContentForReplay,
|
|
18
|
+
awaitThoughtSignatureDurability,
|
|
18
19
|
} from "./responses/thought-signature-replay";
|
|
19
20
|
import { resolveStallTimeoutSec } from "./stall-timeout";
|
|
20
21
|
import { usageDisplayTotalTokens } from "./usage/totals";
|
|
@@ -198,6 +199,16 @@ export function bridgeToResponsesSSE(
|
|
|
198
199
|
declaredToolNames?: ReadonlySet<string>;
|
|
199
200
|
/** Declared parameter schema per tool name; repairs integral-float integer args (#1611). */
|
|
200
201
|
toolParameterSchemas?: ReadonlyMap<string, Record<string, unknown>>;
|
|
202
|
+
/**
|
|
203
|
+
* Wire keep-alive shape. Codex-rs parses at the EVENT level (timeout(idle_timeout,
|
|
204
|
+
* stream.next()) over an eventsource_stream), so an SSE comment line dispatches no event
|
|
205
|
+
* and does NOT re-arm its idle timer — the keep-alive must be a typed frame the parser
|
|
206
|
+
* ignores via its catch-all (110 RCA, 30_patch-direction.md). grok-build's strict
|
|
207
|
+
* async-openai fork is the opposite: it dies on the unknown `response.heartbeat`
|
|
208
|
+
* variant but, being eventsource-based at the byte level, its idle handling tolerates
|
|
209
|
+
* comment lines. Default stays the typed frame; the grok surface opts into comments.
|
|
210
|
+
*/
|
|
211
|
+
heartbeatStyle?: "typed" | "comment";
|
|
201
212
|
translatorBudget?: TranslatorBudget;
|
|
202
213
|
/**
|
|
203
214
|
* Conversation identity for the reasoning replay cache (issue #950).
|
|
@@ -326,10 +337,13 @@ export function bridgeToResponsesSSE(
|
|
|
326
337
|
clearOwnedWatchdog();
|
|
327
338
|
};
|
|
328
339
|
// RC3 keep-alive: Codex's idle timer is timeout(idle_timeout, stream.next()) over an
|
|
329
|
-
// eventsource_stream
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
340
|
+
// eventsource_stream, which parses at the EVENT level — a comment-only frame dispatches no
|
|
341
|
+
// event, so it does NOT re-arm the timer (110 RCA). The default keep-alive is therefore a
|
|
342
|
+
// typed `response.heartbeat` frame the codex-rs parser ignores via `_ => Ok(None)`. The
|
|
343
|
+
// grok surface (strict async-openai decoder that dies on unknown variants) opts into SSE
|
|
344
|
+
// comment lines instead via options.heartbeatStyle. Emit whenever the *wire* has been
|
|
345
|
+
// silent, even if invisible adapter heartbeats are still flowing (web-search buffering +
|
|
346
|
+
// raw-byte progress). Upstream activity only resets the stall watchdog.
|
|
333
347
|
let upstreamActivity = false;
|
|
334
348
|
let wireActivity = false;
|
|
335
349
|
let beat: unknown;
|
|
@@ -396,7 +410,9 @@ export function bridgeToResponsesSSE(
|
|
|
396
410
|
...(endTurn !== undefined ? { end_turn: endTurn } : {}),
|
|
397
411
|
});
|
|
398
412
|
|
|
399
|
-
const heartbeatFrame =
|
|
413
|
+
const heartbeatFrame = options?.heartbeatStyle === "comment"
|
|
414
|
+
? encoder.encode(': opencodex heartbeat\n\n')
|
|
415
|
+
: encoder.encode('event: response.heartbeat\ndata: {"type":"response.heartbeat"}\n\n');
|
|
400
416
|
let stallTicks = 0;
|
|
401
417
|
const stallSec = resolveStallTimeoutSec(options?.stallTimeoutSec);
|
|
402
418
|
const maxStallTicks = Math.ceil((stallSec * 1000) / heartbeatMs);
|
|
@@ -576,9 +592,16 @@ export function bridgeToResponsesSSE(
|
|
|
576
592
|
const closeCurrentRawReasoning = () => {
|
|
577
593
|
if (!currentRawReasoning) return;
|
|
578
594
|
rawReasoningForNextToolCall = currentRawReasoning.text;
|
|
595
|
+
emit("response.reasoning_summary_text.done", {
|
|
596
|
+
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex, summary_index: 0, text: currentRawReasoning.text,
|
|
597
|
+
});
|
|
598
|
+
emit("response.reasoning_summary_part.done", {
|
|
599
|
+
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex, summary_index: 0,
|
|
600
|
+
part: { type: "summary_text", text: currentRawReasoning.text },
|
|
601
|
+
});
|
|
579
602
|
const item = {
|
|
580
|
-
type: "reasoning", id: currentRawReasoning.itemId,
|
|
581
|
-
|
|
603
|
+
type: "reasoning", id: currentRawReasoning.itemId,
|
|
604
|
+
summary: [{ type: "summary_text", text: currentRawReasoning.text }],
|
|
582
605
|
};
|
|
583
606
|
emit("response.output_item.done", { output_index: currentRawReasoning.outputIndex, item });
|
|
584
607
|
retainFinishedItem(item as OutputItem, currentRawReasoning.textBytes, "reasoning");
|
|
@@ -977,8 +1000,12 @@ export function bridgeToResponsesSSE(
|
|
|
977
1000
|
if (currentToolCall) closeCurrentToolCall();
|
|
978
1001
|
if (!currentRawReasoning) {
|
|
979
1002
|
const itemId = `rs_${uuid()}`;
|
|
980
|
-
const item = { type: "reasoning", id: itemId, summary: [] as
|
|
1003
|
+
const item = { type: "reasoning", id: itemId, summary: [] as { type: string; text: string }[] };
|
|
981
1004
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1005
|
+
emit("response.reasoning_summary_part.added", {
|
|
1006
|
+
item_id: itemId, output_index: outputIndex, summary_index: 0,
|
|
1007
|
+
part: { type: "summary_text", text: "" },
|
|
1008
|
+
});
|
|
982
1009
|
currentRawReasoning = { itemId, outputIndex, text: "", textBytes: 0 };
|
|
983
1010
|
}
|
|
984
1011
|
({ value: currentRawReasoning.text, bytes: currentRawReasoning.textBytes } = appendString(
|
|
@@ -987,9 +1014,9 @@ export function bridgeToResponsesSSE(
|
|
|
987
1014
|
event.text,
|
|
988
1015
|
"reasoning",
|
|
989
1016
|
));
|
|
990
|
-
emit("response.
|
|
1017
|
+
emit("response.reasoning_summary_text.delta", {
|
|
991
1018
|
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex,
|
|
992
|
-
|
|
1019
|
+
summary_index: 0, delta: event.text,
|
|
993
1020
|
});
|
|
994
1021
|
break;
|
|
995
1022
|
}
|
|
@@ -1176,6 +1203,9 @@ export function bridgeToResponsesSSE(
|
|
|
1176
1203
|
if (truncationReasonFor(event.stopReason)) {
|
|
1177
1204
|
// Upstream stopped before a normal completion. Surface as incomplete so the
|
|
1178
1205
|
// client can distinguish a truncated/filtered turn from a finished one.
|
|
1206
|
+
// #1926 gap 2: bound the window in which a handed-out thought signature is
|
|
1207
|
+
// not yet durable before the turn becomes externally terminal.
|
|
1208
|
+
await awaitThoughtSignatureDurability();
|
|
1179
1209
|
const response = {
|
|
1180
1210
|
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
1181
1211
|
usage: responsesUsage(event.usage),
|
|
@@ -1190,6 +1220,7 @@ export function bridgeToResponsesSSE(
|
|
|
1190
1220
|
emit("response.incomplete", { response });
|
|
1191
1221
|
reportTerminal("incomplete");
|
|
1192
1222
|
} else {
|
|
1223
|
+
await awaitThoughtSignatureDurability();
|
|
1193
1224
|
const response = { ...responseSnapshot("completed", finishedItems, event.endTurn), usage: responsesUsage(event.usage) };
|
|
1194
1225
|
options?.onCompletedResponse?.(response, event.providerState);
|
|
1195
1226
|
options?.onUsage?.(event.usage);
|
|
@@ -1210,6 +1241,7 @@ export function bridgeToResponsesSSE(
|
|
|
1210
1241
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1211
1242
|
flushHiddenReasoningEnvelope();
|
|
1212
1243
|
options?.onUsage?.(event.usage);
|
|
1244
|
+
await awaitThoughtSignatureDurability();
|
|
1213
1245
|
emit("response.incomplete", {
|
|
1214
1246
|
response: {
|
|
1215
1247
|
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
@@ -1238,6 +1270,7 @@ export function bridgeToResponsesSSE(
|
|
|
1238
1270
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1239
1271
|
const failure = adapterFailureFromEvent(event);
|
|
1240
1272
|
if (event.usage) options?.onUsage?.(event.usage);
|
|
1273
|
+
await awaitThoughtSignatureDurability();
|
|
1241
1274
|
emit("response.failed", {
|
|
1242
1275
|
response: {
|
|
1243
1276
|
...responseSnapshot("failed", finishedItems),
|
|
@@ -1299,6 +1332,7 @@ export function bridgeToResponsesSSE(
|
|
|
1299
1332
|
if (currentToolCall) failCurrentToolCall();
|
|
1300
1333
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1301
1334
|
options?.onUsage?.(undefined);
|
|
1335
|
+
await awaitThoughtSignatureDurability();
|
|
1302
1336
|
emit("response.incomplete", {
|
|
1303
1337
|
response: {
|
|
1304
1338
|
...responseSnapshot("incomplete", finishedItems),
|
|
@@ -1339,6 +1373,10 @@ export function bridgeToResponsesSSE(
|
|
|
1339
1373
|
flushHiddenRawReasoning();
|
|
1340
1374
|
if (currentToolCall) failCurrentToolCall();
|
|
1341
1375
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1376
|
+
// #1926 gap 2 residual: this beat callback is synchronous, so the durability
|
|
1377
|
+
// barrier is not awaited on the stall-timeout kill path. The in-memory store is
|
|
1378
|
+
// already updated; only a crash between here and the queued write loses it,
|
|
1379
|
+
// which is the pre-#1926 status quo for an already-abnormal termination.
|
|
1342
1380
|
emit("response.incomplete", {
|
|
1343
1381
|
response: {
|
|
1344
1382
|
...responseSnapshot("incomplete", finishedItems),
|
|
@@ -1582,8 +1620,8 @@ function buildResponseJSONWithBudget(
|
|
|
1582
1620
|
return;
|
|
1583
1621
|
}
|
|
1584
1622
|
pushOutput({
|
|
1585
|
-
type: "reasoning", id: `rs_${uuid()}`,
|
|
1586
|
-
|
|
1623
|
+
type: "reasoning", id: `rs_${uuid()}`,
|
|
1624
|
+
summary: [{ type: "summary_text", text: currentRawReasoning }],
|
|
1587
1625
|
}, currentRawReasoningBytes, "reasoning");
|
|
1588
1626
|
currentRawReasoning = "";
|
|
1589
1627
|
currentRawReasoningBytes = 0;
|
package/src/chat/inbound.ts
CHANGED
|
@@ -299,6 +299,7 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
|
|
|
299
299
|
if (raw.stop !== undefined) body.stop = raw.stop;
|
|
300
300
|
if (typeof raw.user === "string") body.user = raw.user;
|
|
301
301
|
if (typeof raw.parallel_tool_calls === "boolean") body.parallel_tool_calls = raw.parallel_tool_calls;
|
|
302
|
+
if (typeof raw.service_tier === "string") body.service_tier = raw.service_tier;
|
|
302
303
|
if (typeof raw.prompt_cache_key === "string") body.prompt_cache_key = raw.prompt_cache_key;
|
|
303
304
|
if (raw.metadata !== undefined) body.metadata = raw.metadata;
|
|
304
305
|
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* Ownership contract: this module only creates/overwrites/deletes files matching
|
|
12
12
|
* `ocx-*.md` inside the agents dir. User-authored agents are never touched.
|
|
13
13
|
*/
|
|
14
|
-
import { lstatSync, mkdirSync, readdirSync, readFileSync,
|
|
14
|
+
import { lstatSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import type { OcxConfig } from "../types";
|
|
17
|
+
import { renameAtomicFile } from "../lib/windows-atomic-replace";
|
|
17
18
|
import { claudeCodeAlias, claudeCodeNativeAlias } from "./alias";
|
|
18
19
|
import { AUTO_CONTEXT_OFF, shouldMarkOneMillion, stripOneMillionMarker, withOneMillionMarker } from "./context-windows";
|
|
19
20
|
import { claudeConfigDir } from "./gateway-cache";
|
|
@@ -235,7 +236,7 @@ export function syncClaudeAgentDefs(defs: readonly ClaudeAgentDef[], configDir =
|
|
|
235
236
|
} catch { /* does not exist: ours to create */ }
|
|
236
237
|
const tmp = `${target}.tmp-${process.pid}`;
|
|
237
238
|
writeFileSync(tmp, renderAgentDef(def), { encoding: "utf8", mode: 0o644 });
|
|
238
|
-
|
|
239
|
+
renameAtomicFile(tmp, target, undefined, "claude-agents");
|
|
239
240
|
written.push(def.file);
|
|
240
241
|
}
|
|
241
242
|
return written;
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -204,10 +204,20 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
204
204
|
},
|
|
205
205
|
sync: async deps => {
|
|
206
206
|
const restartCodex = deps.args.slice(1).includes("--restart-codex");
|
|
207
|
-
const synced = await syncModelsToCodex(
|
|
207
|
+
const synced = await syncModelsToCodex(
|
|
208
|
+
(await deps.findLiveProxy())?.port,
|
|
209
|
+
undefined,
|
|
210
|
+
undefined,
|
|
211
|
+
undefined,
|
|
212
|
+
{ catalogEvenWhenNotInjected: true },
|
|
213
|
+
);
|
|
208
214
|
let code = 0;
|
|
209
215
|
if (synced.status === "skipped") {
|
|
210
216
|
console.log("Codex integration is OFF; sync skipped and no Codex files changed.");
|
|
217
|
+
} else if (synced.status === "catalog-only") {
|
|
218
|
+
// Explicit sync with the integration OFF still refreshes the catalog/cache
|
|
219
|
+
// for side profiles that consume the proxy without injection.
|
|
220
|
+
console.log(synced.message ?? "Codex integration is OFF; catalog refreshed, Codex config untouched.");
|
|
211
221
|
} else if (!synced.ok) {
|
|
212
222
|
code = 1;
|
|
213
223
|
console.error("Codex sync did not complete. Fix the reported Codex config issue and retry.");
|
|
@@ -227,19 +237,18 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
227
237
|
},
|
|
228
238
|
"sync-cache": async deps => {
|
|
229
239
|
const restartCodex = deps.args.slice(1).includes("--restart-codex");
|
|
230
|
-
if (!shouldSyncCodexOnStart(deps.loadConfig())) {
|
|
231
|
-
console.log("Codex integration is OFF; cache sync skipped and no Codex files changed.");
|
|
232
|
-
return 0;
|
|
233
|
-
}
|
|
234
240
|
const { withCatalogWriteSerialization } = await import("../codex/catalog-write-serialization");
|
|
235
241
|
const { invalidateCodexModelsCacheWithPermit } = await import("../codex/catalog/sync");
|
|
236
242
|
const { getCodexHome } = await import("../codex/paths");
|
|
237
243
|
const owningCodexHome = getCodexHome();
|
|
244
|
+
const desiredDisabled = !shouldSyncCodexOnStart(deps.loadConfig());
|
|
238
245
|
const invalidated = withCatalogWriteSerialization(owningCodexHome, permit =>
|
|
239
|
-
invalidateCodexModelsCacheWithPermit(permit, owningCodexHome));
|
|
246
|
+
invalidateCodexModelsCacheWithPermit(permit, owningCodexHome, { allowWhenDesiredDisabled: true }));
|
|
240
247
|
// Only warn/restart when models_cache was actually rewritten from a readable catalog.
|
|
241
248
|
if (invalidated.kind === "completed" && invalidated.value) {
|
|
242
249
|
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
250
|
+
} else if (desiredDisabled) {
|
|
251
|
+
console.log("Codex integration is OFF; cache sync skipped (no catalog or cache write).");
|
|
243
252
|
}
|
|
244
253
|
return 0;
|
|
245
254
|
},
|
|
@@ -502,6 +511,10 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
502
511
|
const { cmdMmx } = await import("./minimax");
|
|
503
512
|
return await cmdMmx(deps.args.slice(1));
|
|
504
513
|
},
|
|
514
|
+
zcode: async deps => {
|
|
515
|
+
const { handleZcodeCommand } = await import("./integrations");
|
|
516
|
+
return await handleZcodeCommand(deps.args.slice(1));
|
|
517
|
+
},
|
|
505
518
|
help: async () => {
|
|
506
519
|
printUsage();
|
|
507
520
|
return 0;
|
package/src/cli/help.ts
CHANGED
|
@@ -58,7 +58,7 @@ Usage:
|
|
|
58
58
|
ocx memory [--json] Alias of ocx observe memory
|
|
59
59
|
ocx api-key <sub> Alias of ocx access key
|
|
60
60
|
ocx access <sub> External API keys and endpoint information
|
|
61
|
-
ocx export --client <id> Print a client config wired to the running proxy (
|
|
61
|
+
ocx export --client <id> Print a client config wired to the running proxy (10 clients)
|
|
62
62
|
ocx integration client <sub> Enable, disable, inspect or roll back a client integration
|
|
63
63
|
ocx grok <sub> Grok Build model selection and apply
|
|
64
64
|
ocx system <sub> Runtime settings, startup, sync, and updates
|
|
@@ -69,6 +69,7 @@ Usage:
|
|
|
69
69
|
ocx opencode [args...] Launch opencode wired to the proxy (runtime provider config)
|
|
70
70
|
ocx mcode [args...] Launch MiniMax Code through its managed provider
|
|
71
71
|
ocx mmx text <sub> [args] Launch MiniMax CLI text through the proxy
|
|
72
|
+
ocx zcode [sub] Connect ZCode to the proxy (managed provider)
|
|
72
73
|
ocx help [command] Show help
|
|
73
74
|
ocx --version | -v Print version
|
|
74
75
|
|
package/src/cli/integrations.ts
CHANGED
|
@@ -223,3 +223,38 @@ export async function handleClientIntegrationCommand(
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
export const INTEGRATION_USAGE = { claude: CLAUDE_USAGE, grok: GROK_USAGE, client: CLIENT_USAGE };
|
|
226
|
+
|
|
227
|
+
const ZCODE_USAGE = `Usage:
|
|
228
|
+
ocx zcode [status] [--json]
|
|
229
|
+
ocx zcode <enable|disable> [--json]
|
|
230
|
+
ocx zcode history [--json]
|
|
231
|
+
ocx zcode restore --op <opId> [--confirm-drift] [--json]`;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Thin alias over the client-integration surface for ZCode (Z.ai's desktop
|
|
235
|
+
* client). ZCode is a GUI app with no launch surface to wrap, so unlike
|
|
236
|
+
* `ocx mcode` there is no exec step: connecting the managed provider block is
|
|
237
|
+
* the whole integration, and every safety property (ownership, snapshots,
|
|
238
|
+
* journal, drift refusal) stays behind the shared management API. ZCode reads
|
|
239
|
+
* its config at startup, so enable/disable print a restart reminder.
|
|
240
|
+
*/
|
|
241
|
+
export async function handleZcodeCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
242
|
+
const args = [...argv];
|
|
243
|
+
// Find the first non-flag token so `ocx zcode --json enable` still enables.
|
|
244
|
+
const verbIndex = args.findIndex(arg => !arg.startsWith("-"));
|
|
245
|
+
const action = (verbIndex === -1 ? "status" : args[verbIndex]).toLowerCase();
|
|
246
|
+
const known = ["status", "show", "list", "enable", "disable", "history", "journal", "restore"];
|
|
247
|
+
if (!known.includes(action)) {
|
|
248
|
+
console.error(`unknown zcode command ${action}`);
|
|
249
|
+
console.error(ZCODE_USAGE);
|
|
250
|
+
return 2;
|
|
251
|
+
}
|
|
252
|
+
const rest = verbIndex === -1 ? args : [...args.slice(0, verbIndex), ...args.slice(verbIndex + 1)];
|
|
253
|
+
// `restore` addresses an operation id, not a client, so nothing is injected.
|
|
254
|
+
const forwarded = action === "restore" ? [action, ...rest] : [action, ...rest, "--client", "zcode"];
|
|
255
|
+
const code = await handleClientIntegrationCommand(forwarded, deps);
|
|
256
|
+
if (code === 0 && (action === "enable" || action === "disable")) {
|
|
257
|
+
console.error("Restart ZCode to pick up the provider change.");
|
|
258
|
+
}
|
|
259
|
+
return code;
|
|
260
|
+
}
|
package/src/cli/minimax.ts
CHANGED
|
@@ -190,12 +190,18 @@ export function startMmxTextBridge(
|
|
|
190
190
|
? null
|
|
191
191
|
: clearableDeadline(options.headerTimeoutMs, req.signal);
|
|
192
192
|
try {
|
|
193
|
-
|
|
193
|
+
const upstreamRequest = new Request(target, {
|
|
194
194
|
method: "POST",
|
|
195
195
|
headers,
|
|
196
196
|
body: req.body,
|
|
197
197
|
signal: headerDeadline?.signal ?? req.signal,
|
|
198
|
-
})
|
|
198
|
+
});
|
|
199
|
+
return await fetch(upstreamRequest, {
|
|
200
|
+
// Override HTTP(S)_PROXY with the loopback listener itself. Bun sends
|
|
201
|
+
// the HTTP proxy-form request directly to this exact origin, so the
|
|
202
|
+
// hop cannot leave the machine even when the parent has proxy vars.
|
|
203
|
+
proxy: { url: upstreamOrigin },
|
|
204
|
+
});
|
|
199
205
|
} catch {
|
|
200
206
|
return Response.json({
|
|
201
207
|
type: "error",
|
package/src/cli/registry.ts
CHANGED
|
@@ -216,8 +216,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
216
216
|
{ name: "api-key", usage: "ocx api-key <list|create|remove> ...", summary: "Alias of ocx access key." },
|
|
217
217
|
{
|
|
218
218
|
name: "export",
|
|
219
|
-
usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode> [--json] [--out <path>] [--force]",
|
|
220
|
-
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code) wired to the running proxy.",
|
|
219
|
+
usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode> [--json] [--out <path>] [--force]",
|
|
220
|
+
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode) wired to the running proxy.",
|
|
221
221
|
details: [
|
|
222
222
|
"--json prints the generated document as JSON on stdout; use --out for the client's native format.",
|
|
223
223
|
"--out <path> writes the native config there and refuses to replace an existing file without --force.",
|
|
@@ -302,6 +302,17 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
302
302
|
"The wrapper isolates ~/.mmx credentials and refuses --api-key/--base-url overrides.",
|
|
303
303
|
],
|
|
304
304
|
},
|
|
305
|
+
{
|
|
306
|
+
name: "zcode",
|
|
307
|
+
usage: "ocx zcode [status|enable|disable|history|restore] [--json]",
|
|
308
|
+
summary: "Connect ZCode (Z.ai desktop client) to the proxy via its managed provider.",
|
|
309
|
+
details: [
|
|
310
|
+
"Alias of ocx integration client <sub> --client zcode.",
|
|
311
|
+
"enable writes the managed provider.opencodex block into ~/.zcode/v2/config.json; disable removes only that block.",
|
|
312
|
+
"ZCode reads its config at startup — restart ZCode after enable/disable.",
|
|
313
|
+
"Select OpenCodex Proxy/<provider>/<model> from ZCode's model picker.",
|
|
314
|
+
],
|
|
315
|
+
},
|
|
305
316
|
{
|
|
306
317
|
name: "restart",
|
|
307
318
|
usage: "ocx restart",
|