Skip to content

Commit 7c41ba8

Browse files
Merge pull request #1025 from corbitsdev/cl-7913-re-vendor-1ad0104
Re-vendor Interchange trees to main HEAD for the director loader
2 parents 233bb39 + 5ab66fb commit 7c41ba8

81 files changed

Lines changed: 2824 additions & 1180 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/VENDORING.md

Lines changed: 62 additions & 28 deletions
Large diffs are not rendered by default.

src/config.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import {
2323
SOURCE_MAX_TOKENS,
2424
} from "./config/index.js";
2525
import { DIRECTOR_IDS } from "./agent/directors/types.js";
26+
import {
27+
clearSourceCredentials,
28+
peekSourceCredentialSecret,
29+
} from "./config/source-credentials.js";
2630
import type { Config, UnconfiguredConfig } from "./config/index.js";
2731
import {
2832
mergeProviderIntoSettings,
@@ -69,6 +73,7 @@ afterEach(() => {
6973
resetGoModelDiscoveryForTests();
7074
resetZenModelDiscoveryForTests();
7175
setProviderContextWindowOverrides(undefined);
76+
clearSourceCredentials();
7277
});
7378

7479
function assertConfigured(
@@ -1514,13 +1519,16 @@ describe("buildOpenAISource", () => {
15141519
expect(source.baseURL).toBe("http://localhost:11434/v1");
15151520
});
15161521

1517-
test("substitutes a placeholder apiKey when none is provided (keyless)", () => {
1522+
test("registers the keyless placeholder in the credential cell when none is provided", () => {
15181523
const source = buildOpenAISource({
15191524
id: "local",
15201525
baseURL: "http://localhost:8080/v1",
15211526
model: "local-model",
15221527
});
1523-
expect(source.apiKey).toBe(KEYLESS_API_KEY);
1528+
expect(source.credentialId).toBe("local");
1529+
expect(peekSourceCredentialSecret(source.credentialId)).toBe(
1530+
KEYLESS_API_KEY,
1531+
);
15241532
});
15251533
});
15261534

src/config/index.ts

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
import type { CodexProfile } from "../auth/codex/store.js";
3030
import type { XaiProfile } from "../auth/xai/store.js";
3131
import { listCodexProfiles, listXaiProfiles } from "./oauth-stores.js";
32+
import { registerSourceCredential } from "./source-credentials.js";
3233
import {
3334
codexProfilesToCatalogEntries,
3435
codexProvidersAsSettings,
@@ -105,12 +106,22 @@ import { resolveProfile } from "./profiles.js";
105106
// revert the ceiling.
106107
export const SOURCE_MAX_TOKENS = 16384;
107108

108-
// Placeholder sent in the Authorization header for keyless local providers
109-
// (e.g. Ollama). The runtime's InferenceSource type requires a non-empty
110-
// apiKey string; the value is injected as `Bearer <key>` by the harness but
111-
// keyless servers ignore it entirely.
109+
// Placeholder resolved from the credential cell for keyless local providers
110+
// (e.g. Ollama). Sources that need no secret register this sentinel; the
111+
// harness still sends it as `Bearer <key>` but keyless servers ignore it.
112112
export const KEYLESS_API_KEY = "keyless";
113113

114+
// Registers the secret behind a source id in the credential cell (see
115+
// ./source-credentials.ts), falling back to the keyless sentinel when no key
116+
// was configured. Every buildXSource below calls this so the vendored
117+
// credentialId auth model resolves the secret at send time.
118+
function registerSourceSecret(id: string, apiKey: string | undefined): void {
119+
registerSourceCredential(
120+
id,
121+
apiKey !== undefined && apiKey.length > 0 ? apiKey : KEYLESS_API_KEY,
122+
);
123+
}
124+
114125
function applyPersistedOAuthDefaults(
115126
settings: Settings | null,
116127
projected: Record<string, ProviderSettings>,
@@ -253,16 +264,14 @@ export function buildOpenAISource(fields: {
253264
fields.reasoningEffort !== undefined
254265
? { providerOptions: { reasoning_effort: fields.reasoningEffort } }
255266
: {};
267+
registerSourceSecret(fields.id, fields.apiKey);
256268
return {
257269
id: fields.id,
258270
provider: "openai-compatible",
259271
baseURL: isOllamaProviderId(fields.id)
260272
? ollamaOpenAIBaseURL(fields.baseURL)
261273
: normalizeOpenAICompatibleBaseURL(fields.baseURL),
262-
apiKey:
263-
fields.apiKey !== undefined && fields.apiKey.length > 0
264-
? fields.apiKey
265-
: KEYLESS_API_KEY,
274+
credentialId: fields.id,
266275
model: fields.model,
267276
defaults: { maxTokens: SOURCE_MAX_TOKENS, ...overrides },
268277
...(fields.quirks !== undefined ? { quirks: fields.quirks } : {}),
@@ -318,7 +327,8 @@ export type ProviderCatalogEntry = Omit<
318327
// "codex-responses" adapter (the Codex backend speaks the Responses API, not
319328
// Chat Completions) and carries the account id + a session id through
320329
// providerOptions, where the adapter lifts them into request headers. The
321-
// access token is the apiKey; the harness injects it as the bearer credential.
330+
// access token is registered in the credential cell under the source id; the
331+
// harness resolves it as the bearer credential at send time.
322332
export function buildCodexSource(fields: {
323333
id: string;
324334
apiKey: string;
@@ -334,19 +344,21 @@ export function buildCodexSource(fields: {
334344
providerOptions[CODEX_ACCOUNT_ID_OPTION] = fields.accountId;
335345
if (fields.reasoningEffort !== undefined)
336346
providerOptions["reasoning_effort"] = fields.reasoningEffort;
347+
registerSourceSecret(fields.id, fields.apiKey);
337348
return {
338349
id: fields.id,
339350
provider: CODEX_RESPONSES_PROVIDER,
340351
baseURL: CODEX_BASE_URL,
341-
apiKey: fields.apiKey,
352+
credentialId: fields.id,
342353
model: fields.model,
343354
defaults: { maxTokens: SOURCE_MAX_TOKENS, providerOptions },
344355
};
345356
}
346357

347358
// Build the InferenceSource for an xAI/Grok OAuth profile. Routes to the
348359
// "grok-responses" adapter (the grok-cli proxy speaks the Responses API, not
349-
// Chat Completions). The access token is the apiKey; the caller's user id is
360+
// Chat Completions). The access token is registered in the credential cell
361+
// under the source id; the caller's user id is
350362
// decoded from it and lifted into the x-grok-user-id header by the adapter.
351363
// The session id becomes the request's prompt_cache_key so every call in the
352364
// thread routes to the same cache shard (store:false has no other signal).
@@ -364,11 +376,12 @@ export function buildXaiSource(fields: {
364376
if (userId !== undefined) providerOptions[GROK_USER_ID_OPTION] = userId;
365377
if (fields.reasoningEffort !== undefined)
366378
providerOptions["reasoning_effort"] = fields.reasoningEffort;
379+
registerSourceSecret(fields.id, fields.apiKey);
367380
return {
368381
id: fields.id,
369382
provider: GROK_RESPONSES_PROVIDER,
370383
baseURL: XAI_BASE_URL,
371-
apiKey: fields.apiKey,
384+
credentialId: fields.id,
372385
model: fields.model,
373386
defaults: { maxTokens: SOURCE_MAX_TOKENS, providerOptions },
374387
};
@@ -388,14 +401,12 @@ export function buildBifrostSource(fields: {
388401
fields.reasoningEffort !== undefined
389402
? { providerOptions: { reasoning_effort: fields.reasoningEffort } }
390403
: {};
404+
registerSourceSecret(fields.id, fields.apiKey);
391405
return {
392406
id: fields.id,
393407
provider: BIFROST_PROVIDER,
394408
baseURL: normalizeOpenAICompatibleBaseURL(fields.baseURL),
395-
apiKey:
396-
fields.apiKey !== undefined && fields.apiKey.length > 0
397-
? fields.apiKey
398-
: KEYLESS_API_KEY,
409+
credentialId: fields.id,
399410
model: fields.model,
400411
defaults: { maxTokens: SOURCE_MAX_TOKENS, ...overrides },
401412
};
@@ -408,14 +419,12 @@ export function buildAnthropicSource(fields: {
408419
apiKey?: string;
409420
model: string;
410421
}): InferenceSource {
422+
registerSourceSecret(fields.id, fields.apiKey);
411423
return {
412424
id: fields.id,
413425
provider: "anthropic",
414426
baseURL: fields.baseURL.replace(/\/+$/, ""),
415-
apiKey:
416-
fields.apiKey !== undefined && fields.apiKey.length > 0
417-
? fields.apiKey
418-
: KEYLESS_API_KEY,
427+
credentialId: fields.id,
419428
model: fields.model,
420429
defaults: { maxTokens: SOURCE_MAX_TOKENS },
421430
};
@@ -431,16 +440,13 @@ export function buildGoSource(fields: {
431440
reasoningEffort?: ReasoningEffort;
432441
}): InferenceSource {
433442
const endpoint = resolveGoEndpoint(fields.model);
434-
const apiKey =
435-
fields.apiKey !== undefined && fields.apiKey.length > 0
436-
? fields.apiKey
437-
: KEYLESS_API_KEY;
443+
registerSourceSecret(fields.id, fields.apiKey);
438444
if (endpoint.adapter === "anthropic") {
439445
return {
440446
id: fields.id,
441447
provider: OPENCODE_GO_MESSAGES_PROVIDER,
442448
baseURL: endpoint.baseURL,
443-
apiKey,
449+
credentialId: fields.id,
444450
model: fields.model,
445451
defaults: {
446452
maxTokens: SOURCE_MAX_TOKENS,
@@ -455,7 +461,7 @@ export function buildGoSource(fields: {
455461
id: fields.id,
456462
provider: OPENAI_RESPONSES_PROVIDER,
457463
baseURL: endpoint.baseURL,
458-
apiKey,
464+
credentialId: fields.id,
459465
model: fields.model,
460466
defaults: {
461467
maxTokens: SOURCE_MAX_TOKENS,
@@ -471,7 +477,7 @@ export function buildGoSource(fields: {
471477
id: fields.id,
472478
baseURL:
473479
endpoint.baseURL.length > 0 ? endpoint.baseURL : OPENCODE_GO_BASE_URL,
474-
apiKey,
480+
...(fields.apiKey !== undefined ? { apiKey: fields.apiKey } : {}),
475481
model: fields.model,
476482
...(fields.reasoningEffort !== undefined
477483
? { reasoningEffort: fields.reasoningEffort }
@@ -500,16 +506,13 @@ export function buildZenSource(fields: {
500506
reasoningEffort?: ReasoningEffort;
501507
}): InferenceSource {
502508
const endpoint = resolveZenEndpoint(fields.model);
503-
const apiKey =
504-
fields.apiKey !== undefined && fields.apiKey.length > 0
505-
? fields.apiKey
506-
: KEYLESS_API_KEY;
509+
registerSourceSecret(fields.id, fields.apiKey);
507510
if (endpoint.adapter === "anthropic") {
508511
return {
509512
id: fields.id,
510513
provider: ZEN_MESSAGES_PROVIDER,
511514
baseURL: endpoint.baseURL,
512-
apiKey,
515+
credentialId: fields.id,
513516
model: fields.model,
514517
defaults: {
515518
maxTokens: SOURCE_MAX_TOKENS,
@@ -524,7 +527,7 @@ export function buildZenSource(fields: {
524527
id: fields.id,
525528
provider: OPENAI_RESPONSES_PROVIDER,
526529
baseURL: endpoint.baseURL,
527-
apiKey,
530+
credentialId: fields.id,
528531
model: fields.model,
529532
defaults: {
530533
maxTokens: SOURCE_MAX_TOKENS,
@@ -540,7 +543,7 @@ export function buildZenSource(fields: {
540543
id: fields.id,
541544
baseURL:
542545
endpoint.baseURL.length > 0 ? endpoint.baseURL : ZEN_DEFAULT_BASE_URL,
543-
apiKey,
546+
...(fields.apiKey !== undefined ? { apiKey: fields.apiKey } : {}),
544547
model: fields.model,
545548
...(fields.reasoningEffort !== undefined
546549
? { reasoningEffort: fields.reasoningEffort }

src/config/inference-sources.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from "../provider/context-window.js";
1414
import { createOpenAICompatibleAdapter } from "../provider/openai-compatible-adapter.js";
1515
import { createInferenceDependencies } from "../provider/inference-dependencies.js";
16+
import { clearSourceCredentials } from "./source-credentials.js";
1617
import { OPENAI_RESPONSES_PROVIDER } from "../provider/openai-responses.js";
1718
import { ZEN_MESSAGES_PROVIDER } from "../provider/zen-anthropic-adapter.js";
1819
import { firstClassProviderById } from "../../packages/first-class-providers/src/index.js";
@@ -81,6 +82,7 @@ function settingsWithWindow(): Settings {
8182
afterEach(() => {
8283
setProviderContextWindowOverrides(undefined);
8384
globalThis.fetch = originalFetch;
85+
clearSourceCredentials();
8486
});
8587

8688
describe("contextWindow / maxTokens split (CL-7784)", () => {

src/config/source-credentials.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// First-party credential cell backing the vendored inference auth model.
2+
//
3+
// Since the 1ad0104 re-vendor, `InferenceSource` carries no inline secret:
4+
// it names a `credentialId` and every send resolves the secret through a
5+
// `CredentialMaterialResolver` ("credential cell" in upstream terms — read
6+
// `CredentialMaterialResolver`'s doc comment in the vendored
7+
// `@intx/types`). This module is that cell for first-party API-key and
8+
// OAuth access-token sources: each `buildXSource` in `./index.ts` registers
9+
// the secret it was built with under the source id, and the inference entry
10+
// points (`assemble-runtime`, subagent run, summarizer fallback) hand
11+
// `readSourceCredentialMaterial` to the vendored trees as their resolver.
12+
//
13+
// Keyed by source id because ids are unique per live source within a
14+
// process. The map lives at module scope so sources built in one layer
15+
// (config) resolve in another (agent env, reactor options) without threading
16+
// secrets through every intermediate shape.
17+
import type { CredentialMaterialResolver } from "@intx/types";
18+
19+
const cell = new Map<string, string>();
20+
21+
export function registerSourceCredential(
22+
credentialId: string,
23+
secret: string,
24+
): void {
25+
cell.set(credentialId, secret);
26+
}
27+
28+
/** The resolver handed to vendored inference calls. Fails closed. */
29+
export const readSourceCredentialMaterial: CredentialMaterialResolver = (
30+
credentialId: string,
31+
) => {
32+
const secret = cell.get(credentialId);
33+
if (secret === undefined)
34+
throw new Error(`Unknown inference credential "${credentialId}".`);
35+
return { secret };
36+
};
37+
38+
/** Non-throwing read for "did the token change?" comparisons. */
39+
export function peekSourceCredentialSecret(
40+
credentialId: string,
41+
): string | undefined {
42+
return cell.get(credentialId);
43+
}
44+
45+
/** Test seam: empties the cell between cases. */
46+
export function clearSourceCredentials(): void {
47+
cell.clear();
48+
}

src/context-compactor.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ describe("createPruningCompactor — consolidated handoff (CL-7521)", () => {
953953
provider: "openai",
954954
model: "test-model",
955955
baseURL: "http://localhost:1",
956-
apiKey: "k",
956+
credentialId: "test",
957957
};
958958
let calls = 0;
959959
const summarize = createModelSummarizer({

0 commit comments

Comments
 (0)