@bitkyc08/opencodex 2.65.0-preview.20260925 → 2.66.0-preview.20260925
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 +14 -0
- package/gui/dist/assets/App-BVjA0T6O.js +51 -0
- package/gui/dist/assets/App-gkoJIEOV.css +1 -0
- package/gui/dist/assets/{Tray-DUvc_Wul.js → Tray-BCFtiWwD.js} +1 -1
- package/gui/dist/assets/index-ComVsdSk.css +1 -0
- package/gui/dist/assets/index-vO4dgfiU.js +86 -0
- package/gui/dist/assets/{tray-data-f0lTZ4sF.js → tray-data-C4FiNaxk.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +80 -0
- package/src/adapters/anthropic/passthrough.ts +221 -0
- package/src/adapters/anthropic-image-codec.ts +56 -19
- package/src/adapters/anthropic-image-normalize.ts +20 -10
- package/src/adapters/anthropic.ts +55 -24
- package/src/adapters/coding-agent/protocol.ts +29 -19
- package/src/adapters/cursor/live-transport.ts +18 -3
- package/src/adapters/cursor/native-exec-shell.ts +18 -63
- package/src/adapters/cursor/native-exec.ts +5 -2
- package/src/adapters/cursor/native-foreground-shell.ts +44 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -1
- package/src/adapters/exec-tool-result-normalize.ts +4 -1
- package/src/adapters/google.ts +12 -0
- package/src/adapters/ollama-native.ts +32 -1
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +131 -34
- package/src/adapters/openai-chat.ts +6 -4
- package/src/adapters/openai-responses/passthrough.ts +1 -0
- package/src/adapters/openai-responses/reasoning.ts +45 -1
- package/src/bridge/response-json.ts +10 -2
- package/src/chat/outbound.ts +104 -52
- package/src/claude/claude-code-block.ts +16 -0
- package/src/claude/context-windows.ts +52 -4
- package/src/claude/desktop-first-party.ts +25 -8
- package/src/claude/inbound.ts +22 -0
- package/src/claude/intercept/connect-proxy.ts +37 -4
- package/src/claude/intercept/proxy-auth.ts +166 -0
- package/src/claude/intercept/runtime.ts +21 -0
- package/src/claude/intercept/settings.ts +33 -12
- package/src/claude/outbound.ts +45 -27
- package/src/cli/account-extended.ts +47 -15
- package/src/cli/account-target.ts +115 -0
- package/src/cli/account.ts +20 -28
- package/src/cli/agent.ts +10 -1
- package/src/cli/api-protocols.ts +206 -0
- package/src/cli/capabilities.ts +86 -0
- package/src/cli/combo.ts +2 -2
- package/src/cli/connect.ts +127 -3
- package/src/cli/dispatch.ts +8 -0
- package/src/cli/doctor.ts +33 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/link.ts +288 -0
- package/src/cli/registry.ts +23 -0
- package/src/cli/runtime-api.ts +79 -0
- package/src/cli/system-command.ts +21 -2
- package/src/client/connect.ts +125 -23
- package/src/client/hub-relay.ts +12 -8
- package/src/client/link-join.ts +323 -0
- package/src/client/link-relay.ts +241 -0
- package/src/client/link-state.ts +110 -0
- package/src/client/link-teardown.ts +63 -0
- package/src/client/link-tunnel.ts +465 -0
- package/src/client/machine-listener.ts +14 -5
- package/src/client/runtime.ts +91 -42
- package/src/client/state.ts +4 -0
- package/src/codex/catalog/build-entries.ts +4 -0
- package/src/codex/catalog/derive-entry.ts +5 -0
- package/src/codex/catalog/parsing.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +4 -1
- package/src/codex/desktop-switches.ts +88 -8
- package/src/codex/inject/plan.ts +16 -3
- package/src/codex/inject.ts +3 -0
- package/src/codex/internal/catalog-writer.ts +13 -2
- package/src/combos/failover.ts +3 -2
- package/src/combos/index.ts +12 -0
- package/src/combos/jev.ts +646 -0
- package/src/combos/request.ts +5 -0
- package/src/combos/resolve.ts +17 -15
- package/src/combos/types.ts +47 -3
- package/src/config/atomic-write.ts +3 -0
- package/src/config/live-reconcile.ts +20 -4
- package/src/config/persist-unlocked.ts +20 -7
- package/src/config/schema/config-schema.ts +16 -0
- package/src/config/schema/leaf-validators.ts +38 -1
- package/src/generated/compatibility-version.json +416 -128
- package/src/integrations/omp-yaml-source.ts +109 -4
- package/src/lab/automation/orchestrator.ts +60 -4
- package/src/lab/events/limits.ts +1 -1
- package/src/lib/bounded-body.ts +55 -38
- package/src/lib/config-ownership.ts +11 -22
- package/src/lib/local-desktop-snapshot-capability.ts +56 -0
- package/src/lib/local-management-capability.ts +25 -3
- package/src/lib/optional-shutdown-hooks.ts +17 -0
- package/src/lib/state-store-registrations.ts +2 -2
- package/src/lib/windows-elevation.ts +62 -25
- package/src/lib/windows-secret-acl.ts +114 -23
- package/src/link/admission-wait.ts +73 -0
- package/src/link/compensation.ts +100 -0
- package/src/link/fingerprint.ts +21 -0
- package/src/link/paths.ts +19 -0
- package/src/link/ports.ts +12 -0
- package/src/link/routes.ts +32 -0
- package/src/link/ssh-argv.ts +170 -0
- package/src/link/ssh-config.ts +152 -0
- package/src/link/ssh-runner.ts +161 -0
- package/src/link/status-projection.ts +96 -0
- package/src/link/store.ts +139 -0
- package/src/link/supervisor.ts +404 -0
- package/src/link/tunnel-state.ts +91 -0
- package/src/protocols/baseline.ts +88 -0
- package/src/protocols/codecs/chat.ts +17 -0
- package/src/protocols/codecs/messages.ts +18 -0
- package/src/protocols/codecs/responses.ts +17 -0
- package/src/protocols/contract.ts +185 -0
- package/src/protocols/dto.ts +267 -0
- package/src/protocols/encoders/adapter-events.ts +876 -0
- package/src/protocols/encoders/chat.ts +243 -0
- package/src/protocols/encoders/messages.ts +462 -0
- package/src/protocols/envelope.ts +43 -0
- package/src/protocols/features.ts +295 -0
- package/src/protocols/guard.ts +33 -0
- package/src/protocols/opaque-state.ts +132 -0
- package/src/protocols/path.ts +39 -0
- package/src/protocols/plan-snapshot.ts +226 -0
- package/src/protocols/plan.ts +192 -0
- package/src/protocols/provider-summary.ts +101 -0
- package/src/protocols/settings.ts +118 -0
- package/src/protocols/shadow-plan.ts +36 -0
- package/src/protocols/shadow.ts +62 -0
- package/src/protocols/trace.ts +335 -0
- package/src/providers/model-rename-migration.ts +3 -3
- package/src/providers/registry/entries-core.ts +6 -1
- package/src/providers/registry/entries-extended.ts +15 -0
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/types.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/remote-control/workspace-hub.ts +5 -4
- package/src/responses/apply-patch-envelope.ts +6 -1
- package/src/responses/code-mode-shell-input.ts +4 -2
- package/src/responses/freeform-wrapper-scan.ts +51 -24
- package/src/responses/progressive-freeform-input.ts +14 -20
- package/src/responses/spill-store.ts +187 -33
- package/src/responses/state/snapshot-select.ts +49 -0
- package/src/responses/state/spill-inspect.ts +46 -0
- package/src/responses/state/spill-queue.ts +16 -0
- package/src/responses/state.ts +51 -27
- package/src/server/audio-client.ts +8 -3
- package/src/server/audio-upstream.ts +9 -4
- package/src/server/auth-cors.ts +25 -8
- package/src/server/chat-completions.ts +71 -11
- package/src/server/chat-native-eligibility.ts +74 -0
- package/src/server/chat-native.ts +139 -74
- package/src/server/claude-messages.ts +140 -22
- package/src/server/hub-usage.ts +4 -3
- package/src/server/index/link-listener.ts +195 -0
- package/src/server/index/optional-listeners.ts +115 -0
- package/src/server/index/serve-options.ts +44 -10
- package/src/server/index.ts +10 -9
- package/src/server/inference/attempt.ts +51 -0
- package/src/server/inference/client-encoder-delivery.ts +235 -0
- package/src/server/inference/client-wire-log.ts +49 -0
- package/src/server/inference/client-wire.ts +69 -0
- package/src/server/inference/context.ts +15 -0
- package/src/server/inference/final-log.ts +37 -0
- package/src/server/local-desktop-snapshot-auth.ts +57 -0
- package/src/server/management/agent-settings-routes.ts +15 -13
- package/src/server/management/api-access.ts +11 -1
- package/src/server/management/config-routes.ts +3 -6
- package/src/server/management/context.ts +25 -0
- package/src/server/management/link-routes.ts +535 -0
- package/src/server/management/logs-usage-routes.ts +31 -1
- package/src/server/management/native-integration-routes.ts +6 -11
- package/src/server/management/oauth-account-routes.ts +42 -16
- package/src/server/management/protocol-routes.ts +159 -0
- package/src/server/management/protocol-settings-patch.ts +145 -0
- package/src/server/management/provider-patch-transaction.ts +61 -0
- package/src/server/management/provider-routes.ts +39 -18
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sidebar-routes.ts +8 -3
- package/src/server/management/system-routes.ts +18 -1
- package/src/server/management/usage-aggregate-cache.ts +206 -6
- package/src/server/management-api.ts +26 -1
- package/src/server/management-auth.ts +19 -5
- package/src/server/messages-native-eligibility.ts +161 -0
- package/src/server/messages-native-oauth.ts +149 -0
- package/src/server/messages-native.ts +790 -0
- package/src/server/relay.ts +9 -0
- package/src/server/request-log-filter.ts +92 -0
- package/src/server/request-log.ts +31 -70
- package/src/server/responses/adapter-delivery.ts +43 -16
- package/src/server/responses/agent-task-recovery.ts +90 -22
- package/src/server/responses/codex-ws-exchange.ts +4 -0
- package/src/server/responses/codex-ws-wire.ts +10 -1
- package/src/server/responses/core-combo-native.ts +323 -0
- package/src/server/responses/core-combo.ts +224 -13
- package/src/server/responses/core-options.ts +22 -0
- package/src/server/responses/core.ts +14 -26
- package/src/server/responses/request-sidecar-auth.ts +8 -3
- package/src/server/responses/run-turn-execution.ts +257 -63
- package/src/server/responses/sidecar-execution.ts +7 -4
- package/src/server/system-env.ts +7 -0
- package/src/service/diagnostics.ts +7 -1
- package/src/service/launchd.ts +26 -37
- package/src/service/windows-ops.ts +24 -20
- package/src/types/config.ts +58 -1
- package/src/types.ts +2 -0
- package/src/update/transactional-install.d.mts +0 -1
- package/src/update/transactional-install.mjs +17 -17
- package/src/usage/jev-stats.ts +495 -0
- package/src/usage/log.ts +24 -3
- package/src/web-search/run-turn-loop.ts +568 -0
- package/gui/dist/assets/App-8NMiZxT0.css +0 -1
- package/gui/dist/assets/App-CsYvvpr3.js +0 -50
- package/gui/dist/assets/index--EWgGQvZ.css +0 -1
- package/gui/dist/assets/index-BB0iHG8-.js +0 -86
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow-plan comparison (PF-12, `protocols.rollout.shadowPlan`): does the dispatch-basis plan
|
|
3
|
+
* for a request agree with what the request actually did?
|
|
4
|
+
*
|
|
5
|
+
* LEAF MODULE (see `contract.ts`) and PURE. It compares two finished records and nothing else:
|
|
6
|
+
* no second request, no config read, no state. The caller (`trace.ts`) contains any throw.
|
|
7
|
+
*
|
|
8
|
+
* What is compared, and why only that:
|
|
9
|
+
*
|
|
10
|
+
* - The candidate is the one the request settled on (provider and model of the final route),
|
|
11
|
+
* falling back to the plan's first eligible candidate. A combo that failed over is compared
|
|
12
|
+
* against the target that answered, not against the first one the plan lists.
|
|
13
|
+
* - Mode, upstream wire and request path. The response path is not compared: with
|
|
14
|
+
* `directEncoders` on, the client side leaves the internal Responses hop while the request
|
|
15
|
+
* side keeps it, and the planner does not model the encoder switch.
|
|
16
|
+
* - A Messages request forwarded with the caller's own Anthropic credential is native by the
|
|
17
|
+
* caller's choice; the plan says so with `caller-credential-required` and never predicts it.
|
|
18
|
+
* - A blocked trace agrees with a blocked plan. A compatibility reject is Claude-integration
|
|
19
|
+
* policy the planner does not model, so it is not compared.
|
|
20
|
+
*/
|
|
21
|
+
import type { ProtocolHop } from "./contract";
|
|
22
|
+
import type { ProtocolPlanCandidateV1, ProtocolPlanV1, ProtocolTraceV1 } from "./dto";
|
|
23
|
+
|
|
24
|
+
/** The route the request settled on, as the request log context records it. */
|
|
25
|
+
export interface ShadowSettledRoute {
|
|
26
|
+
provider?: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function samePath(a: readonly ProtocolHop[], b: readonly ProtocolHop[]): boolean {
|
|
31
|
+
return a.length === b.length && a.every((hop, index) => hop === b[index]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function settledCandidate(plan: ProtocolPlanV1, settled: ShadowSettledRoute): ProtocolPlanCandidateV1 | undefined {
|
|
35
|
+
const exact = settled.provider && settled.model
|
|
36
|
+
? plan.candidates.find(candidate => candidate.provider === settled.provider && candidate.model === settled.model)
|
|
37
|
+
: undefined;
|
|
38
|
+
return exact ?? plan.candidates.find(candidate => candidate.eligible);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** True when the plan and the observed trace disagree about the path the request took. */
|
|
42
|
+
export function shadowPlanMismatch(
|
|
43
|
+
plan: ProtocolPlanV1,
|
|
44
|
+
trace: ProtocolTraceV1,
|
|
45
|
+
settled: ShadowSettledRoute = {},
|
|
46
|
+
): boolean {
|
|
47
|
+
if (plan.inbound !== trace.inbound) return true;
|
|
48
|
+
if (trace.mode === "blocked") {
|
|
49
|
+
if (trace.reasonCodes.includes("compatibility-reject")) return false;
|
|
50
|
+
return plan.mode !== "blocked";
|
|
51
|
+
}
|
|
52
|
+
if (trace.inbound === "messages" && trace.mode === "native"
|
|
53
|
+
&& plan.reasonCodes.includes("caller-credential-required")
|
|
54
|
+
&& samePath(trace.requestPath, ["messages", "messages"])) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const candidate = settledCandidate(plan, settled);
|
|
58
|
+
if (!candidate || candidate.mode === "blocked") return true;
|
|
59
|
+
return candidate.mode !== trace.mode
|
|
60
|
+
|| candidate.upstream !== trace.upstream
|
|
61
|
+
|| !samePath(candidate.requestPath, trace.requestPath);
|
|
62
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observed protocol path for one request (PF-02).
|
|
3
|
+
*
|
|
4
|
+
* SERVER SIDE: the dashboard never imports this file; it reads the finished `ProtocolTraceV1`
|
|
5
|
+
* through `dto.ts`. Marks live in WeakMaps keyed by the request log context and by the live
|
|
6
|
+
* attempt objects, so neither `RequestLogContext` nor the persisted attempt row grows a field,
|
|
7
|
+
* and a context that is never finalized takes its marks with it.
|
|
8
|
+
*
|
|
9
|
+
* Every function here is side-effect free apart from its own WeakMap and never throws into the
|
|
10
|
+
* request path: a trace is diagnostics, and a request must not fail because its trace could not
|
|
11
|
+
* be recorded. Nothing conversation-derived is kept — only fixed vocabulary.
|
|
12
|
+
*
|
|
13
|
+
* Shadow plan (PF-12): when `protocols.rollout.shadowPlan` is on, the ingress records the
|
|
14
|
+
* dispatch-basis plan input for the request (`src/protocols/shadow-plan.ts`); at finalize the
|
|
15
|
+
* plan is computed with the pure planner and compared with the observed trace, and a
|
|
16
|
+
* disagreement sets `planMismatch`. Nothing is recorded with the switch off, so the trace is
|
|
17
|
+
* unchanged, and a failed comparison leaves the trace as observed.
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
PROTOCOL_CONTRACT_VERSION,
|
|
21
|
+
isProtocolReasonCode,
|
|
22
|
+
upstreamWireForAdapter,
|
|
23
|
+
type DeliveryMode,
|
|
24
|
+
type Protocol,
|
|
25
|
+
type ProtocolHop,
|
|
26
|
+
type ProtocolReasonCode,
|
|
27
|
+
type UpstreamWire,
|
|
28
|
+
} from "./contract";
|
|
29
|
+
import { PROTOCOL_DTO_LIMITS, PROTOCOL_TRACE_SCHEMA_VERSION, type ProtocolAttemptTraceV1, type ProtocolTraceV1 } from "./dto";
|
|
30
|
+
import { featureEffectsForPath, isProtocolFeature, type ProtocolFeature } from "./features";
|
|
31
|
+
import { deliveryModeForLane, requestPathForLane, responsePathForLane, type ProtocolLane } from "./path";
|
|
32
|
+
import { planProtocol, type ProtocolPlanInput } from "./plan";
|
|
33
|
+
import { shadowPlanMismatch } from "./shadow";
|
|
34
|
+
|
|
35
|
+
/** Features are computed inside the mark, so a thrown feature scan is contained there too. */
|
|
36
|
+
export type ProtocolFeatureSource = Iterable<ProtocolFeature> | (() => Iterable<ProtocolFeature>);
|
|
37
|
+
|
|
38
|
+
interface EntryMark {
|
|
39
|
+
kind: "entry";
|
|
40
|
+
inbound: Protocol;
|
|
41
|
+
lane: ProtocolLane;
|
|
42
|
+
reasonCodes: ProtocolReasonCode[];
|
|
43
|
+
features: ProtocolFeature[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface BlockedMark {
|
|
47
|
+
kind: "blocked";
|
|
48
|
+
inbound: Protocol;
|
|
49
|
+
reasonCodes: ProtocolReasonCode[];
|
|
50
|
+
features: ProtocolFeature[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface AttemptMark {
|
|
54
|
+
mode: Exclude<DeliveryMode, "blocked">;
|
|
55
|
+
requestPath: ProtocolHop[];
|
|
56
|
+
responsePath?: ProtocolHop[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The attempt fields the trace reads. `PersistedUsageAttempt` satisfies it. */
|
|
60
|
+
export interface ProtocolTraceAttempt {
|
|
61
|
+
ordinal: number;
|
|
62
|
+
adapter: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const requestMarks = new WeakMap<object, EntryMark | BlockedMark>();
|
|
66
|
+
const attemptMarks = new WeakMap<object, AttemptMark>();
|
|
67
|
+
const shadowInputs = new WeakMap<object, ProtocolPlanInput>();
|
|
68
|
+
|
|
69
|
+
function boundedReasons(codes: Iterable<ProtocolReasonCode>): ProtocolReasonCode[] {
|
|
70
|
+
const out: ProtocolReasonCode[] = [];
|
|
71
|
+
for (const code of codes) {
|
|
72
|
+
if (!isProtocolReasonCode(code) || out.includes(code)) continue;
|
|
73
|
+
out.push(code);
|
|
74
|
+
if (out.length >= PROTOCOL_DTO_LIMITS.reasonCodes) break;
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function collectFeatures(source: ProtocolFeatureSource | undefined): ProtocolFeature[] {
|
|
80
|
+
if (source === undefined) return [];
|
|
81
|
+
const iterable = typeof source === "function" ? source() : source;
|
|
82
|
+
const out: ProtocolFeature[] = [];
|
|
83
|
+
for (const feature of iterable) {
|
|
84
|
+
if (isProtocolFeature(feature) && !out.includes(feature)) out.push(feature);
|
|
85
|
+
if (out.length >= PROTOCOL_DTO_LIMITS.featureEffects) break;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Record which lane a Chat or Messages ingress chose. A later mark replaces an earlier one:
|
|
92
|
+
* the last decision before the send is the one that describes it.
|
|
93
|
+
*/
|
|
94
|
+
export function markProtocolEntry(
|
|
95
|
+
logCtx: object,
|
|
96
|
+
mark: { inbound: Protocol; lane: ProtocolLane; reasonCodes?: Iterable<ProtocolReasonCode>; features?: ProtocolFeatureSource },
|
|
97
|
+
): void {
|
|
98
|
+
try {
|
|
99
|
+
requestMarks.set(logCtx, {
|
|
100
|
+
kind: "entry",
|
|
101
|
+
inbound: mark.inbound,
|
|
102
|
+
lane: mark.lane,
|
|
103
|
+
reasonCodes: boundedReasons(mark.reasonCodes ?? []),
|
|
104
|
+
features: collectFeatures(mark.features),
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
/* a trace must never fail the request it describes */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Features the request's entry or blocked mark recorded, for the shadow plan input. */
|
|
112
|
+
export function protocolMarkFeatures(logCtx: object): readonly ProtocolFeature[] | undefined {
|
|
113
|
+
try {
|
|
114
|
+
const mark = requestMarks.get(logCtx);
|
|
115
|
+
return mark ? [...mark.features] : undefined;
|
|
116
|
+
} catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Record the dispatch-basis plan input the shadow comparison runs at finalize. The input holds
|
|
123
|
+
* fixed vocabulary and the provider and model names the server already exposes, nothing else.
|
|
124
|
+
*/
|
|
125
|
+
export function markProtocolShadowPlanInput(logCtx: object, input: ProtocolPlanInput): void {
|
|
126
|
+
try {
|
|
127
|
+
shadowInputs.set(logCtx, input);
|
|
128
|
+
} catch {
|
|
129
|
+
/* a trace must never fail the request it describes */
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Record a refusal made before any upstream send. */
|
|
134
|
+
export function markProtocolBlocked(
|
|
135
|
+
logCtx: object,
|
|
136
|
+
mark: { inbound: Protocol; reasonCodes: Iterable<ProtocolReasonCode>; features?: ProtocolFeatureSource },
|
|
137
|
+
): void {
|
|
138
|
+
try {
|
|
139
|
+
requestMarks.set(logCtx, {
|
|
140
|
+
kind: "blocked",
|
|
141
|
+
inbound: mark.inbound,
|
|
142
|
+
reasonCodes: boundedReasons(mark.reasonCodes),
|
|
143
|
+
features: collectFeatures(mark.features),
|
|
144
|
+
});
|
|
145
|
+
} catch {
|
|
146
|
+
/* a trace must never fail the request it describes */
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Add a reason to the request's entry mark: a combo candidate skipped before any send (PF-07).
|
|
152
|
+
* No-op without an entry mark, and a blocked mark is never reopened.
|
|
153
|
+
*/
|
|
154
|
+
export function addProtocolEntryReason(logCtx: object, code: ProtocolReasonCode): void {
|
|
155
|
+
try {
|
|
156
|
+
const mark = requestMarks.get(logCtx);
|
|
157
|
+
if (mark?.kind !== "entry") return;
|
|
158
|
+
mark.reasonCodes = boundedReasons([...mark.reasonCodes, code]);
|
|
159
|
+
} catch {
|
|
160
|
+
/* a trace must never fail the request it describes */
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Record the path one physical attempt actually took, overriding the lane-derived one. For a
|
|
166
|
+
* send site that knows its path better than the ingress lane does.
|
|
167
|
+
*/
|
|
168
|
+
export function markAttemptProtocolPath(
|
|
169
|
+
attempt: object,
|
|
170
|
+
mark: { mode: Exclude<DeliveryMode, "blocked">; requestPath: readonly ProtocolHop[]; responsePath?: readonly ProtocolHop[] },
|
|
171
|
+
): void {
|
|
172
|
+
try {
|
|
173
|
+
if (mark.requestPath.length === 0 || mark.requestPath.length > PROTOCOL_DTO_LIMITS.pathHops) return;
|
|
174
|
+
const responsePath = mark.responsePath && mark.responsePath.length > 0
|
|
175
|
+
&& mark.responsePath.length <= PROTOCOL_DTO_LIMITS.pathHops
|
|
176
|
+
? [...mark.responsePath]
|
|
177
|
+
: undefined;
|
|
178
|
+
attemptMarks.set(attempt, {
|
|
179
|
+
mode: mark.mode,
|
|
180
|
+
requestPath: [...mark.requestPath],
|
|
181
|
+
...(responsePath ? { responsePath } : {}),
|
|
182
|
+
});
|
|
183
|
+
} catch {
|
|
184
|
+
/* a trace must never fail the request it describes */
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isReverse(a: readonly ProtocolHop[], b: readonly ProtocolHop[]): boolean {
|
|
189
|
+
return a.length === b.length && a.every((hop, index) => hop === b[b.length - 1 - index]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface ResolvedPath {
|
|
193
|
+
upstream: UpstreamWire;
|
|
194
|
+
mode: Exclude<DeliveryMode, "blocked">;
|
|
195
|
+
requestPath: ProtocolHop[];
|
|
196
|
+
responsePath: ProtocolHop[];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function pathFor(inbound: Protocol, lane: ProtocolLane, upstream: UpstreamWire): ResolvedPath {
|
|
200
|
+
return {
|
|
201
|
+
upstream,
|
|
202
|
+
mode: deliveryModeForLane(inbound, lane, upstream),
|
|
203
|
+
requestPath: requestPathForLane(inbound, lane, upstream),
|
|
204
|
+
responsePath: responsePathForLane(inbound, lane, upstream),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function attemptPath(inbound: Protocol, lane: ProtocolLane, attempt: ProtocolTraceAttempt): ResolvedPath {
|
|
209
|
+
// A native Chat or Messages lane sends the ingress wire itself, whatever the adapter id says;
|
|
210
|
+
// Responses has no lane and follows its adapter.
|
|
211
|
+
const upstream = lane === "native" && inbound !== "responses" ? inbound : upstreamWireForAdapter(attempt.adapter);
|
|
212
|
+
const derived = pathFor(inbound, lane, upstream);
|
|
213
|
+
const mark = attemptMarks.get(attempt);
|
|
214
|
+
if (!mark) return derived;
|
|
215
|
+
return {
|
|
216
|
+
upstream,
|
|
217
|
+
mode: mark.mode,
|
|
218
|
+
requestPath: [...mark.requestPath],
|
|
219
|
+
responsePath: mark.responsePath ? [...mark.responsePath] : [...mark.requestPath].reverse(),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A reason code the path itself implies, so a trace is never reason-less. */
|
|
224
|
+
function pathReason(path: ResolvedPath): ProtocolReasonCode {
|
|
225
|
+
if (path.upstream === "other") return "upstream-other";
|
|
226
|
+
if (path.mode === "native") return "same-wire-native";
|
|
227
|
+
if (path.mode === "legacy-bridge") return "not-migrated";
|
|
228
|
+
return path.requestPath.includes("ir") ? "cross-wire-ir" : "cross-wire-codec";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The context fields the trace reads at finalize. */
|
|
232
|
+
export type ProtocolTraceContext = object & { inboundProtocol?: Protocol; provider?: string; model?: string };
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Derive the observed trace at finalize. `attempts` are the live attempt objects (the ones
|
|
236
|
+
* `markAttemptProtocolPath` was keyed by), not detached copies.
|
|
237
|
+
*
|
|
238
|
+
* - blocked mark: `blocked`, empty paths.
|
|
239
|
+
* - Responses inbound without a mark: the final adapter's wire decides the path.
|
|
240
|
+
* - Chat or Messages: the entry lane decides, through `path.ts`.
|
|
241
|
+
* - no attempt and no native or blocked mark: `undefined`; nothing is guessed.
|
|
242
|
+
*
|
|
243
|
+
* With a shadow plan input recorded, a disagreeing plan adds `planMismatch: true`.
|
|
244
|
+
*/
|
|
245
|
+
export function protocolTraceForRequest(
|
|
246
|
+
logCtx: ProtocolTraceContext,
|
|
247
|
+
attempts: readonly ProtocolTraceAttempt[] | undefined,
|
|
248
|
+
): ProtocolTraceV1 | undefined {
|
|
249
|
+
const trace = observedTrace(logCtx, attempts);
|
|
250
|
+
return trace ? withShadowPlan(logCtx, trace) : undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Compare the recorded dispatch plan with the observed trace. Pure and local: the planner reads
|
|
255
|
+
* only the recorded input. Any throw leaves the observed trace as it was.
|
|
256
|
+
*/
|
|
257
|
+
function withShadowPlan(logCtx: ProtocolTraceContext, trace: ProtocolTraceV1): ProtocolTraceV1 {
|
|
258
|
+
try {
|
|
259
|
+
const input = shadowInputs.get(logCtx);
|
|
260
|
+
if (!input) return trace;
|
|
261
|
+
const settled = {
|
|
262
|
+
...(typeof logCtx.provider === "string" ? { provider: logCtx.provider } : {}),
|
|
263
|
+
...(typeof logCtx.model === "string" ? { model: logCtx.model } : {}),
|
|
264
|
+
};
|
|
265
|
+
return shadowPlanMismatch(planProtocol(input), trace, settled) ? { ...trace, planMismatch: true } : trace;
|
|
266
|
+
} catch {
|
|
267
|
+
return trace;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function observedTrace(
|
|
272
|
+
logCtx: ProtocolTraceContext,
|
|
273
|
+
attempts: readonly ProtocolTraceAttempt[] | undefined,
|
|
274
|
+
): ProtocolTraceV1 | undefined {
|
|
275
|
+
try {
|
|
276
|
+
const mark = requestMarks.get(logCtx);
|
|
277
|
+
if (mark?.kind === "blocked") {
|
|
278
|
+
return {
|
|
279
|
+
v: PROTOCOL_TRACE_SCHEMA_VERSION,
|
|
280
|
+
inbound: mark.inbound,
|
|
281
|
+
mode: "blocked",
|
|
282
|
+
requestPath: [],
|
|
283
|
+
responsePath: [],
|
|
284
|
+
reasonCodes: mark.reasonCodes,
|
|
285
|
+
contractVersion: PROTOCOL_CONTRACT_VERSION,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
let inbound: Protocol;
|
|
289
|
+
let lane: ProtocolLane;
|
|
290
|
+
if (mark) {
|
|
291
|
+
inbound = mark.inbound;
|
|
292
|
+
lane = mark.lane;
|
|
293
|
+
} else if (logCtx.inboundProtocol === "responses") {
|
|
294
|
+
inbound = "responses";
|
|
295
|
+
lane = "native";
|
|
296
|
+
} else {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
const live = (attempts ?? []).filter(attempt => Number.isInteger(attempt.ordinal) && attempt.ordinal > 0);
|
|
300
|
+
const kept = live.slice(-PROTOCOL_DTO_LIMITS.attempts);
|
|
301
|
+
const attemptPaths = kept.map(attempt => ({ attempt, path: attemptPath(inbound, lane, attempt) }));
|
|
302
|
+
let final: ResolvedPath;
|
|
303
|
+
const last = attemptPaths.at(-1);
|
|
304
|
+
if (last) final = last.path;
|
|
305
|
+
else if (mark && lane === "native") final = pathFor(inbound, lane, inbound);
|
|
306
|
+
else return undefined;
|
|
307
|
+
|
|
308
|
+
const reasonCodes = boundedReasons([...(mark?.reasonCodes ?? []), pathReason(final)]);
|
|
309
|
+
const features = mark?.features ?? [];
|
|
310
|
+
const effects = features.length > 0
|
|
311
|
+
? featureEffectsForPath(inbound, final.requestPath, features).effects.slice(0, PROTOCOL_DTO_LIMITS.featureEffects)
|
|
312
|
+
: [];
|
|
313
|
+
const attemptTraces: ProtocolAttemptTraceV1[] = attemptPaths.map(({ attempt, path }) => ({
|
|
314
|
+
ordinal: attempt.ordinal,
|
|
315
|
+
upstream: path.upstream,
|
|
316
|
+
mode: path.mode,
|
|
317
|
+
requestPath: path.requestPath,
|
|
318
|
+
...(isReverse(path.requestPath, path.responsePath) ? {} : { responsePath: path.responsePath }),
|
|
319
|
+
}));
|
|
320
|
+
return {
|
|
321
|
+
v: PROTOCOL_TRACE_SCHEMA_VERSION,
|
|
322
|
+
inbound,
|
|
323
|
+
mode: final.mode,
|
|
324
|
+
upstream: final.upstream,
|
|
325
|
+
requestPath: final.requestPath,
|
|
326
|
+
responsePath: final.responsePath,
|
|
327
|
+
reasonCodes,
|
|
328
|
+
...(effects.length > 0 ? { featureEffects: effects.map(effect => ({ ...effect })) } : {}),
|
|
329
|
+
...(attemptTraces.length > 0 ? { attempts: attemptTraces } : {}),
|
|
330
|
+
contractVersion: PROTOCOL_CONTRACT_VERSION,
|
|
331
|
+
};
|
|
332
|
+
} catch {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// with nothing left to carry forward (see `isRegistryResidue`, issue #5066).
|
|
19
19
|
// Everything else in the row is left alone.
|
|
20
20
|
|
|
21
|
-
import { PROVIDER_REGISTRY } from "./registry";
|
|
21
|
+
import { PROVIDER_REGISTRY, normalizedProviderEndpoint } from "./registry";
|
|
22
22
|
import { MODEL_KEYED_RECORDS, MODEL_ID_LISTS, MODEL_ID_SCALARS, MODEL_NESTED_RECORDS } from "./model-rename-fields";
|
|
23
23
|
import { providerConfigSeed } from "./derive";
|
|
24
24
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
@@ -256,8 +256,8 @@ function providerStillMatchesRegistry(name: string, prov: OcxProviderConfig): bo
|
|
|
256
256
|
const choices = entry.baseUrlChoices?.map(choice => choice.baseUrl) ?? [];
|
|
257
257
|
const known = [entry.baseUrl, ...choices]
|
|
258
258
|
.filter((url): url is string => typeof url === "string")
|
|
259
|
-
.map(
|
|
260
|
-
return known.includes(prov.baseUrl
|
|
259
|
+
.map(normalizedProviderEndpoint);
|
|
260
|
+
return known.includes(normalizedProviderEndpoint(prov.baseUrl));
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
/** Guard against a stale rename: only apply when the registry actually seeds `to`. */
|
|
@@ -1263,7 +1263,12 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
1263
1263
|
modelReasoningEfforts: Object.fromEntries(DEEPSEEK_NATIVE_THINKING_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
|
|
1264
1264
|
modelReasoningEffortMap: Object.fromEntries(DEEPSEEK_NATIVE_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
|
|
1265
1265
|
modelSupportsReasoningSummaries: Object.fromEntries(DEEPSEEK_NATIVE_THINKING_MODELS.map(id => [id, true])),
|
|
1266
|
-
|
|
1266
|
+
// deepseek-v4-pro is absent from the live sets on purpose (retired upstream, requests
|
|
1267
|
+
// route to V4.1-Flash — see model-seeds.ts), but it stays SELECTABLE in configs that
|
|
1268
|
+
// already carry it, and what serves it is a thinking-mode model. A saved config with
|
|
1269
|
+
// the alias selected would otherwise serialize a bare continuation the upstream 400s
|
|
1270
|
+
// on (#5421). The preserve list governs replay, not advertisement.
|
|
1271
|
+
preserveReasoningContentModels: [...DEEPSEEK_NATIVE_THINKING_MODELS, "deepseek-v4-pro"],
|
|
1267
1272
|
// #4436: first-party deepseek-flash accepts native images on Chat and Responses.
|
|
1268
1273
|
// Keep unprobed compatibility aliases on the #88 sidecar path. This must be fixed
|
|
1269
1274
|
// here: router enrichment unions this list with saved config, so config cannot remove it.
|
|
@@ -117,6 +117,21 @@ import {
|
|
|
117
117
|
} from "./model-seeds";
|
|
118
118
|
|
|
119
119
|
export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
120
|
+
{
|
|
121
|
+
// Verified 2026-09-21: docs.typesafe.ai/introduction/quickstart and /api document the fixed
|
|
122
|
+
// endpoint, Bearer auth, jev-latest, and TYPESAFE_API_KEY; typesafe.ai/legal/mca permits API integration.
|
|
123
|
+
id: "jev",
|
|
124
|
+
label: "TypeSafe JEV",
|
|
125
|
+
baseUrl: "https://api.typesafe.ai/v1/systemone",
|
|
126
|
+
adapter: "jev-decision",
|
|
127
|
+
authKind: "key",
|
|
128
|
+
credentialOnly: true,
|
|
129
|
+
dashboardUrl: "https://console.typesafe.ai",
|
|
130
|
+
liveModels: false,
|
|
131
|
+
apiKeyValidation: "unknown",
|
|
132
|
+
preserveCustomDestination: true,
|
|
133
|
+
note: "TypeSafe JEV decision service for the optional JEV Combo strategy. This credential-only preset does not publish a directly routable model.",
|
|
134
|
+
},
|
|
120
135
|
{
|
|
121
136
|
id: "baseten",
|
|
122
137
|
label: "Baseten Model APIs",
|
|
@@ -112,6 +112,12 @@ export interface ProviderRegistryEntry {
|
|
|
112
112
|
apiKeyTransport?: OcxProviderConfig["apiKeyTransport"];
|
|
113
113
|
alias?: string;
|
|
114
114
|
authKind: ProviderAuthKind;
|
|
115
|
+
/**
|
|
116
|
+
* Credential preset for an auxiliary service rather than a model transport.
|
|
117
|
+
* Its adapter is an identity marker and is intentionally absent from the
|
|
118
|
+
* routable adapter registry.
|
|
119
|
+
*/
|
|
120
|
+
credentialOnly?: boolean;
|
|
115
121
|
codexAccountMode?: CodexAccountMode;
|
|
116
122
|
/** OAuth preset may explicitly honor a persisted API-key billing mode. */
|
|
117
123
|
allowKeyAuthOverride?: boolean;
|
|
@@ -81,7 +81,7 @@ export function registryModelServiceTierCapabilityApplies(
|
|
|
81
81
|
return guard === undefined || guard(provider.baseUrl);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function normalizedProviderEndpoint(value: string): string {
|
|
84
|
+
export function normalizedProviderEndpoint(value: string): string {
|
|
85
85
|
const trimmed = value.trim();
|
|
86
86
|
try {
|
|
87
87
|
const parsed = new URL(trimmed);
|
|
@@ -478,13 +478,14 @@ export class RemoteWorkspaceHub {
|
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
revokeDevice(deviceId: string): boolean {
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
481
|
+
const next = { ...this.state, devices: this.state.devices.filter(device => device.id !== deviceId) };
|
|
482
|
+
if (next.devices.length === this.state.devices.length) return false;
|
|
483
|
+
// Publish only after durable removal: a failed save must remain visible and retryable.
|
|
484
|
+
this.store.save(next);
|
|
485
|
+
this.state = next;
|
|
484
486
|
const connection = this.connections.get(deviceId);
|
|
485
487
|
this.connections.delete(deviceId);
|
|
486
488
|
connection?.close("remote workspace device was revoked");
|
|
487
|
-
this.store.save(this.state);
|
|
488
489
|
return true;
|
|
489
490
|
}
|
|
490
491
|
|
|
@@ -28,6 +28,11 @@ const FREEFORM_FALLBACK_KEYS: Readonly<Record<string, readonly string[]>> = {
|
|
|
28
28
|
apply_patch: ["patch", "content"],
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
/** The field names a tool's freeform wrapper may unwrap through, besides canonical `input`. */
|
|
32
|
+
export function freeformFallbackKeys(toolName: string): readonly string[] {
|
|
33
|
+
return FREEFORM_FALLBACK_KEYS[toolName] ?? [];
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
function stripMarkdownCodeFence(text: string, toolName: string): string {
|
|
32
37
|
if (toolName !== "exec" && toolName !== "apply_patch") return text;
|
|
33
38
|
const match = OUTER_MARKDOWN_CODE_FENCE.exec(text.trim());
|
|
@@ -46,7 +51,7 @@ export function unwrapFreeformToolInput(argumentsText: unknown, toolName = ""):
|
|
|
46
51
|
? stripMarkdownCodeFence(record.input, toolName)
|
|
47
52
|
: argumentsText;
|
|
48
53
|
}
|
|
49
|
-
const fallbackKeys =
|
|
54
|
+
const fallbackKeys = freeformFallbackKeys(toolName);
|
|
50
55
|
const candidates = fallbackKeys.filter(key => typeof record[key] === "string");
|
|
51
56
|
if (candidates.length === 1) {
|
|
52
57
|
return stripMarkdownCodeFence(record[candidates[0]] as string, toolName);
|
|
@@ -40,8 +40,10 @@ export function mayBecomeCodeModeShellInput(argumentsText: string, input: string
|
|
|
40
40
|
const head = input.trimStart();
|
|
41
41
|
if (head === "" || head.startsWith("{")) return true;
|
|
42
42
|
// Canonical JavaScript streams progressively; avoid reparsing its growing wrapper on every
|
|
43
|
-
// delta. The shared prefix scanner is bounded independently of the command's size.
|
|
44
|
-
|
|
43
|
+
// delta. The shared prefix scanner is bounded independently of the command's size. No fallback
|
|
44
|
+
// keys here: a fallback value is a shell command, not JavaScript, so only canonical `input`
|
|
45
|
+
// may short-circuit the parse below.
|
|
46
|
+
if (input === argumentsText || scanFreeformWrapper(argumentsText, []).kind === "input") return false;
|
|
45
47
|
try {
|
|
46
48
|
const args = JSON.parse(argumentsText);
|
|
47
49
|
// A fallback cmd value becomes visible only when the outer object closes. Do not emit
|