Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/agent-core-auth-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/agent-core": patch
---

Thread the host identity through the managed auth facades so OAuth token refreshes from inside the core carry the `X-Msh-*` device headers.
5 changes: 5 additions & 0 deletions .changeset/cli-web-login-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix request headers not being passed correctly on some requests.
5 changes: 5 additions & 0 deletions .changeset/kap-server-host-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kap-server": minor
---

Require a `hostIdentity` when starting the server and derive the default outbound identity headers (User-Agent + `X-Msh-*`) from it, replacing the hardcoded CLI fallback. The `version` option is renamed to `serverVersion`, and session export manifests now record the host product version — plus an optional desktop version for desktop exports — instead of the engine version.
5 changes: 5 additions & 0 deletions .changeset/oauth-host-identity-platform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code-oauth": minor
---

Rework the host identity type: rename `userAgentProduct` to `productName` and add a required `platform` field, so every host explicitly declares the `X-Msh-Platform` value it reports instead of silently inheriting the CLI's. OAuth requests now also send the product User-Agent (with the optional runtime suffix), so the OAuth host can tell client families and surfaces apart.
5 changes: 5 additions & 0 deletions .changeset/sdk-host-identity-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code-sdk": minor
---

Rename `userAgentProduct` to `productName` and require `platform` in the host identity options exposed by the SDK.
5 changes: 5 additions & 0 deletions .changeset/v2-bootstrap-client-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/agent-core-v2": minor
---

Replace the bootstrap `clientVersion` with a required `clientIdentity` host identity object: the OAuth device-flow endpoints now send the full `X-Msh-*` device headers on every host, telemetry reads the client version from the same source, and the session export manifest gains an optional desktop version field.
21 changes: 12 additions & 9 deletions apps/kimi-code/src/cli/sub/web/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@

import { join } from 'node:path';

import { hostRequestHeadersSeed } from '@moonshot-ai/agent-core-v2';
import { createServerLogger, startServer, type ServerLogger } from '@moonshot-ai/kap-server';
import chalk from 'chalk';
import { type Command } from 'commander';

import { PRODUCT_NAME } from '#/constant/app';
import { WEB_USER_AGENT_SUFFIX } from '#/constant/app';
import { getNativeWebAssetsDir } from '#/native/web-assets';
import { darkColors } from '#/tui/theme/colors';
import { openUrl as defaultOpenUrl } from '#/utils/open-url';
import { getDataDir } from '#/utils/paths';

