@adhdev/daemon-core 0.8.65 → 0.8.66
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/index.js +937 -625
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +937 -625
- package/dist/index.mjs.map +1 -1
- package/dist/providers/extension-provider-instance.d.ts +5 -3
- package/dist/providers/provider-input-support.d.ts +9 -0
- package/dist/providers/read-chat-contract.d.ts +2 -0
- package/node_modules/@adhdev/session-host-core/package.json +2 -2
- package/package.json +2 -2
- package/src/agent-stream/provider-adapter.ts +15 -9
- package/src/cli-adapters/provider-cli-adapter.ts +4 -0
- package/src/commands/chat-commands.ts +75 -26
- package/src/commands/cli-manager.ts +7 -0
- package/src/commands/stream-commands.ts +2 -2
- package/src/providers/acp-provider-instance.ts +39 -55
- package/src/providers/cli-provider-instance.ts +2 -0
- package/src/providers/control-effects.ts +21 -29
- package/src/providers/extension-provider-instance.ts +35 -6
- package/src/providers/ide-provider-instance.ts +2 -1
- package/src/providers/provider-input-support.ts +71 -0
- package/src/providers/provider-schema.ts +60 -1
- package/src/providers/read-chat-contract.ts +136 -0
|
@@ -46,10 +46,12 @@ export declare class ExtensionProviderInstance implements ProviderInstance {
|
|
|
46
46
|
private applyProviderResponse;
|
|
47
47
|
private appendRuntimeSystemMessage;
|
|
48
48
|
private appendRuntimeMessage;
|
|
49
|
+
private buildSyntheticTurnKey;
|
|
49
50
|
/**
|
|
50
|
-
* Assign stable receivedAt to extension messages.
|
|
51
|
-
* Same
|
|
52
|
-
*
|
|
51
|
+
* Assign stable receivedAt / synthetic _turnKey to extension messages.
|
|
52
|
+
* Same transcript should keep the same identity across polling cycles and
|
|
53
|
+
* stream resets, while repeated identical text later in the transcript still
|
|
54
|
+
* produces a distinct completion marker via the occurrence suffix.
|
|
53
55
|
*/
|
|
54
56
|
private assignReceivedAt;
|
|
55
57
|
private mergeConversationMessages;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InputEnvelope, ProviderModule } from './contracts.js';
|
|
2
|
+
type InputMediaType = 'text' | 'image' | 'audio' | 'video' | 'resource';
|
|
3
|
+
export declare function assertTextOnlyInput(provider: Pick<ProviderModule, 'name' | 'type'> | null | undefined, input: InputEnvelope): void;
|
|
4
|
+
export declare function getDeclaredProviderInputSupport(provider?: Pick<ProviderModule, 'capabilities'> | null): {
|
|
5
|
+
multipart: boolean;
|
|
6
|
+
mediaTypes: Set<InputMediaType>;
|
|
7
|
+
};
|
|
8
|
+
export declare function assertProviderSupportsDeclaredInput(provider: Pick<ProviderModule, 'name' | 'type' | 'capabilities'> | null | undefined, input: InputEnvelope): void;
|
|
9
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/session-host-core",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "ADHDev local session host core
|
|
3
|
+
"version": "0.8.66",
|
|
4
|
+
"description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "ADHDev daemon core
|
|
3
|
+
"version": "0.8.66",
|
|
4
|
+
"description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
} from './types.js';
|
|
14
14
|
import type { ProviderModule, ProviderScripts } from '../providers/contracts.js';
|
|
15
15
|
import { extractProviderControlValues, normalizeProviderEffects } from '../providers/control-effects.js';
|
|
16
|
+
import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
|
|
16
17
|
import { resolveProviderStateSurface } from '../providers/provider-patch-state.js';
|
|
17
18
|
import { normalizeChatMessages } from '../providers/chat-message-normalization.js';
|
|
18
19
|
|
|
@@ -149,26 +150,31 @@ export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
|
149
150
|
}
|
|
150
151
|
return state;
|
|
151
152
|
}
|
|
153
|
+
const validated = validateReadChatResultPayload(data, `${this.agentType} readChat`);
|
|
154
|
+
const validatedStatus = (validated as any).status as string;
|
|
155
|
+
const streamStatus = validatedStatus === 'generating' || validatedStatus === 'long_generating'
|
|
156
|
+
? 'streaming'
|
|
157
|
+
: validatedStatus;
|
|
152
158
|
const state: AgentStreamState = {
|
|
153
159
|
agentType: this.agentType,
|
|
154
160
|
agentName: this.agentName,
|
|
155
161
|
extensionId: this.extensionId,
|
|
156
|
-
status:
|
|
157
|
-
messages: normalizeChatMessages(
|
|
158
|
-
inputContent:
|
|
159
|
-
activeModal:
|
|
162
|
+
status: streamStatus as AgentStreamState['status'],
|
|
163
|
+
messages: normalizeChatMessages(validated.messages) as any,
|
|
164
|
+
inputContent: typeof validated.inputContent === 'string' ? validated.inputContent : '',
|
|
165
|
+
...(validated.activeModal ? { activeModal: validated.activeModal } : {}),
|
|
160
166
|
};
|
|
161
|
-
if (typeof
|
|
162
|
-
state.title =
|
|
167
|
+
if (typeof validated.title === 'string' && validated.title.trim()) {
|
|
168
|
+
state.title = validated.title.trim();
|
|
163
169
|
}
|
|
164
|
-
const controlValues = extractProviderControlValues(this.provider.controls,
|
|
170
|
+
const controlValues = extractProviderControlValues(this.provider.controls, validated);
|
|
165
171
|
const surface = resolveProviderStateSurface({
|
|
166
172
|
controlValues,
|
|
167
|
-
summaryMetadata:
|
|
173
|
+
summaryMetadata: validated.summaryMetadata,
|
|
168
174
|
});
|
|
169
175
|
if (surface.controlValues) state.controlValues = surface.controlValues;
|
|
170
176
|
if (surface.summaryMetadata) state.summaryMetadata = surface.summaryMetadata as any;
|
|
171
|
-
const effects = normalizeProviderEffects(
|
|
177
|
+
const effects = normalizeProviderEffects(validated);
|
|
172
178
|
if (effects.length > 0) state.effects = effects;
|
|
173
179
|
if (state.messages.length > 0) {
|
|
174
180
|
this.lastSuccessState = state;
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
type CliTraceEntry,
|
|
46
46
|
} from './provider-cli-shared.js';
|
|
47
47
|
import { buildChatMessage } from '../providers/chat-message-normalization.js';
|
|
48
|
+
import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
|
|
48
49
|
import {
|
|
49
50
|
buildCliParseInput,
|
|
50
51
|
buildCliTraceParseSnapshot,
|
|
@@ -1395,6 +1396,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1395
1396
|
runtimeSettings: this.runtimeSettings,
|
|
1396
1397
|
});
|
|
1397
1398
|
const parsed = this.cliScripts.parseOutput(input);
|
|
1399
|
+
if (parsed && typeof parsed === 'object') {
|
|
1400
|
+
Object.assign(parsed, validateReadChatResultPayload(parsed, `${this.cliType} parseOutput`));
|
|
1401
|
+
}
|
|
1398
1402
|
const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === 'string' ? parsed.status : null, input.recentBuffer, input.screenText);
|
|
1399
1403
|
if (parsed && refinedStatus && parsed.status !== refinedStatus) {
|
|
1400
1404
|
parsed.status = refinedStatus;
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import type { CommandResult, CommandHelpers } from './handler.js';
|
|
7
7
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
8
8
|
import { flattenContent, normalizeInputEnvelope, type InputEnvelope, type ProviderModule, type ProviderScripts } from '../providers/contracts.js';
|
|
9
|
+
import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../providers/provider-input-support.js';
|
|
10
|
+
import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
|
|
9
11
|
import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
10
12
|
import { readChatHistory } from '../config/chat-history.js';
|
|
11
13
|
import { LOG } from '../logging/logger.js';
|
|
@@ -80,7 +82,7 @@ function isExtensionTransport(transport: SessionTransport | null): boolean {
|
|
|
80
82
|
return transport === 'cdp-webview';
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
function buildRecentSendKey(h: CommandHelpers, args: any, provider: ProviderModule | undefined,
|
|
85
|
+
function buildRecentSendKey(h: CommandHelpers, args: any, provider: ProviderModule | undefined, signature: string): string {
|
|
84
86
|
const transport = getTargetTransport(h, provider) || 'unknown';
|
|
85
87
|
const target =
|
|
86
88
|
args?.targetSessionId
|
|
@@ -89,7 +91,13 @@ function buildRecentSendKey(h: CommandHelpers, args: any, provider: ProviderModu
|
|
|
89
91
|
|| h.currentProviderType
|
|
90
92
|
|| h.currentManagerKey
|
|
91
93
|
|| 'unknown';
|
|
92
|
-
return `${transport}:${target}:${
|
|
94
|
+
return `${transport}:${target}:${signature.trim()}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildSendInputSignature(input: InputEnvelope): string {
|
|
98
|
+
const text = typeof input.textFallback === 'string' ? input.textFallback.trim() : '';
|
|
99
|
+
if (text) return text;
|
|
100
|
+
return JSON.stringify(input.parts || []);
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
function getSendChatInputEnvelope(args: any): InputEnvelope {
|
|
@@ -292,14 +300,20 @@ function computeReadChatSync(messages: ChatMessage[], cursor: Required<ReadChatC
|
|
|
292
300
|
}
|
|
293
301
|
|
|
294
302
|
function buildReadChatCommandResult(payload: Record<string, any>, args: any): CommandResult {
|
|
295
|
-
|
|
303
|
+
let validatedPayload: Record<string, any>;
|
|
304
|
+
try {
|
|
305
|
+
validatedPayload = validateReadChatResultPayload(payload, 'read_chat command result') as Record<string, any>;
|
|
306
|
+
} catch (error: any) {
|
|
307
|
+
return { success: false, error: error?.message || String(error) };
|
|
308
|
+
}
|
|
309
|
+
const messages = normalizeReadChatMessages(validatedPayload);
|
|
296
310
|
const cursor = normalizeReadChatCursor(args);
|
|
297
311
|
if (!cursor.knownMessageCount && !cursor.lastMessageSignature && cursor.tailLimit > 0 && messages.length > cursor.tailLimit) {
|
|
298
312
|
const tailMessages = messages.slice(-cursor.tailLimit);
|
|
299
313
|
const lastMessageSignature = getChatMessageSignature(tailMessages[tailMessages.length - 1]);
|
|
300
314
|
return {
|
|
301
315
|
success: true,
|
|
302
|
-
...
|
|
316
|
+
...validatedPayload,
|
|
303
317
|
messages: tailMessages,
|
|
304
318
|
syncMode: 'full',
|
|
305
319
|
replaceFrom: 0,
|
|
@@ -310,7 +324,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
310
324
|
const sync = computeReadChatSync(messages, cursor);
|
|
311
325
|
return {
|
|
312
326
|
success: true,
|
|
313
|
-
...
|
|
327
|
+
...validatedPayload,
|
|
314
328
|
messages: sync.messages,
|
|
315
329
|
syncMode: sync.syncMode,
|
|
316
330
|
replaceFrom: sync.replaceFrom,
|
|
@@ -405,12 +419,24 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
405
419
|
const adapter = getTargetedCliAdapter(h, args, provider?.type);
|
|
406
420
|
if (adapter) {
|
|
407
421
|
_log(`${transport} adapter: ${adapter.cliType}`);
|
|
408
|
-
const
|
|
422
|
+
const parsedStatus = typeof adapter.getScriptParsedStatus === 'function'
|
|
423
|
+
? parseMaybeJson(adapter.getScriptParsedStatus())
|
|
424
|
+
: null;
|
|
425
|
+
const parsedRecord = parsedStatus && typeof parsedStatus === 'object'
|
|
426
|
+
? parsedStatus as Record<string, any>
|
|
427
|
+
: null;
|
|
428
|
+
const status = parsedRecord || adapter.getStatus();
|
|
429
|
+
const title = typeof parsedRecord?.title === 'string' ? parsedRecord.title : undefined;
|
|
430
|
+
const providerSessionId = typeof parsedRecord?.providerSessionId === 'string'
|
|
431
|
+
? parsedRecord.providerSessionId
|
|
432
|
+
: undefined;
|
|
409
433
|
if (status) {
|
|
410
434
|
return buildReadChatCommandResult({
|
|
411
435
|
messages: status.messages || [],
|
|
412
436
|
status: status.status,
|
|
413
437
|
activeModal: status.activeModal,
|
|
438
|
+
...(title ? { title } : {}),
|
|
439
|
+
...(providerSessionId ? { providerSessionId } : {}),
|
|
414
440
|
}, args);
|
|
415
441
|
}
|
|
416
442
|
}
|
|
@@ -425,25 +451,26 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
425
451
|
let parsed = evalResult.result;
|
|
426
452
|
if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
|
|
427
453
|
if (parsed && typeof parsed === 'object') {
|
|
428
|
-
|
|
454
|
+
const validated = validateReadChatResultPayload(parsed, 'extension read_chat');
|
|
455
|
+
_log(`Extension OK: ${validated.messages?.length || 0} msgs`);
|
|
429
456
|
traceProviderEvent(args, 'provider', 'extension.read_chat.success', {
|
|
430
457
|
h,
|
|
431
458
|
provider,
|
|
432
459
|
payload: {
|
|
433
460
|
method: 'evaluateProviderScript',
|
|
434
461
|
result: evalResult.result,
|
|
435
|
-
parsed,
|
|
436
|
-
messageCount: Array.isArray(
|
|
462
|
+
parsed: validated,
|
|
463
|
+
messageCount: Array.isArray(validated.messages) ? validated.messages.length : 0,
|
|
437
464
|
},
|
|
438
465
|
});
|
|
439
466
|
h.historyWriter.appendNewMessages(
|
|
440
467
|
provider?.type || 'unknown_extension',
|
|
441
|
-
toHistoryPersistedMessages(normalizeReadChatMessages(
|
|
442
|
-
|
|
468
|
+
toHistoryPersistedMessages(normalizeReadChatMessages(validated)),
|
|
469
|
+
validated.title,
|
|
443
470
|
args?.targetSessionId,
|
|
444
471
|
historySessionId,
|
|
445
472
|
);
|
|
446
|
-
return buildReadChatCommandResult(
|
|
473
|
+
return buildReadChatCommandResult(validated as Record<string, any>, args);
|
|
447
474
|
}
|
|
448
475
|
}
|
|
449
476
|
} catch (e: any) {
|
|
@@ -500,15 +527,16 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
500
527
|
let parsed: any = raw;
|
|
501
528
|
if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
|
|
502
529
|
if (parsed && typeof parsed === 'object') {
|
|
503
|
-
|
|
530
|
+
const validated = validateReadChatResultPayload(parsed, 'webview read_chat');
|
|
531
|
+
_log(`Webview OK: ${validated.messages?.length || 0} msgs`);
|
|
504
532
|
h.historyWriter.appendNewMessages(
|
|
505
533
|
provider?.type || getCurrentProviderType(h, 'unknown_webview'),
|
|
506
|
-
toHistoryPersistedMessages(normalizeReadChatMessages(
|
|
507
|
-
|
|
534
|
+
toHistoryPersistedMessages(normalizeReadChatMessages(validated)),
|
|
535
|
+
validated.title,
|
|
508
536
|
args?.targetSessionId,
|
|
509
537
|
historySessionId,
|
|
510
538
|
);
|
|
511
|
-
return buildReadChatCommandResult(
|
|
539
|
+
return buildReadChatCommandResult(validated as Record<string, any>, args);
|
|
512
540
|
}
|
|
513
541
|
}
|
|
514
542
|
} catch (e: any) {
|
|
@@ -526,25 +554,26 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
526
554
|
let parsed: any = evalResult.result;
|
|
527
555
|
if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
|
|
528
556
|
if (parsed && typeof parsed === 'object' && parsed.messages?.length > 0) {
|
|
529
|
-
|
|
557
|
+
const validated = validateReadChatResultPayload(parsed, 'ide read_chat');
|
|
558
|
+
_log(`OK: ${validated.messages?.length} msgs`);
|
|
530
559
|
traceProviderEvent(args, 'provider', 'ide.read_chat.success', {
|
|
531
560
|
h,
|
|
532
561
|
provider,
|
|
533
562
|
payload: {
|
|
534
563
|
method: 'evaluate',
|
|
535
564
|
result: evalResult.result,
|
|
536
|
-
parsed,
|
|
537
|
-
messageCount: Array.isArray(
|
|
565
|
+
parsed: validated,
|
|
566
|
+
messageCount: Array.isArray(validated.messages) ? validated.messages.length : 0,
|
|
538
567
|
},
|
|
539
568
|
});
|
|
540
569
|
h.historyWriter.appendNewMessages(
|
|
541
570
|
provider?.type || getCurrentProviderType(h, 'unknown_ide'),
|
|
542
|
-
toHistoryPersistedMessages(normalizeReadChatMessages(
|
|
543
|
-
|
|
571
|
+
toHistoryPersistedMessages(normalizeReadChatMessages(validated)),
|
|
572
|
+
validated.title,
|
|
544
573
|
args?.targetSessionId,
|
|
545
574
|
historySessionId,
|
|
546
575
|
);
|
|
547
|
-
return buildReadChatCommandResult(
|
|
576
|
+
return buildReadChatCommandResult(validated as Record<string, any>, args);
|
|
548
577
|
}
|
|
549
578
|
}
|
|
550
579
|
} catch (e: any) {
|
|
@@ -564,11 +593,12 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
564
593
|
export async function handleSendChat(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
565
594
|
const input = getSendChatInputEnvelope(args);
|
|
566
595
|
const text = input.textFallback;
|
|
567
|
-
|
|
596
|
+
const hasInput = input.parts.length > 0 || (typeof text === 'string' && text.trim().length > 0);
|
|
597
|
+
if (!hasInput) return { success: false, error: 'input required' };
|
|
568
598
|
const _log = (msg: string) => LOG.debug('Command', `[send_chat] ${msg}`);
|
|
569
599
|
const provider = h.getProvider(args?.agentType);
|
|
570
600
|
const transport = getTargetTransport(h, provider);
|
|
571
|
-
const dedupeKey = buildRecentSendKey(h, args, provider,
|
|
601
|
+
const dedupeKey = buildRecentSendKey(h, args, provider, buildSendInputSignature(input));
|
|
572
602
|
|
|
573
603
|
const _logSendSuccess = (method: string, targetAgent?: string) => {
|
|
574
604
|
// Sending and transcript persistence are intentionally decoupled.
|
|
@@ -582,12 +612,28 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
582
612
|
return { success: true, sent: false, deduplicated: true };
|
|
583
613
|
}
|
|
584
614
|
|
|
585
|
-
|
|
586
|
-
|
|
615
|
+
if (transport === 'acp') {
|
|
616
|
+
const target = getTargetInstance(h, args);
|
|
617
|
+
if (!target || target.category !== 'acp') {
|
|
618
|
+
return { success: false, error: `ACP instance not found for ${provider?.type || args?.agentType || 'unknown'}` };
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
assertProviderSupportsDeclaredInput(provider, input);
|
|
622
|
+
target.onEvent('send_message', { input });
|
|
623
|
+
return _logSendSuccess('acp-instance', target.type);
|
|
624
|
+
} catch (e: any) {
|
|
625
|
+
return { success: false, error: `acp send failed: ${e.message}` };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// PTY transport: text-only send via adapter
|
|
630
|
+
if (transport === 'pty') {
|
|
587
631
|
const adapter = getTargetedCliAdapter(h, args, provider?.type);
|
|
588
632
|
if (adapter) {
|
|
589
633
|
_log(`${transport} adapter: ${adapter.cliType}`);
|
|
590
634
|
try {
|
|
635
|
+
assertTextOnlyInput(provider, input);
|
|
636
|
+
if (!text) return { success: false, error: 'text required for PTY send' };
|
|
591
637
|
await adapter.sendMessage(text);
|
|
592
638
|
return _logSendSuccess(`${transport}-adapter`, adapter.cliType);
|
|
593
639
|
} catch (e: any) {
|
|
@@ -596,6 +642,9 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
596
642
|
}
|
|
597
643
|
}
|
|
598
644
|
|
|
645
|
+
assertTextOnlyInput(provider, input);
|
|
646
|
+
if (!text) return { success: false, error: 'text required' };
|
|
647
|
+
|
|
599
648
|
// Extension transport: via AgentStreamManager
|
|
600
649
|
if (isExtensionTransport(transport)) {
|
|
601
650
|
_log(`Extension: ${provider?.type || 'unknown_extension'}`);
|
|
@@ -23,6 +23,7 @@ import { AcpProviderInstance } from '../providers/acp-provider-instance.js';
|
|
|
23
23
|
import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
24
24
|
import { ProviderLoader } from '../providers/provider-loader.js';
|
|
25
25
|
import { normalizeInputEnvelope, type ProviderModule, type ProviderResumeCapability } from '../providers/contracts.js';
|
|
26
|
+
import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../providers/provider-input-support.js';
|
|
26
27
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
27
28
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
28
29
|
import type { SessionRegistry } from '../sessions/registry.js';
|
|
@@ -950,6 +951,12 @@ export class DaemonCliManager {
|
|
|
950
951
|
|
|
951
952
|
if (action === 'send_chat') {
|
|
952
953
|
const input = normalizeInputEnvelope(args?.input ? { input: args.input } : args);
|
|
954
|
+
const provider = this.providerLoader.resolve(agentType) || this.providerLoader.getMeta(agentType);
|
|
955
|
+
if (provider?.category === 'acp') {
|
|
956
|
+
assertProviderSupportsDeclaredInput(provider, input);
|
|
957
|
+
} else {
|
|
958
|
+
assertTextOnlyInput(provider, input);
|
|
959
|
+
}
|
|
953
960
|
const message = input.textFallback;
|
|
954
961
|
if (!message) throw new Error('message required for send_chat');
|
|
955
962
|
await adapter.sendMessage(message);
|
|
@@ -179,7 +179,7 @@ export function normalizeProviderScriptArgs(args: any, scriptName?: string): Rec
|
|
|
179
179
|
|
|
180
180
|
function buildControlScriptResult(scriptName: string, payload: any): Record<string, unknown> {
|
|
181
181
|
if (!payload || typeof payload !== 'object') return {};
|
|
182
|
-
if (Array.isArray(payload.options)
|
|
182
|
+
if (Array.isArray(payload.options)) {
|
|
183
183
|
return { controlResult: normalizeControlListResult(payload) };
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -189,7 +189,7 @@ function buildControlScriptResult(scriptName: string, payload: any): Record<stri
|
|
|
189
189
|
if (looksLikeValueMutation) {
|
|
190
190
|
return { controlResult: normalizeControlSetResult(payload) };
|
|
191
191
|
}
|
|
192
|
-
if (payload.ok !== undefined ||
|
|
192
|
+
if (payload.ok !== undefined || Array.isArray(payload.effects) || typeof payload.error === 'string') {
|
|
193
193
|
return { controlResult: normalizeControlInvokeResult(payload) };
|
|
194
194
|
}
|
|
195
195
|
return {};
|
|
@@ -46,8 +46,9 @@ import {
|
|
|
46
46
|
type ToolCallStatus,
|
|
47
47
|
type SessionConfigOption,
|
|
48
48
|
} from '@agentclientprotocol/sdk';
|
|
49
|
-
import type { ProviderModule, ContentBlock, InputEnvelope,
|
|
49
|
+
import type { ProviderModule, ContentBlock, InputEnvelope, ToolCallInfo, ToolCallContent as TCC, ToolKind, ToolCallStatus as TCS } from './contracts.js';
|
|
50
50
|
import { normalizeContent, flattenContent, normalizeInputEnvelope } from './contracts.js';
|
|
51
|
+
import { assertProviderSupportsDeclaredInput } from './provider-input-support.js';
|
|
51
52
|
import type { ProviderInstance, ProviderState, AcpProviderState, ProviderErrorReason, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
52
53
|
import { StatusMonitor } from './status-monitor.js';
|
|
53
54
|
import { buildLegacyModelModeSummaryMetadata } from './summary-metadata.js';
|
|
@@ -112,27 +113,6 @@ function getPromptCapabilityFlags(agentCapabilities?: Record<string, any>): Prom
|
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
function getResourceNameFromUri(uri: string, fallback: string): string {
|
|
116
|
-
try {
|
|
117
|
-
if (uri.startsWith('file://')) {
|
|
118
|
-
return path.basename(new URL(uri).pathname) || fallback;
|
|
119
|
-
}
|
|
120
|
-
return path.basename(uri) || fallback;
|
|
121
|
-
} catch {
|
|
122
|
-
return fallback;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function inputPartToResourceLink(part: Extract<InputPart, { type: 'image' | 'audio' | 'video' | 'resource' }>, fallbackName: string): ContentBlock | null {
|
|
127
|
-
if (!part.uri) return null;
|
|
128
|
-
return {
|
|
129
|
-
type: 'resource_link',
|
|
130
|
-
uri: part.uri,
|
|
131
|
-
name: getResourceNameFromUri(part.uri, fallbackName),
|
|
132
|
-
...(part.mimeType ? { mimeType: part.mimeType } : {}),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
116
|
function appendPromptText(promptParts: ContentBlock[], text: string | undefined): void {
|
|
137
117
|
const normalized = typeof text === 'string' ? text.trim() : '';
|
|
138
118
|
if (!normalized) return;
|
|
@@ -152,61 +132,64 @@ export function buildAcpPromptParts(input: InputEnvelope, agentCapabilities?: Re
|
|
|
152
132
|
}
|
|
153
133
|
|
|
154
134
|
if (part.type === 'image') {
|
|
155
|
-
if (caps.image
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
...(part.uri ? { uri: part.uri } : {}),
|
|
161
|
-
});
|
|
162
|
-
continue;
|
|
135
|
+
if (!caps.image) {
|
|
136
|
+
throw new Error('ACP agent does not support input type: image');
|
|
137
|
+
}
|
|
138
|
+
if (!part.data) {
|
|
139
|
+
throw new Error('ACP image input requires inline image data');
|
|
163
140
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
141
|
+
promptParts.push({
|
|
142
|
+
type: 'image',
|
|
143
|
+
data: part.data,
|
|
144
|
+
mimeType: part.mimeType,
|
|
145
|
+
...(part.uri ? { uri: part.uri } : {}),
|
|
146
|
+
});
|
|
167
147
|
continue;
|
|
168
148
|
}
|
|
169
149
|
|
|
170
150
|
if (part.type === 'audio') {
|
|
171
|
-
if (caps.audio
|
|
172
|
-
|
|
173
|
-
type: 'audio',
|
|
174
|
-
data: part.data,
|
|
175
|
-
mimeType: part.mimeType,
|
|
176
|
-
});
|
|
177
|
-
continue;
|
|
151
|
+
if (!caps.audio) {
|
|
152
|
+
throw new Error('ACP agent does not support input type: audio');
|
|
178
153
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
154
|
+
if (!part.data) {
|
|
155
|
+
throw new Error('ACP audio input requires inline audio data');
|
|
156
|
+
}
|
|
157
|
+
promptParts.push({
|
|
158
|
+
type: 'audio',
|
|
159
|
+
data: part.data,
|
|
160
|
+
mimeType: part.mimeType,
|
|
161
|
+
});
|
|
182
162
|
continue;
|
|
183
163
|
}
|
|
184
164
|
|
|
185
165
|
if (part.type === 'resource') {
|
|
186
|
-
if (caps.embeddedContext
|
|
166
|
+
if (!caps.embeddedContext) {
|
|
167
|
+
throw new Error('ACP agent does not support input type: resource');
|
|
168
|
+
}
|
|
169
|
+
if (part.text) {
|
|
187
170
|
promptParts.push({
|
|
188
171
|
type: 'resource',
|
|
189
|
-
resource: part.text
|
|
190
|
-
? { uri: part.uri, text: part.text, mimeType: part.mimeType ?? null }
|
|
191
|
-
: { uri: part.uri, blob: part.data || '', mimeType: part.mimeType ?? null },
|
|
172
|
+
resource: { uri: part.uri, text: part.text, mimeType: part.mimeType ?? null },
|
|
192
173
|
});
|
|
193
174
|
continue;
|
|
194
175
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
176
|
+
if (part.data) {
|
|
177
|
+
promptParts.push({
|
|
178
|
+
type: 'resource',
|
|
179
|
+
resource: { uri: part.uri, blob: part.data, mimeType: part.mimeType ?? null },
|
|
180
|
+
});
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
throw new Error('ACP resource input requires embedded text or binary data');
|
|
199
184
|
}
|
|
200
185
|
|
|
201
186
|
if (part.type === 'video') {
|
|
202
|
-
|
|
203
|
-
if (fallback) promptParts.push(fallback);
|
|
204
|
-
appendPromptText(promptParts, !part.uri ? `Attached video (${part.mimeType})` : undefined);
|
|
187
|
+
throw new Error('ACP agent does not support input type: video');
|
|
205
188
|
}
|
|
206
189
|
}
|
|
207
190
|
|
|
208
191
|
if (!promptParts.some((part) => part.type === 'text') && input.textFallback) {
|
|
209
|
-
promptParts
|
|
192
|
+
appendPromptText(promptParts, input.textFallback);
|
|
210
193
|
}
|
|
211
194
|
|
|
212
195
|
return promptParts;
|
|
@@ -366,6 +349,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
366
349
|
onEvent(event: string, data?: any): void {
|
|
367
350
|
if (event === 'send_message') {
|
|
368
351
|
const input = normalizeInputEnvelope(data)
|
|
352
|
+
assertProviderSupportsDeclaredInput(this.provider, input)
|
|
369
353
|
const promptParts = buildAcpPromptParts(input, this.agentCapabilities)
|
|
370
354
|
this.sendPrompt(input.textFallback, promptParts.length > 0 ? promptParts : undefined).catch(e =>
|
|
371
355
|
this.log.warn(`[${this.type}] sendPrompt error: ${e?.message}`)
|
|
@@ -11,6 +11,7 @@ import * as crypto from 'crypto';
|
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import { normalizeInputEnvelope, type ProviderModule, flattenContent } from './contracts.js';
|
|
14
|
+
import { assertTextOnlyInput } from './provider-input-support.js';
|
|
14
15
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
15
16
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
16
17
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
@@ -412,6 +413,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
412
413
|
onEvent(event: string, data?: any): void {
|
|
413
414
|
if (event === 'send_message') {
|
|
414
415
|
const input = normalizeInputEnvelope(data);
|
|
416
|
+
assertTextOnlyInput(this.provider, input);
|
|
415
417
|
if (input.textFallback) {
|
|
416
418
|
void this.adapter.sendMessage(input.textFallback).catch((e: any) => {
|
|
417
419
|
LOG.warn('CLI', `[${this.type}] send_message failed: ${e?.message || e}`);
|
|
@@ -171,51 +171,43 @@ export function buildPersistedProviderEffectMessage(effect: ProviderEffect | nul
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
export function normalizeControlListResult(data: any): ControlListResult {
|
|
174
|
-
if (data
|
|
175
|
-
|
|
176
|
-
options: normalizeControlOptions(data.options),
|
|
177
|
-
...(isScalarControlValue(data.currentValue) ? { currentValue: data.currentValue } : {}),
|
|
178
|
-
...(typeof data.error === 'string' ? { error: data.error } : {}),
|
|
179
|
-
};
|
|
174
|
+
if (!data || typeof data !== 'object' || !Array.isArray(data.options)) {
|
|
175
|
+
throw new Error('Provider control list results must use the typed shape { options, currentValue?, error? }');
|
|
180
176
|
}
|
|
181
|
-
|
|
182
|
-
const rawOptions = Array.isArray(data?.models)
|
|
183
|
-
? data.models
|
|
184
|
-
: Array.isArray(data?.modes)
|
|
185
|
-
? data.modes
|
|
186
|
-
: Array.isArray(data?.options)
|
|
187
|
-
? data.options
|
|
188
|
-
: [];
|
|
189
|
-
const options = normalizeControlOptions(rawOptions);
|
|
190
177
|
return {
|
|
191
|
-
options,
|
|
192
|
-
...(isScalarControlValue(data
|
|
193
|
-
...(
|
|
194
|
-
...(typeof data?.error === 'string' ? { error: data.error } : {}),
|
|
178
|
+
options: normalizeControlOptions(data.options),
|
|
179
|
+
...(isScalarControlValue(data.currentValue) ? { currentValue: data.currentValue } : {}),
|
|
180
|
+
...(typeof data.error === 'string' ? { error: data.error } : {}),
|
|
195
181
|
};
|
|
196
182
|
}
|
|
197
183
|
|
|
198
184
|
export function normalizeControlSetResult(data: any): ControlSetResult {
|
|
199
|
-
|
|
185
|
+
if (!data || typeof data !== 'object' || typeof data.ok !== 'boolean') {
|
|
186
|
+
throw new Error('Provider control set results must use the typed shape { ok, currentValue?, effects?, error? }');
|
|
187
|
+
}
|
|
188
|
+
const currentValue = isScalarControlValue(data.currentValue)
|
|
200
189
|
? data.currentValue
|
|
201
|
-
: (isScalarControlValue(data
|
|
190
|
+
: (isScalarControlValue(data.value) ? data.value : undefined);
|
|
202
191
|
return {
|
|
203
|
-
ok: data
|
|
192
|
+
ok: data.ok,
|
|
204
193
|
...(currentValue !== undefined ? { currentValue } : {}),
|
|
205
|
-
...(Array.isArray(data
|
|
206
|
-
...(typeof data
|
|
194
|
+
...(Array.isArray(data.effects) ? { effects: normalizeProviderEffects(data) } : {}),
|
|
195
|
+
...(typeof data.error === 'string' ? { error: data.error } : {}),
|
|
207
196
|
};
|
|
208
197
|
}
|
|
209
198
|
|
|
210
199
|
export function normalizeControlInvokeResult(data: any): ControlInvokeResult {
|
|
211
|
-
|
|
200
|
+
if (!data || typeof data !== 'object' || typeof data.ok !== 'boolean') {
|
|
201
|
+
throw new Error('Provider control invoke results must use the typed shape { ok, currentValue?, effects?, error? }');
|
|
202
|
+
}
|
|
203
|
+
const currentValue = isScalarControlValue(data.currentValue)
|
|
212
204
|
? data.currentValue
|
|
213
|
-
: (isScalarControlValue(data
|
|
205
|
+
: (isScalarControlValue(data.value) ? data.value : undefined);
|
|
214
206
|
return {
|
|
215
|
-
ok: data
|
|
207
|
+
ok: data.ok,
|
|
216
208
|
...(currentValue !== undefined ? { currentValue } : {}),
|
|
217
|
-
...(Array.isArray(data
|
|
218
|
-
...(typeof data
|
|
209
|
+
...(Array.isArray(data.effects) ? { effects: normalizeProviderEffects(data) } : {}),
|
|
210
|
+
...(typeof data.error === 'string' ? { error: data.error } : {}),
|
|
219
211
|
};
|
|
220
212
|
}
|
|
221
213
|
|