@workos/quickstudy 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/package.json +1 -1
- package/src/adapters/codex.ts +38 -15
- package/src/adapters/types.ts +6 -4
- package/src/build-info.generated.ts +1 -1
- package/src/runner/execute.ts +4 -4
package/README.md
CHANGED
|
@@ -130,10 +130,11 @@ preflight key probe at it:
|
|
|
130
130
|
| Agent | Overrides | How the CLI gets them |
|
|
131
131
|
| ------ | ------------------------------------------------ | -------------------------------------------------------------------------------------- |
|
|
132
132
|
| claude | `ANTHROPIC_BASE_URL`, `ANTHROPIC_CUSTOM_HEADERS` | env, read natively by Claude Code (and by the `diagnose` analyst) |
|
|
133
|
-
| codex | `OPENAI_BASE_URL`
|
|
133
|
+
| codex | `OPENAI_BASE_URL`, `OPENAI_CUSTOM_HEADERS` | rendered into `config.toml` as a custom model provider (`base_url`, `http_headers`); codex has no env channel for either |
|
|
134
134
|
|
|
135
|
-
`
|
|
136
|
-
artifacts like a key
|
|
135
|
+
The `*_CUSTOM_HEADERS` bundles are `Name: Value` lines and are redacted from
|
|
136
|
+
artifacts like a key; a `cf-aig-metadata` line is how a gateway attributes
|
|
137
|
+
the traffic per app. With Cloudflare AI Gateway holding the provider keys,
|
|
137
138
|
the gateway token *is* the API key:
|
|
138
139
|
|
|
139
140
|
```bash
|
|
@@ -142,8 +143,12 @@ export ANTHROPIC_API_KEY=<gateway token>
|
|
|
142
143
|
export ANTHROPIC_CUSTOM_HEADERS="cf-aig-authorization: Bearer <gateway token>"
|
|
143
144
|
export OPENAI_BASE_URL=https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/openai
|
|
144
145
|
export OPENAI_API_KEY=<gateway token>
|
|
146
|
+
export OPENAI_CUSTOM_HEADERS='cf-aig-metadata: {"app":"my-evals"}'
|
|
145
147
|
```
|
|
146
148
|
|
|
149
|
+
Leave every override unset and both agents run vendor-direct against
|
|
150
|
+
api.anthropic.com and api.openai.com — nothing is rendered, nothing changes.
|
|
151
|
+
|
|
147
152
|
## How it works
|
|
148
153
|
|
|
149
154
|
Every attempt is one point in a 2-D plan:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workos/quickstudy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "An open-source, company-agnostic eval harness for measuring whether coding agents can complete real integration tasks, across an agent x feature x framework x surface matrix.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/adapters/codex.ts
CHANGED
|
@@ -10,38 +10,55 @@
|
|
|
10
10
|
* command wrapper pipes it into `codex login --with-api-key` (stdin, never
|
|
11
11
|
* argv) to write the auth.json the CLI requires.
|
|
12
12
|
*
|
|
13
|
-
* Endpoint
|
|
13
|
+
* Endpoint overrides: an OPENAI_BASE_URL set on the host (an LLM gateway) is
|
|
14
14
|
* rendered into the same config.toml as a custom model provider — codex
|
|
15
15
|
* 0.149 ignores the env var for its built-in provider (verified live: it
|
|
16
16
|
* still dialed api.openai.com), while a `[model_providers.*]` entry with
|
|
17
17
|
* `env_key = "OPENAI_API_KEY"` routes every request through the override.
|
|
18
|
+
* OPENAI_CUSTOM_HEADERS (`Name: Value` lines, the Codex counterpart of Claude
|
|
19
|
+
* Code's ANTHROPIC_CUSTOM_HEADERS) becomes that provider's `http_headers` —
|
|
20
|
+
* e.g. `cf-aig-metadata` so a gateway can attribute the traffic. Codex has
|
|
21
|
+
* no env-var channel for extra headers, so the values land in config.toml
|
|
22
|
+
* inside the attempt container: the same trust boundary as the env that
|
|
23
|
+
* already carries them, written over stdin, destroyed with the container.
|
|
24
|
+
* With neither override set nothing is rendered and the built-in provider
|
|
25
|
+
* talks to api.openai.com — vendor-direct stays the default.
|
|
18
26
|
*/
|
|
19
27
|
|
|
20
28
|
import type { McpServerConfig } from "../isolation/mcp.ts";
|
|
21
|
-
import { endpointUrl } from "../secrets.ts";
|
|
29
|
+
import { endpointUrl, parseHeaderLines } from "../secrets.ts";
|
|
22
30
|
import { parseCodexStream } from "./parse.ts";
|
|
23
31
|
import type { AttemptMetrics, ContainerAgentAdapter, ContainerAttemptContext } from "./types.ts";
|
|
24
32
|
|
|
25
33
|
/** Container path of the Codex config (containers run as root). */
|
|
26
34
|
export const CODEX_CONFIG_PATH = "/root/.codex/config.toml";
|
|
27
35
|
|
|
28
|
-
/** The config.toml model provider id
|
|
36
|
+
/** The config.toml model provider id the OPENAI_* overrides are rendered as. */
|
|
29
37
|
export const CODEX_GATEWAY_PROVIDER = "quickstudy-gateway";
|
|
30
38
|
|
|
39
|
+
/** Codex's own default for the built-in provider; the probe and a headers-only override use it. */
|
|
40
|
+
const OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
41
|
+
|
|
31
42
|
/**
|
|
32
43
|
* A custom model provider pointing at `baseUrl` (the Responses API, which
|
|
33
|
-
* codex requires),
|
|
34
|
-
* table, so this block always
|
|
44
|
+
* codex requires), carrying `headers` on every request, selected as the
|
|
45
|
+
* default. Top-level keys must precede any table, so this block always
|
|
46
|
+
* renders first in the file. JSON string escapes are valid TOML basic-string
|
|
47
|
+
* escapes, which keeps header values such as JSON metadata intact.
|
|
35
48
|
*/
|
|
36
|
-
function renderCodexProviderToml(baseUrl: string): string {
|
|
49
|
+
function renderCodexProviderToml(baseUrl: string, headers: Readonly<Record<string, string>>): string {
|
|
50
|
+
const entries = Object.entries(headers);
|
|
37
51
|
return [
|
|
38
52
|
`model_provider = ${JSON.stringify(CODEX_GATEWAY_PROVIDER)}`,
|
|
39
53
|
"",
|
|
40
54
|
`[model_providers.${CODEX_GATEWAY_PROVIDER}]`,
|
|
41
|
-
'name = "OpenAI (
|
|
55
|
+
'name = "OpenAI (quickstudy endpoint overrides)"',
|
|
42
56
|
`base_url = ${JSON.stringify(baseUrl)}`,
|
|
43
57
|
'env_key = "OPENAI_API_KEY"',
|
|
44
58
|
'wire_api = "responses"',
|
|
59
|
+
...(entries.length > 0
|
|
60
|
+
? [`http_headers = { ${entries.map(([name, value]) => `${JSON.stringify(name)} = ${JSON.stringify(value)}`).join(", ")} }`]
|
|
61
|
+
: []),
|
|
45
62
|
"",
|
|
46
63
|
].join("\n");
|
|
47
64
|
}
|
|
@@ -66,13 +83,15 @@ export class CodexAdapter implements ContainerAgentAdapter {
|
|
|
66
83
|
readonly versionCommand = ["codex", "--version"] as const;
|
|
67
84
|
readonly requiredHostEnv = ["OPENAI_API_KEY"] as const;
|
|
68
85
|
/**
|
|
69
|
-
* Endpoint
|
|
86
|
+
* Endpoint overrides, forwarded only when set: OPENAI_BASE_URL (vendor
|
|
70
87
|
* default https://api.openai.com/v1) — e.g. Cloudflare AI Gateway's
|
|
71
|
-
* `/openai` endpoint, where the gateway token doubles as OPENAI_API_KEY
|
|
72
|
-
*
|
|
73
|
-
*
|
|
88
|
+
* `/openai` endpoint, where the gateway token doubles as OPENAI_API_KEY —
|
|
89
|
+
* and OPENAI_CUSTOM_HEADERS (`Name: Value` lines) for whatever else the
|
|
90
|
+
* endpoint wants on each request, such as `cf-aig-metadata`. Both are
|
|
91
|
+
* rendered into config.toml by setupFiles (see the module comment); the
|
|
92
|
+
* key probe follows them.
|
|
74
93
|
*/
|
|
75
|
-
readonly optionalHostEnv = ["OPENAI_BASE_URL"] as const;
|
|
94
|
+
readonly optionalHostEnv = ["OPENAI_BASE_URL", "OPENAI_CUSTOM_HEADERS"] as const;
|
|
76
95
|
/**
|
|
77
96
|
* Egress-proxy allowlist (harness-default layer). Codex is reqwest-based;
|
|
78
97
|
* reqwest honors HTTP_PROXY/HTTPS_PROXY env by default (system proxies) —
|
|
@@ -103,15 +122,19 @@ export class CodexAdapter implements ContainerAgentAdapter {
|
|
|
103
122
|
|
|
104
123
|
keyProbe(env: Record<string, string>): { url: string; headers: Record<string, string> } {
|
|
105
124
|
return {
|
|
106
|
-
url: endpointUrl(env["OPENAI_BASE_URL"],
|
|
107
|
-
headers: { Authorization: `Bearer ${env["OPENAI_API_KEY"] ?? ""}` },
|
|
125
|
+
url: endpointUrl(env["OPENAI_BASE_URL"], OPENAI_DEFAULT_BASE_URL, "models"),
|
|
126
|
+
headers: { ...parseHeaderLines(env["OPENAI_CUSTOM_HEADERS"]), Authorization: `Bearer ${env["OPENAI_API_KEY"] ?? ""}` },
|
|
108
127
|
};
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
setupFiles(ctx: ContainerAttemptContext): Record<string, string> {
|
|
112
131
|
const sections: string[] = [];
|
|
113
132
|
const baseUrl = ctx.overrides?.["OPENAI_BASE_URL"];
|
|
114
|
-
|
|
133
|
+
const headers = parseHeaderLines(ctx.overrides?.["OPENAI_CUSTOM_HEADERS"]);
|
|
134
|
+
// Either override needs the custom provider; headers alone keep the vendor endpoint.
|
|
135
|
+
if (baseUrl !== undefined || Object.keys(headers).length > 0) {
|
|
136
|
+
sections.push(renderCodexProviderToml(baseUrl ?? OPENAI_DEFAULT_BASE_URL, headers));
|
|
137
|
+
}
|
|
115
138
|
if (ctx.mcpServers !== undefined) sections.push(renderCodexMcpToml(ctx.mcpServers));
|
|
116
139
|
if (sections.length === 0) return {};
|
|
117
140
|
return { [CODEX_CONFIG_PATH]: sections.join("\n") };
|
package/src/adapters/types.ts
CHANGED
|
@@ -133,10 +133,12 @@ export interface ContainerAttemptContext {
|
|
|
133
133
|
/** The runtime's native web-tool policy (default: blocked). */
|
|
134
134
|
webPolicy: WebPolicy;
|
|
135
135
|
/**
|
|
136
|
-
* The adapter's endpoint overrides that are set on the host (
|
|
137
|
-
* `optionalHostEnv`
|
|
138
|
-
*
|
|
139
|
-
*
|
|
136
|
+
* The adapter's own endpoint overrides that are set on the host (every
|
|
137
|
+
* `optionalHostEnv` value, header bundles included — never the provider
|
|
138
|
+
* keys), for a CLI that takes a config-file entry rather than an env var.
|
|
139
|
+
* Anything rendered from these is written inside the attempt container
|
|
140
|
+
* only, the same boundary the env already crosses. Absent when none are
|
|
141
|
+
* set.
|
|
140
142
|
*/
|
|
141
143
|
overrides?: Readonly<Record<string, string>>;
|
|
142
144
|
}
|
|
@@ -9,7 +9,7 @@ export const EMBEDDED_BUILD_INFO: {
|
|
|
9
9
|
// (the `x-release-please-version` marker anchors the replacement). It is
|
|
10
10
|
// restored as-is after a binary build (scripts/build-binary.ts), so the
|
|
11
11
|
// committed source `--version` stays in lockstep with package.json.
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.10.0", // x-release-please-version
|
|
13
13
|
revision: null,
|
|
14
14
|
dirty: null,
|
|
15
15
|
sourceHash: null,
|
package/src/runner/execute.ts
CHANGED
|
@@ -823,7 +823,6 @@ async function runContainerAttemptV3(
|
|
|
823
823
|
// Endpoint overrides set on the host for this agent (gateway base URLs
|
|
824
824
|
// and the headers they need) — see ContainerAgentAdapter.optionalHostEnv.
|
|
825
825
|
const overrides = agentAdapter !== undefined ? collectOptionalHostEnv([agentAdapter]) : {};
|
|
826
|
-
const publicOverrides = Object.fromEntries(Object.entries(overrides).filter(([name]) => !isSecretEnvName(name)));
|
|
827
826
|
if (agentAdapter !== undefined) {
|
|
828
827
|
// The agent's provider keys, validated at run preflight — injected
|
|
829
828
|
// at create via the 0600 env-file channel, never argv.
|
|
@@ -902,9 +901,10 @@ async function runContainerAttemptV3(
|
|
|
902
901
|
trialIndex: entry.trialIndex,
|
|
903
902
|
...(rewrittenServers !== undefined ? { mcpServers: rewrittenServers } : {}),
|
|
904
903
|
webPolicy: environment?.webPolicy ?? "native-web-blocked",
|
|
905
|
-
//
|
|
906
|
-
// config-file entry (codex's model provider
|
|
907
|
-
|
|
904
|
+
// The adapter's own overrides (never the provider keys): a CLI
|
|
905
|
+
// that wants a config-file entry (codex's model provider and its
|
|
906
|
+
// http_headers) renders them from here, inside the container.
|
|
907
|
+
...(Object.keys(overrides).length > 0 ? { overrides } : {}),
|
|
908
908
|
};
|
|
909
909
|
// Treatment config files (e.g. the MCP treatment's vendor config).
|
|
910
910
|
await inStage("environment", async () => {
|