@bitkyc08/opencodex 2.11.1 → 2.12.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 +1 -1
- package/bin/ocx.mjs +4 -0
- package/gui/dist/assets/index-BNVYzdn0.css +1 -0
- package/gui/dist/assets/index-Cw1_mxO8.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/command-code.ts +94 -5
- package/src/adapters/cursor/live-transport.ts +14 -1
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google.ts +24 -7
- package/src/adapters/openai-chat.ts +233 -305
- package/src/adapters/openai-responses.ts +62 -0
- package/src/adapters/tool-catalog-nudge.ts +2 -11
- package/src/claude/outbound.ts +42 -14
- package/src/cli/claude.ts +61 -9
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +8 -1
- package/src/cli/config-command.ts +4 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/help.ts +15 -0
- package/src/cli/index.ts +27 -11
- package/src/cli/lab.ts +367 -0
- package/src/cli/provider.ts +9 -1
- package/src/clients/config-export.ts +100 -3
- package/src/codex/app-server-processes.ts +11 -8
- package/src/codex/auth-api.ts +92 -28
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/catalog/metadata.ts +7 -3
- package/src/codex/catalog/provider-fetch.ts +191 -47
- package/src/codex/catalog.ts +2 -2
- package/src/codex/history-job.ts +83 -18
- package/src/codex/history-migration-guardian.ts +9 -16
- package/src/codex/history-provider.ts +178 -1
- package/src/codex/history-transition.ts +5 -5
- package/src/codex/history-worker.ts +22 -2
- package/src/codex/native-profile-store.ts +1 -1
- package/src/codex/plan.ts +15 -0
- package/src/codex/quota.ts +6 -6
- package/src/codex/routing.ts +3 -3
- package/src/codex/subagent-model-fallback.ts +246 -22
- package/src/codex/user-identity.ts +63 -12
- package/src/config.ts +265 -32
- package/src/images/loop.ts +3 -1
- package/src/images/plan.ts +23 -13
- package/src/integrations/omp-yaml-source.ts +225 -0
- package/src/integrations/ownership.ts +5 -5
- package/src/integrations/state.ts +45 -6
- package/src/integrations/writer.ts +38 -3
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +24 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +160 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +57 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +191 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +228 -0
- package/src/lab/events/validate.ts +780 -0
- package/src/lab/index.ts +36 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +241 -0
- package/src/lab/ledger/store.ts +263 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +311 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +116 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +344 -0
- package/src/lab/observe/from-live.ts +103 -0
- package/src/lab/paths.ts +65 -0
- package/src/lab/projection/rebuild.ts +477 -0
- package/src/lab/projection/schema.ts +122 -0
- package/src/lab/projection/verdicts.ts +438 -0
- package/src/lab/projection/verification.ts +332 -0
- package/src/lab/query/catalog.ts +72 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/index.ts +34 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +81 -0
- package/src/lab/subject/route-subject.ts +73 -0
- package/src/lib/bounded-body.ts +104 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +45 -0
- package/src/lib/pinned-http.ts +36 -7
- package/src/oauth/index.ts +20 -1
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/login-cli.ts +38 -4
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/codex-capacity.ts +7 -4
- package/src/providers/command-code-efforts.ts +5 -2
- package/src/providers/derive.ts +105 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/openai-tiers.ts +59 -3
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/quota.ts +67 -21
- package/src/providers/registry.ts +118 -5
- package/src/router.ts +15 -1
- package/src/routing/analytics.ts +8 -7
- package/src/routing/evaluator.ts +51 -5
- package/src/routing/profile.ts +10 -1
- package/src/routing/trace.ts +42 -6
- package/src/server/auth-cors.ts +15 -0
- package/src/server/background-lifecycle.ts +169 -0
- package/src/server/chat-completions.ts +18 -3
- package/src/server/claude-messages.ts +1 -0
- package/src/server/images.ts +78 -11
- package/src/server/index.ts +72 -18
- package/src/server/lifecycle.ts +6 -0
- package/src/server/live.ts +5 -1
- package/src/server/management/agent-settings-routes.ts +41 -24
- package/src/server/management/body.ts +6 -0
- package/src/server/management/config-routes.ts +51 -8
- package/src/server/management/lab-routes.ts +388 -0
- package/src/server/management/logs-usage-routes.ts +19 -1
- package/src/server/management/model-rows.ts +25 -11
- package/src/server/management/native-integration-routes.ts +3 -3
- package/src/server/management/provider-routes.ts +5 -0
- package/src/server/management/shared.ts +8 -1
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management/vision-sidecar-options.ts +125 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/request-decompress.ts +106 -5
- package/src/server/responses/collaboration.ts +4 -1
- package/src/server/responses/core.ts +117 -31
- package/src/server/responses-json-events.ts +50 -12
- package/src/server/search.ts +23 -7
- package/src/server/ws-bridge.ts +3 -1
- package/src/service.ts +40 -20
- package/src/types.ts +74 -0
- package/src/update/job.ts +10 -2
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +9 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/describe.ts +2 -2
- package/src/vision/eligibility.ts +229 -0
- package/src/vision/index.ts +20 -2
- package/src/web-search/executor.ts +2 -2
- package/src/web-search/index.ts +4 -2
- package/src/web-search/loop.ts +3 -1
- package/src/web-search/parse.ts +72 -3
- package/gui/dist/assets/index-BA1dgo4q.css +0 -1
- package/gui/dist/assets/index-DDZpgzKk.js +0 -70
package/src/cli/lab.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ocx lab` — read-only Compatibility Lab inspection (CL-04).
|
|
3
|
+
*
|
|
4
|
+
* Local SQLite projection reads; no daemon, network, probes, or rebuilds.
|
|
5
|
+
*/
|
|
6
|
+
import { getConfigDir } from "../config";
|
|
7
|
+
import {
|
|
8
|
+
ARTIFACT_CLASSES,
|
|
9
|
+
EVIDENCE_LAYERS,
|
|
10
|
+
EVENT_KINDS,
|
|
11
|
+
EXECUTION_MODES,
|
|
12
|
+
OUTCOMES,
|
|
13
|
+
VERDICTS,
|
|
14
|
+
type ArtifactClass,
|
|
15
|
+
type CompatibilityVerdict,
|
|
16
|
+
type EvidenceLayer,
|
|
17
|
+
type ExecutionMode,
|
|
18
|
+
type LabEventKind,
|
|
19
|
+
type ObservationOutcome,
|
|
20
|
+
} from "../lab/constants";
|
|
21
|
+
import {
|
|
22
|
+
InvalidCursorError,
|
|
23
|
+
LabProjectionIncompatibleError,
|
|
24
|
+
LabProjectionUnavailableError,
|
|
25
|
+
queryLabArtifactByDigest,
|
|
26
|
+
queryLabArtifacts,
|
|
27
|
+
queryLabCatalogEntries,
|
|
28
|
+
queryLabEventById,
|
|
29
|
+
queryLabEvents,
|
|
30
|
+
queryLabObservations,
|
|
31
|
+
queryLabStatus,
|
|
32
|
+
queryLabSubjectById,
|
|
33
|
+
queryLabSubjects,
|
|
34
|
+
queryLabVerdicts,
|
|
35
|
+
} from "../lab/query";
|
|
36
|
+
import {
|
|
37
|
+
CliUsageError,
|
|
38
|
+
RuntimeApiError,
|
|
39
|
+
printData,
|
|
40
|
+
rejectArgs,
|
|
41
|
+
runCliAction,
|
|
42
|
+
takeFlag,
|
|
43
|
+
takeIntegerOption,
|
|
44
|
+
takeOption,
|
|
45
|
+
} from "./runtime-api";
|
|
46
|
+
|
|
47
|
+
const USAGE = `Usage:
|
|
48
|
+
ocx lab status [--json]
|
|
49
|
+
ocx lab verdicts [--subject <id>] [--layer <layer>] [--suite <id>] [--verdict <v>] [--from <ms>] [--to <ms>] [--limit <n>] [--cursor <c>] [--json]
|
|
50
|
+
ocx lab subjects [--kind <kind>] [--limit <n>] [--cursor <c>] [--json]
|
|
51
|
+
ocx lab subject <subjectId> [--json]
|
|
52
|
+
ocx lab observations [--subject <id>] [--layer <layer>] [--suite <id>] [--scenario <id>] [--outcome <o>] [--execution-mode <m>] [--from <ms>] [--to <ms>] [--limit <n>] [--cursor <c>] [--json]
|
|
53
|
+
ocx lab events [--event-kind <k>] [--subject <id>] [--from <ms>] [--to <ms>] [--excluded <true|false>] [--limit <n>] [--cursor <c>] [--json]
|
|
54
|
+
ocx lab event <eventId> [--json]
|
|
55
|
+
ocx lab artifacts [--status <s>] [--artifact-class <c>] [--limit <n>] [--cursor <c>] [--json]
|
|
56
|
+
ocx lab artifact <digest> [--json]
|
|
57
|
+
ocx lab catalog [--layer <layer>] [--suite <id>] [--json]`;
|
|
58
|
+
|
|
59
|
+
const ARTIFACT_STATUSES = ["present", "corrupt", "purged_unavailable"] as const;
|
|
60
|
+
type ArtifactStatus = (typeof ARTIFACT_STATUSES)[number];
|
|
61
|
+
|
|
62
|
+
export interface LabCliDeps {
|
|
63
|
+
configDir?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class LabStateError extends RuntimeApiError {
|
|
67
|
+
constructor(message: string) {
|
|
68
|
+
super(message, 503, null);
|
|
69
|
+
this.name = "LabStateError";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function labErrorMessage(err: unknown): string {
|
|
74
|
+
if (err instanceof LabProjectionUnavailableError) return "lab projection is not available";
|
|
75
|
+
if (err instanceof LabProjectionIncompatibleError) return "lab projection schema or spec version is incompatible";
|
|
76
|
+
if (err instanceof InvalidCursorError) return "invalid cursor";
|
|
77
|
+
return "lab read failed";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function takeEnumOption<T extends string>(
|
|
81
|
+
args: string[],
|
|
82
|
+
flag: string,
|
|
83
|
+
values: readonly T[],
|
|
84
|
+
message: string,
|
|
85
|
+
): T | undefined {
|
|
86
|
+
const raw = takeOption(args, flag);
|
|
87
|
+
if (raw === undefined) return undefined;
|
|
88
|
+
if (!(values as readonly string[]).includes(raw)) {
|
|
89
|
+
throw new CliUsageError(message, USAGE);
|
|
90
|
+
}
|
|
91
|
+
return raw as T;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function assertRange(from: number | undefined, to: number | undefined): void {
|
|
95
|
+
if (from !== undefined && to !== undefined && from > to) {
|
|
96
|
+
throw new CliUsageError("--from must not be greater than --to", USAGE);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function appendPaginationHint(
|
|
101
|
+
lines: string[],
|
|
102
|
+
page: { hasMore: boolean; nextCursor?: string | null },
|
|
103
|
+
): void {
|
|
104
|
+
if (page.hasMore) lines.push(`(more available; pass --cursor ${page.nextCursor ?? ""})`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function statusSummary(status: ReturnType<typeof queryLabStatus>): string[] {
|
|
108
|
+
if (!status.projectionAvailable) {
|
|
109
|
+
if (status.projectionIncompatible) return ["Lab projection: incompatible"];
|
|
110
|
+
return ["Lab projection: unavailable"];
|
|
111
|
+
}
|
|
112
|
+
return [
|
|
113
|
+
"Lab projection: available",
|
|
114
|
+
`SQLite schema: ${status.sqliteSchemaVersion}`,
|
|
115
|
+
`Projection spec: ${status.projectionSpecVersion}`,
|
|
116
|
+
`Built at: ${status.builtAtMs}`,
|
|
117
|
+
`Events: ${status.eventCount} | Subjects: ${status.subjectCount} | Observations: ${status.observationCount}`,
|
|
118
|
+
`Claims: ${status.claimCount} | Verdicts: ${status.verdictCount} | Artifacts: ${status.artifactCount}`,
|
|
119
|
+
`Corruption rows: ${status.corruptionCount}`,
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function verdictLines(page: ReturnType<typeof queryLabVerdicts>): string[] {
|
|
124
|
+
const lines = page.items.map((v) =>
|
|
125
|
+
`${v.verdict} ${v.evidenceLayer} ${v.suiteId} subject=${v.subjectId} asOf=${v.asOf}`,
|
|
126
|
+
);
|
|
127
|
+
appendPaginationHint(lines, page);
|
|
128
|
+
return lines.length > 0 ? lines : ["No verdicts"];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function subjectListLines(page: ReturnType<typeof queryLabSubjects>): string[] {
|
|
132
|
+
const lines = page.items.map((s) => `${s.subjectId} (${s.subjectKind})`);
|
|
133
|
+
appendPaginationHint(lines, page);
|
|
134
|
+
return lines.length > 0 ? lines : ["No subjects"];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function observationLines(page: ReturnType<typeof queryLabObservations>): string[] {
|
|
138
|
+
const lines = page.items.map((o) =>
|
|
139
|
+
`${o.outcome} ${o.evidenceLayer} ${o.scenarioId} event=${o.eventId} completed=${o.completedAt}`,
|
|
140
|
+
);
|
|
141
|
+
appendPaginationHint(lines, page);
|
|
142
|
+
return lines.length > 0 ? lines : ["No observations"];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function eventListLines(page: ReturnType<typeof queryLabEvents>): string[] {
|
|
146
|
+
const lines = page.items.map((e) =>
|
|
147
|
+
`${e.eventKind} ${e.eventId} recorded=${e.recordedAt}${e.excluded ? " excluded" : ""}`,
|
|
148
|
+
);
|
|
149
|
+
appendPaginationHint(lines, page);
|
|
150
|
+
return lines.length > 0 ? lines : ["No events"];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function artifactLines(page: ReturnType<typeof queryLabArtifacts>): string[] {
|
|
154
|
+
const lines = page.items.map((a) => `${a.status} ${a.digest} class=${a.artifactClass ?? "unknown"}`);
|
|
155
|
+
appendPaginationHint(lines, page);
|
|
156
|
+
return lines.length > 0 ? lines : ["No artifacts"];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function catalogLines(scenarios: ReturnType<typeof queryLabCatalogEntries>): string[] {
|
|
160
|
+
const lines = scenarios.map((s) =>
|
|
161
|
+
`${s.evidenceLayer} ${s.suiteId} ${s.scenarioId} digest=${s.scenarioManifestDigest.slice(0, 12)}…`,
|
|
162
|
+
);
|
|
163
|
+
return lines.length > 0 ? lines : ["No catalog scenarios"];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): Promise<number> {
|
|
167
|
+
return runCliAction(async () => {
|
|
168
|
+
const configDir = deps.configDir ?? getConfigDir();
|
|
169
|
+
const argvCopy = [...argv];
|
|
170
|
+
const wantsJson = takeFlag(argvCopy, "--json");
|
|
171
|
+
const [sub = "status", ...rest] = argvCopy;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
switch (sub) {
|
|
175
|
+
case "status": {
|
|
176
|
+
rejectArgs(rest, USAGE);
|
|
177
|
+
const status = queryLabStatus(configDir);
|
|
178
|
+
printData(status, wantsJson, statusSummary(status));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
case "verdicts": {
|
|
182
|
+
const subjectId = takeOption(rest, "--subject");
|
|
183
|
+
const layer = takeEnumOption<EvidenceLayer>(
|
|
184
|
+
rest,
|
|
185
|
+
"--layer",
|
|
186
|
+
EVIDENCE_LAYERS,
|
|
187
|
+
"--layer must be a supported evidence layer",
|
|
188
|
+
);
|
|
189
|
+
const suiteId = takeOption(rest, "--suite");
|
|
190
|
+
const verdict = takeEnumOption<CompatibilityVerdict>(
|
|
191
|
+
rest,
|
|
192
|
+
"--verdict",
|
|
193
|
+
VERDICTS,
|
|
194
|
+
"--verdict must be a supported compatibility verdict",
|
|
195
|
+
);
|
|
196
|
+
const from = takeIntegerOption(rest, "--from", { min: 0 });
|
|
197
|
+
const to = takeIntegerOption(rest, "--to", { min: 0 });
|
|
198
|
+
assertRange(from, to);
|
|
199
|
+
const limit = takeIntegerOption(rest, "--limit", { min: 1 });
|
|
200
|
+
const cursor = takeOption(rest, "--cursor");
|
|
201
|
+
rejectArgs(rest, USAGE);
|
|
202
|
+
const page = queryLabVerdicts({
|
|
203
|
+
subjectId,
|
|
204
|
+
layer,
|
|
205
|
+
suiteId,
|
|
206
|
+
verdict,
|
|
207
|
+
from,
|
|
208
|
+
to,
|
|
209
|
+
}, cursor, limit, configDir);
|
|
210
|
+
printData(page, wantsJson, verdictLines(page));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
case "subjects": {
|
|
214
|
+
const kind = takeOption(rest, "--kind");
|
|
215
|
+
const limit = takeIntegerOption(rest, "--limit", { min: 1 });
|
|
216
|
+
const cursor = takeOption(rest, "--cursor");
|
|
217
|
+
rejectArgs(rest, USAGE);
|
|
218
|
+
const page = queryLabSubjects(kind, cursor, limit, configDir);
|
|
219
|
+
printData(page, wantsJson, subjectListLines(page));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
case "subject": {
|
|
223
|
+
const subjectId = rest[0];
|
|
224
|
+
if (!subjectId) throw new CliUsageError("subject id required", USAGE);
|
|
225
|
+
rest.splice(0, 1);
|
|
226
|
+
rejectArgs(rest, USAGE);
|
|
227
|
+
const subject = queryLabSubjectById(subjectId, configDir);
|
|
228
|
+
if (!subject) throw new CliUsageError("unknown subject", USAGE);
|
|
229
|
+
printData({ subject }, wantsJson, [`Subject ${subjectId}`]);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
case "observations": {
|
|
233
|
+
const subjectId = takeOption(rest, "--subject");
|
|
234
|
+
const layer = takeEnumOption<EvidenceLayer>(
|
|
235
|
+
rest,
|
|
236
|
+
"--layer",
|
|
237
|
+
EVIDENCE_LAYERS,
|
|
238
|
+
"--layer must be a supported evidence layer",
|
|
239
|
+
);
|
|
240
|
+
const suiteId = takeOption(rest, "--suite");
|
|
241
|
+
const scenarioId = takeOption(rest, "--scenario");
|
|
242
|
+
const outcome = takeEnumOption<ObservationOutcome>(
|
|
243
|
+
rest,
|
|
244
|
+
"--outcome",
|
|
245
|
+
OUTCOMES,
|
|
246
|
+
"--outcome must be a supported observation outcome",
|
|
247
|
+
);
|
|
248
|
+
const executionMode = takeEnumOption<ExecutionMode>(
|
|
249
|
+
rest,
|
|
250
|
+
"--execution-mode",
|
|
251
|
+
EXECUTION_MODES,
|
|
252
|
+
"--execution-mode must be a supported execution mode",
|
|
253
|
+
);
|
|
254
|
+
const from = takeIntegerOption(rest, "--from", { min: 0 });
|
|
255
|
+
const to = takeIntegerOption(rest, "--to", { min: 0 });
|
|
256
|
+
assertRange(from, to);
|
|
257
|
+
const limit = takeIntegerOption(rest, "--limit", { min: 1 });
|
|
258
|
+
const cursor = takeOption(rest, "--cursor");
|
|
259
|
+
rejectArgs(rest, USAGE);
|
|
260
|
+
const page = queryLabObservations({
|
|
261
|
+
subjectId,
|
|
262
|
+
layer,
|
|
263
|
+
suiteId,
|
|
264
|
+
scenarioId,
|
|
265
|
+
outcome,
|
|
266
|
+
executionMode,
|
|
267
|
+
from,
|
|
268
|
+
to,
|
|
269
|
+
}, cursor, limit, configDir);
|
|
270
|
+
printData(page, wantsJson, observationLines(page));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
case "events": {
|
|
274
|
+
const eventKind = takeEnumOption<LabEventKind>(
|
|
275
|
+
rest,
|
|
276
|
+
"--event-kind",
|
|
277
|
+
EVENT_KINDS,
|
|
278
|
+
"--event-kind must be a supported lab event kind",
|
|
279
|
+
);
|
|
280
|
+
const subjectId = takeOption(rest, "--subject");
|
|
281
|
+
const from = takeIntegerOption(rest, "--from", { min: 0 });
|
|
282
|
+
const to = takeIntegerOption(rest, "--to", { min: 0 });
|
|
283
|
+
assertRange(from, to);
|
|
284
|
+
const excludedRaw = takeOption(rest, "--excluded");
|
|
285
|
+
if (excludedRaw !== undefined && excludedRaw !== "true" && excludedRaw !== "false") {
|
|
286
|
+
throw new CliUsageError("--excluded must be true or false", USAGE);
|
|
287
|
+
}
|
|
288
|
+
const limit = takeIntegerOption(rest, "--limit", { min: 1 });
|
|
289
|
+
const cursor = takeOption(rest, "--cursor");
|
|
290
|
+
rejectArgs(rest, USAGE);
|
|
291
|
+
const page = queryLabEvents({
|
|
292
|
+
eventKind,
|
|
293
|
+
subjectId,
|
|
294
|
+
from,
|
|
295
|
+
to,
|
|
296
|
+
excluded: excludedRaw === "true" ? true : excludedRaw === "false" ? false : undefined,
|
|
297
|
+
}, cursor, limit, configDir);
|
|
298
|
+
printData(page, wantsJson, eventListLines(page));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
case "event": {
|
|
302
|
+
const eventId = rest[0];
|
|
303
|
+
if (!eventId) throw new CliUsageError("event id required", USAGE);
|
|
304
|
+
rest.splice(0, 1);
|
|
305
|
+
rejectArgs(rest, USAGE);
|
|
306
|
+
const event = queryLabEventById(eventId, configDir);
|
|
307
|
+
if (!event) throw new CliUsageError("unknown event", USAGE);
|
|
308
|
+
printData({ event }, wantsJson, [`Event ${eventId}`]);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
case "artifacts": {
|
|
312
|
+
const status = takeEnumOption<ArtifactStatus>(
|
|
313
|
+
rest,
|
|
314
|
+
"--status",
|
|
315
|
+
ARTIFACT_STATUSES,
|
|
316
|
+
"--status must be present, corrupt, or purged_unavailable",
|
|
317
|
+
);
|
|
318
|
+
const artifactClass = takeEnumOption<ArtifactClass>(
|
|
319
|
+
rest,
|
|
320
|
+
"--artifact-class",
|
|
321
|
+
ARTIFACT_CLASSES,
|
|
322
|
+
"--artifact-class must be a supported artifact class",
|
|
323
|
+
);
|
|
324
|
+
const limit = takeIntegerOption(rest, "--limit", { min: 1 });
|
|
325
|
+
const cursor = takeOption(rest, "--cursor");
|
|
326
|
+
rejectArgs(rest, USAGE);
|
|
327
|
+
const page = queryLabArtifacts({ status, artifactClass }, cursor, limit, configDir);
|
|
328
|
+
printData(page, wantsJson, artifactLines(page));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
case "artifact": {
|
|
332
|
+
const digest = rest[0];
|
|
333
|
+
if (!digest) throw new CliUsageError("artifact digest required", USAGE);
|
|
334
|
+
rest.splice(0, 1);
|
|
335
|
+
rejectArgs(rest, USAGE);
|
|
336
|
+
const artifact = queryLabArtifactByDigest(digest, configDir);
|
|
337
|
+
if (!artifact) throw new CliUsageError("unknown artifact", USAGE);
|
|
338
|
+
printData({ artifact }, wantsJson, [`Artifact ${digest}`]);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
case "catalog": {
|
|
342
|
+
const layer = takeEnumOption<EvidenceLayer>(
|
|
343
|
+
rest,
|
|
344
|
+
"--layer",
|
|
345
|
+
EVIDENCE_LAYERS,
|
|
346
|
+
"--layer must be a supported evidence layer",
|
|
347
|
+
);
|
|
348
|
+
const suiteId = takeOption(rest, "--suite");
|
|
349
|
+
rejectArgs(rest, USAGE);
|
|
350
|
+
const scenarios = queryLabCatalogEntries({ layer, suiteId });
|
|
351
|
+
printData({ scenarios }, wantsJson, catalogLines(scenarios));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
default:
|
|
355
|
+
throw new CliUsageError(`unknown lab subcommand: ${sub}`, USAGE);
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
if (err instanceof CliUsageError) throw err;
|
|
359
|
+
if (err instanceof LabProjectionUnavailableError || err instanceof LabProjectionIncompatibleError) {
|
|
360
|
+
throw new LabStateError(labErrorMessage(err));
|
|
361
|
+
}
|
|
362
|
+
throw new CliUsageError(labErrorMessage(err), USAGE);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export const LAB_USAGE = USAGE;
|
package/src/cli/provider.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* show <name> Show provider config details (secrets masked)
|
|
9
9
|
* set-default <name> Change the default provider
|
|
10
10
|
*/
|
|
11
|
-
import { apiKeyTransportConfigError, hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "../config";
|
|
11
|
+
import { apiKeyTransportConfigError, hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig } from "../config";
|
|
12
12
|
import { hasHelpFlag } from "./help";
|
|
13
13
|
import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
|
|
14
14
|
import { providerConfigSeed } from "../providers/derive";
|
|
@@ -210,7 +210,14 @@ async function handleAdd(args: string[]): Promise<void> {
|
|
|
210
210
|
provConfig.apiKeyTransport = apiKeyTransport;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
const existingProvider = config.providers[name];
|
|
213
214
|
config.providers[name] = provConfig;
|
|
215
|
+
// A --force overwrite rotates the key/endpoint but must not drop a
|
|
216
|
+
// user-configured price overlay (same rule as the /api/providers path and
|
|
217
|
+
// the login paths); there is no explicit clear/replace flag yet.
|
|
218
|
+
if (existingProvider?.modelCosts !== undefined && provConfig.modelCosts === undefined) {
|
|
219
|
+
provConfig.modelCosts = existingProvider.modelCosts;
|
|
220
|
+
}
|
|
214
221
|
if (allowPrivateNetwork) provConfig.allowPrivateNetwork = true;
|
|
215
222
|
if (setDefault) config.defaultProvider = name;
|
|
216
223
|
|
|
@@ -349,6 +356,7 @@ function handleShow(args: string[]): void {
|
|
|
349
356
|
const prov = config.providers[name];
|
|
350
357
|
const display = {
|
|
351
358
|
...prov,
|
|
359
|
+
...(prov.modelCosts !== undefined ? { modelCosts: sanitizeModelCostsForDisplay(prov.modelCosts) } : {}),
|
|
352
360
|
...(prov.apiKey ? { apiKey: maskSecret(prov.apiKey) } : {}),
|
|
353
361
|
...(prov.apiKeyPool ? { apiKeyPool: prov.apiKeyPool.map(e => ({ ...e, key: maskSecret(e.key) })) } : {}),
|
|
354
362
|
};
|
|
@@ -391,6 +391,9 @@ export interface ExportModel {
|
|
|
391
391
|
displayName?: string;
|
|
392
392
|
contextWindow?: number;
|
|
393
393
|
inputModalities?: string[];
|
|
394
|
+
/** Optional effort ladder exported only to clients that support it. */
|
|
395
|
+
reasoningEfforts?: string[];
|
|
396
|
+
defaultReasoningEffort?: string;
|
|
394
397
|
}
|
|
395
398
|
|
|
396
399
|
export interface ExportContext {
|
|
@@ -637,6 +640,50 @@ export interface PiGeneratedConfig {
|
|
|
637
640
|
providers: Record<string, PiProviderBlock>;
|
|
638
641
|
}
|
|
639
642
|
|
|
643
|
+
/**
|
|
644
|
+
* omp accepts a model-level API override. Keep the provider on Chat
|
|
645
|
+
* Completions so routed providers retain their established wire format, while
|
|
646
|
+
* native OpenAI models can use the lossless Responses surface.
|
|
647
|
+
*/
|
|
648
|
+
export interface OmpModelEntry extends PiModelEntry {
|
|
649
|
+
api?: "openai-responses";
|
|
650
|
+
/** omp requires this flag before it honors a thinking block. */
|
|
651
|
+
reasoning?: true;
|
|
652
|
+
thinking?: {
|
|
653
|
+
mode: "effort";
|
|
654
|
+
efforts: string[];
|
|
655
|
+
defaultLevel?: string;
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
export interface OmpProviderBlock {
|
|
660
|
+
baseUrl: string;
|
|
661
|
+
api: typeof PI_API_DIALECT;
|
|
662
|
+
apiKey: string;
|
|
663
|
+
models: OmpModelEntry[];
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export interface OmpGeneratedConfig {
|
|
667
|
+
providers: Record<string, OmpProviderBlock>;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* omp validates model entries strictly. These are its documented effort
|
|
672
|
+
* values; omit an unknown value rather than invalidating the whole provider.
|
|
673
|
+
*/
|
|
674
|
+
const OMP_EFFORT_VOCABULARY = new Set(["minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
675
|
+
|
|
676
|
+
function ompEfforts(model: ExportModel): string[] {
|
|
677
|
+
const efforts: string[] = [];
|
|
678
|
+
for (const effort of model.reasoningEfforts ?? []) {
|
|
679
|
+
const normalized = effort.trim().toLowerCase();
|
|
680
|
+
if (OMP_EFFORT_VOCABULARY.has(normalized) && !efforts.includes(normalized)) {
|
|
681
|
+
efforts.push(normalized);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return efforts;
|
|
685
|
+
}
|
|
686
|
+
|
|
640
687
|
/**
|
|
641
688
|
* Hermes `~/.hermes/config.yaml`. We emit ONLY the provider entry — never
|
|
642
689
|
* `model.default` — because hijacking the user's main model is not what a
|
|
@@ -772,6 +819,51 @@ function buildPiClientConfig(ctx: ExportContext): PiGeneratedConfig {
|
|
|
772
819
|
};
|
|
773
820
|
}
|
|
774
821
|
|
|
822
|
+
/**
|
|
823
|
+
* omp's models.yml is Pi-like, but it supports effort metadata and a per-model
|
|
824
|
+
* API dialect. Native OpenAI models use Responses; all routed models inherit
|
|
825
|
+
* the provider's existing Chat Completions dialect.
|
|
826
|
+
*/
|
|
827
|
+
function buildOmpClientConfig(ctx: ExportContext): OmpGeneratedConfig {
|
|
828
|
+
const models: OmpModelEntry[] = [];
|
|
829
|
+
for (const model of normalizeExportModels(ctx.models)) {
|
|
830
|
+
const input = inputModalitiesForClient("pi", model.inputModalities);
|
|
831
|
+
if (input === null) continue;
|
|
832
|
+
const entry: OmpModelEntry = {
|
|
833
|
+
id: model.namespaced,
|
|
834
|
+
name: exportModelLabel(model),
|
|
835
|
+
input,
|
|
836
|
+
...(model.native && model.provider === "openai" ? { api: "openai-responses" } : {}),
|
|
837
|
+
};
|
|
838
|
+
const context = authoritativeContextWindow(model.contextWindow);
|
|
839
|
+
if (context !== undefined) {
|
|
840
|
+
entry.contextWindow = context;
|
|
841
|
+
entry.maxTokens = outputBudgetFor(context);
|
|
842
|
+
}
|
|
843
|
+
const efforts = ompEfforts(model);
|
|
844
|
+
if (efforts.length > 0) {
|
|
845
|
+
const defaultLevel = model.defaultReasoningEffort?.trim().toLowerCase();
|
|
846
|
+
entry.reasoning = true;
|
|
847
|
+
entry.thinking = {
|
|
848
|
+
mode: "effort",
|
|
849
|
+
efforts,
|
|
850
|
+
...(defaultLevel && efforts.includes(defaultLevel) ? { defaultLevel } : {}),
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
models.push(entry);
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
providers: {
|
|
857
|
+
[OPENCODE_PROVIDER_ID]: {
|
|
858
|
+
baseUrl: ctx.baseUrl,
|
|
859
|
+
api: PI_API_DIALECT,
|
|
860
|
+
apiKey: LOOPBACK_API_KEY_PLACEHOLDER,
|
|
861
|
+
models,
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
775
867
|
/** Extra headers a non-loopback bind needs, or nothing on loopback. */
|
|
776
868
|
function proxyAdmissionHeaders(config: OcxConfig | undefined, envRef: string): Record<string, string> | undefined {
|
|
777
869
|
return shouldInjectApiAuthHeader(config) ? { "x-opencodex-api-key": envRef } : undefined;
|
|
@@ -899,6 +991,11 @@ function summarizePi(document: unknown): { modelCount: number; modelsWithoutLimi
|
|
|
899
991
|
return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
|
|
900
992
|
}
|
|
901
993
|
|
|
994
|
+
function summarizeOmp(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
|
|
995
|
+
const models = (document as OmpGeneratedConfig | undefined)?.providers?.[OPENCODE_PROVIDER_ID]?.models ?? [];
|
|
996
|
+
return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
|
|
997
|
+
}
|
|
998
|
+
|
|
902
999
|
function summarizeHermes(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
|
|
903
1000
|
const models = (document as HermesGeneratedConfig | undefined)?.providers?.[OPENCODE_PROVIDER_ID]?.models ?? [];
|
|
904
1001
|
// Hermes carries selectors only; it has no per-model limit to be missing.
|
|
@@ -938,7 +1035,7 @@ function buildPiContribution(ctx: ExportContext): ManagedContribution {
|
|
|
938
1035
|
}
|
|
939
1036
|
|
|
940
1037
|
function buildOmpContribution(ctx: ExportContext): ManagedContribution {
|
|
941
|
-
const doc =
|
|
1038
|
+
const doc = buildOmpClientConfig(ctx);
|
|
942
1039
|
return singleFragment("omp", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
|
|
943
1040
|
}
|
|
944
1041
|
|
|
@@ -1007,9 +1104,9 @@ export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
|
|
|
1007
1104
|
destination: env => ompModelsConfigPath(env),
|
|
1008
1105
|
apiKeyEnv: "",
|
|
1009
1106
|
exportHint: "OMP reads a non-secret placeholder from models.yml; loopback needs no key.",
|
|
1010
|
-
build:
|
|
1107
|
+
build: buildOmpClientConfig,
|
|
1011
1108
|
format: "yaml",
|
|
1012
|
-
summarize:
|
|
1109
|
+
summarize: summarizeOmp,
|
|
1013
1110
|
buildContribution: buildOmpContribution,
|
|
1014
1111
|
// OMP supports provider-level headers, but remote credential wiring is
|
|
1015
1112
|
// intentionally deferred from this initial loopback-only integration.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
11
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
12
|
import { isProcessAlive, waitForExit } from "../lib/process-control";
|
|
13
|
+
import { resolveTrustedWindowsPowerShellExe } from "../lib/windows-elevation";
|
|
13
14
|
import { readCodexCatalogPath } from "./catalog/parsing";
|
|
14
15
|
|
|
15
16
|
export const STALE_CODEX_APP_SERVER_HINT =
|
|
@@ -271,12 +272,12 @@ function listDarwinSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
|
271
272
|
// Top-level exec failure propagates: callers decide their own safe default
|
|
272
273
|
// (restart flow → treat as none; staleness check → unknown, never "fresh").
|
|
273
274
|
const output = uid !== undefined
|
|
274
|
-
? execFileSync("ps", ["-u", String(uid), "-o", "pid=,command="], {
|
|
275
|
+
? execFileSync("/bin/ps", ["-u", String(uid), "-o", "pid=,command="], {
|
|
275
276
|
encoding: "utf-8",
|
|
276
277
|
stdio: ["ignore", "pipe", "ignore"],
|
|
277
278
|
timeout: 5_000,
|
|
278
279
|
})
|
|
279
|
-
: execFileSync("ps", ["-axo", "pid=,uid=,command="], {
|
|
280
|
+
: execFileSync("/bin/ps", ["-axo", "pid=,uid=,command="], {
|
|
280
281
|
encoding: "utf-8",
|
|
281
282
|
stdio: ["ignore", "pipe", "ignore"],
|
|
282
283
|
timeout: 5_000,
|
|
@@ -345,8 +346,10 @@ export function listWindowsSnapshots(): ProcessSnapshot[] {
|
|
|
345
346
|
" } catch { \"__OCX_ENUM_INCOMPLETE__\" }",
|
|
346
347
|
"}",
|
|
347
348
|
].join("\n");
|
|
348
|
-
// Top-level exec failure propagates (see listDarwinSnapshots note).
|
|
349
|
-
|
|
349
|
+
// Top-level exec failure propagates (see listDarwinSnapshots note). The
|
|
350
|
+
// executable resolves from the trusted System32 directory (never PATH), and
|
|
351
|
+
// windowsHide keeps the enumeration console-less on desktop sessions (#1278).
|
|
352
|
+
const output = execFileSync(resolveTrustedWindowsPowerShellExe(), [
|
|
350
353
|
"-NoProfile", "-NoLogo", "-NonInteractive", "-WindowStyle", "Hidden",
|
|
351
354
|
"-Command",
|
|
352
355
|
psCommand,
|
|
@@ -441,7 +444,7 @@ function readLinuxProcStartMs(pid: number): number | null {
|
|
|
441
444
|
/** `ps` lstart → epoch ms, or null (macOS). */
|
|
442
445
|
function readDarwinProcStartMs(pid: number): number | null {
|
|
443
446
|
try {
|
|
444
|
-
const out = execFileSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
447
|
+
const out = execFileSync("/bin/ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
445
448
|
encoding: "utf-8",
|
|
446
449
|
stdio: ["ignore", "pipe", "ignore"],
|
|
447
450
|
timeout: 4_000,
|
|
@@ -457,7 +460,7 @@ function readDarwinProcStartMs(pid: number): number | null {
|
|
|
457
460
|
/** Win32_Process.CreationDate → epoch ms, or null (Windows). */
|
|
458
461
|
function readWindowsProcStartMs(pid: number): number | null {
|
|
459
462
|
try {
|
|
460
|
-
const out = execFileSync(
|
|
463
|
+
const out = execFileSync(resolveTrustedWindowsPowerShellExe(), [
|
|
461
464
|
"-NoProfile", "-NoLogo", "-NonInteractive", "-WindowStyle", "Hidden",
|
|
462
465
|
"-Command",
|
|
463
466
|
`(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").CreationDate.ToUniversalTime().ToString("o")`,
|
|
@@ -490,7 +493,7 @@ export function readProcessStartMsBatch(
|
|
|
490
493
|
if (pids.length === 0) return out;
|
|
491
494
|
if (platform === "darwin") {
|
|
492
495
|
try {
|
|
493
|
-
const stdout = execFileSync("ps", ["-o", "pid=,lstart=", "-p", pids.join(",")], {
|
|
496
|
+
const stdout = execFileSync("/bin/ps", ["-o", "pid=,lstart=", "-p", pids.join(",")], {
|
|
494
497
|
encoding: "utf-8",
|
|
495
498
|
stdio: ["ignore", "pipe", "ignore"],
|
|
496
499
|
timeout: 3_000,
|
|
@@ -513,7 +516,7 @@ export function readProcessStartMsBatch(
|
|
|
513
516
|
if (platform === "win32") {
|
|
514
517
|
try {
|
|
515
518
|
const filter = pids.map(pid => `ProcessId=${pid}`).join(" OR ");
|
|
516
|
-
const stdout = execFileSync(
|
|
519
|
+
const stdout = execFileSync(resolveTrustedWindowsPowerShellExe(), [
|
|
517
520
|
"-NoProfile", "-NoLogo", "-NonInteractive", "-WindowStyle", "Hidden",
|
|
518
521
|
"-Command",
|
|
519
522
|
`Get-CimInstance Win32_Process -Filter "${filter}" | ForEach-Object { "$($_.ProcessId)\t$($_.CreationDate.ToUniversalTime().ToString("o"))" }`,
|