Skip to content

Commit 1d4beb8

Browse files
committed
Re-apply ledgered Interchange patches onto main HEAD
Every PATCHES.md entry re-carried against 1ad0104 with an as-is / adapt triage recorded in the ledgers; none was subsumed upstream. Assembly resolves direct-wins-over-deps for the new contextTransforms field, harness/reactor adaptations follow the rewritten usage emission and tryCorrelate regions, and VENDORING.md records the new pin, the loader provenance row, and the tool-packaging decision.
1 parent 5a291aa commit 1d4beb8

44 files changed

Lines changed: 1472 additions & 262 deletions

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: 60 additions & 28 deletions
Large diffs are not rendered by default.

‎src/config.test.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
SOURCE_MAX_TOKENS,
2424
} from "./config/index.js";
2525
import { DIRECTOR_IDS } from "./agent/directors/types.js";
26+
import { peekSourceCredentialSecret } from "./config/source-credentials.js";
2627
import type { Config, UnconfiguredConfig } from "./config/index.js";
2728
import {
2829
mergeProviderIntoSettings,
@@ -1505,13 +1506,16 @@ describe("buildOpenAISource", () => {
15051506
expect(source.baseURL).toBe("http://localhost:11434/v1");
15061507
});
15071508

1508-
test("substitutes a placeholder apiKey when none is provided (keyless)", () => {
1509+
test("registers the keyless placeholder in the credential cell when none is provided", () => {
15091510
const source = buildOpenAISource({
15101511
id: "local",
15111512
baseURL: "http://localhost:8080/v1",
15121513
model: "local-model",
15131514
});
1514-
expect(source.apiKey).toBe(KEYLESS_API_KEY);
1515+
expect(source.credentialId).toBe("local");
1516+
expect(peekSourceCredentialSecret(source.credentialId)).toBe(
1517+
KEYLESS_API_KEY,
1518+
);
15151519
});
15161520
});
15171521

‎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/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({

‎src/exec/runner.ts‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import {
1414
isCodexProviderName,
1515
} from "../config/codex-providers.js";
1616
import { xaiProfileFromProviderName } from "../config/xai-providers.js";
17+
import {
18+
peekSourceCredentialSecret,
19+
registerSourceCredential,
20+
} from "../config/source-credentials.js";
1721
import { formatDirectorSystemPrompt } from "../agent/directors/identity.js";
1822
import { DIRECTOR_REGISTRY } from "../agent/directors/registry.js";
1923
import type { DirectorId, DirectorPackage } from "../agent/directors/types.js";
@@ -772,7 +776,7 @@ export async function runExec(config: Config): Promise<ExecResult> {
772776
const { access } = await refreshSelectedProviderCredential(() =>
773777
getValidCodexToken(initialCodexProfile),
774778
);
775-
liveSource = { ...liveSource, apiKey: access };
779+
registerSourceCredential(liveSource.credentialId, access);
776780
liveSubAgentProvider.current = {
777781
...liveSubAgentProvider.current,
778782
apiKey: access,
@@ -782,7 +786,7 @@ export async function runExec(config: Config): Promise<ExecResult> {
782786
const { access } = await refreshSelectedProviderCredential(() =>
783787
getValidXaiToken(initialXaiProfile),
784788
);
785-
liveSource = { ...liveSource, apiKey: access };
789+
registerSourceCredential(liveSource.credentialId, access);
786790
liveSubAgentProvider.current = {
787791
...liveSubAgentProvider.current,
788792
apiKey: access,
@@ -798,12 +802,13 @@ export async function runExec(config: Config): Promise<ExecResult> {
798802
// A 401 here usually means the shared OAuth file rotated under another
799803
// process; re-read it so the retry runs on the fresh token.
800804
refreshAuth: async () => {
805+
const before = peekSourceCredentialSecret(liveSource.credentialId);
801806
const fresh = await ensureFreshInferenceSource(
802807
liveSource,
803808
config.providers,
804809
);
805-
if (fresh.apiKey === liveSource.apiKey) return;
806810
liveSource = fresh;
811+
if (peekSourceCredentialSecret(fresh.credentialId) === before) return;
807812
if (currentAgent !== null)
808813
setAgentSourceUnlessClosed(currentAgent, fresh);
809814
},
@@ -859,10 +864,10 @@ export async function runExec(config: Config): Promise<ExecResult> {
859864
inferenceDeps,
860865
getSources: () => {
861866
const sources = liveSources.length > 0 ? liveSources : [liveSource];
862-
// Prefer liveSource credentials on the active id when OAuth was refreshed.
863-
return sources.map((s) =>
864-
s.id === liveSource.id ? { ...s, apiKey: liveSource.apiKey } : s,
865-
);
867+
// OAuth refreshes land in the shared credential cell (keyed by source
868+
// id), so every source already resolves the live secret — no per-send
869+
// credential copy is needed.
870+
return sources;
866871
},
867872
getDefaultSource: () =>
868873
liveDefaultSource.length > 0 ? liveDefaultSource : liveSource.id,
@@ -1005,15 +1010,15 @@ export async function runExec(config: Config): Promise<ExecResult> {
10051010
// Final OAuth refresh immediately before send (token may have aged during MCP).
10061011
if (initialCodexProfile !== undefined) {
10071012
const { access } = await getValidCodexToken(initialCodexProfile);
1008-
if (access !== liveSource.apiKey) {
1009-
liveSource = { ...liveSource, apiKey: access };
1013+
if (access !== peekSourceCredentialSecret(liveSource.credentialId)) {
1014+
registerSourceCredential(liveSource.credentialId, access);
10101015
setAgentSourceUnlessClosed(activeAgent, liveSource);
10111016
}
10121017
}
10131018
if (initialXaiProfile !== undefined) {
10141019
const { access } = await getValidXaiToken(initialXaiProfile);
1015-
if (access !== liveSource.apiKey) {
1016-
liveSource = { ...liveSource, apiKey: access };
1020+
if (access !== peekSourceCredentialSecret(liveSource.credentialId)) {
1021+
registerSourceCredential(liveSource.credentialId, access);
10171022
setAgentSourceUnlessClosed(activeAgent, liveSource);
10181023
}
10191024
}

‎src/session/assemble-runtime.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ function stubChatAgentWiring(
219219
id: "s",
220220
provider: "test",
221221
baseURL: "http://localhost",
222-
apiKey: "k",
222+
credentialId: "s",
223223
model: "m",
224224
},
225225
],

0 commit comments

Comments
 (0)