Skip to content

Commit ab4bd09

Browse files
HynoRliruifengv
andauthored
fix(oauth): report macOS product version in device model (#33)
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
1 parent c4dd1c7 commit ab4bd09

3 files changed

Lines changed: 49 additions & 1 deletion

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@moonshot-ai/kimi-code": patch
3+
"@moonshot-ai/kimi-code-sdk": patch
4+
---
5+
6+
Report the macOS product version in OAuth device information instead of the Darwin kernel version.

‎packages/oauth/src/identity.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* production state.
88
*/
99

10+
import { execFileSync } from 'node:child_process';
1011
import { randomUUID } from 'node:crypto';
1112
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
1213
import { arch, hostname, release, type } from 'node:os';
@@ -111,11 +112,23 @@ function deviceModel(): string {
111112
const os = type();
112113
const version = release();
113114
const osArch = arch();
114-
if (os === 'Darwin') return `macOS ${version} ${osArch}`;
115+
if (os === 'Darwin') return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
115116
if (os === 'Windows_NT') return `Windows ${version} ${osArch}`;
116117
return `${os} ${version} ${osArch}`.trim();
117118
}
118119

120+
function macOsProductVersion(): string | undefined {
121+
try {
122+
const version = execFileSync('/usr/bin/sw_vers', ['-productVersion'], {
123+
encoding: 'utf-8',
124+
timeout: 1000,
125+
}).trim();
126+
return version.length > 0 ? version : undefined;
127+
} catch {
128+
return undefined;
129+
}
130+
}
131+
119132
function asciiHeader(value: string, fallback = 'unknown'): string {
120133
const cleaned = value.replaceAll(/[^\u0020-\u007E]/g, '').trim();
121134
return cleaned.length > 0 ? cleaned : fallback;

‎packages/oauth/test/identity.test.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,33 @@ describe('ascii header value sanitization', () => {
147147
vi.resetModules();
148148
}
149149
});
150+
151+
it('falls back to Darwin kernel version when sw_vers is unavailable', async () => {
152+
vi.resetModules();
153+
vi.doMock('node:os', async () => ({
154+
...(await vi.importActual<typeof import('node:os')>('node:os')),
155+
hostname: () => 'my-mac',
156+
release: () => '25.5.0',
157+
type: () => 'Darwin',
158+
arch: () => 'arm64',
159+
}));
160+
// Force the sw_vers lookup to fail so the test is deterministic on macOS too,
161+
// where the real binary would otherwise return the host's product version.
162+
vi.doMock('node:child_process', async () => ({
163+
...(await vi.importActual<typeof import('node:child_process')>('node:child_process')),
164+
execFileSync: () => {
165+
throw new Error('ENOENT');
166+
},
167+
}));
168+
169+
try {
170+
const { createKimiDeviceHeaders } = await import('../src/identity');
171+
const headers = createKimiDeviceHeaders({ homeDir: tempHome(), version: '1.0.0' });
172+
expect(headers['X-Msh-Device-Model']).toBe('macOS 25.5.0 arm64');
173+
} finally {
174+
vi.doUnmock('node:os');
175+
vi.doUnmock('node:child_process');
176+
vi.resetModules();
177+
}
178+
});
150179
});

0 commit comments

Comments
 (0)