chore: apply requested changes

This commit is contained in:
Ralph Khreish
2025-09-29 16:23:37 +02:00
parent 986ac117ae
commit 100c3dc47d
17 changed files with 3052 additions and 2511 deletions

View File

@@ -2,16 +2,12 @@
"name": "@tm/ai-sdk-provider-grok-cli",
"private": true,
"description": "AI SDK provider for Grok CLI integration",
"keywords": ["ai", "grok", "x.ai", "cli", "language-model", "provider"],
"license": "MIT",
"sideEffects": false,
"type": "module",
"types": "./src/index.ts",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": "./src/index.ts"
},
"files": ["dist/**/*", "README.md"],
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
@@ -28,14 +24,11 @@
"typescript": "^5.9.2",
"vitest": "^3.2.4"
},
"peerDependencies": {
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/provider-utils": "^3.0.10",
"jsonc-parser": "^3.3.1"
},
"engines": {
"node": ">=18"
},
"keywords": ["ai", "grok", "x.ai", "cli", "language-model", "provider"],
"files": ["dist/**/*", "README.md"],
"publishConfig": {
"access": "public"
}

View File

@@ -110,7 +110,7 @@ export class GrokCliLanguageModel implements LanguageModelV2 {
*/
private async executeGrokCli(
args: string[],
options: { timeout?: number } = {}
options: { timeout?: number; apiKey?: string } = {}
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
// Default timeout based on model type
let defaultTimeout = 120000; // 2 minutes default
@@ -123,7 +123,11 @@ export class GrokCliLanguageModel implements LanguageModelV2 {
return new Promise((resolve, reject) => {
const child = spawn('grok', args, {
stdio: 'pipe',
cwd: this.settings.workingDirectory || process.cwd()
cwd: this.settings.workingDirectory || process.cwd(),
env:
options.apiKey === undefined
? process.env
: { ...process.env, GROK_CLI_API_KEY: options.apiKey }
});
let stdout = '';
@@ -295,7 +299,7 @@ export class GrokCliLanguageModel implements LanguageModelV2 {
}
try {
const result = await this.executeGrokCli(args);
const result = await this.executeGrokCli(args, { apiKey });
if (result.exitCode !== 0) {
// Handle authentication errors
@@ -323,11 +327,14 @@ export class GrokCliLanguageModel implements LanguageModelV2 {
let text = response.text || '';
// Extract JSON if in object-json mode
if (
'mode' in options &&
(options as any).mode?.type === 'object-json' &&
text
) {
const isObjectJson = (
o: unknown
): o is { mode: { type: 'object-json' } } =>
!!o &&
typeof o === 'object' &&
'mode' in o &&
(o as any).mode?.type === 'object-json';
if (isObjectJson(options) && text) {
text = extractJson(text);
}

View File

@@ -9,49 +9,49 @@ describe('extractJson', () => {
it('should extract JSON from markdown code blocks', () => {
const text = '```json\n{"name": "test", "value": 42}\n```';
const result = extractJson(text);
expect(result).toBe('{"name": "test", "value": 42}');
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should extract JSON from generic code blocks', () => {
const text = '```\n{"name": "test", "value": 42}\n```';
const result = extractJson(text);
expect(result).toBe('{"name": "test", "value": 42}');
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should remove JavaScript variable declarations', () => {
const text = 'const result = {"name": "test", "value": 42};';
const result = extractJson(text);
expect(result).toBe('{"name": "test", "value": 42}');
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should handle let variable declarations', () => {
const text = 'let data = {"name": "test", "value": 42};';
const result = extractJson(text);
expect(result).toBe('{"name": "test", "value": 42}');
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should handle var variable declarations', () => {
const text = 'var config = {"name": "test", "value": 42};';
const result = extractJson(text);
expect(result).toBe('{"name": "test", "value": 42}');
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should extract JSON arrays', () => {
const text = '[{"name": "test1"}, {"name": "test2"}]';
const result = extractJson(text);
expect(result).toBe('[{"name": "test1"}, {"name": "test2"}]');
expect(JSON.parse(result)).toEqual([{ name: 'test1' }, { name: 'test2' }]);
});
it('should convert JavaScript object literals to JSON', () => {
const text = "{name: 'test', value: 42}";
const result = extractJson(text);
expect(result).toBe('{"name": "test", "value": 42}');
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should return valid JSON as-is', () => {
it('should return valid JSON (canonical formatting)', () => {
const text = '{"name": "test", "value": 42}';
const result = extractJson(text);
expect(result).toBe('{"name": "test", "value": 42}');
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should return original text when JSON parsing fails completely', () => {
@@ -76,6 +76,6 @@ describe('extractJson', () => {
it('should handle mixed quotes in object literals', () => {
const text = `{name: "test", value: 'mixed quotes'}`;
const result = extractJson(text);
expect(result).toBe('{"name": "test", "value": "mixed quotes"}');
expect(JSON.parse(result)).toEqual({ name: 'test', value: 'mixed quotes' });
});
});

View File

@@ -121,6 +121,19 @@ describe('createPromptFromMessages', () => {
expect(result).toBe('custom: Custom message');
});
it('should trim whitespace from message content', () => {
const messages = [
{ role: 'user', content: ' Hello with spaces ' },
{ role: 'assistant', content: '\n\nResponse with newlines\n\n' }
];
const result = createPromptFromMessages(messages);
expect(result).toBe(
'User: Hello with spaces\n\nAssistant: Response with newlines'
);
});
});
describe('escapeShellArg', () => {

View File

@@ -81,7 +81,7 @@ export function convertFromGrokCliResponse(responseText: string): {
// Find the last assistant message
const assistantMessage = messages
.filter((msg: any) => msg.role === 'assistant')
.filter((msg) => msg.role === 'assistant')
.pop();
if (assistantMessage && assistantMessage.content) {

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"module": "ESNext",
"lib": ["ES2022"],
"declaration": true,
"declarationMap": true,
@@ -24,7 +24,7 @@
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "NodeNext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"types": ["node"],
"resolveJsonModule": true,