agent-gear 1.1.4 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/bridge.d.ts +70 -3
- package/dist/acp/bridge.d.ts.map +1 -1
- package/dist/acp/bridge.js +261 -59
- package/dist/acp/bridge.js.map +1 -1
- package/dist/acp/bridge.test.js +1 -1
- package/dist/acp/bridge.test.js.map +1 -1
- package/dist/acp/detector.d.ts +2 -0
- package/dist/acp/detector.d.ts.map +1 -1
- package/dist/acp/detector.js +28 -2
- package/dist/acp/detector.js.map +1 -1
- package/dist/acp/sync-bridge.d.ts +4 -1
- package/dist/acp/sync-bridge.d.ts.map +1 -1
- package/dist/acp/sync-bridge.js +17 -3
- package/dist/acp/sync-bridge.js.map +1 -1
- package/dist/socketio-client.d.ts.map +1 -1
- package/dist/socketio-client.js +131 -1
- package/dist/socketio-client.js.map +1 -1
- package/package.json +2 -2
- package/src/acp/bridge.test.ts +1 -1
- package/src/acp/bridge.ts +327 -60
- package/src/acp/detector.ts +28 -2
- package/src/acp/sync-bridge.ts +19 -3
- package/src/socketio-client.ts +154 -2
- package/tests/acp-bridge.test.ts +250 -31
- package/tests/acp-detector.test.ts +66 -6
- package/tests/acp-sync-bridge.test.ts +39 -1
- package/tests/socketio-client.test.ts +211 -0
package/src/socketio-client.ts
CHANGED
|
@@ -48,11 +48,12 @@ interface AcpStartPayload {
|
|
|
48
48
|
serverType: string;
|
|
49
49
|
cwd: string;
|
|
50
50
|
mcpServers?: Array<{ name: string; command: string; args?: string[]; env?: Array<{ name: string; value: string }> }>;
|
|
51
|
+
additionalDirectories?: string[];
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
interface AcpMessagePayload {
|
|
54
55
|
sessionId: string;
|
|
55
|
-
message: { type: string; content: string };
|
|
56
|
+
message: { type: string; content: string; attachments?: Array<Record<string, unknown>> };
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
interface AcpCancelPayload {
|
|
@@ -63,6 +64,29 @@ interface AcpStopPayload {
|
|
|
63
64
|
sessionId: string;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
interface AcpSetConfigOptionPayload {
|
|
68
|
+
sessionId: string;
|
|
69
|
+
configId: string;
|
|
70
|
+
value: string | boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface AcpDeleteHistorySessionPayload {
|
|
74
|
+
requestId: string;
|
|
75
|
+
sessionId: string;
|
|
76
|
+
externalSessionId: string;
|
|
77
|
+
cwd: string;
|
|
78
|
+
serverType?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface AcpAuthenticatePayload {
|
|
82
|
+
sessionId: string;
|
|
83
|
+
methodId: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface AcpLogoutPayload {
|
|
87
|
+
sessionId: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
66
90
|
interface AcpSetModePayload {
|
|
67
91
|
sessionId: string;
|
|
68
92
|
modeId: string;
|
|
@@ -79,12 +103,14 @@ interface AcpResumePayload {
|
|
|
79
103
|
externalSessionId: string;
|
|
80
104
|
serverType: string;
|
|
81
105
|
cwd: string;
|
|
106
|
+
additionalDirectories?: string[];
|
|
82
107
|
}
|
|
83
108
|
|
|
84
109
|
interface AcpListSessionsPayload {
|
|
85
110
|
requestId: string;
|
|
86
111
|
serverType: string;
|
|
87
112
|
cwd: string;
|
|
113
|
+
cursor?: string | null;
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
interface AcpLoadSessionPayload {
|
|
@@ -226,6 +252,7 @@ export function connectToServer(
|
|
|
226
252
|
sessionId: data.sessionId,
|
|
227
253
|
cwd: data.cwd,
|
|
228
254
|
mcpServers: data.mcpServers,
|
|
255
|
+
additionalDirectories: data.additionalDirectories,
|
|
229
256
|
}, spawner);
|
|
230
257
|
|
|
231
258
|
acpBridges.set(data.sessionId, bridge);
|
|
@@ -250,6 +277,13 @@ export function connectToServer(
|
|
|
250
277
|
if (payload.currentModel) result.currentModel = payload.currentModel;
|
|
251
278
|
result.availableModels = payload.availableModels ?? [];
|
|
252
279
|
if (payload.currentModelId) result.currentModelId = payload.currentModelId;
|
|
280
|
+
result.configOptions = payload.configOptions ?? [];
|
|
281
|
+
if (payload.promptCapabilities) result.promptCapabilities = payload.promptCapabilities;
|
|
282
|
+
if (payload.agentInfo) result.agentInfo = payload.agentInfo;
|
|
283
|
+
if (payload.authMethods) result.authMethods = payload.authMethods;
|
|
284
|
+
if (payload.supportsAdditionalDirectories != null) {
|
|
285
|
+
result.supportsAdditionalDirectories = payload.supportsAdditionalDirectories;
|
|
286
|
+
}
|
|
253
287
|
socket.emit('acp:start:result', result);
|
|
254
288
|
};
|
|
255
289
|
|
|
@@ -291,6 +325,7 @@ export function connectToServer(
|
|
|
291
325
|
const bridge = new AcpSessionBridge(socket, {
|
|
292
326
|
sessionId: data.sessionId,
|
|
293
327
|
cwd: data.cwd,
|
|
328
|
+
additionalDirectories: data.additionalDirectories,
|
|
294
329
|
}, spawner);
|
|
295
330
|
|
|
296
331
|
acpBridges.set(data.sessionId, bridge);
|
|
@@ -313,6 +348,10 @@ export function connectToServer(
|
|
|
313
348
|
if (payload.currentModel) result.currentModel = payload.currentModel;
|
|
314
349
|
result.availableModels = payload.availableModels ?? [];
|
|
315
350
|
if (payload.currentModelId) result.currentModelId = payload.currentModelId;
|
|
351
|
+
result.configOptions = payload.configOptions ?? [];
|
|
352
|
+
if (payload.promptCapabilities) result.promptCapabilities = payload.promptCapabilities;
|
|
353
|
+
if (payload.agentInfo) result.agentInfo = payload.agentInfo;
|
|
354
|
+
if (payload.authMethods) result.authMethods = payload.authMethods;
|
|
316
355
|
socket.emit('acp:resume:result', result);
|
|
317
356
|
};
|
|
318
357
|
|
|
@@ -351,7 +390,7 @@ export function connectToServer(
|
|
|
351
390
|
const bridge = new AcpSyncBridge({ cwd: data.cwd }, spawner);
|
|
352
391
|
try {
|
|
353
392
|
await bridge.connect();
|
|
354
|
-
const result = await bridge.listSessions(data.cwd);
|
|
393
|
+
const result = await bridge.listSessions(data.cwd, data.cursor);
|
|
355
394
|
await bridge.disconnect();
|
|
356
395
|
socket.emit('acp:list-sessions:result', {
|
|
357
396
|
requestId: data.requestId,
|
|
@@ -362,6 +401,7 @@ export function connectToServer(
|
|
|
362
401
|
cwd: s.cwd,
|
|
363
402
|
updatedAt: s.updatedAt ?? null,
|
|
364
403
|
})),
|
|
404
|
+
...(result.nextCursor ? { nextCursor: result.nextCursor } : {}),
|
|
365
405
|
});
|
|
366
406
|
} catch (error) {
|
|
367
407
|
await bridge.disconnect().catch(() => {});
|
|
@@ -509,6 +549,118 @@ export function connectToServer(
|
|
|
509
549
|
}
|
|
510
550
|
});
|
|
511
551
|
|
|
552
|
+
socket.on('acp:set-config-option', (data: AcpSetConfigOptionPayload) => {
|
|
553
|
+
console.log(`[ACP] acp:set-config-option received: sessionId=${data.sessionId} configId=${data.configId}`);
|
|
554
|
+
const bridge = acpBridges.get(data.sessionId);
|
|
555
|
+
if (!bridge) {
|
|
556
|
+
socket.emit('acp:set-config-option:result', {
|
|
557
|
+
sessionId: data.sessionId,
|
|
558
|
+
success: false,
|
|
559
|
+
configId: data.configId,
|
|
560
|
+
error: 'ACP bridge not found for session',
|
|
561
|
+
});
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
// 成功时 bridge 内部已用响应快照更新 configOptions 并 emit acp:config-options
|
|
565
|
+
bridge.setConfigOption(data.configId, data.value).then(
|
|
566
|
+
() => {
|
|
567
|
+
socket.emit('acp:set-config-option:result', {
|
|
568
|
+
sessionId: data.sessionId,
|
|
569
|
+
success: true,
|
|
570
|
+
configId: data.configId,
|
|
571
|
+
});
|
|
572
|
+
},
|
|
573
|
+
(err) => {
|
|
574
|
+
console.error(`[ACP] setConfigOption failed: ${(err as Error).message}`);
|
|
575
|
+
socket.emit('acp:set-config-option:result', {
|
|
576
|
+
sessionId: data.sessionId,
|
|
577
|
+
success: false,
|
|
578
|
+
configId: data.configId,
|
|
579
|
+
error: (err as Error).message,
|
|
580
|
+
});
|
|
581
|
+
},
|
|
582
|
+
);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
socket.on('acp:delete-history-session', async (data: AcpDeleteHistorySessionPayload) => {
|
|
586
|
+
const serverType = data.serverType ?? 'opencode';
|
|
587
|
+
console.log(`[ACP sync] acp:delete-history-session received: sessionId=${data.externalSessionId} cwd=${data.cwd}`);
|
|
588
|
+
const spawner = createSpawner(serverType, `sync_${Date.now()}`, data.cwd);
|
|
589
|
+
const bridge = new AcpSyncBridge({ cwd: data.cwd }, spawner);
|
|
590
|
+
try {
|
|
591
|
+
await bridge.connect();
|
|
592
|
+
if (!bridge.supportsDelete()) {
|
|
593
|
+
await bridge.disconnect().catch(() => {});
|
|
594
|
+
socket.emit('acp:delete-history-session:result', {
|
|
595
|
+
requestId: data.requestId,
|
|
596
|
+
sessionId: data.sessionId,
|
|
597
|
+
success: false,
|
|
598
|
+
supported: false,
|
|
599
|
+
error: 'Session delete not supported by agent',
|
|
600
|
+
});
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
await bridge.deleteSession(data.externalSessionId);
|
|
604
|
+
await bridge.disconnect();
|
|
605
|
+
socket.emit('acp:delete-history-session:result', {
|
|
606
|
+
requestId: data.requestId,
|
|
607
|
+
sessionId: data.sessionId,
|
|
608
|
+
success: true,
|
|
609
|
+
supported: true,
|
|
610
|
+
});
|
|
611
|
+
} catch (error) {
|
|
612
|
+
await bridge.disconnect().catch(() => {});
|
|
613
|
+
console.error(`[ACP sync] delete-history-session failed: ${(error as Error).message}`);
|
|
614
|
+
socket.emit('acp:delete-history-session:result', {
|
|
615
|
+
requestId: data.requestId,
|
|
616
|
+
sessionId: data.sessionId,
|
|
617
|
+
success: false,
|
|
618
|
+
supported: true,
|
|
619
|
+
error: (error as Error).message,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
socket.on('acp:authenticate', async (data: AcpAuthenticatePayload) => {
|
|
625
|
+
console.log(`[ACP] acp:authenticate received: sessionId=${data.sessionId} methodId=${data.methodId}`);
|
|
626
|
+
const bridge = acpBridges.get(data.sessionId);
|
|
627
|
+
try {
|
|
628
|
+
if (!bridge) throw new Error('ACP bridge not found for session');
|
|
629
|
+
await bridge.authenticate(data.methodId);
|
|
630
|
+
socket.emit('acp:authenticate:result', {
|
|
631
|
+
sessionId: data.sessionId,
|
|
632
|
+
success: true,
|
|
633
|
+
});
|
|
634
|
+
} catch (error) {
|
|
635
|
+
console.error(`[ACP] authenticate failed: ${(error as Error).message}`);
|
|
636
|
+
socket.emit('acp:authenticate:result', {
|
|
637
|
+
sessionId: data.sessionId,
|
|
638
|
+
success: false,
|
|
639
|
+
error: (error as Error).message,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
socket.on('acp:logout', async (data: AcpLogoutPayload) => {
|
|
645
|
+
console.log(`[ACP] acp:logout received: sessionId=${data.sessionId}`);
|
|
646
|
+
const bridge = acpBridges.get(data.sessionId);
|
|
647
|
+
try {
|
|
648
|
+
if (!bridge) throw new Error('ACP bridge not found for session');
|
|
649
|
+
await bridge.logout();
|
|
650
|
+
socket.emit('acp:logout:result', {
|
|
651
|
+
sessionId: data.sessionId,
|
|
652
|
+
success: true,
|
|
653
|
+
});
|
|
654
|
+
} catch (error) {
|
|
655
|
+
console.error(`[ACP] logout failed: ${(error as Error).message}`);
|
|
656
|
+
socket.emit('acp:logout:result', {
|
|
657
|
+
sessionId: data.sessionId,
|
|
658
|
+
success: false,
|
|
659
|
+
error: (error as Error).message,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
512
664
|
// File system events
|
|
513
665
|
socket.on('fs:browse', async (data: FsBrowsePayload) => {
|
|
514
666
|
console.log(`FS browse requested: requestId=${data.requestId} path=${data.path}`);
|
package/tests/acp-bridge.test.ts
CHANGED
|
@@ -9,7 +9,9 @@ function buildMockConn() {
|
|
|
9
9
|
cancel: vi.fn<[unknown], Promise<void>>().mockResolvedValue(undefined),
|
|
10
10
|
closeSession: vi.fn<[unknown], Promise<object>>().mockResolvedValue({}),
|
|
11
11
|
setSessionMode: vi.fn<[unknown], Promise<object>>().mockResolvedValue({}),
|
|
12
|
-
|
|
12
|
+
setSessionConfigOption: vi.fn<[unknown], Promise<object>>().mockResolvedValue({}),
|
|
13
|
+
authenticate: vi.fn<[unknown], Promise<object>>().mockResolvedValue({}),
|
|
14
|
+
logout: vi.fn<[unknown], Promise<object>>().mockResolvedValue({}),
|
|
13
15
|
};
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -20,7 +22,7 @@ vi.mock('@agentclientprotocol/sdk', () => ({
|
|
|
20
22
|
ClientSideConnection: vi.fn(function () { return mockConnInstance; }),
|
|
21
23
|
}));
|
|
22
24
|
|
|
23
|
-
import { AcpSessionBridge } from '../../src/acp/bridge.js';
|
|
25
|
+
import { AcpSessionBridge, extractConfigOptions } from '../../src/acp/bridge.js';
|
|
24
26
|
|
|
25
27
|
class MockAcpSpawner {
|
|
26
28
|
start = vi.fn();
|
|
@@ -74,8 +76,12 @@ describe('AcpSessionBridge', () => {
|
|
|
74
76
|
expect(mockSpawner.start).toHaveBeenCalledOnce();
|
|
75
77
|
expect(mc().initialize).toHaveBeenCalledWith({
|
|
76
78
|
protocolVersion: 1,
|
|
77
|
-
clientCapabilities: {
|
|
78
|
-
|
|
79
|
+
clientCapabilities: {
|
|
80
|
+
session: {
|
|
81
|
+
configOptions: { boolean: {} },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
clientInfo: { name: 'agent-gear', version: '1.1.5' },
|
|
79
85
|
});
|
|
80
86
|
expect(mc().newSession).toHaveBeenCalledWith({
|
|
81
87
|
cwd: '/home/user',
|
|
@@ -121,10 +127,16 @@ describe('AcpSessionBridge', () => {
|
|
|
121
127
|
it('should include currentModel in acp:initialized when available', async () => {
|
|
122
128
|
mockConnInstance.newSession = vi.fn().mockResolvedValue({
|
|
123
129
|
sessionId: 'acp-sess-abc',
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
configOptions: [
|
|
131
|
+
{
|
|
132
|
+
id: 'model',
|
|
133
|
+
name: 'Model',
|
|
134
|
+
category: 'model',
|
|
135
|
+
type: 'select',
|
|
136
|
+
currentValue: 'gpt-4',
|
|
137
|
+
options: [{ value: 'gpt-4', name: 'GPT-4' }],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
128
140
|
});
|
|
129
141
|
const bridge = createBridge();
|
|
130
142
|
const emitSpy = vi.fn();
|
|
@@ -141,13 +153,19 @@ describe('AcpSessionBridge', () => {
|
|
|
141
153
|
it('should include availableModels and currentModelId in acp:initialized', async () => {
|
|
142
154
|
mockConnInstance.newSession = vi.fn().mockResolvedValue({
|
|
143
155
|
sessionId: 'acp-sess-abc',
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
configOptions: [
|
|
157
|
+
{
|
|
158
|
+
id: 'model',
|
|
159
|
+
name: 'Model',
|
|
160
|
+
category: 'model',
|
|
161
|
+
type: 'select',
|
|
162
|
+
currentValue: 'claude-3',
|
|
163
|
+
options: [
|
|
164
|
+
{ value: 'gpt-4', name: 'GPT-4' },
|
|
165
|
+
{ value: 'claude-3', name: 'Claude 3' },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
151
169
|
});
|
|
152
170
|
const bridge = createBridge();
|
|
153
171
|
const emitSpy = vi.fn();
|
|
@@ -165,6 +183,22 @@ describe('AcpSessionBridge', () => {
|
|
|
165
183
|
}));
|
|
166
184
|
});
|
|
167
185
|
|
|
186
|
+
it('should map auth method without type field to kind agent', async () => {
|
|
187
|
+
mc().initialize.mockResolvedValue({
|
|
188
|
+
protocolVersion: 1,
|
|
189
|
+
authMethods: [{ id: 'oauth', description: 'Login with agent' }],
|
|
190
|
+
});
|
|
191
|
+
const bridge = createBridge();
|
|
192
|
+
|
|
193
|
+
bridge.start();
|
|
194
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
195
|
+
|
|
196
|
+
expect(mockSocket.emit).toHaveBeenCalledWith('acp:auth:required', {
|
|
197
|
+
sessionId: 'sess-1',
|
|
198
|
+
authMethods: [{ id: 'oauth', kind: 'agent', description: null, url: null }],
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
168
202
|
it('should emit acp:stopped on initialize failure', async () => {
|
|
169
203
|
mc().initialize.mockRejectedValue(new Error('init failed'));
|
|
170
204
|
const bridge = createBridge();
|
|
@@ -511,51 +545,236 @@ describe('AcpSessionBridge', () => {
|
|
|
511
545
|
await expect(bridge.setMode('mode-1')).resolves.toBeUndefined();
|
|
512
546
|
expect(mc().setSessionMode).not.toHaveBeenCalled();
|
|
513
547
|
});
|
|
548
|
+
|
|
549
|
+
it('should use setSessionConfigOption when mode config option exists', async () => {
|
|
550
|
+
mockConnInstance.newSession = vi.fn().mockResolvedValue({
|
|
551
|
+
sessionId: 'acp-sess-abc',
|
|
552
|
+
configOptions: [
|
|
553
|
+
{
|
|
554
|
+
id: 'mode',
|
|
555
|
+
name: 'Mode',
|
|
556
|
+
category: 'mode',
|
|
557
|
+
type: 'select',
|
|
558
|
+
currentValue: 'code',
|
|
559
|
+
options: [{ value: 'code', name: 'Code' }, { value: 'plan', name: 'Plan' }],
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
});
|
|
563
|
+
const bridge = createBridge();
|
|
564
|
+
bridge.start();
|
|
565
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
566
|
+
|
|
567
|
+
await bridge.setMode('plan');
|
|
568
|
+
|
|
569
|
+
expect(mc().setSessionConfigOption).toHaveBeenCalledWith({
|
|
570
|
+
sessionId: 'acp-sess-abc',
|
|
571
|
+
configId: 'mode',
|
|
572
|
+
value: 'plan',
|
|
573
|
+
});
|
|
574
|
+
});
|
|
514
575
|
});
|
|
515
576
|
|
|
516
577
|
describe('setModel', () => {
|
|
517
|
-
it('should call
|
|
578
|
+
it('should call setSessionConfigOption when model config option exists', async () => {
|
|
579
|
+
mockConnInstance.newSession = vi.fn().mockResolvedValue({
|
|
580
|
+
sessionId: 'acp-sess-abc',
|
|
581
|
+
configOptions: [
|
|
582
|
+
{
|
|
583
|
+
id: 'model',
|
|
584
|
+
name: 'Model',
|
|
585
|
+
category: 'model',
|
|
586
|
+
type: 'select',
|
|
587
|
+
currentValue: 'gpt-4',
|
|
588
|
+
options: [{ value: 'gpt-4', name: 'GPT-4' }, { value: 'claude-3', name: 'Claude 3' }],
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
});
|
|
518
592
|
const bridge = createBridge();
|
|
519
593
|
bridge.start();
|
|
520
594
|
await vi.advanceTimersByTimeAsync(0);
|
|
521
595
|
|
|
522
|
-
await bridge.setModel('
|
|
596
|
+
await bridge.setModel('claude-3');
|
|
523
597
|
|
|
524
|
-
expect(mc().
|
|
598
|
+
expect(mc().setSessionConfigOption).toHaveBeenCalledWith({
|
|
525
599
|
sessionId: 'acp-sess-abc',
|
|
526
|
-
|
|
600
|
+
configId: 'model',
|
|
601
|
+
value: 'claude-3',
|
|
527
602
|
});
|
|
528
603
|
});
|
|
529
604
|
|
|
605
|
+
it('should throw when initialized but no model config option', async () => {
|
|
606
|
+
const bridge = createBridge();
|
|
607
|
+
bridge.start();
|
|
608
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
609
|
+
|
|
610
|
+
await expect(bridge.setModel('model-1')).rejects.toThrow(
|
|
611
|
+
'Model switching is not supported',
|
|
612
|
+
);
|
|
613
|
+
expect(mc().setSessionConfigOption).not.toHaveBeenCalled();
|
|
614
|
+
});
|
|
615
|
+
|
|
530
616
|
it('should not throw when not initialized', async () => {
|
|
531
617
|
const bridge = createBridge();
|
|
532
618
|
|
|
533
619
|
await expect(bridge.setModel('model-1')).resolves.toBeUndefined();
|
|
534
|
-
expect(mc().unstable_setSessionModel).not.toHaveBeenCalled();
|
|
535
620
|
});
|
|
536
621
|
});
|
|
537
622
|
|
|
538
|
-
describe('
|
|
539
|
-
it('should
|
|
623
|
+
describe('setConfigOption', () => {
|
|
624
|
+
it('should call connection.setSessionConfigOption with boolean value', async () => {
|
|
540
625
|
const bridge = createBridge();
|
|
626
|
+
bridge.start();
|
|
627
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
628
|
+
|
|
629
|
+
await bridge.setConfigOption('opt-1', true);
|
|
541
630
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
631
|
+
expect(mc().setSessionConfigOption).toHaveBeenCalledWith({
|
|
632
|
+
sessionId: 'acp-sess-abc',
|
|
633
|
+
configId: 'opt-1',
|
|
634
|
+
value: true,
|
|
635
|
+
type: 'boolean',
|
|
636
|
+
});
|
|
545
637
|
});
|
|
546
638
|
|
|
547
|
-
it('should
|
|
639
|
+
it('should call connection.setSessionConfigOption with select value', async () => {
|
|
548
640
|
const bridge = createBridge();
|
|
641
|
+
bridge.start();
|
|
642
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
643
|
+
|
|
644
|
+
await bridge.setConfigOption('opt-2', 'val-1');
|
|
549
645
|
|
|
550
|
-
|
|
551
|
-
|
|
646
|
+
expect(mc().setSessionConfigOption).toHaveBeenCalledWith({
|
|
647
|
+
sessionId: 'acp-sess-abc',
|
|
648
|
+
configId: 'opt-2',
|
|
649
|
+
value: 'val-1',
|
|
650
|
+
});
|
|
552
651
|
});
|
|
553
652
|
|
|
554
|
-
it('should
|
|
653
|
+
it('should update snapshot and emit acp:config-options from set_config_option response', async () => {
|
|
654
|
+
mockConnInstance.setSessionConfigOption = vi.fn().mockResolvedValue({
|
|
655
|
+
configOptions: [
|
|
656
|
+
{
|
|
657
|
+
id: 'flags',
|
|
658
|
+
name: 'Flags',
|
|
659
|
+
category: null,
|
|
660
|
+
type: 'boolean',
|
|
661
|
+
currentValue: true,
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
});
|
|
555
665
|
const bridge = createBridge();
|
|
666
|
+
bridge.start();
|
|
667
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
556
668
|
|
|
557
|
-
|
|
558
|
-
|
|
669
|
+
await bridge.setConfigOption('flags', 'true');
|
|
670
|
+
|
|
671
|
+
expect(bridge.getConfigOptions()).toEqual([
|
|
672
|
+
{
|
|
673
|
+
id: 'flags',
|
|
674
|
+
name: 'Flags',
|
|
675
|
+
description: null,
|
|
676
|
+
category: null,
|
|
677
|
+
type: 'boolean',
|
|
678
|
+
currentValue: 'true',
|
|
679
|
+
options: [],
|
|
680
|
+
},
|
|
681
|
+
]);
|
|
682
|
+
expect(mockSocket.emit).toHaveBeenCalledWith('acp:config-options', {
|
|
683
|
+
sessionId: 'sess-1',
|
|
684
|
+
configOptions: bridge.getConfigOptions(),
|
|
685
|
+
});
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('should not emit acp:config-options when response has no configOptions', async () => {
|
|
689
|
+
mockConnInstance.setSessionConfigOption = vi.fn().mockResolvedValue({});
|
|
690
|
+
const bridge = createBridge();
|
|
691
|
+
bridge.start();
|
|
692
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
693
|
+
|
|
694
|
+
await bridge.setConfigOption('opt-2', 'val-1');
|
|
695
|
+
|
|
696
|
+
expect(mockSocket.emit).not.toHaveBeenCalledWith('acp:config-options', expect.anything());
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
describe('extractConfigOptions', () => {
|
|
701
|
+
it('should return empty array when configOptions is null or undefined', () => {
|
|
702
|
+
expect(extractConfigOptions(null)).toEqual([]);
|
|
703
|
+
expect(extractConfigOptions(undefined)).toEqual([]);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('should flatten grouped select options with group name prefix', () => {
|
|
707
|
+
const result = extractConfigOptions([
|
|
708
|
+
{
|
|
709
|
+
id: 'model',
|
|
710
|
+
name: 'Model',
|
|
711
|
+
category: 'model',
|
|
712
|
+
type: 'select',
|
|
713
|
+
currentValue: 'gpt-4',
|
|
714
|
+
options: [
|
|
715
|
+
{
|
|
716
|
+
group: 'openai',
|
|
717
|
+
name: 'OpenAI',
|
|
718
|
+
options: [
|
|
719
|
+
{ value: 'gpt-4', name: 'GPT-4' },
|
|
720
|
+
{ value: 'gpt-4o', name: 'GPT-4o' },
|
|
721
|
+
],
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
group: 'anthropic',
|
|
725
|
+
name: 'Anthropic',
|
|
726
|
+
options: [{ value: 'claude-3', name: 'Claude 3' }],
|
|
727
|
+
},
|
|
728
|
+
],
|
|
729
|
+
},
|
|
730
|
+
]);
|
|
731
|
+
|
|
732
|
+
expect(result[0].options).toEqual([
|
|
733
|
+
{ value: 'gpt-4', name: 'OpenAI/GPT-4' },
|
|
734
|
+
{ value: 'gpt-4o', name: 'OpenAI/GPT-4o' },
|
|
735
|
+
{ value: 'claude-3', name: 'Anthropic/Claude 3' },
|
|
736
|
+
]);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it('should map select and boolean config options', () => {
|
|
740
|
+
const result = extractConfigOptions([
|
|
741
|
+
{
|
|
742
|
+
id: 'model',
|
|
743
|
+
name: 'Model',
|
|
744
|
+
category: 'model',
|
|
745
|
+
type: 'select',
|
|
746
|
+
currentValue: 'gpt-4',
|
|
747
|
+
options: [{ value: 'gpt-4', name: 'GPT-4' }],
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
id: 'flags',
|
|
751
|
+
name: 'Flags',
|
|
752
|
+
category: null,
|
|
753
|
+
type: 'boolean',
|
|
754
|
+
currentValue: true,
|
|
755
|
+
},
|
|
756
|
+
]);
|
|
757
|
+
|
|
758
|
+
expect(result).toEqual([
|
|
759
|
+
{
|
|
760
|
+
id: 'model',
|
|
761
|
+
name: 'Model',
|
|
762
|
+
description: null,
|
|
763
|
+
category: 'model',
|
|
764
|
+
type: 'select',
|
|
765
|
+
currentValue: 'gpt-4',
|
|
766
|
+
options: [{ value: 'gpt-4', name: 'GPT-4' }],
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
id: 'flags',
|
|
770
|
+
name: 'Flags',
|
|
771
|
+
description: null,
|
|
772
|
+
category: null,
|
|
773
|
+
type: 'boolean',
|
|
774
|
+
currentValue: 'true',
|
|
775
|
+
options: [],
|
|
776
|
+
},
|
|
777
|
+
]);
|
|
559
778
|
});
|
|
560
779
|
});
|
|
561
780
|
});
|