import {
buildKimiDefaultHeaders,
createKimiCodeHostIdentity,
getHostPackageRoot,
getVersion,
} from '../../version';
Expand Down Expand Up @@ -268,8 +267,16 @@ async function runServerInProcess(
homeDir: getDataDir(),
// Report the CLI's product version as `server_version` (/meta, web UI)
// rather than kap-server's private package version.
version,
hostIdentity: { productName: PRODUCT_NAME },
serverVersion: version,
// The CLI's host identity: feeds the engine's bootstrap client identity
// and the derived outbound headers (User-Agent + X-Msh-*), so web-UI
// OAuth flows and model / WebSearch requests carry the CLI identity. The
// `web` User-Agent suffix distinguishes web-UI traffic from direct CLI
// runs upstream (same product token, same platform).
hostIdentity: {
...createKimiCodeHostIdentity(version),
userAgentSuffix: WEB_USER_AGENT_SUFFIX,
},
logLevel: options.logLevel,
logger,
debugEndpoints: options.debugEndpoints,
Expand All @@ -282,10 +289,6 @@ async function runServerInProcess(
// the first web session is created. Config and environment toggles still
// gate delivery.
telemetry: true,
// Seed the CLI's Kimi identity headers so the engine's outbound
// requests (model, WebSearch, FetchURL) carry the same User-Agent +
// X-Msh-* identity as direct CLI runs.
seeds: hostRequestHeadersSeed(buildKimiDefaultHeaders(version)),
webAssetsDir: serverWebAssetsDir(),
});
logger.info('serving the REST/WS API and the bundled web UI');
Expand Down
4 changes: 2 additions & 2 deletions apps/kimi-code/src/cli/v2/run-v2-print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ export async function runV2Print(
const identity = createKimiCodeHostIdentity(version);
const hostHeaders = createKimiDefaultHeaders({ homeDir, ...identity });

const { app } = bootstrap({ homeDir, clientVersion: version }, [
const { app } = bootstrap({ homeDir, clientIdentity: identity }, [
...logSeed(logging),
...hostIdentitySeed({ productName: PRODUCT_NAME }),
...hostIdentitySeed({ displayName: PRODUCT_NAME }),
...hostRequestHeadersSeed(hostHeaders),
// `--skillsDir` (v1 print parity): explicit skill dirs replace default
// user / project discovery for this process.
Expand Down
13 changes: 3 additions & 10 deletions apps/kimi-code/src/cli/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
import { existsSync, readFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';

import { createKimiDefaultHeaders, createKimiUserAgent, type KimiHostIdentity } from '@moonshot-ai/kimi-code-oauth';
import { createKimiUserAgent, KIMI_CODE_PLATFORM, type KimiHostIdentity } from '@moonshot-ai/kimi-code-oauth';

import { CLI_USER_AGENT_PRODUCT } from '#/constant/app';

import { getDataDir } from '../utils/paths';
import { KIMI_BUILD_INFO } from './build-info';

const MODULE_DIR = import.meta.dirname;
Expand Down Expand Up @@ -50,8 +49,9 @@ export function getVersion(): string {

export function createKimiCodeHostIdentity(version = getVersion()): KimiHostIdentity {
return {
userAgentProduct: CLI_USER_AGENT_PRODUCT,
productName: CLI_USER_AGENT_PRODUCT,
version,
platform: KIMI_CODE_PLATFORM,
};
}

Expand All @@ -62,10 +62,3 @@ export function createKimiCodeHostIdentity(version = getVersion()): KimiHostIden
export function createKimiCodeUserAgent(version = getVersion()): string {
return createKimiUserAgent(createKimiCodeHostIdentity(version));
}

export function buildKimiDefaultHeaders(version: string): Record<string, string> {
return createKimiDefaultHeaders({
homeDir: getDataDir(),
...createKimiCodeHostIdentity(version),
});
}
4 changes: 4 additions & 0 deletions apps/kimi-code/src/constant/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const CLI_UI_MODE = 'shell';
// Telemetry ui_mode for the `echadron web` host. Same product
// as the CLI (CLI_USER_AGENT_PRODUCT); the surface is distinguished by ui_mode.
export const WEB_UI_MODE = 'web';
// User-Agent suffix for the `kimi web` host: its requests go out as
// `kimi-code-cli/<version> (web)` so upstream can tell web-UI traffic
// apart from direct CLI runs without changing the product token or platform.
export const WEB_USER_AGENT_SUFFIX = 'web';

// Give telemetry a short flush window without making CLI exit feel stuck.
export const CLI_SHUTDOWN_TIMEOUT_MS = 3000;
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/test/cli/run-shell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ describe('runShell', () => {
expect(mocks.kimiHarnessConstructor).toHaveBeenCalledWith(
expect.objectContaining({
identity: expect.objectContaining({
userAgentProduct: 'echadron-cli',
productName: 'echadron-cli',
version: '1.2.3-test',
}),
sessionStartedProperties: { yolo: true, auto: false, plan: true, afk: false },
Expand Down
12 changes: 8 additions & 4 deletions apps/kimi-code/test/cli/telemetry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ vi.mock('@moonshot-ai/kimi-telemetry', () => ({
withTelemetryContext: vi.fn(),
}));

vi.mock('@moonshot-ai/kimi-code-oauth', () => ({
createKimiDeviceId: mocks.createKimiDeviceId,
KIMI_CODE_PROVIDER_NAME: 'managed:kimi-code',
}));
vi.mock('@moonshot-ai/kimi-code-oauth', async (importOriginal) => {
const actual = await importOriginal<typeof import('@moonshot-ai/kimi-code-oauth')>();
return {
...actual,
createKimiDeviceId: mocks.createKimiDeviceId,
KIMI_CODE_PROVIDER_NAME: 'managed:kimi-code',
};
});

vi.mock('@moonshot-ai/kimi-code-sdk', async (importOriginal) => {
const actual = await importOriginal<typeof import('@moonshot-ai/kimi-code-sdk')>();
Expand Down
6 changes: 5 additions & 1 deletion apps/kimi-code/test/cli/v2-run-print.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,11 @@ function makeFakeHarness() {
{
platform: 'linux',
arch: 'x64',
clientVersion: '1.2.3-test',
clientIdentity: {
productName: 'test-product',
version: '1.2.3-test',
platform: 'test_platform',
},
osHomeDir: '/home/test',
getEnv: () => undefined,
},
Expand Down
7 changes: 0 additions & 7 deletions apps/kimi-code/test/cli/version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { dirname, join } from 'node:path';
import { describe, expect, it } from 'vitest';

import {
buildKimiDefaultHeaders,
createKimiCodeUserAgent,
getHostPackageJsonPath,
getHostPackageRoot,
Expand All @@ -21,12 +20,6 @@ describe('cli version helpers', () => {
expect(getVersion()).toBe(pkg.version);
});

it('builds default headers with the echadron-cli user-agent', () => {
const headers = buildKimiDefaultHeaders('1.2.3');

expect(headers['User-Agent']).toBe('echadron-cli/1.2.3');
});

it('builds the product user-agent for ad-hoc fetches', () => {
expect(createKimiCodeUserAgent('1.2.3')).toBe('echadron-cli/1.2.3');
});
Expand Down
2 changes: 1 addition & 1 deletion apps/vscode/docs/node-sdk-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ stay in the trusted Extension Host.

The runtime constructs the SDK client with:

- `userAgentProduct: "echadron-vscode"`
- `productName: "echadron-vscode"`
- `version` from `apps/vscode/package.json`
- `uiMode: "vscode"`

Expand Down
3 changes: 2 additions & 1 deletion apps/vscode/src/runtime/echadron-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ export class EchadronRuntime {
createKimiHarness({
...(options.homeDir === undefined ? {} : { homeDir: options.homeDir }),
identity: {
userAgentProduct: "echadron-vscode",
productName: "echadron-vscode",
version: options.version,
platform: "kimi_code_vscode",
},
uiMode: "vscode",
});
Expand Down
2 changes: 1 addition & 1 deletion apps/vscode/test/kimi-harness.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ async function createRuntimeRig(extraAliases: readonly string[] = []): Promise<R
async function createPlainHarness(homeDir: string): Promise<KimiHarness> {
const harness = createKimiHarness({
homeDir,
identity: { userAgentProduct: "kimi-code-cli", version: "test" },
identity: { productName: "kimi-code-cli", version: "test", platform: "kimi_code_cli" },
});
cleanups.push(() => harness.close());
return harness;
Expand Down
2 changes: 1 addition & 1 deletion apps/vscode/test/replay-resume.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async function createReplayRig(): Promise<ReplayRig> {
const provider = await createFakeProviderHarness();
const harness = createKimiHarness({
homeDir,
identity: { userAgentProduct: "kimi-code-vscode", version: "test" },
identity: { productName: "kimi-code-vscode", version: "test", platform: "kimi_code_vscode" },
});
await harness.setConfig({
providers: {
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core-v2/src/app/auth/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1038,7 +1038,7 @@ function managedModel(
class OAuthToolkitService extends KimiOAuthToolkit implements IOAuthToolkit {
declare readonly _serviceBrand: undefined;
constructor(@IBootstrapService bootstrap: IBootstrapService) {
super({ homeDir: bootstrap.homeDir });
super({ homeDir: bootstrap.homeDir, identity: bootstrap.clientIdentity });
}
}

Expand Down
20 changes: 12 additions & 8 deletions packages/agent-core-v2/src/app/bootstrap/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Defines the `IBootstrapService`, the snapshot of the world the process runs
* in, resolved once at startup and frozen for the process: observed host facts
* (`platform`, `arch`, `cwd`, `osHomeDir`, `getEnv`, `clientVersion`) and the
* (`platform`, `arch`, `cwd`, `osHomeDir`, `getEnv`, `clientIdentity`) and the
* app path layout (`homeDir`, `configPath`, …). `resolveBootstrapOptions` is
* the single place that reads `process.env` / `os.homedir()` / invocation
* input to resolve the snapshot; everything downstream reads from
Expand All @@ -19,6 +19,8 @@ import { homedir } from 'node:os';
import { join } from 'pathe';
import { applyEchadronEnvironmentAliases } from '@moonshot-ai/kimi-code-oauth';

import type { KimiHostIdentity } from '@moonshot-ai/kimi-code-oauth';

import { SyncDescriptor } from '#/_base/di/descriptors';
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
import { createAppScope, type Scope, type ScopeSeed } from '#/_base/di/scope';
Expand All @@ -37,7 +39,7 @@ export interface IBootstrapOptions {
readonly arch: string;
readonly cwd: string;
readonly env: NodeJS.ProcessEnv;
readonly clientVersion: string;
readonly clientIdentity: KimiHostIdentity;
}

export const IBootstrapOptions: ServiceIdentifier<IBootstrapOptions> =
Expand All @@ -62,7 +64,7 @@ export interface IBootstrapService {
readonly osHomeDir: string;
readonly homeDir: string;
readonly configPath: string;
readonly clientVersion: string;
readonly clientIdentity: KimiHostIdentity;
readonly sessionsDir: string;
readonly blobsDir: string;
readonly storeDir: string;
Expand All @@ -88,10 +90,12 @@ export interface BootstrapInput {
readonly platform?: NodeJS.Platform;
readonly arch?: string;
readonly cwd?: string;
readonly clientVersion?: string;
/** Required: every process names its host. There is deliberately no default
— a fabricated identity would silently misreport the host upstream. */
readonly clientIdentity: KimiHostIdentity;
}

export function resolveBootstrapOptions(input: BootstrapInput = {}): IBootstrapOptions {
export function resolveBootstrapOptions(input: BootstrapInput): IBootstrapOptions {
const env = input.env ?? process.env;
applyEchadronEnvironmentAliases(env);
const osHomeDir = input.osHomeDir ?? homedir();
Expand All @@ -105,19 +109,19 @@ export function resolveBootstrapOptions(input: BootstrapInput = {}): IBootstrapO
arch: input.arch ?? process.arch,
cwd: input.cwd ?? process.cwd(),
env,
clientVersion: input.clientVersion ?? 'unknown',
clientIdentity: input.clientIdentity,
};
}

export function bootstrapSeed(input: BootstrapInput = {}): ScopeSeed {
export function bootstrapSeed(input: BootstrapInput): ScopeSeed {
return [[IBootstrapOptions as ServiceIdentifier<unknown>, resolveBootstrapOptions(input)]];
}

export interface BootstrapResult {
readonly app: Scope;
}

export function bootstrap(input: BootstrapInput = {}, extraSeeds: ScopeSeed = []): BootstrapResult {
export function bootstrap(input: BootstrapInput, extraSeeds: ScopeSeed = []): BootstrapResult {
const options = resolveBootstrapOptions(input);
const app = createAppScope({
extra: [...bootstrapSeed(input), ...storageSeed(options), ...skillSeed(), ...extraSeeds],
Expand Down
6 changes: 4 additions & 2 deletions packages/agent-core-v2/src/app/bootstrap/bootstrapService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import { basename, join, relative } from 'pathe';

import type { KimiHostIdentity } from '@moonshot-ai/kimi-code-oauth';

import { LifecycleScope, ScopeActivation, registerScopedService } from '#/_base/di/scope';

import {
Expand All @@ -32,7 +34,7 @@ export class BootstrapService implements IBootstrapService {
readonly osHomeDir: string;
readonly homeDir: string;
readonly configPath: string;
readonly clientVersion: string;
readonly clientIdentity: KimiHostIdentity;
readonly sessionsDir: string;
readonly blobsDir: string;
readonly storeDir: string;
Expand All @@ -51,7 +53,7 @@ export class BootstrapService implements IBootstrapService {
this.env = options.env;
this.homeDir = options.homeDir;
this.configPath = options.configPath;
this.clientVersion = options.clientVersion;
this.clientIdentity = options.clientIdentity;
this.sessionsDir = join(options.homeDir, 'sessions');
this.blobsDir = join(options.homeDir, 'blobs');
this.storeDir = join(options.homeDir, 'store');
Expand Down
12 changes: 6 additions & 6 deletions packages/agent-core-v2/src/app/hostIdentity/hostIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* `hostIdentity` domain (L3) — runtime identity of the embedding host.
*
* Holds process-level overrides the host product (CLI, desktop, …) injects at
* the composition root: `productName` fills the `${product_name}` slot in the
* the composition root: `displayName` fills the `${product_name}` slot in the
* base system-prompt template, `replyStyleGuide` replaces the
* `${reply_style_guide}` block (the CLI default describes Markdown rendering
* in a terminal). Composition roots set them through {@link hostIdentitySeed};
Expand All @@ -13,8 +13,8 @@
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
import { LifecycleScope, registerScopedService, ScopeActivation, type ScopeSeed } from '#/_base/di/scope';

export interface HostIdentityOverrides {
readonly productName?: string;
export interface PromptIdentityOverrides {
readonly displayName?: string;
readonly replyStyleGuide?: string;
}

Expand All @@ -36,13 +36,13 @@ export class HostIdentity implements IHostIdentity {
) {}
}

export function hostIdentitySeed(overrides: HostIdentityOverrides | undefined): ScopeSeed {
export function hostIdentitySeed(overrides: PromptIdentityOverrides | undefined): ScopeSeed {
if (overrides === undefined) return [];
if (overrides.productName === undefined && overrides.replyStyleGuide === undefined) return [];
if (overrides.displayName === undefined && overrides.replyStyleGuide === undefined) return [];
return [
[
IHostIdentity as ServiceIdentifier<unknown>,
new HostIdentity(overrides.productName, overrides.replyStyleGuide),
new HostIdentity(overrides.displayName, overrides.replyStyleGuide),
],
];
}
Expand Down
Loading
Loading