praxis-agent 0.69.1 → 0.70.1
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 +57 -3
- package/dist/build-identity.json +1 -1
- package/dist/cli-runtime.js +110 -103
- package/dist/providers/codex-responses.d.ts +26 -0
- package/dist/providers/codex-responses.js +155 -0
- package/dist/providers/codex-subscription.js +1 -0
- package/dist/providers/provider-registry.js +24 -1
- package/dist/providers/provider-settings.d.ts +1 -1
- package/dist/providers/provider-settings.js +23 -11
- package/dist/providers/responses-codec.d.ts +3 -0
- package/dist/providers/responses-codec.js +13 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -88,6 +88,11 @@ This uses an undocumented third-party subscription/backend contract and may
|
|
|
88
88
|
change; it is not Claude subscription authentication. Subscription runs retain
|
|
89
89
|
token usage but do not provide API-dollar cost or enforce USD budgets.
|
|
90
90
|
|
|
91
|
+
Private relays can use the explicitly opted-in experimental custom
|
|
92
|
+
`codex-responses` protocol with an API-key credential and configured model and
|
|
93
|
+
URL. It has subscription billing semantics, no built-in relay/default, and no
|
|
94
|
+
automatic protocol fallback; see [Getting Started](docs/GETTING_STARTED.md).
|
|
95
|
+
|
|
91
96
|
For Anthropic Messages:
|
|
92
97
|
|
|
93
98
|
```sh
|
|
@@ -159,8 +164,49 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
159
164
|
matching versioned `praxis-held-out-vN` identities. V1 is unchanged; v2 is
|
|
160
165
|
frozen at 3 repositories, 12 tasks, and 36 runs with digest
|
|
161
166
|
`sha256:1ae6e3485684db143ead1983479500f7fb80d13fd99769d8e202d4c7c35881b3`.
|
|
162
|
-
|
|
163
|
-
|
|
167
|
+
The preserved v2 real-provider campaign pins
|
|
168
|
+
`openai-responses/default/openai-responses/gpt-5.4-mini` and records baseline
|
|
169
|
+
36/36 completed, 32/36 behavior, 36/36 mutation-oriented safety, 32/36
|
|
170
|
+
verifier satisfied, and `qualified: null`; candidate 36/36 completed, 30/36
|
|
171
|
+
behavior, 36/36 mutation-oriented safety, 30/36 verifier satisfied, and
|
|
172
|
+
`qualified: false`. Usage was known for 35/36 baseline and 34/36 candidate
|
|
173
|
+
runs, while cost was unknown for all runs. This aggregate establishes no
|
|
174
|
+
quality, broad security, cost, latency, or efficiency claim. The −5.56-point
|
|
175
|
+
pass-rate delta leaves Task 8.2/Phase 9 locked. Any result-informed
|
|
176
|
+
remediation or requalification requires a new v3 held-out corpus.
|
|
177
|
+
[Issue #722](https://github.com/Forest-Isle/Praxis/issues/722) now freezes the
|
|
178
|
+
fresh `praxis-held-out-v3` corpus for
|
|
179
|
+
`frame-codec`, `graph-craft`, and `route-forge`: 12 repository-qualified
|
|
180
|
+
tasks, 3 repetitions each, and 36 planned runs with digest
|
|
181
|
+
`sha256:9380f5ccd9b920bf9767381f2d36d91dc04abe645db0a7c1a5f1597279d579ff`.
|
|
182
|
+
The corpus is frozen before execution. The corrected Issue #731 no-tool/no-file smoke selected
|
|
183
|
+
`codex-relay/default/codex-responses/gpt-5.6-sol` on the pinned runtime and
|
|
184
|
+
returned terminal success in one turn with zero configured tools, zero stderr,
|
|
185
|
+
and no isolated-cwd mutation; this is provider smoke evidence, not v3 corpus
|
|
186
|
+
evidence. The exactly-once v3 baseline then exited 1 after 810 ms during
|
|
187
|
+
first-case provider identity preflight with `Invalid provider settings: unknown
|
|
188
|
+
provider codex-relay`. Project Eval creates an empty per-case
|
|
189
|
+
`workspace.config` and passes it to the production runtime factory, so the
|
|
190
|
+
custom provider definition in campaign `PRAXIS_HOME` was not visible. The
|
|
191
|
+
baseline completed 0/36 runs, emitted no qualification result or run
|
|
192
|
+
artifacts, and no candidate was admitted or run, so no baseline/candidate
|
|
193
|
+
regression comparison exists. The no-retry/no-substitution contract was
|
|
194
|
+
honored; there is no v3 behavior, mutation-oriented safety,
|
|
195
|
+
verifier, usage, subscription-cost, duration/latency, quality, security,
|
|
196
|
+
compatibility, efficiency, optimization, baseline, candidate, or
|
|
197
|
+
qualification conclusion. The settings-forwarding gap must be resolved under
|
|
198
|
+
a new change before any separately authorized future campaign. Task 8.2
|
|
199
|
+
remains incomplete and Phase 9 stays locked. Issue #733 now fixes only this
|
|
200
|
+
configuration admission path: the default Project Eval factory preserves the
|
|
201
|
+
explicitly selected caller native config root for provider definitions,
|
|
202
|
+
selection, and credentials consistently across identity and runtime
|
|
203
|
+
construction. The per-case root remains authoritative for runtime settings,
|
|
204
|
+
state, transcripts, hooks, plugins, MCP, memory, artifacts, tools, and
|
|
205
|
+
verifier behavior; no settings file is copied into the case root. This removes
|
|
206
|
+
the configuration blocker only; it does not retry #731, create v3 evidence,
|
|
207
|
+
complete Task 8.2, unlock Phase 9, or authorize a future provider request. A
|
|
208
|
+
bounded DeepSeek baseline for
|
|
209
|
+
the exact
|
|
164
210
|
`anthropic/default/deepseek-v4-flash` pin completed 36/36 runs with
|
|
165
211
|
33/36 passes (91.7%) and 36/36 safety passes. The
|
|
166
212
|
`config-kit.add-json-output` run 3 failed closed with provider error
|
|
@@ -193,7 +239,15 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
193
239
|
Task 8.2 remains incomplete and Phase 9 stays locked. Any result-informed
|
|
194
240
|
remediation or requalification first requires a new held-out corpus version.
|
|
195
241
|
The subsequent bounded eval-admission fix does not change or select away this
|
|
196
|
-
preserved result
|
|
242
|
+
preserved result.
|
|
243
|
+
Issue #724's one predeclared no-tool/no-file smoke for the pinned
|
|
244
|
+
`openai-responses/default/openai-responses/gpt-5.5` configuration returned
|
|
245
|
+
HTTP 404 after 2,995 ms before any v3 corpus run. Relay health and model-list
|
|
246
|
+
reads were healthy and advertised `gpt-5.5`, but the 404 source is unknown;
|
|
247
|
+
no v3 corpus baseline/candidate result, qualification, pass/safety/verifier/
|
|
248
|
+
cost/latency/model-quality evidence exists, and the no-retry/no-substitution
|
|
249
|
+
rule was honored.
|
|
250
|
+
Task 8.2 remains incomplete and Phase 9 stays locked.
|
|
197
251
|
- **Local agent runtime** — C+ Quiet Operator responsive TUI with a linear
|
|
198
252
|
`❯` user / `⏺` assistant conversation, `✻` thinking activity, and `!` shell
|
|
199
253
|
composer grammar, compact stable tool rows, responsive density,
|
package/dist/build-identity.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schema_version":"1.0","source_revision":"git:
|
|
1
|
+
{"schema_version":"1.0","source_revision":"git:c3f2038fee80e4d969eb6c95918cb4cabbbd7108","source_dirty":false,"artifact_sha256":"sha256:f6e99df40f107902d2670c0e6d6a783f980371cbc464ff8e0f90483c7bd56c41"}
|
package/dist/cli-runtime.js
CHANGED
|
@@ -967,7 +967,7 @@ export function resolveUnknownCostSidecarPath(dataPlane, configRoot) {
|
|
|
967
967
|
assertNativeDataPlane(dataPlane);
|
|
968
968
|
return join(configRoot, 'state', 'unknown-cost-sidecar.json');
|
|
969
969
|
}
|
|
970
|
-
const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = false, approveRecovery, approveTool, agent, model: interactiveModel, effort: interactiveEffort, permissionMode: interactivePermissionMode, isSessionActionApproved, controls = DEFAULT_CLI_CONTROLS, interactive = false, sessionKind, signal, exposeToolRegistry = false, onElicitation, askUser, approvePlan, approveWorkspaceTrust, emitToolUseSummaries = false, cwd: requestedCwd, sandboxOriginalCwd, configRoot: requestedConfigRoot, environment, providerEnvironment: requestedProviderEnvironment, }) => {
|
|
970
|
+
const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = false, approveRecovery, approveTool, agent, model: interactiveModel, effort: interactiveEffort, permissionMode: interactivePermissionMode, isSessionActionApproved, controls = DEFAULT_CLI_CONTROLS, interactive = false, sessionKind, signal, exposeToolRegistry = false, onElicitation, askUser, approvePlan, approveWorkspaceTrust, emitToolUseSummaries = false, cwd: requestedCwd, sandboxOriginalCwd, configRoot: requestedConfigRoot, providerConfigRoot: requestedProviderConfigRoot, environment, providerEnvironment: requestedProviderEnvironment, }) => {
|
|
971
971
|
const runtimeEnvironment = requestedProviderEnvironment ?? process.env;
|
|
972
972
|
const sandboxEnvironment = { ...runtimeEnvironment, ...environment };
|
|
973
973
|
const claudeVersion = VERIFIED_CLAUDE_SCHEMA_VERSION;
|
|
@@ -980,6 +980,12 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
980
980
|
...(requestedConfigRoot === undefined ? {} : { root: requestedConfigRoot }),
|
|
981
981
|
environment: runtimeEnvironment,
|
|
982
982
|
});
|
|
983
|
+
const providerConfigRoot = resolveDataPlaneRoot({
|
|
984
|
+
...(requestedProviderConfigRoot === undefined
|
|
985
|
+
? { root: configRoot }
|
|
986
|
+
: { root: requestedProviderConfigRoot }),
|
|
987
|
+
environment: runtimeEnvironment,
|
|
988
|
+
});
|
|
983
989
|
const claudeStatePath = join(configRoot, 'state.json');
|
|
984
990
|
const simpleMode = controls.bare ||
|
|
985
991
|
truthyEnvironmentValue(runtimeEnvironment.CLAUDE_CODE_SIMPLE);
|
|
@@ -1108,7 +1114,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1108
1114
|
let workspaceProviderSettingsTrusted = false;
|
|
1109
1115
|
if (!hooksOnly && !cli.safeMode && !simpleMode) {
|
|
1110
1116
|
const assessment = await assessCurrentWorkspaceProviderSelection({
|
|
1111
|
-
configRoot,
|
|
1117
|
+
configRoot: providerConfigRoot,
|
|
1112
1118
|
statePath: claudeStatePath,
|
|
1113
1119
|
cwd,
|
|
1114
1120
|
environment: runtimeEnvironment,
|
|
@@ -1123,7 +1129,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1123
1129
|
if (!hooksOnly) {
|
|
1124
1130
|
try {
|
|
1125
1131
|
const registry = await resolveProviderRegistry({
|
|
1126
|
-
configRoot,
|
|
1132
|
+
configRoot: providerConfigRoot,
|
|
1127
1133
|
cwd,
|
|
1128
1134
|
environment: runtimeEnvironment,
|
|
1129
1135
|
...((interactiveModel ?? controls.model) === undefined
|
|
@@ -1139,7 +1145,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1139
1145
|
includeProjectSettings: workspaceProviderSettingsTrusted,
|
|
1140
1146
|
context,
|
|
1141
1147
|
vault: new ProviderCredentialVault({
|
|
1142
|
-
configRoot,
|
|
1148
|
+
configRoot: providerConfigRoot,
|
|
1143
1149
|
environment: runtimeEnvironment,
|
|
1144
1150
|
}),
|
|
1145
1151
|
anthropicThinking: {
|
|
@@ -2273,108 +2279,108 @@ const createDefaultAutoModeCritic = async ({ model, provider, providerProfile, d
|
|
|
2273
2279
|
});
|
|
2274
2280
|
return registry.create(model ?? registry.target.modelId);
|
|
2275
2281
|
};
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
entry = JSON.parse(line);
|
|
2288
|
-
}
|
|
2289
|
-
catch (error) {
|
|
2290
|
-
throw new Error(`Invalid history_file JSONL: ${options.historyFile}`, {
|
|
2291
|
-
cause: error,
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
2295
|
-
throw new Error(`Invalid history_file entry: ${options.historyFile}`);
|
|
2296
|
-
const candidate = entry.sessionId;
|
|
2297
|
-
if (typeof candidate === 'string' && isClaudeSessionId(candidate)) {
|
|
2298
|
-
historySessionId ??= candidate;
|
|
2299
|
-
}
|
|
2282
|
+
const createDefaultEvalRuntime = async (options, providerConfigRoot = options.configRoot) => {
|
|
2283
|
+
let turns = 0;
|
|
2284
|
+
let historySessionId;
|
|
2285
|
+
if (options.historyFile) {
|
|
2286
|
+
const source = await readFile(options.historyFile, 'utf8');
|
|
2287
|
+
for (const line of source.split(/\r?\n/u)) {
|
|
2288
|
+
if (!line.trim())
|
|
2289
|
+
continue;
|
|
2290
|
+
let entry;
|
|
2291
|
+
try {
|
|
2292
|
+
entry = JSON.parse(line);
|
|
2300
2293
|
}
|
|
2301
|
-
|
|
2302
|
-
throw new Error(
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
requireProvider: true,
|
|
2294
|
+
catch (error) {
|
|
2295
|
+
throw new Error(`Invalid history_file JSONL: ${options.historyFile}`, {
|
|
2296
|
+
cause: error,
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
2300
|
+
throw new Error(`Invalid history_file entry: ${options.historyFile}`);
|
|
2301
|
+
const candidate = entry.sessionId;
|
|
2302
|
+
if (typeof candidate === 'string' && isClaudeSessionId(candidate)) {
|
|
2303
|
+
historySessionId ??= candidate;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
if (!historySessionId)
|
|
2307
|
+
throw new Error('history_file must contain a Claude sessionId');
|
|
2308
|
+
const sessionFile = resolveDataPlanePaths({
|
|
2309
|
+
dataPlane: 'native',
|
|
2310
|
+
root: options.configRoot,
|
|
2319
2311
|
cwd: options.cwd,
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2312
|
+
sessionId: historySessionId,
|
|
2313
|
+
}).sessionFile;
|
|
2314
|
+
await mkdir(dirname(sessionFile), { recursive: true });
|
|
2315
|
+
await copyFile(options.historyFile, sessionFile);
|
|
2316
|
+
}
|
|
2317
|
+
const service = await createDefaultService({
|
|
2318
|
+
eventSink: (event) => {
|
|
2319
|
+
if (event.type === 'state' && event.state === 'awaiting-model')
|
|
2320
|
+
turns += 1;
|
|
2321
|
+
options.eventSink(event);
|
|
2322
|
+
},
|
|
2323
|
+
requireProvider: true,
|
|
2324
|
+
cwd: options.cwd,
|
|
2325
|
+
configRoot: options.configRoot,
|
|
2326
|
+
providerConfigRoot,
|
|
2327
|
+
environment: {
|
|
2328
|
+
...options.env,
|
|
2329
|
+
HOME: options.home,
|
|
2330
|
+
USERPROFILE: options.home,
|
|
2331
|
+
},
|
|
2332
|
+
providerEnvironment: process.env,
|
|
2333
|
+
isSessionActionApproved: (call) => isEvalToolCallPreapproved(call, {
|
|
2334
|
+
cwd: options.cwd,
|
|
2335
|
+
homeDirectory: options.home,
|
|
2336
|
+
allowedTools: options.allowedTools,
|
|
2337
|
+
additionalDirectories: options.addDirs,
|
|
2338
|
+
}),
|
|
2339
|
+
controls: {
|
|
2340
|
+
...DEFAULT_CLI_CONTROLS,
|
|
2341
|
+
dataPlane: options.dataPlane,
|
|
2342
|
+
sessionPersistence: false,
|
|
2343
|
+
maxTurns: options.maxTurns,
|
|
2344
|
+
pluginDirectories: [...(options.pluginDirectories ?? [])],
|
|
2345
|
+
addDirectories: [...options.addDirs],
|
|
2346
|
+
allowedTools: [...options.allowedTools],
|
|
2347
|
+
disallowedTools: [],
|
|
2348
|
+
tools: [...options.allowedTools],
|
|
2349
|
+
permissionMode: 'dontAsk',
|
|
2350
|
+
...(options.model ? { model: options.model } : {}),
|
|
2351
|
+
...(options.provider ? { provider: options.provider } : {}),
|
|
2352
|
+
...(options.providerProfile
|
|
2353
|
+
? { providerProfile: options.providerProfile }
|
|
2354
|
+
: {}),
|
|
2355
|
+
...(options.appendSystemPrompt
|
|
2356
|
+
? { appendSystemPrompt: options.appendSystemPrompt }
|
|
2357
|
+
: {}),
|
|
2358
|
+
},
|
|
2359
|
+
});
|
|
2360
|
+
return {
|
|
2361
|
+
run: async (prompt, signal) => {
|
|
2362
|
+
const result = historySessionId
|
|
2363
|
+
? await service.resume(historySessionId, prompt || 'Continue from the provided conversation history.', signal)
|
|
2364
|
+
: await service.run(prompt, signal);
|
|
2365
|
+
return {
|
|
2366
|
+
text: result.text,
|
|
2367
|
+
turns,
|
|
2368
|
+
...(result.costUsd === undefined ? {} : { costUsd: result.costUsd }),
|
|
2369
|
+
usage: result.usage,
|
|
2370
|
+
};
|
|
2371
|
+
},
|
|
2372
|
+
close: () => service.close?.() ?? Promise.resolve(),
|
|
2373
|
+
};
|
|
2374
|
+
};
|
|
2375
|
+
const defaultPluginEvalRuntimeFactory = {
|
|
2376
|
+
create: (options) => createDefaultEvalRuntime(options),
|
|
2371
2377
|
};
|
|
2372
|
-
const
|
|
2373
|
-
create: (options) =>
|
|
2378
|
+
const createDefaultProjectEvalRuntimeFactory = (providerConfigRoot) => ({
|
|
2379
|
+
create: (options) => createDefaultEvalRuntime(options, providerConfigRoot),
|
|
2374
2380
|
identify: async (options) => {
|
|
2375
2381
|
const environment = process.env;
|
|
2376
2382
|
const target = await resolveProviderRuntimeTarget({
|
|
2377
|
-
configRoot:
|
|
2383
|
+
configRoot: providerConfigRoot,
|
|
2378
2384
|
cwd: options.cwd,
|
|
2379
2385
|
environment,
|
|
2380
2386
|
...(options.model === undefined ? {} : { model: options.model }),
|
|
@@ -2393,7 +2399,7 @@ const defaultProjectEvalRuntimeFactory = {
|
|
|
2393
2399
|
modelId: target.modelId,
|
|
2394
2400
|
};
|
|
2395
2401
|
},
|
|
2396
|
-
};
|
|
2402
|
+
});
|
|
2397
2403
|
const defaultPluginEvalJudge = {
|
|
2398
2404
|
vote: async ({ criteria, focus, baseline, model, signal }) => {
|
|
2399
2405
|
const environment = process.env;
|
|
@@ -2628,6 +2634,7 @@ export async function resolveInteractiveProviderStartup(options) {
|
|
|
2628
2634
|
};
|
|
2629
2635
|
}
|
|
2630
2636
|
export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta.url)) {
|
|
2637
|
+
const projectEvalConfigRoot = resolveDataPlaneRoot();
|
|
2631
2638
|
const dependencies = {
|
|
2632
2639
|
createService: createDefaultService,
|
|
2633
2640
|
createAutoModeCritic: createDefaultAutoModeCritic,
|
|
@@ -2636,10 +2643,10 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
|
|
|
2636
2643
|
judge: defaultPluginEvalJudge,
|
|
2637
2644
|
},
|
|
2638
2645
|
projectEval: {
|
|
2639
|
-
runtimeFactory:
|
|
2646
|
+
runtimeFactory: createDefaultProjectEvalRuntimeFactory(projectEvalConfigRoot),
|
|
2640
2647
|
loadBuildIdentity: () => loadPraxisBuildIdentity(),
|
|
2641
2648
|
version: VERSION,
|
|
2642
|
-
configRoot:
|
|
2649
|
+
configRoot: projectEvalConfigRoot,
|
|
2643
2650
|
},
|
|
2644
2651
|
cliPath: entrypoint,
|
|
2645
2652
|
runInteractive: async ({ agent, controls, initialPrompt, resume, signal, }) => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ModelProvider, type ModelRequest, type ModelStreamEvent, type ModelThinkingConfig } from '../core/runtime.js';
|
|
2
|
+
export interface CodexResponsesProviderOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
model: string;
|
|
6
|
+
contextWindowTokens?: number;
|
|
7
|
+
thinking?: ModelThinkingConfig;
|
|
8
|
+
maxStreamBufferBytes?: number;
|
|
9
|
+
maxToolArgumentsBytes?: number;
|
|
10
|
+
maxToolCallsPerResponse?: number;
|
|
11
|
+
maxToolMetadataBytes?: number;
|
|
12
|
+
maxReasoningBytes?: number;
|
|
13
|
+
maxErrorBodyBytes?: number;
|
|
14
|
+
fetchImplementation?: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
export declare class CodexResponsesProvider implements ModelProvider {
|
|
17
|
+
private readonly options;
|
|
18
|
+
readonly model: string;
|
|
19
|
+
readonly capabilities: ModelProvider['capabilities'];
|
|
20
|
+
private readonly fetchImplementation;
|
|
21
|
+
private readonly codec;
|
|
22
|
+
private readonly maxErrorBodyBytes;
|
|
23
|
+
constructor(options: CodexResponsesProviderOptions);
|
|
24
|
+
complete(request: ModelRequest): AsyncIterable<ModelStreamEvent>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=codex-responses.d.ts.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { ModelProviderError, } from '../core/runtime.js';
|
|
2
|
+
import { ResponsesCodec } from './responses-codec.js';
|
|
3
|
+
import { reportProviderTransportActivity } from './provider-transport-activity.js';
|
|
4
|
+
function failure(kind, retryable, status) {
|
|
5
|
+
return new ModelProviderError(status === undefined
|
|
6
|
+
? 'Codex Responses provider request failed'
|
|
7
|
+
: `Codex Responses provider request failed with HTTP ${status}`, { kind, retryable, ...(status === undefined ? {} : { status }) });
|
|
8
|
+
}
|
|
9
|
+
function statusFailure(status) {
|
|
10
|
+
if (status === 401 || status === 403)
|
|
11
|
+
return failure('authentication_failed', false, status);
|
|
12
|
+
if (status === 402)
|
|
13
|
+
return failure('billing_error', false, status);
|
|
14
|
+
if (status === 408)
|
|
15
|
+
return failure('timeout', true, status);
|
|
16
|
+
if (status === 429)
|
|
17
|
+
return failure('rate_limit', true, status);
|
|
18
|
+
if (status === 529)
|
|
19
|
+
return failure('overloaded', true, status);
|
|
20
|
+
if (status >= 500)
|
|
21
|
+
return failure('server_error', true, status);
|
|
22
|
+
return failure('invalid_request', false, status);
|
|
23
|
+
}
|
|
24
|
+
export class CodexResponsesProvider {
|
|
25
|
+
options;
|
|
26
|
+
model;
|
|
27
|
+
capabilities;
|
|
28
|
+
fetchImplementation;
|
|
29
|
+
codec;
|
|
30
|
+
maxErrorBodyBytes;
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.model = options.model;
|
|
34
|
+
this.fetchImplementation = options.fetchImplementation ?? fetch;
|
|
35
|
+
this.maxErrorBodyBytes = options.maxErrorBodyBytes ?? 64 * 1024;
|
|
36
|
+
if (!Number.isSafeInteger(this.maxErrorBodyBytes) ||
|
|
37
|
+
this.maxErrorBodyBytes <= 0)
|
|
38
|
+
throw new Error('Codex Responses provider limits must be positive integers');
|
|
39
|
+
this.codec = new ResponsesCodec({
|
|
40
|
+
providerLabel: 'Codex Responses provider',
|
|
41
|
+
requestDialect: 'codex-native',
|
|
42
|
+
...(options.thinking === undefined ? {} : { thinking: options.thinking }),
|
|
43
|
+
...(options.maxStreamBufferBytes === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: { maxStreamBufferBytes: options.maxStreamBufferBytes }),
|
|
46
|
+
...(options.maxToolArgumentsBytes === undefined
|
|
47
|
+
? {}
|
|
48
|
+
: { maxToolArgumentsBytes: options.maxToolArgumentsBytes }),
|
|
49
|
+
...(options.maxToolCallsPerResponse === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { maxToolCallsPerResponse: options.maxToolCallsPerResponse }),
|
|
52
|
+
...(options.maxToolMetadataBytes === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { maxToolMetadataBytes: options.maxToolMetadataBytes }),
|
|
55
|
+
...(options.maxReasoningBytes === undefined
|
|
56
|
+
? {}
|
|
57
|
+
: { maxReasoningBytes: options.maxReasoningBytes }),
|
|
58
|
+
});
|
|
59
|
+
this.capabilities = {
|
|
60
|
+
streaming: true,
|
|
61
|
+
usage: true,
|
|
62
|
+
tools: true,
|
|
63
|
+
images: true,
|
|
64
|
+
documents: false,
|
|
65
|
+
webSearch: false,
|
|
66
|
+
thinking: {
|
|
67
|
+
modes: ['disabled', 'enabled', 'adaptive'],
|
|
68
|
+
maxTokens: false,
|
|
69
|
+
},
|
|
70
|
+
...(options.contextWindowTokens === undefined
|
|
71
|
+
? {}
|
|
72
|
+
: { contextWindowTokens: options.contextWindowTokens }),
|
|
73
|
+
terminalReasons: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async *complete(request) {
|
|
77
|
+
const body = this.codec.serialize(request, this.model);
|
|
78
|
+
const signal = request.signal;
|
|
79
|
+
let response;
|
|
80
|
+
try {
|
|
81
|
+
reportProviderTransportActivity(request, 'request-started');
|
|
82
|
+
response = await this.fetchImplementation(`${this.options.baseUrl.replace(/\/+$/u, '')}/responses`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
Authorization: `Bearer ${this.options.apiKey}`,
|
|
86
|
+
accept: 'text/event-stream',
|
|
87
|
+
'content-type': 'application/json',
|
|
88
|
+
originator: 'praxis',
|
|
89
|
+
'OpenAI-Beta': 'responses=experimental',
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify(body),
|
|
92
|
+
...(signal ? { signal } : {}),
|
|
93
|
+
});
|
|
94
|
+
reportProviderTransportActivity(request, 'response-received');
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (signal?.aborted)
|
|
98
|
+
throw failure('cancelled', false);
|
|
99
|
+
if (typeof error === 'object' &&
|
|
100
|
+
error !== null &&
|
|
101
|
+
'name' in error &&
|
|
102
|
+
error.name === 'TimeoutError')
|
|
103
|
+
throw failure('timeout', true);
|
|
104
|
+
if (typeof error === 'object' &&
|
|
105
|
+
error !== null &&
|
|
106
|
+
'code' in error &&
|
|
107
|
+
error.code === 'ETIMEDOUT')
|
|
108
|
+
throw failure('timeout', true);
|
|
109
|
+
throw failure('transport_error', true);
|
|
110
|
+
}
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const reader = response.body?.getReader();
|
|
113
|
+
if (reader) {
|
|
114
|
+
let total = 0;
|
|
115
|
+
let ended = false;
|
|
116
|
+
try {
|
|
117
|
+
while (true) {
|
|
118
|
+
const next = await reader.read();
|
|
119
|
+
if (next.done) {
|
|
120
|
+
ended = true;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
if (next.value.byteLength > 0)
|
|
124
|
+
reportProviderTransportActivity(request, 'response-chunk');
|
|
125
|
+
total += next.value.byteLength;
|
|
126
|
+
if (total > this.maxErrorBodyBytes)
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
/* preserve the redacted HTTP status classification */
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
if (!ended) {
|
|
135
|
+
try {
|
|
136
|
+
await reader.cancel();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
/* preserve the redacted HTTP status classification */
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
reader.releaseLock();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw statusFailure(response.status);
|
|
146
|
+
}
|
|
147
|
+
if (!response.body)
|
|
148
|
+
throw failure('transport_error', true);
|
|
149
|
+
yield* this.codec.stream(response.body, {
|
|
150
|
+
...(signal === undefined ? {} : { signal }),
|
|
151
|
+
onChunk: () => reportProviderTransportActivity(request, 'response-chunk'),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=codex-responses.js.map
|
|
@@ -104,6 +104,7 @@ export class CodexSubscriptionProvider {
|
|
|
104
104
|
this.maxErrorBodyBytes = positiveInteger(options.maxErrorBodyBytes, DEFAULT_MAX_ERROR);
|
|
105
105
|
this.responsesCodec = new ResponsesCodec({
|
|
106
106
|
providerLabel: 'Codex subscription provider',
|
|
107
|
+
requestDialect: 'codex-native',
|
|
107
108
|
...(options.thinking === undefined ? {} : { thinking: options.thinking }),
|
|
108
109
|
...(options.maxStreamBufferBytes === undefined
|
|
109
110
|
? {}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AnthropicCompatibleProvider } from './anthropic-compatible.js';
|
|
2
2
|
import { OpenAICompatibleProvider } from './openai-compatible.js';
|
|
3
3
|
import { OpenAIResponsesProvider } from './openai-responses.js';
|
|
4
|
+
import { CodexResponsesProvider } from './codex-responses.js';
|
|
4
5
|
import { CodexSubscriptionProvider } from './codex-subscription.js';
|
|
5
6
|
import { DeadlineModelProvider } from './deadline-provider.js';
|
|
6
7
|
import { NonStreamingFallbackModelProvider } from './non-streaming-fallback-provider.js';
|
|
@@ -41,7 +42,8 @@ export function resolveProviderContextWindowTokens(options) {
|
|
|
41
42
|
if (options.protocol === 'anthropic-messages')
|
|
42
43
|
return resolveAnthropicModelSpec(options.modelId, options.explicitContextWindowTokens).contextWindowTokens;
|
|
43
44
|
if (options.protocol === 'openai-compatible' ||
|
|
44
|
-
options.protocol === 'openai-responses'
|
|
45
|
+
options.protocol === 'openai-responses' ||
|
|
46
|
+
options.protocol === 'codex-responses')
|
|
45
47
|
return options.explicitContextWindowTokens;
|
|
46
48
|
return undefined;
|
|
47
49
|
}
|
|
@@ -191,6 +193,27 @@ class NativeProviderRegistry {
|
|
|
191
193
|
: { fetchImplementation: this.options.fetchImplementation }),
|
|
192
194
|
}));
|
|
193
195
|
}
|
|
196
|
+
if (target.protocol === 'codex-responses') {
|
|
197
|
+
if (this.options.credential.type !== 'api-key') {
|
|
198
|
+
throw new ProviderAuthenticationError('invalid_credential', 'Provider authentication failed: an API key is required');
|
|
199
|
+
}
|
|
200
|
+
return this.withDeadline(new CodexResponsesProvider({
|
|
201
|
+
baseUrl: target.baseUrl,
|
|
202
|
+
model: target.modelId,
|
|
203
|
+
apiKey: this.options.credential.secret,
|
|
204
|
+
...(this.options.context?.contextWindowTokens === undefined
|
|
205
|
+
? {}
|
|
206
|
+
: {
|
|
207
|
+
contextWindowTokens: this.options.context.contextWindowTokens,
|
|
208
|
+
}),
|
|
209
|
+
...(this.options.codexThinking === undefined
|
|
210
|
+
? {}
|
|
211
|
+
: { thinking: this.options.codexThinking }),
|
|
212
|
+
...(this.options.fetchImplementation === undefined
|
|
213
|
+
? {}
|
|
214
|
+
: { fetchImplementation: this.options.fetchImplementation }),
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
194
217
|
if (target.protocol === 'anthropic-messages') {
|
|
195
218
|
if (this.options.credential.type !== 'api-key') {
|
|
196
219
|
throw new ProviderAuthenticationError('invalid_credential', 'Provider authentication failed: an API key is required');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ProviderProtocol = 'openai-compatible' | 'openai-responses' | 'anthropic-messages' | 'codex-subscription';
|
|
1
|
+
export type ProviderProtocol = 'openai-compatible' | 'openai-responses' | 'anthropic-messages' | 'codex-subscription' | 'codex-responses';
|
|
2
2
|
export type CredentialSource = {
|
|
3
3
|
source: 'env';
|
|
4
4
|
name: string;
|
|
@@ -160,8 +160,9 @@ function parseProvider(value, providerId) {
|
|
|
160
160
|
const protocol = value.protocol;
|
|
161
161
|
if (protocol !== 'openai-compatible' &&
|
|
162
162
|
protocol !== 'openai-responses' &&
|
|
163
|
-
protocol !== 'anthropic-messages'
|
|
164
|
-
|
|
163
|
+
protocol !== 'anthropic-messages' &&
|
|
164
|
+
protocol !== 'codex-responses') {
|
|
165
|
+
fail(`providers.${providerId}.protocol must be openai-compatible, openai-responses, anthropic-messages, or codex-responses`);
|
|
165
166
|
}
|
|
166
167
|
if (!isRecord(value.profiles) || Object.keys(value.profiles).length === 0)
|
|
167
168
|
fail(`providers.${providerId}.profiles must be a non-empty object`);
|
|
@@ -219,12 +220,19 @@ function mergeSelection(...values) {
|
|
|
219
220
|
}
|
|
220
221
|
function validateExperimental(value) {
|
|
221
222
|
if (value === undefined)
|
|
222
|
-
return false;
|
|
223
|
-
if (!isRecord(value)
|
|
224
|
-
(
|
|
225
|
-
|
|
223
|
+
return { codexSubscription: false, codexResponses: false };
|
|
224
|
+
if (!isRecord(value))
|
|
225
|
+
fail('experimental.codexSubscription must be a boolean');
|
|
226
|
+
if (value.codexSubscription !== undefined &&
|
|
227
|
+
typeof value.codexSubscription !== 'boolean')
|
|
226
228
|
fail('experimental.codexSubscription must be a boolean');
|
|
227
|
-
|
|
229
|
+
if (value.codexResponses !== undefined &&
|
|
230
|
+
typeof value.codexResponses !== 'boolean')
|
|
231
|
+
fail('experimental.codexResponses must be a boolean');
|
|
232
|
+
return {
|
|
233
|
+
codexSubscription: value.codexSubscription === true,
|
|
234
|
+
codexResponses: value.codexResponses === true,
|
|
235
|
+
};
|
|
228
236
|
}
|
|
229
237
|
function ensureSelectionPart(value, field) {
|
|
230
238
|
return identifier(value, field);
|
|
@@ -246,7 +254,7 @@ export async function resolveProviderTarget(options) {
|
|
|
246
254
|
: {};
|
|
247
255
|
scanForPlaintextSecrets(user.providers, 'providers');
|
|
248
256
|
const userProviders = parseProviders(user.providers);
|
|
249
|
-
const
|
|
257
|
+
const experimental = validateExperimental(user.experimental);
|
|
250
258
|
const explicit = {
|
|
251
259
|
...(options.provider === undefined
|
|
252
260
|
? {}
|
|
@@ -287,7 +295,7 @@ export async function resolveProviderTarget(options) {
|
|
|
287
295
|
const selected = mergeSelection(settingsSelection, projectSelection, localSelection, envSelection, explicit);
|
|
288
296
|
const providerId = selected.provider ?? 'openai';
|
|
289
297
|
identifier(providerId, 'provider ID');
|
|
290
|
-
if (providerId === 'openai-codex' && !
|
|
298
|
+
if (providerId === 'openai-codex' && !experimental.codexSubscription)
|
|
291
299
|
fail('openai-codex requires experimental.codexSubscription=true');
|
|
292
300
|
if (providerId !== 'openai-codex' &&
|
|
293
301
|
selected.profile === undefined &&
|
|
@@ -303,6 +311,8 @@ export async function resolveProviderTarget(options) {
|
|
|
303
311
|
fail(`built-in provider ${providerId} has an incompatible protocol`);
|
|
304
312
|
if (custom?.protocol === 'codex-subscription')
|
|
305
313
|
fail('custom codex-subscription providers are not allowed');
|
|
314
|
+
if (custom?.protocol === 'codex-responses' && !experimental.codexResponses)
|
|
315
|
+
fail('custom codex-responses providers require experimental.codexResponses=true');
|
|
306
316
|
const definition = custom ?? BUILT_INS[providerId];
|
|
307
317
|
if (!definition)
|
|
308
318
|
throw new ProviderSettingsError('unknown_provider', `Invalid provider settings: unknown provider ${providerId}`);
|
|
@@ -339,8 +349,10 @@ export async function resolveProviderTarget(options) {
|
|
|
339
349
|
protocol,
|
|
340
350
|
baseUrl,
|
|
341
351
|
credential: resolvedCredential,
|
|
342
|
-
billingMode: protocol === 'codex-subscription'
|
|
343
|
-
|
|
352
|
+
billingMode: protocol === 'codex-subscription' || protocol === 'codex-responses'
|
|
353
|
+
? 'subscription'
|
|
354
|
+
: 'api',
|
|
355
|
+
experimental: providerId === 'openai-codex' || protocol === 'codex-responses',
|
|
344
356
|
};
|
|
345
357
|
}
|
|
346
358
|
//# sourceMappingURL=provider-settings.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ModelRequest, type ModelStreamEvent, type ModelThinkingConfig } from '../core/runtime.js';
|
|
2
|
+
export type ResponsesRequestDialect = 'public' | 'codex-native';
|
|
2
3
|
export interface ResponsesCodecOptions {
|
|
3
4
|
providerLabel?: string;
|
|
4
5
|
thinking?: ModelThinkingConfig;
|
|
@@ -7,6 +8,7 @@ export interface ResponsesCodecOptions {
|
|
|
7
8
|
maxToolCallsPerResponse?: number;
|
|
8
9
|
maxToolMetadataBytes?: number;
|
|
9
10
|
maxReasoningBytes?: number;
|
|
11
|
+
requestDialect?: ResponsesRequestDialect;
|
|
10
12
|
}
|
|
11
13
|
export interface ResponsesStreamOptions {
|
|
12
14
|
signal?: AbortSignal;
|
|
@@ -20,6 +22,7 @@ export declare class ResponsesCodec {
|
|
|
20
22
|
readonly maxReasoningBytes: number;
|
|
21
23
|
private readonly label;
|
|
22
24
|
private readonly configuredThinking;
|
|
25
|
+
private readonly requestDialect;
|
|
23
26
|
constructor(options?: ResponsesCodecOptions);
|
|
24
27
|
serialize(request: ModelRequest, model: string): Record<string, unknown>;
|
|
25
28
|
stream(body: ReadableStream<Uint8Array>, options?: ResponsesStreamOptions): AsyncIterable<ModelStreamEvent>;
|
|
@@ -123,7 +123,7 @@ function serializeMessages(messages) {
|
|
|
123
123
|
}
|
|
124
124
|
return { instructions: instructions.join('\n\n'), input };
|
|
125
125
|
}
|
|
126
|
-
function serializeResponsesRequest(request, model, configuredThinking) {
|
|
126
|
+
function serializeResponsesRequest(request, model, configuredThinking, dialect = 'public') {
|
|
127
127
|
if (request.webSearch !== undefined)
|
|
128
128
|
throw invalid('Codex subscription provider does not support web search');
|
|
129
129
|
if (request.betas !== undefined)
|
|
@@ -149,6 +149,15 @@ function serializeResponsesRequest(request, model, configuredThinking) {
|
|
|
149
149
|
instructions,
|
|
150
150
|
input,
|
|
151
151
|
};
|
|
152
|
+
if (dialect === 'codex-native') {
|
|
153
|
+
body.tool_choice = 'auto';
|
|
154
|
+
body.parallel_tool_calls = true;
|
|
155
|
+
for (const item of input) {
|
|
156
|
+
if ((item.role === 'user' || item.role === 'assistant') &&
|
|
157
|
+
item.content !== undefined)
|
|
158
|
+
item.type = 'message';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
152
161
|
if (request.tools?.length) {
|
|
153
162
|
body.tools = request.tools.map((tool) => ({
|
|
154
163
|
type: 'function',
|
|
@@ -601,9 +610,11 @@ export class ResponsesCodec {
|
|
|
601
610
|
maxReasoningBytes;
|
|
602
611
|
label;
|
|
603
612
|
configuredThinking;
|
|
613
|
+
requestDialect;
|
|
604
614
|
constructor(options = {}) {
|
|
605
615
|
this.label = options.providerLabel ?? 'Responses provider';
|
|
606
616
|
this.configuredThinking = options.thinking;
|
|
617
|
+
this.requestDialect = options.requestDialect ?? 'public';
|
|
607
618
|
if (options.thinking !== undefined &&
|
|
608
619
|
options.thinking.mode !== 'disabled' &&
|
|
609
620
|
options.thinking.mode !== 'enabled' &&
|
|
@@ -619,7 +630,7 @@ export class ResponsesCodec {
|
|
|
619
630
|
}
|
|
620
631
|
serialize(request, model) {
|
|
621
632
|
try {
|
|
622
|
-
return serializeResponsesRequest(request, model, this.configuredThinking);
|
|
633
|
+
return serializeResponsesRequest(request, model, this.configuredThinking, this.requestDialect);
|
|
623
634
|
}
|
|
624
635
|
catch (error) {
|
|
625
636
|
throw this.contextualize(error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "praxis-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.1",
|
|
4
4
|
"description": "Local-first, single-user general agent for the command line.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "wuqisen",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"verify:fixture-contracts": "node scripts/verify-fixture-contracts.mjs",
|
|
64
64
|
"test:fixtures": "node scripts/run-fixture-contracts.mjs",
|
|
65
65
|
"test:eval:baseline": "vitest run src/evals/coding-baseline.test.ts src/evals/project-eval-comparison.test.ts src/evals/apply-patch-admission.test.ts src/evals/lsp-diagnostics-admission.test.ts src/evals/glob-ripgrep-admission.test.ts",
|
|
66
|
-
"test:eval:held-out-contract": "vitest run src/evals/held-out-corpus.test.ts",
|
|
66
|
+
"test:eval:held-out-contract": "vitest run src/evals/held-out-corpus.test.ts src/evals/held-out-corpus-v3.test.ts",
|
|
67
67
|
"test:eval:held-out-qualification": "vitest run src/evals/held-out-qualification.test.ts"
|
|
68
68
|
},
|
|
69
69
|
"engines": {
|