@bitkyc08/opencodex 2.76.0 → 2.77.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/gui/dist/assets/App-COdEeqSj.css +1 -0
- package/gui/dist/assets/App-DEpCJEP_.js +54 -0
- package/gui/dist/assets/{Tray-DjNwwrWS.js → Tray-gloIYNaW.js} +1 -1
- package/gui/dist/assets/index-Cd7ittMv.css +1 -0
- package/gui/dist/assets/index-DwJKFJm5.js +94 -0
- package/gui/dist/assets/quota-summary-DGcEJU3h.js +1 -0
- package/gui/dist/index.html +4 -3
- package/package.json +1 -1
- package/src/adapters/antigravity-refusal.ts +28 -0
- package/src/adapters/cursor/protobuf-request.ts +6 -0
- package/src/adapters/cursor/request-builder.ts +3 -0
- package/src/adapters/cursor/structured-output.ts +13 -0
- package/src/adapters/cursor/types.ts +2 -0
- package/src/adapters/ollama-native.ts +29 -2
- package/src/adapters/openai-responses/passthrough.ts +133 -103
- package/src/adapters/openai-responses/request-strips.ts +10 -4
- package/src/adapters/zed.ts +26 -4
- package/src/bridge/response-json.ts +14 -14
- package/src/bridge/sse.ts +15 -5
- package/src/chatgpt/app-server-shim/bundle-trust.ts +40 -10
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +3 -0
- package/src/claude/agents-inject.ts +4 -43
- package/src/claude/gateway-cache.ts +16 -13
- package/src/claude/inbound-model-options.ts +9 -2
- package/src/claude/intercept/cli-catalog.ts +27 -16
- package/src/claude/intercept/picker-bootstrap.ts +37 -16
- package/src/claude/intercept/picker-budget.ts +41 -0
- package/src/claude/outbound.ts +2 -0
- package/src/claude/subagent-force-status.ts +53 -0
- package/src/claude/subagent-model.ts +93 -0
- package/src/cli/account-api.ts +3 -0
- package/src/cli/account.ts +3 -1
- package/src/cli/agent.ts +10 -0
- package/src/cli/capabilities.ts +9 -0
- package/src/cli/chatgpt-command.ts +10 -7
- package/src/cli/claude.ts +22 -6
- package/src/cli/codex-shim-readiness.ts +89 -0
- package/src/cli/connect.ts +19 -0
- package/src/cli/dispatch.ts +3 -3
- package/src/cli/export-command.ts +20 -9
- package/src/cli/gui.ts +8 -3
- package/src/cli/help-catalog.ts +84 -0
- package/src/cli/help-models-context.ts +17 -0
- package/src/cli/help-navigation.ts +50 -0
- package/src/cli/help-recovery.ts +66 -0
- package/src/cli/help.ts +52 -8
- package/src/cli/models-runtime.ts +2 -1
- package/src/cli/opencode.ts +54 -10
- package/src/cli/provider.ts +3 -29
- package/src/cli/registry.ts +29 -5
- package/src/cli/root.ts +30 -9
- package/src/cli/runtime-api.ts +1 -1
- package/src/cli/tray-proxy.ts +3 -2
- package/src/clients/config-export/contracts.ts +100 -0
- package/src/clients/config-export/droid.ts +54 -2
- package/src/clients/config-export/kilo.ts +12 -2
- package/src/clients/config-export/model-metadata.ts +10 -4
- package/src/clients/config-export/reasoning-metadata.ts +47 -0
- package/src/clients/config-export.ts +46 -66
- package/src/clients/omo-role-models.ts +17 -3
- package/src/codex/account-credit-use.ts +4 -2
- package/src/codex/account-runtime-state.ts +21 -0
- package/src/codex/account-store.ts +11 -47
- package/src/codex/account-usability.ts +11 -8
- package/src/codex/auth-api/account-list.ts +73 -63
- package/src/codex/auth-api/main-account-probe.ts +25 -12
- package/src/codex/auth-api/pool-mode-gate.ts +6 -9
- package/src/codex/auth-api/pool-quota-probe.ts +2 -4
- package/src/codex/auth-api/reset-credit-service.ts +3 -2
- package/src/codex/auth-context.ts +30 -11
- package/src/codex/catalog/aggregation.ts +3 -0
- package/src/codex/catalog/model-hints.ts +2 -1
- package/src/codex/catalog/model-visibility.ts +6 -3
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +13 -4
- package/src/codex/catalog/retained-sync.ts +2 -1
- package/src/codex/chatgpt-refresh-failure.ts +56 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/main-account.ts +144 -11
- package/src/codex/quota-refresh-outcome.ts +17 -2
- package/src/codex/quota-types.ts +33 -1
- package/src/codex/quota.ts +56 -15
- package/src/codex/routing/cooldown-math.ts +10 -4
- package/src/codex/routing/selection.ts +11 -4
- package/src/codex/routing.ts +9 -9
- package/src/codex/shim-path-resolution.ts +133 -0
- package/src/codex/shim.ts +39 -39
- package/src/codex/subagent-model-fallback.ts +2 -1
- package/src/combos/failover.ts +82 -1
- package/src/combos/jev.ts +13 -3
- package/src/config/diagnostics.ts +4 -0
- package/src/config/load-degrade.ts +4 -0
- package/src/config/provider-validation.ts +3 -0
- package/src/config/schema/compaction-triggers.ts +8 -0
- package/src/config/schema/config-schema.ts +11 -0
- package/src/config/schema/leaf-validators.ts +5 -1
- package/src/config/subagent-models.ts +12 -0
- package/src/generated/compatibility-version.json +245 -153
- package/src/grok/session-identity.ts +33 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +4 -2
- package/src/integrations/state.ts +68 -6
- package/src/integrations/writer.ts +18 -4
- package/src/lib/gui-pair-capability.ts +17 -0
- package/src/lib/provider-client-headers.ts +62 -0
- package/src/oauth/anthropic-continuity.ts +65 -0
- package/src/oauth/anthropic-identity.ts +75 -0
- package/src/oauth/anthropic-routing.ts +3 -3
- package/src/oauth/anthropic.ts +7 -1
- package/src/oauth/generic-account-failover.ts +21 -0
- package/src/oauth/health.ts +32 -5
- package/src/oauth/index.ts +36 -27
- package/src/oauth/local-token-detect.ts +7 -0
- package/src/oauth/refresh-policy.ts +2 -0
- package/src/oauth/store.ts +55 -6
- package/src/oauth/types.ts +9 -0
- package/src/protocols/encoders/messages.ts +4 -3
- package/src/providers/antigravity-effort-families.ts +75 -0
- package/src/providers/antigravity-models.ts +54 -16
- package/src/providers/antigravity-wire-snapshot.ts +66 -0
- package/src/providers/derive.ts +2 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/new-model-policy.ts +48 -10
- package/src/providers/registry/entries-extended.ts +3 -0
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/model-seeds.ts +5 -0
- package/src/providers/registry/types.ts +4 -0
- package/src/responses/compaction.ts +19 -0
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +9 -0
- package/src/server/chat-completions.ts +10 -0
- package/src/server/claude-messages.ts +18 -11
- package/src/server/droid-reasoning-default.ts +50 -0
- package/src/server/github-copilot-responses-repair.ts +3 -6
- package/src/server/grok-responses-control-frame.ts +2 -2
- package/src/server/grok-responses-snapshot-repair.ts +4 -5
- package/src/server/gui-session.ts +33 -11
- package/src/server/index/claude-intercept-lifecycle.ts +4 -0
- package/src/server/index/serve-options.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +6 -129
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/integration-routes.ts +64 -6
- package/src/server/management/link-routes.ts +20 -13
- package/src/server/management/model-row-export-metadata.ts +150 -0
- package/src/server/management/model-rows.ts +148 -9
- package/src/server/management/route-registry.ts +4 -3
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/subagent-model-routes.ts +214 -0
- package/src/server/relay.ts +14 -44
- package/src/server/request-log-generation-window.ts +38 -0
- package/src/server/request-log.ts +13 -0
- package/src/server/responses/adapter-dispatch.ts +100 -1
- package/src/server/responses/codex-auth-error.ts +10 -2
- package/src/server/responses/combo-stream-preflight.ts +29 -3
- package/src/server/responses/compaction-routing.ts +10 -1
- package/src/server/responses/core-auth.ts +57 -26
- package/src/server/responses/core-codex-account.ts +6 -3
- package/src/server/responses/core-combo-failure.ts +5 -1
- package/src/server/responses/core-combo-native.ts +7 -1
- package/src/server/responses/core-combo.ts +18 -1
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/fetch-helpers.ts +9 -0
- package/src/server/responses/passthrough-delivery.ts +1 -1
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/request-prepare.ts +5 -0
- package/src/server/responses/request-spend.ts +30 -30
- package/src/server/responses-custom-tool-repair.ts +2 -2
- package/src/server/responses-hosted-image-display.ts +70 -9
- package/src/server/responses-terminal-repair.ts +15 -2
- package/src/server/sse-frame-buffer.ts +38 -36
- package/src/server/sse-payload-rewrite.ts +78 -49
- package/src/server/ws-bridge.ts +1 -11
- package/src/service/windows-taskxml.ts +1 -2
- package/src/service-manager-probe.ts +36 -2
- package/src/types/config.ts +4 -0
- package/src/types/provider.ts +18 -0
- package/src/update/npm-invocation.mjs +35 -3
- package/src/update/proxy-liveness-probe.mjs +38 -8
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +7 -0
- package/src/usage/summary.ts +82 -1
- package/src/web-search/loop.ts +11 -8
- package/src/web-search/run-turn-loop.ts +10 -7
- package/gui/dist/assets/App-CddorHdA.js +0 -51
- package/gui/dist/assets/App-DMudtISW.css +0 -1
- package/gui/dist/assets/index-Btze1I3T.css +0 -1
- package/gui/dist/assets/index-CvnY7E6i.js +0 -86
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +0 -1
package/src/cli/root.ts
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* the exit paths and the shim preflight, then returns the dispatchable head
|
|
9
9
|
* for the command switch in src/cli/index.ts.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { printFullUsage, printSubcommandUsage, printUsage, printVersion } from "./help";
|
|
12
12
|
import { parseReadyArgs, type ReadyArgs } from "./ready";
|
|
13
13
|
import { parseResolveArgs, type ResolveArgs } from "./resolve";
|
|
14
14
|
import { parseStopApproval } from "./stop-approval";
|
|
15
15
|
import { maybeAutoRestoreCodexShim } from "./codex-shim-autorestore";
|
|
16
|
+
import { findCommand } from "./registry";
|
|
17
|
+
import { printUnknownCommand } from "./help-recovery";
|
|
16
18
|
|
|
17
19
|
export interface CliHead {
|
|
18
20
|
kind: "version" | "help" | "ready" | "resolve" | "command";
|
|
@@ -20,6 +22,10 @@ export interface CliHead {
|
|
|
20
22
|
args: string[];
|
|
21
23
|
/** For kind "help": the subcommand whose usage should print, if any. */
|
|
22
24
|
helpTarget?: string;
|
|
25
|
+
/** Explicit multi-token help topic; never passed to command execution. */
|
|
26
|
+
helpPath?: string[];
|
|
27
|
+
/** Complete static reference requested from root help. */
|
|
28
|
+
helpAll?: boolean;
|
|
23
29
|
/** Present only for `ready`; undefined when the ready args failed to parse. */
|
|
24
30
|
readyArgs?: ReadyArgs;
|
|
25
31
|
/** Present only for `resolve`; undefined when the resolve args failed to parse. */
|
|
@@ -32,19 +38,26 @@ export function parseCliHead(argv: string[]): CliHead {
|
|
|
32
38
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
33
39
|
return { kind: "version", command, args };
|
|
34
40
|
}
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const boundary = argv.indexOf("--");
|
|
42
|
+
const prefix = boundary < 0 ? argv : argv.slice(0, boundary);
|
|
43
|
+
const rootHelp = command === undefined || command === "help" || command === "--help" || command === "-h";
|
|
44
|
+
const commandHelp = prefix[1] === "help";
|
|
45
|
+
if (rootHelp || commandHelp || prefix.slice(1).some(value => value === "--help" || value === "-h")) {
|
|
46
|
+
const topic = rootHelp ? prefix.slice(1)
|
|
47
|
+
: commandHelp ? [command!, ...prefix.slice(2)] : prefix;
|
|
48
|
+
// Options and their operands are not part of a help topic. Keep argv intact.
|
|
49
|
+
const option = topic.findIndex((value, index) => value.startsWith("-")
|
|
50
|
+
&& (index > 0 || (rootHelp && ["--all", "--help", "-h"].includes(value))));
|
|
51
|
+
const path = option < 0 ? topic : topic.slice(0, option);
|
|
37
52
|
return {
|
|
38
53
|
kind: "help",
|
|
39
54
|
command,
|
|
40
55
|
args,
|
|
41
|
-
...(
|
|
56
|
+
...(path.length ? { helpTarget: path[0] } : {}),
|
|
57
|
+
...(path.length > 1 ? { helpPath: path } : {}),
|
|
58
|
+
...(rootHelp && path.length === 0 && prefix.includes("--all") ? { helpAll: true } : {}),
|
|
42
59
|
};
|
|
43
60
|
}
|
|
44
|
-
if (command !== "help" && hasHelpFlag(args.slice(1))) {
|
|
45
|
-
// `ocx <cmd> --help|-h|help` prints that command's usage, not the full list.
|
|
46
|
-
return { kind: "help", command, args, helpTarget: command };
|
|
47
|
-
}
|
|
48
61
|
// P1: pre-parse `ocx ready` and reject invalid arguments with exit 64 BEFORE
|
|
49
62
|
// maybeAutoRestoreCodexShim (or any discovery/probe/filesystem-capable global
|
|
50
63
|
// preflight) runs. `ready --help` / `help ready` already exited above, so this
|
|
@@ -72,7 +85,11 @@ export async function runCli(argv: string[]): Promise<CliHead> {
|
|
|
72
85
|
printVersion();
|
|
73
86
|
process.exit(0);
|
|
74
87
|
case "help": {
|
|
75
|
-
if (head.
|
|
88
|
+
if (head.helpAll) printFullUsage();
|
|
89
|
+
else if (head.helpTarget) printSubcommandUsage(head.helpTarget, head.helpPath, {
|
|
90
|
+
fallbackToParent: head.command !== "help" && head.command !== "--help"
|
|
91
|
+
&& head.command !== "-h" && head.args[1] !== "help",
|
|
92
|
+
});
|
|
76
93
|
else printUsage();
|
|
77
94
|
process.exit(0);
|
|
78
95
|
}
|
|
@@ -104,6 +121,10 @@ export async function runCli(argv: string[]): Promise<CliHead> {
|
|
|
104
121
|
return head;
|
|
105
122
|
}
|
|
106
123
|
case "command":
|
|
124
|
+
if (head.command !== "internal" && !findCommand(head.command ?? "")) {
|
|
125
|
+
printUnknownCommand(head.command);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
107
128
|
if (head.command === "stop" && !parseStopApproval(head.args.slice(1)).ok) {
|
|
108
129
|
console.error("Usage: ocx stop [--json [--expect-pid <pid> --expect-port <port> --expect-hostname <host> --expect-config-home <home> --expect-cli-version <version> --expect-compatibility-token <hex>]]");
|
|
109
130
|
process.exit(64);
|
package/src/cli/runtime-api.ts
CHANGED
|
@@ -471,7 +471,7 @@ export async function readSecretBytes(
|
|
|
471
471
|
|
|
472
472
|
export function printData(value: unknown, wantsJson: boolean, lines?: string[]): void {
|
|
473
473
|
if (wantsJson || !lines) console.log(JSON.stringify(value, null, 2));
|
|
474
|
-
else for (const line of lines) console.log(line);
|
|
474
|
+
else for (const line of lines) console.log(terminalSafeText(line));
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
/**
|
package/src/cli/tray-proxy.ts
CHANGED
|
@@ -128,11 +128,12 @@ export function reobserveRestartReplacement(
|
|
|
128
128
|
deadlineAt: number,
|
|
129
129
|
observe: (end: number) => Promise<ProxyRestartDiscovery>,
|
|
130
130
|
): Promise<ProxyRestartDiscovery> {
|
|
131
|
-
const
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
const windowMs = Math.min(5_000, deadlineAt - now);
|
|
132
133
|
if (windowMs < 1_500) return Promise.resolve({
|
|
133
134
|
status: "uncertain", error: new Error("restart_reobserve_window_exhausted"),
|
|
134
135
|
});
|
|
135
|
-
const end =
|
|
136
|
+
const end = now + windowMs;
|
|
136
137
|
return pollReplacementDeparture(() => observe(end), previous, () => Date.now() + 750 < end, () => Bun.sleep(250));
|
|
137
138
|
}
|
|
138
139
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Shared client export contracts.
|
|
2
|
+
import { canonicalizeReasoningEfforts } from "../../reasoning-effort";
|
|
2
3
|
import type { OcxConfig } from "../../types";
|
|
3
4
|
import type { ConfigFormat } from "../../integrations/serialize";
|
|
4
5
|
|
|
6
|
+
export const DROID_DEFAULT_EFFORT_HEADER = "x-opencodex-droid-default-effort";
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* One entry opencodex owns inside a client's config: the JSON path to it and
|
|
7
10
|
* the value we put there.
|
|
@@ -38,6 +41,12 @@ export interface OpencodeCatalogModel {
|
|
|
38
41
|
id?: string;
|
|
39
42
|
contextWindow?: number;
|
|
40
43
|
maxTokens?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Authoritative input ceiling, distinct from `contextWindow` (GPT-5.6 advertises 922k
|
|
46
|
+
* usable input under a 1.05M window). Carried from the catalog row so a client that has a
|
|
47
|
+
* separate input limit never treats the whole window as prompt budget.
|
|
48
|
+
*/
|
|
49
|
+
maxInputTokens?: number;
|
|
41
50
|
displayName?: string;
|
|
42
51
|
/**
|
|
43
52
|
* Declared input modalities, carried verbatim from `/api/models`. Serialized as opencode's
|
|
@@ -54,6 +63,9 @@ export interface OpencodeCatalogModel {
|
|
|
54
63
|
* model-level setting — see {@link opencodeEffortVariants} for why.
|
|
55
64
|
*/
|
|
56
65
|
defaultReasoningEffort?: string;
|
|
66
|
+
supportsTools?: boolean;
|
|
67
|
+
supportsReasoning?: boolean;
|
|
68
|
+
supportsReasoningSummaries?: boolean;
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
/**
|
|
@@ -73,10 +85,94 @@ export interface ExportModel {
|
|
|
73
85
|
displayName?: string;
|
|
74
86
|
contextWindow?: number;
|
|
75
87
|
maxTokens?: number;
|
|
88
|
+
/** Authoritative input ceiling; optional and never guessed from the context window. */
|
|
89
|
+
maxInputTokens?: number;
|
|
76
90
|
inputModalities?: string[];
|
|
77
91
|
/** Optional effort ladder exported only to clients that support it. */
|
|
78
92
|
reasoningEfforts?: string[];
|
|
79
93
|
defaultReasoningEffort?: string;
|
|
94
|
+
supportsTools?: boolean;
|
|
95
|
+
supportsReasoning?: boolean;
|
|
96
|
+
supportsReasoningSummaries?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Effective, override-applied export metadata for one model row: what a client export should
|
|
101
|
+
* serialize, as distinct from the raw editor representation `/api/models` also carries.
|
|
102
|
+
*
|
|
103
|
+
* Custom rows are the reason this exists. Their row fields are the operator's stored
|
|
104
|
+
* OVERRIDES (an absent field means "not overridden", an empty ladder means "no rungs"), so a
|
|
105
|
+
* serializer reading only them drops every capability the model inherits from the provider,
|
|
106
|
+
* the registry, or the gathered catalog. The management row list resolves that projection
|
|
107
|
+
* from the same canonical sources routed rows use and attaches it additively — the raw
|
|
108
|
+
* override fields stay exactly what the editor wrote.
|
|
109
|
+
*/
|
|
110
|
+
export interface EffectiveModelExportMetadata {
|
|
111
|
+
contextWindow?: number;
|
|
112
|
+
/** Input ceiling; distinct from `contextWindow`, carried only when a source asserts one. */
|
|
113
|
+
maxInputTokens?: number;
|
|
114
|
+
/** Output ceiling (a catalog row's `maxOutputTokens`). */
|
|
115
|
+
maxTokens?: number;
|
|
116
|
+
inputModalities?: string[];
|
|
117
|
+
/** Effective ladder; `[]` is an explicit "no rungs" declaration and survives as `[]`. */
|
|
118
|
+
reasoningEfforts?: string[];
|
|
119
|
+
/**
|
|
120
|
+
* A default some canonical source declared — never one synthesized from the ladder's
|
|
121
|
+
* preference order, and only when it is a member of the effective ladder. An empty
|
|
122
|
+
* ladder suppresses it entirely: there is no rung to default to.
|
|
123
|
+
*/
|
|
124
|
+
defaultReasoningEffort?: string;
|
|
125
|
+
supportsTools?: boolean;
|
|
126
|
+
supportsReasoning?: boolean;
|
|
127
|
+
supportsReasoningSummaries?: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The per-row evidence the capability booleans are derived from. Only positive, canonical
|
|
132
|
+
* signals — no independent provider fact tables, no name-based inference.
|
|
133
|
+
*/
|
|
134
|
+
export interface ExportCapabilityEvidence {
|
|
135
|
+
native?: boolean;
|
|
136
|
+
reasoningEfforts?: readonly string[];
|
|
137
|
+
supportsReasoningSummaries?: boolean;
|
|
138
|
+
/** Normalized upstream capability names a catalog row carried (e.g. `"tools"`). */
|
|
139
|
+
capabilities?: readonly string[];
|
|
140
|
+
/** Provider-level parallel tool call opt-in for this model. */
|
|
141
|
+
parallelToolCalls?: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Tool support a canonical source positively asserts, or undefined when nothing is known.
|
|
146
|
+
*
|
|
147
|
+
* Mirrors the routing reader (`src/routing/capability.ts`): a catalog row without `"tools"`
|
|
148
|
+
* is UNKNOWN, never a negative — the row may simply not enumerate capabilities. `native`
|
|
149
|
+
* rows and the parallel-call opt-in are positive evidence on their own.
|
|
150
|
+
*/
|
|
151
|
+
export function knownToolsSupport(evidence: ExportCapabilityEvidence): boolean | undefined {
|
|
152
|
+
if (evidence.native === true) return true;
|
|
153
|
+
if (evidence.capabilities?.includes("tools") === true) return true;
|
|
154
|
+
if (evidence.parallelToolCalls === true) return true;
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Reasoning support a canonical source positively asserts, or undefined when nothing is
|
|
160
|
+
* known.
|
|
161
|
+
*
|
|
162
|
+
* Never `false` from a missing or empty effort ladder: an empty ladder says "no ADJUSTABLE
|
|
163
|
+
* effort", which is not the claim "cannot reason" — a model can reason at a fixed depth with
|
|
164
|
+
* no rung to select. Known evidence is a positive reasoning rung (the catalog's own statement
|
|
165
|
+
* that the model accepts reasoning parameters) or delivered reasoning summaries. `none` is
|
|
166
|
+
* the off sentinel, not a rung: a ladder of only `none` offers an off variant and asserts
|
|
167
|
+
* nothing about whether the model reasons, so it is not positive evidence on its own.
|
|
168
|
+
*/
|
|
169
|
+
export function knownReasoningSupport(evidence: ExportCapabilityEvidence): boolean | undefined {
|
|
170
|
+
if (evidence.supportsReasoningSummaries === true) return true;
|
|
171
|
+
if (evidence.reasoningEfforts !== undefined
|
|
172
|
+
&& canonicalizeReasoningEfforts(evidence.reasoningEfforts).some(effort => effort !== "none")) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
80
176
|
}
|
|
81
177
|
|
|
82
178
|
export interface ExportContext {
|
|
@@ -88,8 +184,12 @@ export interface ExportContext {
|
|
|
88
184
|
* admission from `apiKey` to the `x-opencodex-api-key` header.
|
|
89
185
|
*/
|
|
90
186
|
config?: OcxConfig;
|
|
187
|
+
droidReasoningDefaults?: DroidReasoningDefaults;
|
|
91
188
|
}
|
|
92
189
|
|
|
190
|
+
/** Namespaced model selector to one of that model's declared reasoning efforts. */
|
|
191
|
+
export type DroidReasoningDefaults = Record<string, string>;
|
|
192
|
+
|
|
93
193
|
export type ExportClientId =
|
|
94
194
|
| "opencode"
|
|
95
195
|
| "pi"
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { win32, join } from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { exportPresentationLabel } from "../model-presentation";
|
|
4
|
-
import type { ExportContext, ManagedContribution, OpencodeLaunchEnv } from "./contracts";
|
|
4
|
+
import type { DroidReasoningDefaults, ExportContext, ExportModel, ManagedContribution, OpencodeLaunchEnv } from "./contracts";
|
|
5
5
|
import { normalizeExportModels } from "./model-metadata";
|
|
6
|
-
import { formatSelectorConjunction } from "../../integrations/merge";
|
|
6
|
+
import { formatSelectorConjunction, readPath } from "../../integrations/merge";
|
|
7
|
+
|
|
8
|
+
import { DROID_DEFAULT_EFFORT_HEADER } from "./contracts";
|
|
7
9
|
|
|
8
10
|
/** Factory personal settings: https://docs.factory.ai/model-independence/byok */
|
|
9
11
|
export interface DroidModelEntry {
|
|
@@ -12,6 +14,7 @@ export interface DroidModelEntry {
|
|
|
12
14
|
baseUrl: string;
|
|
13
15
|
provider: "generic-chat-completion-api";
|
|
14
16
|
noImageSupport: boolean;
|
|
17
|
+
extraHeaders?: Record<string, string>;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export interface DroidGeneratedConfig { customModels: DroidModelEntry[] }
|
|
@@ -43,11 +46,60 @@ function buildDroidRows(ctx: ExportContext): Array<{ row: DroidModelEntry; selec
|
|
|
43
46
|
baseUrl: ctx.baseUrl,
|
|
44
47
|
provider: "generic-chat-completion-api",
|
|
45
48
|
noImageSupport: !model.inputModalities?.includes("image"),
|
|
49
|
+
...(ctx.droidReasoningDefaults && Object.hasOwn(ctx.droidReasoningDefaults, model.namespaced)
|
|
50
|
+
? { extraHeaders: { [DROID_DEFAULT_EFFORT_HEADER]: ctx.droidReasoningDefaults[model.namespaced]! } }
|
|
51
|
+
: {}),
|
|
46
52
|
} });
|
|
47
53
|
}
|
|
48
54
|
return rows;
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
export function droidReasoningModels(models: readonly ExportModel[]): Array<{ model: string; label: string; efforts: string[] }> {
|
|
58
|
+
const effortsByModel = new Map(normalizeExportModels(models).map(model => [model.namespaced, model.reasoningEfforts ?? []]));
|
|
59
|
+
return buildDroidRows({ baseUrl: "", models }).map(({ row }) => ({
|
|
60
|
+
model: row.model,
|
|
61
|
+
label: row.displayName.replace(/^OpenCodex: /, ""),
|
|
62
|
+
efforts: effortsByModel.get(row.model) ?? [],
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function droidDefaultsFromOwnedRows(
|
|
67
|
+
ctx: ExportContext,
|
|
68
|
+
document: unknown,
|
|
69
|
+
fragmentPaths: readonly (readonly string[])[],
|
|
70
|
+
): DroidReasoningDefaults {
|
|
71
|
+
const exportableModels = new Map(droidReasoningModels(ctx.models).map(model => [model.model, model.efforts]));
|
|
72
|
+
const defaults: DroidReasoningDefaults = {};
|
|
73
|
+
for (const path of fragmentPaths) {
|
|
74
|
+
if (path.length !== 2 || path[0] !== "customModels") continue;
|
|
75
|
+
const value = readPath(document, path);
|
|
76
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
77
|
+
const current = value as Record<string, unknown>;
|
|
78
|
+
if (typeof current.model !== "string" || !exportableModels.has(current.model)) continue;
|
|
79
|
+
const headers = current.extraHeaders;
|
|
80
|
+
if (!headers || typeof headers !== "object" || Array.isArray(headers)) continue;
|
|
81
|
+
const header = headers as Record<string, unknown>;
|
|
82
|
+
const effort = header[DROID_DEFAULT_EFFORT_HEADER];
|
|
83
|
+
if (typeof effort !== "string" || !exportableModels.get(current.model)?.includes(effort)) continue;
|
|
84
|
+
defaults[current.model] = effort;
|
|
85
|
+
}
|
|
86
|
+
return defaults;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function validateDroidReasoningDefaults(
|
|
90
|
+
models: readonly ExportModel[],
|
|
91
|
+
defaults: unknown,
|
|
92
|
+
): string | null {
|
|
93
|
+
if (!defaults || typeof defaults !== "object" || Array.isArray(defaults)) return "droidReasoningDefaults must be an object";
|
|
94
|
+
const addressable = new Map(droidReasoningModels(models).map(model => [model.model, model.efforts]));
|
|
95
|
+
for (const [model, effort] of Object.entries(defaults)) {
|
|
96
|
+
const efforts = addressable.get(model);
|
|
97
|
+
if (!efforts) return `model ${model} is not currently exportable to Droid`;
|
|
98
|
+
if (typeof effort !== "string" || !efforts.includes(effort)) return `effort for ${model} is not declared by that model`;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
51
103
|
export function buildDroidClientConfig(ctx: ExportContext): DroidGeneratedConfig {
|
|
52
104
|
const rows = buildDroidRows(ctx);
|
|
53
105
|
if (ctx.models.length > 0 && rows.length === 0) {
|
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
normalizeExportModels,
|
|
15
15
|
opencodeModelCapabilities,
|
|
16
16
|
outputBudgetFor,
|
|
17
|
+
inputBudgetFor,
|
|
17
18
|
proxyAdmissionHeaders,
|
|
18
19
|
singleFragment,
|
|
19
20
|
} from "./model-metadata";
|
|
21
|
+
import { legacyReasoningMetadata, type LegacyEffortVariant } from "./reasoning-metadata";
|
|
20
22
|
|
|
21
23
|
export const KILO_CONFIG_CANDIDATES = [
|
|
22
24
|
"kilo.jsonc",
|
|
@@ -28,9 +30,14 @@ export const KILO_CONFIG_CANDIDATES = [
|
|
|
28
30
|
|
|
29
31
|
export interface KiloModelEntry {
|
|
30
32
|
name: string;
|
|
31
|
-
limit?: { context: number; output: number };
|
|
33
|
+
limit?: { context: number; input?: number; output: number };
|
|
32
34
|
attachment?: boolean;
|
|
33
35
|
modalities?: { input: string[]; output: string[] };
|
|
36
|
+
tool_call?: boolean;
|
|
37
|
+
reasoning?: boolean;
|
|
38
|
+
interleaved?: { field: "reasoning_content" };
|
|
39
|
+
options?: { reasoningEffort: string };
|
|
40
|
+
variants?: Record<string, LegacyEffortVariant>;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export interface KiloProviderBlock {
|
|
@@ -77,11 +84,14 @@ function kiloProviderBlock(ctx: ExportContext): KiloProviderBlock {
|
|
|
77
84
|
const config = ctx.config ?? OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG;
|
|
78
85
|
const models: Record<string, KiloModelEntry> = {};
|
|
79
86
|
for (const model of normalizeExportModels(ctx.models)) {
|
|
80
|
-
const entry: KiloModelEntry = { name: exportModelLabel(model) };
|
|
87
|
+
const entry: KiloModelEntry = { name: exportModelLabel(model), ...legacyReasoningMetadata(model) };
|
|
81
88
|
const context = authoritativeContextWindow(model.contextWindow);
|
|
82
89
|
if (context !== undefined) {
|
|
83
90
|
entry.limit = { context, output: outputBudgetFor(context, model) };
|
|
91
|
+
const input = inputBudgetFor(context, model);
|
|
92
|
+
if (input !== undefined) entry.limit.input = input;
|
|
84
93
|
}
|
|
94
|
+
if (typeof model.supportsTools === "boolean") entry.tool_call = model.supportsTools;
|
|
85
95
|
const capabilities = opencodeModelCapabilities(model.inputModalities);
|
|
86
96
|
if (capabilities) {
|
|
87
97
|
entry.attachment = capabilities.attachment;
|
|
@@ -29,6 +29,12 @@ export function outputBudgetFor(context: number, model: OpencodeCatalogModel): n
|
|
|
29
29
|
return Math.min(limit, context);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** Input limits are optional: unlike output, a missing limit needs no schema fallback. */
|
|
33
|
+
export function inputBudgetFor(context: number, model: OpencodeCatalogModel): number | undefined {
|
|
34
|
+
const limit = authoritativeContextWindow(model.maxInputTokens);
|
|
35
|
+
return limit === undefined ? undefined : Math.min(limit, context);
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* Modalities a given client's schema will actually accept.
|
|
34
40
|
*
|
|
@@ -89,12 +95,12 @@ export function inputModalitiesForClient(
|
|
|
89
95
|
const OPENCODE_INPUT_MODALITIES: ReadonlySet<string> = new Set(["text", "audio", "image", "video", "pdf"]);
|
|
90
96
|
|
|
91
97
|
/**
|
|
92
|
-
*
|
|
98
|
+
* Legacy per-model modality fields for one catalog row, or `undefined` when the row
|
|
93
99
|
* declares nothing.
|
|
94
100
|
*
|
|
95
|
-
* `undefined` rather than `{ input: ["text"] }`:
|
|
96
|
-
*
|
|
97
|
-
*
|
|
101
|
+
* `undefined` rather than guessing `{ input: ["text"] }`: unknown capabilities leave the
|
|
102
|
+
* client's fallback assumptions in place. V2 translates these lists into native capability
|
|
103
|
+
* fields; legacy clients also receive the attachment flag. A declared list is carried across as-is, so
|
|
98
104
|
* an audio-only row keeps `attachment: true` instead of being rewritten to text it cannot
|
|
99
105
|
* read — the same call Pi's exporter makes, in the opposite direction.
|
|
100
106
|
*/
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { canonicalizeReasoningEfforts, isDeclaredReasoningEffort } from "../../reasoning-effort";
|
|
2
|
+
import { knownReasoningSupport, type OpencodeCatalogModel } from "./contracts";
|
|
3
|
+
|
|
4
|
+
/** Unknown and explicitly empty ladders have different client-side meanings. */
|
|
5
|
+
export function exportReasoningEfforts(model: OpencodeCatalogModel): string[] | undefined {
|
|
6
|
+
return model.reasoningEfforts === undefined ? undefined : canonicalizeReasoningEfforts(model.reasoningEfforts);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Carry an authoritative default, never invent one or revive a cleared ladder. */
|
|
10
|
+
export function exportDefaultReasoningEffort(model: OpencodeCatalogModel): string | undefined {
|
|
11
|
+
const effort = model.defaultReasoningEffort;
|
|
12
|
+
if (typeof effort !== "string" || !isDeclaredReasoningEffort(effort)) return undefined;
|
|
13
|
+
const efforts = exportReasoningEfforts(model);
|
|
14
|
+
return efforts === undefined || efforts.includes(effort) ? effort : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type LegacyEffortVariant = { reasoningEffort: string } | { disabled: true };
|
|
18
|
+
|
|
19
|
+
/** Shared V1-family controls. A disabled-only map suppresses client-invented effort ladders. */
|
|
20
|
+
export function legacyReasoningMetadata(model: OpencodeCatalogModel): {
|
|
21
|
+
reasoning?: boolean;
|
|
22
|
+
options?: { reasoningEffort: string };
|
|
23
|
+
interleaved?: { field: "reasoning_content" };
|
|
24
|
+
variants?: Record<string, LegacyEffortVariant>;
|
|
25
|
+
} {
|
|
26
|
+
const efforts = exportReasoningEfforts(model);
|
|
27
|
+
const reasoning = model.supportsReasoning ?? knownReasoningSupport({
|
|
28
|
+
...model, reasoningEfforts: efforts,
|
|
29
|
+
});
|
|
30
|
+
const defaultEffort = exportDefaultReasoningEffort(model);
|
|
31
|
+
// Both legacy runtimes merge generated variants with config, then filter disabled IDs.
|
|
32
|
+
// An unrelated sentinel leaves their generated choices alive. Disable the actual IDs
|
|
33
|
+
// instead (live-tested on Kilo 7.8.3, checked against stock OpenCode V1's merge).
|
|
34
|
+
const suppressed: Record<string, LegacyEffortVariant> = Object.fromEntries(
|
|
35
|
+
["none", "minimal", "low", "medium", "high", "xhigh", "max"].map(id => [id, { disabled: true }]),
|
|
36
|
+
);
|
|
37
|
+
const variants = efforts?.length
|
|
38
|
+
? Object.fromEntries(efforts.map(effort => [effort, { reasoningEffort: effort }]))
|
|
39
|
+
: reasoning === true ? suppressed : undefined;
|
|
40
|
+
return {
|
|
41
|
+
...(typeof reasoning === "boolean" ? { reasoning } : {}),
|
|
42
|
+
...(defaultEffort !== undefined ? { options: { reasoningEffort: defaultEffort } } : {}),
|
|
43
|
+
...(reasoning === true || model.supportsReasoningSummaries === true
|
|
44
|
+
? { interleaved: { field: "reasoning_content" } } : {}),
|
|
45
|
+
...(variants !== undefined ? { variants } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|