Skip to content

Commit a66d7f8

Browse files
authored
feat(kiloclaw): pass org ID header to Kilo Gateway for org instances (#1836)
* feat(kiloclaw): pass org ID header to Kilo Gateway for org instances When KILOCODE_ORGANIZATION_ID env var is present (set by buildEnvVars for org instances), the controller now injects X-KiloCode-OrganizationId header into the kilocode provider config. This allows OpenClaw provider requests to include the org scope, enabling usage attribution to organizations. * fix: remove stale org header when KILOCODE_ORGANIZATION_ID is absent Address review feedback: if an instance previously had KILOCODE_ORGANIZATION_ID set and later boots without it, explicitly delete the X-KiloCode-OrganizationId header to prevent stale org attribution.
1 parent 0d47c5d commit a66d7f8

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed

kiloclaw/controller/src/config-writer.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,76 @@ describe('generateBaseConfig', () => {
243243
expect(config.models.providers.kilocode.models).toEqual([{ id: 'kept/model', name: 'Kept' }]);
244244
});
245245

246+
it('sets X-KiloCode-OrganizationId header when KILOCODE_ORGANIZATION_ID is set', () => {
247+
const { deps } = fakeDeps();
248+
const env = { ...minimalEnv(), KILOCODE_ORGANIZATION_ID: 'org_abc123' };
249+
const config = generateBaseConfig(env, '/tmp/openclaw.json', deps);
250+
251+
expect(config.models.providers.kilocode.headers['X-KiloCode-OrganizationId']).toBe(
252+
'org_abc123'
253+
);
254+
expect(config.models.providers.kilocode.models).toEqual([]);
255+
});
256+
257+
it('does not set org header when KILOCODE_ORGANIZATION_ID is not set', () => {
258+
const { deps } = fakeDeps();
259+
const config = generateBaseConfig(minimalEnv(), '/tmp/openclaw.json', deps);
260+
261+
// No kilocode provider entry created when neither baseUrl nor orgId is set
262+
expect(config.models).toBeUndefined();
263+
});
264+
265+
it('preserves existing kilocode config when adding org header', () => {
266+
const existing = JSON.stringify({
267+
models: {
268+
providers: {
269+
kilocode: {
270+
baseUrl: 'https://tunnel.example.com/',
271+
headers: { 'X-Custom': 'value' },
272+
models: [{ id: 'kept/model', name: 'Kept' }],
273+
},
274+
},
275+
},
276+
});
277+
const { deps } = fakeDeps(existing);
278+
const env = { ...minimalEnv(), KILOCODE_ORGANIZATION_ID: 'org_xyz789' };
279+
const config = generateBaseConfig(env, '/tmp/openclaw.json', deps);
280+
281+
expect(config.models.providers.kilocode.headers['X-KiloCode-OrganizationId']).toBe(
282+
'org_xyz789'
283+
);
284+
expect(config.models.providers.kilocode.headers['X-Custom']).toBe('value');
285+
expect(config.models.providers.kilocode.baseUrl).toBe('https://tunnel.example.com/');
286+
expect(config.models.providers.kilocode.models).toEqual([{ id: 'kept/model', name: 'Kept' }]);
287+
});
288+
289+
it('removes stale org header when KILOCODE_ORGANIZATION_ID is no longer set', () => {
290+
const existing = JSON.stringify({
291+
models: {
292+
providers: {
293+
kilocode: {
294+
baseUrl: 'https://tunnel.example.com/',
295+
headers: {
296+
'X-KiloCode-OrganizationId': 'org_old_stale',
297+
'X-Custom': 'preserved',
298+
},
299+
models: [{ id: 'kept/model', name: 'Kept' }],
300+
},
301+
},
302+
},
303+
});
304+
const { deps } = fakeDeps(existing);
305+
// No KILOCODE_ORGANIZATION_ID in env
306+
const config = generateBaseConfig(minimalEnv(), '/tmp/openclaw.json', deps);
307+
308+
// Stale org header removed
309+
expect(config.models.providers.kilocode.headers['X-KiloCode-OrganizationId']).toBeUndefined();
310+
// Other headers and config preserved
311+
expect(config.models.providers.kilocode.headers['X-Custom']).toBe('preserved');
312+
expect(config.models.providers.kilocode.baseUrl).toBe('https://tunnel.example.com/');
313+
expect(config.models.providers.kilocode.models).toEqual([{ id: 'kept/model', name: 'Kept' }]);
314+
});
315+
246316
it('removes agents.defaults.models allowlist left by openclaw onboard', () => {
247317
const existing = JSON.stringify({
248318
agents: {

kiloclaw/controller/src/config-writer.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,24 @@ export function generateBaseConfig(
180180
console.log(`Overriding kilocode base URL: ${env.KILOCODE_API_BASE_URL}`);
181181
}
182182

183+
// Pass org scope to KiloCode provider as request header when available.
184+
// This is used by OpenClaw provider requests (not Kilo CLI).
185+
// Header name matches ORGANIZATION_ID_HEADER in src/lib/constants.ts.
186+
if (env.KILOCODE_ORGANIZATION_ID) {
187+
config.models = config.models ?? {};
188+
config.models.providers = config.models.providers ?? {};
189+
config.models.providers.kilocode = config.models.providers.kilocode ?? {};
190+
config.models.providers.kilocode.headers = config.models.providers.kilocode.headers ?? {};
191+
config.models.providers.kilocode.headers['X-KiloCode-OrganizationId'] =
192+
env.KILOCODE_ORGANIZATION_ID;
193+
config.models.providers.kilocode.models = config.models.providers.kilocode.models ?? [];
194+
console.log('Configured KiloCode organization header from KILOCODE_ORGANIZATION_ID');
195+
} else {
196+
// Remove stale org header from previous boots (e.g., instance was transferred
197+
// from org to personal, or org was deleted).
198+
delete config.models?.providers?.kilocode?.headers?.['X-KiloCode-OrganizationId'];
199+
}
200+
183201
// User-selected default model override.
184202
if (env.KILOCODE_DEFAULT_MODEL) {
185203
config.agents = config.agents ?? {};

0 commit comments

Comments
 (0)