@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 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` | rendered into `config.toml` as a custom model provider (codex ignores the env var) |
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
- `ANTHROPIC_CUSTOM_HEADERS` is `Name: Value` lines and is redacted from
136
- artifacts like a key. With Cloudflare AI Gateway holding the provider keys,
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.9.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",
@@ -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 override: an OPENAI_BASE_URL set on the host (an LLM gateway) is
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 an OPENAI_BASE_URL override is rendered as. */
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), selected as the default. Top-level keys must precede any
34
- * table, so this block always renders first in the file.
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 (via OPENAI_BASE_URL)"',
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 override, forwarded only when set: OPENAI_BASE_URL (vendor
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
- * Rendered into config.toml by setupFiles (see the module comment); the
73
- * key probe follows it.
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"], "https://api.openai.com/v1", "models"),
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
- if (baseUrl !== undefined) sections.push(renderCodexProviderToml(baseUrl));
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") };
@@ -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 (its
137
- * `optionalHostEnv` minus secret-named entries — never provider keys or
138
- * header bundles), for a CLI that takes a config-file entry rather than an
139
- * env var. Absent when none are set.
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.9.0", // x-release-please-version
12
+ version: "0.10.0", // x-release-please-version
13
13
  revision: null,
14
14
  dirty: null,
15
15
  sourceHash: null,
@@ -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
- // Never the keys or header bundles: adapters whose CLI wants a
906
- // config-file entry (codex's model provider) render from here.
907
- ...(Object.keys(publicOverrides).length > 0 ? { overrides: publicOverrides } : {}),
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 () => {