@bitkyc08/opencodex 2.7.43-preview.20260728 → 2.8.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 +8 -1
- package/bin/ocx.mjs +47 -22
- package/gui/dist/assets/index-BDjpkcRN.js +67 -0
- package/gui/dist/assets/index-BHsKRFh9.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic.ts +15 -6
- package/src/adapters/cursor/discovery.ts +4 -1
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/native-exec-shell.ts +18 -6
- package/src/adapters/cursor/protobuf-events.ts +24 -2
- package/src/adapters/cursor/protobuf-request.ts +1 -2
- package/src/adapters/cursor/tool-definitions.ts +68 -29
- package/src/adapters/google-wire-compiler.ts +4 -0
- package/src/adapters/google.ts +128 -2
- package/src/adapters/identity.ts +12 -2
- package/src/adapters/kiro.ts +64 -7
- package/src/adapters/mimo-free.ts +2 -0
- package/src/adapters/openai-responses.ts +246 -59
- package/src/claude/agents-inject.ts +5 -0
- package/src/claude/alias.ts +94 -14
- package/src/claude/inbound.ts +26 -9
- package/src/claude/outbound.ts +6 -3
- package/src/cli/account-auth.ts +1 -1
- package/src/cli/agent-driven.ts +37 -0
- package/src/cli/catalog-prewarm.ts +24 -0
- package/src/cli/claude.ts +35 -10
- package/src/cli/doctor.ts +71 -19
- package/src/cli/help.ts +42 -6
- package/src/cli/index.ts +93 -19
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/opencode.ts +701 -0
- package/src/cli/provider-runtime.ts +3 -0
- package/src/cli/provider.ts +31 -10
- package/src/cli/star-prompt.ts +79 -18
- package/src/cli/status.ts +47 -13
- package/src/cli/v2.ts +10 -1
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-lifecycle.ts +4 -1
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-store.ts +2 -0
- package/src/codex/account-usability.ts +6 -1
- package/src/codex/app-server-processes.ts +511 -0
- package/src/codex/auth-api.ts +293 -34
- package/src/codex/auth-collision.ts +2 -1
- package/src/codex/auth-context.ts +60 -17
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/parsing.ts +42 -2
- package/src/codex/catalog/provider-fetch.ts +264 -70
- package/src/codex/catalog/sync.ts +45 -8
- package/src/codex/catalog.ts +2 -2
- package/src/codex/features.ts +524 -5
- package/src/codex/history-provider.ts +145 -1
- package/src/codex/inject.ts +114 -14
- package/src/codex/main-account.ts +2 -8
- package/src/codex/pool-rotation.ts +186 -0
- package/src/codex/quota.ts +92 -2
- package/src/codex/routing.ts +695 -106
- package/src/codex/runtime.ts +10 -1
- package/src/codex/shim.ts +4 -1
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +2 -0
- package/src/codex/sync.ts +3 -0
- package/src/config.ts +574 -25
- package/src/generated/jawcode-model-metadata.ts +12 -12
- package/src/github/star-state.ts +191 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +111 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +789 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +6 -20
- package/src/lib/config-ownership.ts +327 -0
- package/src/lib/crash-guard.ts +2 -0
- package/src/lib/destination-policy.ts +132 -7
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/process-control.ts +2 -2
- package/src/lib/provider-outbound.ts +167 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/shadow-call.ts +30 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/win-exec.ts +12 -2
- package/src/lib/windows-elevation.ts +81 -3
- package/src/lib/windows-secret-acl.ts +189 -12
- package/src/lib/winsw.ts +2 -0
- package/src/oauth/anthropic-routing.ts +570 -0
- package/src/oauth/health.ts +6 -0
- package/src/oauth/index.ts +310 -75
- package/src/oauth/key-providers.ts +38 -8
- package/src/oauth/kimi.ts +2 -0
- package/src/oauth/kiro-credentials.ts +373 -12
- package/src/oauth/kiro.ts +424 -43
- package/src/oauth/login-cli.ts +33 -6
- package/src/oauth/store.ts +56 -4
- package/src/oauth/types.ts +11 -0
- package/src/providers/alibaba-region-migration.ts +16 -3
- package/src/providers/antigravity-models.ts +3 -0
- package/src/providers/api-keys.ts +13 -6
- package/src/providers/derive.ts +8 -2
- package/src/providers/key-failover.ts +24 -4
- package/src/providers/model-discovery.ts +356 -0
- package/src/providers/quota.ts +233 -29
- package/src/providers/registry.ts +125 -3
- package/src/responses/parser.ts +11 -0
- package/src/responses/state.ts +22 -8
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +19 -7
- package/src/server/auth-cors.ts +114 -24
- package/src/server/claude-messages.ts +8 -1
- package/src/server/gui-static.ts +30 -6
- package/src/server/images.ts +303 -9
- package/src/server/index.ts +77 -9
- package/src/server/lifecycle.ts +25 -1
- package/src/server/live.ts +75 -25
- package/src/server/management/agent-settings-routes.ts +106 -8
- package/src/server/management/combo-routes.ts +7 -0
- package/src/server/management/config-routes.ts +22 -7
- package/src/server/management/context.ts +11 -1
- package/src/server/management/logs-usage-routes.ts +167 -3
- package/src/server/management/model-routes.ts +46 -13
- package/src/server/management/oauth-account-routes.ts +163 -17
- package/src/server/management/provider-routes.ts +73 -10
- package/src/server/management/shared.ts +2 -2
- package/src/server/management/sidebar-routes.ts +39 -0
- package/src/server/management/system-restart.ts +172 -0
- package/src/server/management/system-routes.ts +33 -10
- package/src/server/management-api.ts +5 -3
- package/src/server/management-auth.ts +216 -0
- package/src/server/proxy-liveness.ts +14 -3
- package/src/server/responses/compact.ts +21 -13
- package/src/server/responses/core.ts +614 -172
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +118 -0
- package/src/server/responses-item-id-repair.ts +10 -85
- package/src/server/sse-payload-rewrite.ts +116 -0
- package/src/server/startup-action-control.ts +30 -14
- package/src/server/system-env.ts +28 -10
- package/src/service.ts +284 -19
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +1504 -28
- package/src/storage/policy-job.ts +387 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +53 -0
- package/src/storage/policy.ts +522 -0
- package/src/storage/restore-job.ts +253 -0
- package/src/storage/restore-worker.ts +52 -0
- package/src/storage/storage-mutation-coordinator.ts +109 -0
- package/src/storage/worker-lifecycle.ts +81 -0
- package/src/tray/windows.ts +34 -4
- package/src/types.ts +107 -1
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +36 -18
- package/src/update/job.ts +111 -16
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/usage/debug.ts +2 -0
- package/src/usage/expected-prices.ts +6 -5
- package/src/usage/log.ts +12 -0
- package/src/web-search/loop.ts +57 -16
- package/gui/dist/assets/index-CjKFJHSC.js +0 -65
- package/gui/dist/assets/index-DfVGuN88.css +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { IncomingMeta, ProviderAdapter } from "./base";
|
|
3
|
-
import type
|
|
3
|
+
import { namespacedToolName, type AdapterEvent, type OcxParsedRequest, type OcxProviderConfig, type OcxUsage } from "../types";
|
|
4
4
|
import { catalogModelSupportsReasoningSummaries } from "../codex/catalog";
|
|
5
5
|
import { COMPACT_PROMPT, decodeCompactionSummary, SUMMARY_PREFIX } from "../responses/compaction";
|
|
6
|
+
import { collectResponsesToolGroups } from "../responses/tool-groups";
|
|
6
7
|
import { decodeServerSentEvents } from "../lib/sse-decoder";
|
|
7
8
|
import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
|
|
8
9
|
import { OCX_REASONING_PREFIX } from "../responses/reasoning-envelope";
|
|
@@ -500,72 +501,252 @@ function stripUnsupportedForwardParams(body: unknown): unknown {
|
|
|
500
501
|
return rest;
|
|
501
502
|
}
|
|
502
503
|
|
|
504
|
+
const IMAGE_GEN_NAMESPACE = "image_gen";
|
|
505
|
+
const HOSTED_IMAGE_GENERATION_TOOL = "image_generation";
|
|
506
|
+
const IMAGE_GEN_DOTTED_PREFIX = `${IMAGE_GEN_NAMESPACE}.`;
|
|
507
|
+
const IMAGE_GEN_WIRE_PREFIX = `${IMAGE_GEN_NAMESPACE}__`;
|
|
508
|
+
|
|
509
|
+
/** Remove a supported client prefix before constructing the canonical image-gen wire alias. */
|
|
510
|
+
function imageGenLocalName(name: string): string {
|
|
511
|
+
if (name.startsWith(IMAGE_GEN_DOTTED_PREFIX)) return name.slice(IMAGE_GEN_DOTTED_PREFIX.length);
|
|
512
|
+
if (name.startsWith(IMAGE_GEN_WIRE_PREFIX)) return name.slice(IMAGE_GEN_WIRE_PREFIX.length);
|
|
513
|
+
return name;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Build the flat public-Responses name used only on the upstream wire. */
|
|
517
|
+
function imageGenWireName(name: string): string {
|
|
518
|
+
return namespacedToolName(IMAGE_GEN_NAMESPACE, imageGenLocalName(name));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Match client image-gen declarations across namespace, legacy dotted, and canonical wire forms. */
|
|
522
|
+
function isImageGenClientName(name: string): boolean {
|
|
523
|
+
return name === IMAGE_GEN_NAMESPACE
|
|
524
|
+
|| name.startsWith(IMAGE_GEN_DOTTED_PREFIX)
|
|
525
|
+
|| name.startsWith(IMAGE_GEN_WIRE_PREFIX);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Identify declarations that should activate image-gen request normalization. */
|
|
529
|
+
function declaresImageGenClientTool(tool: unknown): boolean {
|
|
530
|
+
if (!isPlainObject(tool) || typeof tool.name !== "string") return false;
|
|
531
|
+
if (tool.type === "namespace") return tool.name === IMAGE_GEN_NAMESPACE;
|
|
532
|
+
return isImageGenClientName(tool.name);
|
|
533
|
+
}
|
|
534
|
+
|
|
503
535
|
/**
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* `
|
|
509
|
-
*
|
|
510
|
-
*
|
|
536
|
+
* Lower one complete Codex image-gen namespace to public Responses function tools.
|
|
537
|
+
*
|
|
538
|
+
* The public API reserves the `image_gen` namespace and restricts function names to a flat safe
|
|
539
|
+
* alphabet. `image_gen__<tool>` is therefore an upstream-only alias; client-facing responses are
|
|
540
|
+
* restored to explicit `{ namespace: "image_gen", name: "<tool>" }` calls by the server. Only a
|
|
541
|
+
* non-empty namespace containing named function tools is safe to lower. Malformed, empty, and
|
|
542
|
+
* future namespace shapes stay untouched instead of silently losing client capabilities.
|
|
511
543
|
*/
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
544
|
+
function flattenImageGenNamespace(tool: unknown): Record<string, unknown>[] | undefined {
|
|
545
|
+
if (
|
|
546
|
+
!isPlainObject(tool)
|
|
547
|
+
|| tool.type !== "namespace"
|
|
548
|
+
|| tool.name !== IMAGE_GEN_NAMESPACE
|
|
549
|
+
|| !Array.isArray(tool.tools)
|
|
550
|
+
|| tool.tools.length === 0
|
|
551
|
+
) return undefined;
|
|
552
|
+
|
|
553
|
+
for (const innerTool of tool.tools) {
|
|
554
|
+
if (
|
|
555
|
+
!isPlainObject(innerTool)
|
|
556
|
+
|| innerTool.type !== "function"
|
|
557
|
+
|| typeof innerTool.name !== "string"
|
|
558
|
+
|| innerTool.name.length === 0
|
|
559
|
+
) return undefined;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return tool.tools.map(innerTool => {
|
|
563
|
+
const functionTool = innerTool as Record<string, unknown> & { name: string };
|
|
564
|
+
return {
|
|
565
|
+
...functionTool,
|
|
566
|
+
name: imageGenWireName(functionTool.name),
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** Convert a legacy dotted function declaration while preserving all other function metadata. */
|
|
572
|
+
function normalizeFlatImageGenFunction(tool: unknown): unknown {
|
|
573
|
+
if (
|
|
574
|
+
!isPlainObject(tool)
|
|
575
|
+
|| tool.type !== "function"
|
|
576
|
+
|| typeof tool.name !== "string"
|
|
577
|
+
|| !tool.name.startsWith(IMAGE_GEN_DOTTED_PREFIX)
|
|
578
|
+
) return tool;
|
|
579
|
+
return { ...tool, name: imageGenWireName(tool.name) };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** Return the image-gen function name used for stable cross-container deduplication. */
|
|
583
|
+
function imageGenFunctionName(tool: unknown): string | undefined {
|
|
584
|
+
if (!isPlainObject(tool) || tool.type !== "function" || typeof tool.name !== "string") {
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
return isImageGenClientName(tool.name) ? tool.name : undefined;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** True only when a declaration can yield a callable upstream-safe image-gen function alias. */
|
|
591
|
+
function declaresUsableImageGenAlias(tool: unknown): boolean {
|
|
592
|
+
if (flattenImageGenNamespace(tool)) return true;
|
|
593
|
+
if (!isPlainObject(tool) || tool.type !== "function" || typeof tool.name !== "string") {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
if (tool.name.startsWith(IMAGE_GEN_DOTTED_PREFIX)) {
|
|
597
|
+
return tool.name.length > IMAGE_GEN_DOTTED_PREFIX.length;
|
|
598
|
+
}
|
|
599
|
+
return tool.name.startsWith(IMAGE_GEN_WIRE_PREFIX)
|
|
600
|
+
&& tool.name.length > IMAGE_GEN_WIRE_PREFIX.length;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** Collect client tool-choice names and the exact upstream aliases declared for them. */
|
|
604
|
+
function imageGenToolChoiceAliases(toolGroups: unknown[][]): Map<string, string> {
|
|
605
|
+
const aliases = new Map<string, string>();
|
|
606
|
+
|
|
607
|
+
for (const group of toolGroups) {
|
|
608
|
+
for (const tool of group) {
|
|
609
|
+
const flattened = flattenImageGenNamespace(tool);
|
|
610
|
+
if (flattened) {
|
|
611
|
+
for (const candidate of flattened) {
|
|
612
|
+
const wireName = candidate.name as string;
|
|
613
|
+
aliases.set(`${IMAGE_GEN_DOTTED_PREFIX}${imageGenLocalName(wireName)}`, wireName);
|
|
614
|
+
aliases.set(wireName, wireName);
|
|
615
|
+
}
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (!isPlainObject(tool) || tool.type !== "function" || typeof tool.name !== "string") {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (
|
|
622
|
+
tool.name.startsWith(IMAGE_GEN_DOTTED_PREFIX)
|
|
623
|
+
&& tool.name.length > IMAGE_GEN_DOTTED_PREFIX.length
|
|
624
|
+
) {
|
|
625
|
+
aliases.set(tool.name, imageGenWireName(tool.name));
|
|
626
|
+
} else if (
|
|
627
|
+
tool.name.startsWith(IMAGE_GEN_WIRE_PREFIX)
|
|
628
|
+
&& tool.name.length > IMAGE_GEN_WIRE_PREFIX.length
|
|
629
|
+
) {
|
|
630
|
+
aliases.set(tool.name, tool.name);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return aliases;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** Rewrite function selectors only when their corresponding declaration receives a wire alias. */
|
|
639
|
+
function normalizeImageGenToolChoice(
|
|
640
|
+
toolChoice: unknown,
|
|
641
|
+
aliases: ReadonlyMap<string, string>,
|
|
642
|
+
): unknown {
|
|
643
|
+
if (!isPlainObject(toolChoice)) return toolChoice;
|
|
644
|
+
|
|
645
|
+
if (toolChoice.type === "function" && typeof toolChoice.name === "string") {
|
|
646
|
+
const alias = aliases.get(toolChoice.name);
|
|
647
|
+
return alias && alias !== toolChoice.name ? { ...toolChoice, name: alias } : toolChoice;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (toolChoice.type !== "allowed_tools" || !Array.isArray(toolChoice.tools)) return toolChoice;
|
|
651
|
+
let changed = false;
|
|
652
|
+
const tools = toolChoice.tools.map(tool => {
|
|
653
|
+
if (!isPlainObject(tool) || tool.type !== "function" || typeof tool.name !== "string") {
|
|
654
|
+
return tool;
|
|
655
|
+
}
|
|
656
|
+
const alias = aliases.get(tool.name);
|
|
657
|
+
if (!alias || alias === tool.name) return tool;
|
|
658
|
+
changed = true;
|
|
659
|
+
return { ...tool, name: alias };
|
|
660
|
+
});
|
|
661
|
+
return changed ? { ...toolChoice, tools } : toolChoice;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/** Identify replayed image-gen calls that require upstream wire encoding. */
|
|
665
|
+
function declaresImageGenFunctionCall(item: unknown): boolean {
|
|
666
|
+
if (!isPlainObject(item) || item.type !== "function_call" || typeof item.name !== "string") {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
return item.namespace === IMAGE_GEN_NAMESPACE || isImageGenClientName(item.name);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** Encode native or legacy replay calls to the same flat name used by tool declarations. */
|
|
673
|
+
function normalizeImageGenFunctionCall(item: unknown): unknown {
|
|
674
|
+
if (!declaresImageGenFunctionCall(item) || !isPlainObject(item) || typeof item.name !== "string") {
|
|
675
|
+
return item;
|
|
676
|
+
}
|
|
677
|
+
if (item.namespace === IMAGE_GEN_NAMESPACE) {
|
|
678
|
+
const { namespace: _namespace, ...rest } = item;
|
|
679
|
+
return { ...rest, name: imageGenWireName(item.name) };
|
|
680
|
+
}
|
|
681
|
+
if (item.name.startsWith(IMAGE_GEN_DOTTED_PREFIX)) {
|
|
682
|
+
return { ...item, name: imageGenWireName(item.name) };
|
|
683
|
+
}
|
|
684
|
+
return item;
|
|
685
|
+
}
|
|
515
686
|
|
|
516
687
|
/**
|
|
517
|
-
*
|
|
518
|
-
* HOSTED_TOOL_NAME_CONFLICTS). Only applies on the API-key platform path: the ChatGPT backend
|
|
519
|
-
* ("forward" mode) accepts the pair, and stripping there would disable native imagegen. No-op
|
|
520
|
-
* (returns the original reference) when nothing matches.
|
|
688
|
+
* Normalize Codex's private image-gen tool declaration for API-key Responses providers.
|
|
521
689
|
*
|
|
522
|
-
*
|
|
523
|
-
* the
|
|
524
|
-
*
|
|
525
|
-
*
|
|
690
|
+
* A complete `image_gen` namespace is flattened to safe `image_gen__<tool>` aliases even when it is
|
|
691
|
+
* the only image tool in the request. Replayed client calls are encoded to the same alias, including
|
|
692
|
+
* legacy dotted calls from older compatibility attempts. When a usable alias replaces a client
|
|
693
|
+
* image-gen declaration, the duplicate hosted `image_generation` entry is removed. Duplicate aliases
|
|
694
|
+
* are resolved in stable container order: top-level tools first, then Responses Lite
|
|
695
|
+
* `additional_tools` entries.
|
|
696
|
+
*
|
|
697
|
+
* This function is called only on the API-key path. ChatGPT forward mode understands the private
|
|
698
|
+
* namespace and must keep it. Copy-on-write preserves the original request reference when no
|
|
699
|
+
* namespace is flattened, hosted tool removed, or duplicate function discarded.
|
|
526
700
|
*/
|
|
527
|
-
function
|
|
701
|
+
function normalizeImageGenClientTools(body: unknown): unknown {
|
|
528
702
|
if (!isPlainObject(body)) return body;
|
|
529
703
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
704
|
+
const toolGroups = collectResponsesToolGroups(body);
|
|
705
|
+
const hasImageGenClientTool = toolGroups.some(group => group.some(declaresImageGenClientTool))
|
|
706
|
+
|| (Array.isArray(body.input) && body.input.some(declaresImageGenFunctionCall));
|
|
707
|
+
if (!hasImageGenClientTool) return body;
|
|
708
|
+
const hasUsableImageGenAlias = toolGroups.some(group => group.some(declaresUsableImageGenAlias));
|
|
709
|
+
const toolChoiceAliases = imageGenToolChoiceAliases(toolGroups);
|
|
710
|
+
|
|
711
|
+
const seenFunctionNames = new Set<string>();
|
|
712
|
+
const normalizeGroup = (tools: unknown[]): unknown[] => {
|
|
713
|
+
const normalized: unknown[] = [];
|
|
714
|
+
let groupChanged = false;
|
|
715
|
+
|
|
716
|
+
for (const tool of tools) {
|
|
717
|
+
if (
|
|
718
|
+
hasUsableImageGenAlias
|
|
719
|
+
&& isPlainObject(tool)
|
|
720
|
+
&& tool.type === HOSTED_IMAGE_GENERATION_TOOL
|
|
721
|
+
) {
|
|
722
|
+
groupChanged = true;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const flattened = flattenImageGenNamespace(tool);
|
|
727
|
+
const candidates = flattened ?? [tool];
|
|
728
|
+
if (flattened) groupChanged = true;
|
|
729
|
+
|
|
730
|
+
for (const candidate of candidates) {
|
|
731
|
+
const normalizedCandidate = normalizeFlatImageGenFunction(candidate);
|
|
732
|
+
if (normalizedCandidate !== candidate) groupChanged = true;
|
|
733
|
+
const functionName = imageGenFunctionName(normalizedCandidate);
|
|
734
|
+
if (functionName && seenFunctionNames.has(functionName)) {
|
|
735
|
+
groupChanged = true;
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (functionName) seenFunctionNames.add(functionName);
|
|
739
|
+
normalized.push(normalizedCandidate);
|
|
538
740
|
}
|
|
539
741
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
// Detect conflicts across containers because the client namespace and the hosted tool may be
|
|
544
|
-
// declared in different groups while still sharing one platform-validated namespace.
|
|
545
|
-
const conflicting = HOSTED_TOOL_NAME_CONFLICTS.filter(c => toolGroups.some(group =>
|
|
546
|
-
group.some(t => {
|
|
547
|
-
if (!isPlainObject(t) || typeof t.name !== "string") return false;
|
|
548
|
-
if (t.type === "namespace") return t.name === c.namePrefix;
|
|
549
|
-
return t.name === c.namePrefix || t.name.startsWith(`${c.namePrefix}.`);
|
|
550
|
-
}),
|
|
551
|
-
));
|
|
552
|
-
if (conflicting.length === 0) return body;
|
|
553
|
-
|
|
554
|
-
// Allocate a replacement only when a hosted tool is removed. Preserve malformed entries,
|
|
555
|
-
// unrelated tools, and future tool types so this compatibility rule remains narrowly scoped.
|
|
556
|
-
const stripGroup = (tools: unknown[]): unknown[] => {
|
|
557
|
-
const filtered = tools.filter(t => {
|
|
558
|
-
const type = isPlainObject(t) && typeof t.type === "string" ? t.type : undefined;
|
|
559
|
-
if (!type) return true;
|
|
560
|
-
return !conflicting.some(c => c.hostedType === type);
|
|
561
|
-
});
|
|
562
|
-
return filtered.length === tools.length ? tools : filtered;
|
|
742
|
+
|
|
743
|
+
return groupChanged ? normalized : tools;
|
|
563
744
|
};
|
|
564
745
|
|
|
565
746
|
let changed = false;
|
|
566
747
|
let tools = body.tools;
|
|
567
748
|
if (Array.isArray(body.tools)) {
|
|
568
|
-
tools =
|
|
749
|
+
tools = normalizeGroup(body.tools);
|
|
569
750
|
changed ||= tools !== body.tools;
|
|
570
751
|
}
|
|
571
752
|
|
|
@@ -573,13 +754,15 @@ function stripConflictingHostedTools(body: unknown): unknown {
|
|
|
573
754
|
if (Array.isArray(body.input)) {
|
|
574
755
|
let nestedChanged = false;
|
|
575
756
|
const mappedInput = body.input.map(item => {
|
|
576
|
-
if (
|
|
577
|
-
|
|
757
|
+
if (isPlainObject(item) && item.type === "additional_tools" && Array.isArray(item.tools)) {
|
|
758
|
+
const nestedTools = normalizeGroup(item.tools);
|
|
759
|
+
if (nestedTools === item.tools) return item;
|
|
760
|
+
nestedChanged = true;
|
|
761
|
+
return { ...item, tools: nestedTools };
|
|
578
762
|
}
|
|
579
|
-
const
|
|
580
|
-
if (
|
|
581
|
-
|
|
582
|
-
return { ...item, tools: nestedTools };
|
|
763
|
+
const normalizedCall = normalizeImageGenFunctionCall(item);
|
|
764
|
+
if (normalizedCall !== item) nestedChanged = true;
|
|
765
|
+
return normalizedCall;
|
|
583
766
|
});
|
|
584
767
|
if (nestedChanged) {
|
|
585
768
|
input = mappedInput;
|
|
@@ -587,11 +770,15 @@ function stripConflictingHostedTools(body: unknown): unknown {
|
|
|
587
770
|
}
|
|
588
771
|
}
|
|
589
772
|
|
|
773
|
+
const toolChoice = normalizeImageGenToolChoice(body.tool_choice, toolChoiceAliases);
|
|
774
|
+
changed ||= toolChoice !== body.tool_choice;
|
|
775
|
+
|
|
590
776
|
if (!changed) return body;
|
|
591
777
|
return {
|
|
592
778
|
...body,
|
|
593
779
|
...(Array.isArray(body.tools) ? { tools } : {}),
|
|
594
780
|
...(Array.isArray(body.input) ? { input } : {}),
|
|
781
|
+
...(Object.prototype.hasOwnProperty.call(body, "tool_choice") ? { tool_choice: toolChoice } : {}),
|
|
595
782
|
};
|
|
596
783
|
}
|
|
597
784
|
|
|
@@ -740,7 +927,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
740
927
|
outBody = repairOrphanedInputItems(outBody, unexpandedMiss);
|
|
741
928
|
outBody = stripUnsupportedForwardParams(outBody);
|
|
742
929
|
}
|
|
743
|
-
else outBody =
|
|
930
|
+
else outBody = normalizeImageGenClientTools(outBody);
|
|
744
931
|
if (forward || parsed._previousResponseInputExpanded === true) {
|
|
745
932
|
outBody = repairOversizedReplayCallIds(outBody);
|
|
746
933
|
}
|
|
@@ -27,6 +27,7 @@ export interface ClaudeAgentDef {
|
|
|
27
27
|
name: string;
|
|
28
28
|
model: string;
|
|
29
29
|
description: string;
|
|
30
|
+
effort?: NonNullable<OcxConfig["claudeCode"]>["subagentEffort"];
|
|
30
31
|
blockedSkills: readonly string[];
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -99,6 +100,7 @@ export function buildClaudeAgentDefs(config: OcxConfig, windows: Record<string,
|
|
|
99
100
|
name: `${OWNED_PREFIX}${unique}`,
|
|
100
101
|
model,
|
|
101
102
|
description,
|
|
103
|
+
effort: config.claudeCode?.subagentEffort,
|
|
102
104
|
blockedSkills: blockedSkillsFor(model),
|
|
103
105
|
});
|
|
104
106
|
};
|
|
@@ -124,6 +126,7 @@ export function buildClaudeAgentDefs(config: OcxConfig, windows: Record<string,
|
|
|
124
126
|
name: `${OWNED_PREFIX}self`,
|
|
125
127
|
model: marked,
|
|
126
128
|
description: `Self-clone: delegate to your default main model (${marked}), synced from the /model picker at launch. ${NO_MODEL_ARG}`,
|
|
129
|
+
effort: config.claudeCode?.subagentEffort,
|
|
127
130
|
blockedSkills: blockedSkillsFor(marked),
|
|
128
131
|
});
|
|
129
132
|
}
|
|
@@ -149,6 +152,7 @@ function renderAgentDef(def: ClaudeAgentDef): string {
|
|
|
149
152
|
`name: ${JSON.stringify(def.name)}`,
|
|
150
153
|
`description: ${JSON.stringify(def.description)}`,
|
|
151
154
|
`model: ${JSON.stringify(def.model)}`,
|
|
155
|
+
...(def.effort ? [`effort: ${JSON.stringify(def.effort)}`] : []),
|
|
152
156
|
"---",
|
|
153
157
|
"",
|
|
154
158
|
`<!-- ${GENERATED_MARKER} -->`,
|
|
@@ -157,6 +161,7 @@ function renderAgentDef(def: ClaudeAgentDef): string {
|
|
|
157
161
|
// BODY rides the subagent's system prompt verbatim. The proxy detects this
|
|
158
162
|
// directive and overrides the request model before routing/passthrough.
|
|
159
163
|
`<!-- ocx-route: ${def.model} -->`,
|
|
164
|
+
...(def.effort ? [`<!-- ocx-effort: ${def.effort} -->`] : []),
|
|
160
165
|
"",
|
|
161
166
|
`You are a delegated worker running on \`${def.model}\` through the local opencodex proxy.`,
|
|
162
167
|
`IDENTITY: your ACTUAL underlying model is \`${def.model}\` — the opencodex proxy routes this`,
|
package/src/claude/alias.ts
CHANGED
|
@@ -7,9 +7,19 @@
|
|
|
7
7
|
* deterministic, reversible, and STABLE across releases (picker selections
|
|
8
8
|
* persist to Claude Code's settings.json `model` field).
|
|
9
9
|
*
|
|
10
|
+
* Versioned prefixes:
|
|
11
|
+
* - `claude-ocx-` (v1) — legacy / plain model ids with no `/` or `~`. Decode
|
|
12
|
+
* is literal (no escape expansion), so a persisted model id that literally
|
|
13
|
+
* contained the two-char sequences `~s` / `~t` keeps resolving.
|
|
14
|
+
* - `claude-ocx2-` (v2) — used whenever the model id needs escape encoding
|
|
15
|
+
* (`/` → `~s`, `~` → `~t`). Decode expands those escapes. New slash/tilde
|
|
16
|
+
* models always mint v2 so they cannot collide with v1 literals.
|
|
17
|
+
*
|
|
10
18
|
* Reversibility rules:
|
|
11
19
|
* - providers containing `--` or `/` are not aliased (split boundary safety);
|
|
12
|
-
* - model ids
|
|
20
|
+
* - model ids MAY contain `/` or `~` — minted under the v2 prefix with escapes
|
|
21
|
+
* (e.g. openrouter `anthropic/claude-opus-4-8` →
|
|
22
|
+
* `claude-ocx2-openrouter--anthropic~sclaude-opus-4-8`);
|
|
13
23
|
* - model ids MAY contain `--` (resolve splits on the FIRST `--` only);
|
|
14
24
|
* - native OpenAI slugs use the pseudo-provider `native` and resolve back to
|
|
15
25
|
* the bare slug; a real provider named "native" is therefore never aliased.
|
|
@@ -17,20 +27,82 @@
|
|
|
17
27
|
|
|
18
28
|
import { desktop3pAlias } from "./desktop-3p";
|
|
19
29
|
|
|
20
|
-
|
|
30
|
+
/** Legacy / plain readable prefix (literal model portion on decode). */
|
|
31
|
+
export const CLAUDE_ALIAS_PREFIX_V1 = "claude-ocx-";
|
|
32
|
+
/** Escape-encoded readable prefix (`~s`/`~t` expanded on decode). */
|
|
33
|
+
export const CLAUDE_ALIAS_PREFIX_V2 = "claude-ocx2-";
|
|
34
|
+
/**
|
|
35
|
+
* Current write prefix for plain (unescaped) model ids.
|
|
36
|
+
* Escape-needing models mint {@link CLAUDE_ALIAS_PREFIX_V2} instead.
|
|
37
|
+
*/
|
|
38
|
+
export const CLAUDE_ALIAS_PREFIX = CLAUDE_ALIAS_PREFIX_V1;
|
|
39
|
+
|
|
40
|
+
/** Encoded `/` inside the model portion of a v2 Claude Code alias. */
|
|
41
|
+
const CLAUDE_ALIAS_SLASH_ENC = "~s";
|
|
42
|
+
/** Encoded literal `~` inside the model portion of a v2 Claude Code alias. */
|
|
43
|
+
const CLAUDE_ALIAS_TILDE_ENC = "~t";
|
|
21
44
|
const NATIVE_PSEUDO_PROVIDER = "native";
|
|
22
45
|
|
|
46
|
+
function modelNeedsEscapeEncoding(modelId: string): boolean {
|
|
47
|
+
return modelId.includes("/") || modelId.includes("~");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function encodeModelId(modelId: string): string {
|
|
51
|
+
// Escape literal tildes first so slash encoding cannot create ambiguity.
|
|
52
|
+
return modelId
|
|
53
|
+
.replaceAll("~", CLAUDE_ALIAS_TILDE_ENC)
|
|
54
|
+
.replaceAll("/", CLAUDE_ALIAS_SLASH_ENC);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function decodeEscapedModelId(encoded: string): string {
|
|
58
|
+
let out = "";
|
|
59
|
+
for (let i = 0; i < encoded.length; i++) {
|
|
60
|
+
if (encoded[i] === "~" && i + 1 < encoded.length) {
|
|
61
|
+
const next = encoded[i + 1];
|
|
62
|
+
if (next === "s") {
|
|
63
|
+
out += "/";
|
|
64
|
+
i += 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (next === "t") {
|
|
68
|
+
out += "~";
|
|
69
|
+
i += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
out += encoded[i];
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function splitAlias(id: string, prefix: string): { provider: string; model: string } | null {
|
|
79
|
+
const rest = id.slice(prefix.length);
|
|
80
|
+
const sep = rest.indexOf("--");
|
|
81
|
+
if (sep <= 0) return null;
|
|
82
|
+
const provider = rest.slice(0, sep);
|
|
83
|
+
const model = rest.slice(sep + 2);
|
|
84
|
+
if (!provider || !model) return null;
|
|
85
|
+
return { provider, model };
|
|
86
|
+
}
|
|
87
|
+
|
|
23
88
|
/** Alias for a routed "<provider>/<model>" pair; null when not representable. */
|
|
24
89
|
export function aliasForRoute(provider: string, modelId: string): string | null {
|
|
25
90
|
if (!provider || provider.includes("--") || provider.includes("/") || provider === NATIVE_PSEUDO_PROVIDER) return null;
|
|
26
|
-
if (!modelId
|
|
27
|
-
|
|
91
|
+
if (!modelId) return null;
|
|
92
|
+
if (modelNeedsEscapeEncoding(modelId)) {
|
|
93
|
+
return `${CLAUDE_ALIAS_PREFIX_V2}${provider}--${encodeModelId(modelId)}`;
|
|
94
|
+
}
|
|
95
|
+
return `${CLAUDE_ALIAS_PREFIX_V1}${provider}--${modelId}`;
|
|
28
96
|
}
|
|
29
97
|
|
|
30
98
|
/** Alias for a native OpenAI slug (bare model id, no provider namespace). */
|
|
31
99
|
export function aliasForNative(slug: string): string | null {
|
|
100
|
+
// Reject "/" — native ids are bare slugs. Literal `~` is fine via v2 + ~t.
|
|
32
101
|
if (!slug || slug.includes("/") || slug.includes("--")) return null;
|
|
33
|
-
|
|
102
|
+
if (modelNeedsEscapeEncoding(slug)) {
|
|
103
|
+
return `${CLAUDE_ALIAS_PREFIX_V2}${NATIVE_PSEUDO_PROVIDER}--${encodeModelId(slug)}`;
|
|
104
|
+
}
|
|
105
|
+
return `${CLAUDE_ALIAS_PREFIX_V1}${NATIVE_PSEUDO_PROVIDER}--${slug}`;
|
|
34
106
|
}
|
|
35
107
|
|
|
36
108
|
/**
|
|
@@ -38,20 +110,28 @@ export function aliasForNative(slug: string): string | null {
|
|
|
38
110
|
* routed -> "<provider>/<model>", native -> bare slug. Null when not an alias.
|
|
39
111
|
*/
|
|
40
112
|
export function resolveAlias(id: string): string | null {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
113
|
+
// Check v2 before v1 for clarity (prefixes are disjoint: ocx2 vs ocx-).
|
|
114
|
+
if (id.startsWith(CLAUDE_ALIAS_PREFIX_V2)) {
|
|
115
|
+
const parts = splitAlias(id, CLAUDE_ALIAS_PREFIX_V2);
|
|
116
|
+
if (!parts) return null;
|
|
117
|
+
const model = decodeEscapedModelId(parts.model);
|
|
118
|
+
if (!model) return null;
|
|
119
|
+
return parts.provider === NATIVE_PSEUDO_PROVIDER ? model : `${parts.provider}/${model}`;
|
|
120
|
+
}
|
|
121
|
+
if (id.startsWith(CLAUDE_ALIAS_PREFIX_V1)) {
|
|
122
|
+
const parts = splitAlias(id, CLAUDE_ALIAS_PREFIX_V1);
|
|
123
|
+
if (!parts) return null;
|
|
124
|
+
// Literal decode — preserves pre-escape aliases whose model id contained
|
|
125
|
+
// the two-char sequences ~s / ~t.
|
|
126
|
+
return parts.provider === NATIVE_PSEUDO_PROVIDER ? parts.model : `${parts.provider}/${parts.model}`;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
49
129
|
}
|
|
50
130
|
|
|
51
131
|
/**
|
|
52
132
|
* Claude Code (CLI) surface alias — devlog 050 + audit 051 #2.
|
|
53
133
|
*
|
|
54
|
-
* The readable `claude-ocx
|
|
134
|
+
* The readable `claude-ocx*` form when representable; otherwise the desktop-3p
|
|
55
135
|
* hash so the model still appears in discovery (collisions follow the same
|
|
56
136
|
* first-wins policy as the desktop registry — audit 051 #1). Real Anthropic
|
|
57
137
|
* models pass through unchanged (they must keep hitting the sk-ant passthrough).
|
package/src/claude/inbound.ts
CHANGED
|
@@ -147,23 +147,40 @@ export function effectiveBlockedSkillNames(cc?: Pick<OcxClaudeCodeConfig, "block
|
|
|
147
147
|
* FIRST directive wins; the scan is bounded to the system field.
|
|
148
148
|
*/
|
|
149
149
|
const OCX_ROUTE_RE = /<!--\s*ocx-route:\s*([^\s]+)\s*-->/;
|
|
150
|
+
const OCX_EFFORT_RE = /<!--\s*ocx-effort:\s*(low|medium|high|xhigh|max)\s*-->/;
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
function systemText(body: unknown): string | null {
|
|
152
153
|
if (!isRec(body)) return null;
|
|
153
154
|
const system = body.system;
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
text
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
155
|
+
if (typeof system === "string") return system || null;
|
|
156
|
+
if (!Array.isArray(system)) return null;
|
|
157
|
+
const text = system
|
|
158
|
+
.filter((b): b is Rec => isRec(b) && b.type === "text" && typeof b.text === "string")
|
|
159
|
+
.map(b => b.text as string)
|
|
160
|
+
.join("\n");
|
|
161
|
+
return text || null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function extractOcxRouteDirective(body: unknown): string | null {
|
|
165
|
+
const text = systemText(body);
|
|
162
166
|
if (!text) return null;
|
|
163
167
|
const match = OCX_ROUTE_RE.exec(text);
|
|
164
168
|
return match ? match[1]! : null;
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Claude Code 2.1.220 collapses custom-agent frontmatter `effort: max` and
|
|
173
|
+
* `effort: xhigh` into the legacy `thinking.budget_tokens` shape. Preserve the
|
|
174
|
+
* exact generated-agent setting through the same trusted system-body channel as
|
|
175
|
+
* ocx-route so the inbound translator can restore `output_config.effort`.
|
|
176
|
+
*/
|
|
177
|
+
export function extractOcxEffortDirective(body: unknown): NonNullable<OcxClaudeCodeConfig["subagentEffort"]> | null {
|
|
178
|
+
const text = systemText(body);
|
|
179
|
+
if (!text) return null;
|
|
180
|
+
const match = OCX_EFFORT_RE.exec(text);
|
|
181
|
+
return match ? match[1] as NonNullable<OcxClaudeCodeConfig["subagentEffort"]> : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
167
184
|
/** Injected-skill payloads below this size are never stubbed (not worth it). */
|
|
168
185
|
const SKILL_ELISION_MIN_CHARS = 10_000;
|
|
169
186
|
const SKILL_TEXT_MARKER = "Base directory for this skill: ";
|
package/src/claude/outbound.ts
CHANGED
|
@@ -453,12 +453,15 @@ export function responsesSseToAnthropicSse(
|
|
|
453
453
|
if (line.startsWith("event: ")) eventName = line.slice(7).trim();
|
|
454
454
|
else if (line.startsWith("data: ")) dataLine += line.slice(6);
|
|
455
455
|
}
|
|
456
|
-
if (!
|
|
456
|
+
if (!dataLine) continue;
|
|
457
457
|
let data: unknown;
|
|
458
458
|
try { data = JSON.parse(dataLine); } catch { continue; }
|
|
459
459
|
if (!isRec(data)) continue;
|
|
460
|
-
|
|
461
|
-
|
|
460
|
+
// Responses-compatible gateways may omit the optional SSE event field
|
|
461
|
+
// while retaining the event name in the JSON payload's required type.
|
|
462
|
+
const resolvedEventName = eventName || (typeof data.type === "string" ? data.type : "");
|
|
463
|
+
if (!resolvedEventName || terminated) continue;
|
|
464
|
+
handleFrame(resolvedEventName, data);
|
|
462
465
|
}
|
|
463
466
|
}
|
|
464
467
|
// EOF without a terminal frame is a TRUNCATION, not success (devlog 100:
|
package/src/cli/account-auth.ts
CHANGED
|
@@ -137,11 +137,11 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
137
137
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
138
138
|
await Bun.sleep(2_000);
|
|
139
139
|
const state = await runtimeRequest<Record<string, unknown>>(`/api/oauth/status?provider=${encodeURIComponent(provider)}`, {}, deps);
|
|
140
|
+
if (state.error) throw new CliUsageError(String(state.error));
|
|
140
141
|
if (state.loggedIn === true) {
|
|
141
142
|
printData(state, wantsJson, [`Logged in to ${provider}.`]);
|
|
142
143
|
return;
|
|
143
144
|
}
|
|
144
|
-
if (state.error) throw new CliUsageError(String(state.error));
|
|
145
145
|
}
|
|
146
146
|
throw new CliUsageError("login timed out");
|
|
147
147
|
}
|