@adhdev/daemon-core 0.9.76-rc.60 → 0.9.76-rc.62
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/commands/chat-commands.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +818 -311
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +811 -316
- package/dist/index.mjs.map +1 -1
- package/dist/providers/chat-message-normalization.d.ts +35 -6
- package/dist/providers/cli-provider-instance.d.ts +4 -1
- package/dist/providers/contracts.d.ts +20 -1
- package/dist/providers/io-contracts.d.ts +17 -1
- package/dist/providers/provider-input-support.d.ts +18 -2
- package/dist/providers/provider-instance.d.ts +2 -0
- package/dist/shared-types.d.ts +4 -0
- package/package.json +1 -1
- package/src/chat/subscription-updates.ts +3 -1
- package/src/commands/chat-commands.ts +40 -5
- package/src/commands/mesh-coordinator.ts +8 -1
- package/src/commands/router.ts +4 -0
- package/src/index.ts +13 -1
- package/src/mesh/coordinator-prompt.ts +7 -0
- package/src/providers/acp-provider-instance.ts +118 -30
- package/src/providers/chat-message-normalization.ts +211 -50
- package/src/providers/cli-provider-instance.ts +96 -5
- package/src/providers/contracts.ts +25 -1
- package/src/providers/io-contracts.ts +63 -5
- package/src/providers/provider-input-support.ts +125 -1
- package/src/providers/provider-instance.ts +2 -0
- package/src/providers/provider-schema.ts +38 -8
- package/src/shared-types.ts +4 -0
- package/src/status/builders.ts +5 -3
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
} from '@agentclientprotocol/sdk';
|
|
49
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
|
+
import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport } from './provider-input-support.js';
|
|
52
52
|
import type { ProviderInstance, ProviderState, AcpProviderState, ProviderErrorReason, ProviderEvent, InstanceContext, SessionModalState } from './provider-instance.js';
|
|
53
53
|
import { StatusMonitor } from './status-monitor.js';
|
|
54
54
|
import { buildLegacyModelModeSummaryMetadata } from './summary-metadata.js';
|
|
@@ -121,6 +121,41 @@ function appendPromptText(promptParts: ContentBlock[], text: string | undefined)
|
|
|
121
121
|
promptParts.push({ type: 'text', text: normalized });
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function getUriDisplayName(uri: string | undefined, fallback: string): string {
|
|
125
|
+
if (!uri) return fallback;
|
|
126
|
+
try {
|
|
127
|
+
const pathname = uri.startsWith('file://') ? new URL(uri).pathname : uri;
|
|
128
|
+
return pathname.split(/[\\/]/).filter(Boolean).pop() || fallback;
|
|
129
|
+
} catch {
|
|
130
|
+
return uri.split(/[\\/]/).filter(Boolean).pop() || fallback;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function appendResourceLink(
|
|
135
|
+
promptParts: ContentBlock[],
|
|
136
|
+
uri: string,
|
|
137
|
+
fallbackName: string,
|
|
138
|
+
mimeType?: string,
|
|
139
|
+
description?: string,
|
|
140
|
+
metadata?: Pick<Extract<ContentBlock, { type: 'resource_link' }>, 'title' | 'size' | 'annotations'> & { name?: string },
|
|
141
|
+
): void {
|
|
142
|
+
promptParts.push({
|
|
143
|
+
type: 'resource_link',
|
|
144
|
+
uri,
|
|
145
|
+
name: metadata?.name || getUriDisplayName(uri, fallbackName),
|
|
146
|
+
...(metadata?.title ? { title: metadata.title } : {}),
|
|
147
|
+
...(mimeType ? { mimeType } : {}),
|
|
148
|
+
...(description ? { description } : {}),
|
|
149
|
+
...(typeof metadata?.size === 'number' ? { size: metadata.size } : {}),
|
|
150
|
+
...(metadata?.annotations ? { annotations: metadata.annotations } : {}),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function appendMediaFallbackText(promptParts: ContentBlock[], label: string, details: Array<string | undefined>): void {
|
|
155
|
+
const normalizedDetails = details.map((value) => typeof value === 'string' ? value.trim() : '').filter(Boolean);
|
|
156
|
+
appendPromptText(promptParts, `[${[label, ...normalizedDetails].join(': ')}]`);
|
|
157
|
+
}
|
|
158
|
+
|
|
124
159
|
export function buildAcpPromptParts(input: InputEnvelope, agentCapabilities?: Record<string, any>): ContentBlock[] {
|
|
125
160
|
const caps = getPromptCapabilityFlags(agentCapabilities);
|
|
126
161
|
const promptParts: ContentBlock[] = [];
|
|
@@ -132,59 +167,82 @@ export function buildAcpPromptParts(input: InputEnvelope, agentCapabilities?: Re
|
|
|
132
167
|
}
|
|
133
168
|
|
|
134
169
|
if (part.type === 'image') {
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
if (caps.image && part.data) {
|
|
171
|
+
promptParts.push({
|
|
172
|
+
type: 'image',
|
|
173
|
+
data: part.data,
|
|
174
|
+
mimeType: part.mimeType,
|
|
175
|
+
...(part.uri ? { uri: part.uri } : {}),
|
|
176
|
+
...(part.alt ? { alt: part.alt } : {}),
|
|
177
|
+
});
|
|
178
|
+
if (part.alt) appendPromptText(promptParts, part.alt);
|
|
179
|
+
} else if (part.uri) {
|
|
180
|
+
appendResourceLink(promptParts, part.uri, 'image', part.mimeType, part.alt);
|
|
181
|
+
if (part.alt) appendPromptText(promptParts, part.alt);
|
|
182
|
+
} else {
|
|
183
|
+
appendMediaFallbackText(promptParts, 'Image attachment', [part.alt, part.mimeType]);
|
|
140
184
|
}
|
|
141
|
-
promptParts.push({
|
|
142
|
-
type: 'image',
|
|
143
|
-
data: part.data,
|
|
144
|
-
mimeType: part.mimeType,
|
|
145
|
-
...(part.uri ? { uri: part.uri } : {}),
|
|
146
|
-
});
|
|
147
185
|
continue;
|
|
148
186
|
}
|
|
149
187
|
|
|
150
188
|
if (part.type === 'audio') {
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
189
|
+
if (caps.audio && part.data) {
|
|
190
|
+
promptParts.push({
|
|
191
|
+
type: 'audio',
|
|
192
|
+
data: part.data,
|
|
193
|
+
mimeType: part.mimeType,
|
|
194
|
+
...(part.uri ? { uri: part.uri } : {}),
|
|
195
|
+
...(part.transcript ? { transcript: part.transcript } : {}),
|
|
196
|
+
});
|
|
197
|
+
if (part.transcript) appendPromptText(promptParts, part.transcript);
|
|
198
|
+
} else if (part.uri) {
|
|
199
|
+
appendResourceLink(promptParts, part.uri, 'audio', part.mimeType, part.transcript);
|
|
200
|
+
if (part.transcript) appendPromptText(promptParts, part.transcript);
|
|
201
|
+
} else {
|
|
202
|
+
appendMediaFallbackText(promptParts, 'Audio attachment', [part.transcript, part.mimeType]);
|
|
156
203
|
}
|
|
157
|
-
promptParts.push({
|
|
158
|
-
type: 'audio',
|
|
159
|
-
data: part.data,
|
|
160
|
-
mimeType: part.mimeType,
|
|
161
|
-
});
|
|
162
204
|
continue;
|
|
163
205
|
}
|
|
164
206
|
|
|
165
207
|
if (part.type === 'resource') {
|
|
166
|
-
if (
|
|
167
|
-
throw new Error('ACP agent does not support input type: resource');
|
|
168
|
-
}
|
|
169
|
-
if (part.text) {
|
|
208
|
+
if (caps.embeddedContext && part.text) {
|
|
170
209
|
promptParts.push({
|
|
171
210
|
type: 'resource',
|
|
172
211
|
resource: { uri: part.uri, text: part.text, mimeType: part.mimeType ?? null },
|
|
173
212
|
});
|
|
174
213
|
continue;
|
|
175
214
|
}
|
|
176
|
-
if (part.data) {
|
|
215
|
+
if (caps.embeddedContext && part.data) {
|
|
177
216
|
promptParts.push({
|
|
178
217
|
type: 'resource',
|
|
179
218
|
resource: { uri: part.uri, blob: part.data, mimeType: part.mimeType ?? null },
|
|
180
219
|
});
|
|
181
220
|
continue;
|
|
182
221
|
}
|
|
183
|
-
|
|
222
|
+
appendResourceLink(promptParts, part.uri, part.name || 'resource', part.mimeType, part.text);
|
|
223
|
+
if (part.text) appendPromptText(promptParts, part.text);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (part.type === 'resource_link') {
|
|
228
|
+
appendResourceLink(promptParts, part.uri, part.name, part.mimeType, part.description, {
|
|
229
|
+
name: part.name,
|
|
230
|
+
...(part.title ? { title: part.title } : {}),
|
|
231
|
+
...(typeof part.size === 'number' ? { size: part.size } : {}),
|
|
232
|
+
...(part.annotations ? { annotations: part.annotations } : {}),
|
|
233
|
+
});
|
|
234
|
+
continue;
|
|
184
235
|
}
|
|
185
236
|
|
|
186
237
|
if (part.type === 'video') {
|
|
187
|
-
|
|
238
|
+
// ACP v0.16 prompt capabilities do not advertise native video input. Preserve meaning by
|
|
239
|
+
// sending a linked resource when possible, plus transcript/descriptive text when present.
|
|
240
|
+
if (part.uri) {
|
|
241
|
+
appendResourceLink(promptParts, part.uri, 'video', part.mimeType, part.transcript);
|
|
242
|
+
if (part.transcript) appendPromptText(promptParts, part.transcript);
|
|
243
|
+
} else {
|
|
244
|
+
appendMediaFallbackText(promptParts, 'Video attachment', [part.transcript, part.mimeType]);
|
|
245
|
+
}
|
|
188
246
|
}
|
|
189
247
|
}
|
|
190
248
|
|
|
@@ -346,6 +404,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
346
404
|
lastUpdated: Date.now(),
|
|
347
405
|
settings: this.settings,
|
|
348
406
|
pendingEvents: this.flushEvents(),
|
|
407
|
+
messageInput: getEffectiveMessageInputSupport(this.provider, this.agentCapabilities),
|
|
349
408
|
// ACP-specific: expose available models/modes for dashboard
|
|
350
409
|
acpConfigOptions: this.configOptions,
|
|
351
410
|
acpModes: this.availableModes,
|
|
@@ -962,6 +1021,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
962
1021
|
data: b.data,
|
|
963
1022
|
mimeType: b.mimeType,
|
|
964
1023
|
...(b.uri ? { uri: b.uri } : {}),
|
|
1024
|
+
...(b.alt ? { alt: b.alt } : {}),
|
|
965
1025
|
};
|
|
966
1026
|
}
|
|
967
1027
|
if (b.type === 'audio') {
|
|
@@ -969,14 +1029,31 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
969
1029
|
type: 'audio',
|
|
970
1030
|
data: b.data,
|
|
971
1031
|
mimeType: b.mimeType,
|
|
1032
|
+
...(b.uri ? { uri: b.uri } : {}),
|
|
1033
|
+
...(b.transcript ? { transcript: b.transcript } : {}),
|
|
972
1034
|
};
|
|
973
1035
|
}
|
|
1036
|
+
if (b.type === 'video') {
|
|
1037
|
+
return b.uri
|
|
1038
|
+
? {
|
|
1039
|
+
type: 'resource_link',
|
|
1040
|
+
uri: b.uri,
|
|
1041
|
+
name: path.basename(b.uri),
|
|
1042
|
+
mimeType: b.mimeType,
|
|
1043
|
+
...(b.transcript ? { description: b.transcript } : {}),
|
|
1044
|
+
}
|
|
1045
|
+
: { type: 'text', text: b.transcript || `[Video attachment: ${b.mimeType}]` };
|
|
1046
|
+
}
|
|
974
1047
|
if (b.type === 'resource_link') {
|
|
975
1048
|
return {
|
|
976
1049
|
type: 'resource_link',
|
|
977
1050
|
uri: b.uri,
|
|
978
1051
|
name: b.name,
|
|
1052
|
+
...(b.title ? { title: b.title } : {}),
|
|
1053
|
+
...(b.description ? { description: b.description } : {}),
|
|
979
1054
|
...(b.mimeType ? { mimeType: b.mimeType } : {}),
|
|
1055
|
+
...(typeof b.size === 'number' ? { size: b.size } : {}),
|
|
1056
|
+
...(b.annotations ? { annotations: b.annotations } : {}),
|
|
980
1057
|
};
|
|
981
1058
|
}
|
|
982
1059
|
if (b.type === 'resource') return { type: 'resource', resource: b.resource };
|
|
@@ -1056,7 +1133,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1056
1133
|
|
|
1057
1134
|
switch (update.sessionUpdate) {
|
|
1058
1135
|
case 'agent_message_chunk': {
|
|
1059
|
-
const content = update.content;
|
|
1136
|
+
const content: any = update.content;
|
|
1060
1137
|
if (content.type === 'text') {
|
|
1061
1138
|
this.partialContent += content.text;
|
|
1062
1139
|
} else if (content.type === 'image') {
|
|
@@ -1071,6 +1148,17 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1071
1148
|
type: 'audio',
|
|
1072
1149
|
data: content.data,
|
|
1073
1150
|
mimeType: content.mimeType,
|
|
1151
|
+
...(content.uri ? { uri: content.uri } : {}),
|
|
1152
|
+
...(content.transcript ? { transcript: content.transcript } : {}),
|
|
1153
|
+
});
|
|
1154
|
+
} else if (content.type === 'video') {
|
|
1155
|
+
this.partialBlocks.push({
|
|
1156
|
+
type: 'video',
|
|
1157
|
+
data: content.data,
|
|
1158
|
+
mimeType: content.mimeType,
|
|
1159
|
+
...(content.uri ? { uri: content.uri } : {}),
|
|
1160
|
+
...(content.transcript ? { transcript: content.transcript } : {}),
|
|
1161
|
+
...(content.posterUri ? { posterUri: content.posterUri } : {}),
|
|
1074
1162
|
});
|
|
1075
1163
|
} else if (content.type === 'resource_link') {
|
|
1076
1164
|
this.partialBlocks.push({
|
|
@@ -5,6 +5,43 @@ export const BUILTIN_CHAT_MESSAGE_KINDS = ['standard', 'thought', 'tool', 'termi
|
|
|
5
5
|
export type BuiltinChatMessageKind = typeof BUILTIN_CHAT_MESSAGE_KINDS[number];
|
|
6
6
|
export type ChatMessageKind = BuiltinChatMessageKind | (string & {});
|
|
7
7
|
|
|
8
|
+
export const CHAT_MESSAGE_VISIBILITIES = ['user', 'debug', 'internal', 'hidden'] as const;
|
|
9
|
+
export const CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES = ['visible', 'chat', 'user', 'debug', 'internal', 'hidden'] as const;
|
|
10
|
+
export const CHAT_MESSAGE_AUDIENCES = ['chat', 'debug', 'trace', 'internal'] as const;
|
|
11
|
+
export const CHAT_MESSAGE_SOURCES = [
|
|
12
|
+
'assistant_text',
|
|
13
|
+
'tool_call',
|
|
14
|
+
'terminal_command',
|
|
15
|
+
'runtime_activity',
|
|
16
|
+
'runtime_status',
|
|
17
|
+
'provider_chrome',
|
|
18
|
+
'control',
|
|
19
|
+
] as const;
|
|
20
|
+
export const CHAT_MESSAGE_ACTIVITY_SOURCES = ['tool_call', 'terminal_command', 'runtime_activity'] as const;
|
|
21
|
+
export const CHAT_MESSAGE_INTERNAL_SOURCES = ['runtime_status', 'provider_chrome', 'control'] as const;
|
|
22
|
+
|
|
23
|
+
export type ChatMessageVisibility = typeof CHAT_MESSAGE_VISIBILITIES[number] | (string & {});
|
|
24
|
+
export type ChatMessageTranscriptVisibility = typeof CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES[number] | (string & {});
|
|
25
|
+
export type ChatMessageAudience = typeof CHAT_MESSAGE_AUDIENCES[number] | (string & {});
|
|
26
|
+
export type ChatMessageSource = typeof CHAT_MESSAGE_SOURCES[number] | (string & {});
|
|
27
|
+
export type ChatMessageTranscriptSurface = 'chat' | 'activity' | 'internal';
|
|
28
|
+
|
|
29
|
+
export interface ChatMessageVisibilityClassification {
|
|
30
|
+
surface: ChatMessageTranscriptSurface;
|
|
31
|
+
isUserFacing: boolean;
|
|
32
|
+
isActivityFacing: boolean;
|
|
33
|
+
isInternal: boolean;
|
|
34
|
+
explicitUserFacing: boolean;
|
|
35
|
+
explicitHidden: boolean;
|
|
36
|
+
role: string;
|
|
37
|
+
kind: ChatMessageKind;
|
|
38
|
+
visibility: string;
|
|
39
|
+
transcriptVisibility: string;
|
|
40
|
+
audience: string;
|
|
41
|
+
source: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
8
45
|
const KNOWN_CHAT_MESSAGE_KINDS = new Set<string>(BUILTIN_CHAT_MESSAGE_KINDS);
|
|
9
46
|
const CHAT_MESSAGE_KIND_ALIASES: Record<string, BuiltinChatMessageKind> = {
|
|
10
47
|
text: 'standard',
|
|
@@ -183,71 +220,195 @@ function readStringField(value: unknown): string {
|
|
|
183
220
|
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
184
221
|
}
|
|
185
222
|
|
|
186
|
-
function
|
|
223
|
+
function readRecordField(message: ChatMessage, meta: Record<string, unknown> | null, key: string): unknown {
|
|
187
224
|
const record = message as ChatMessage & Record<string, unknown>;
|
|
188
|
-
return
|
|
225
|
+
return record[key] ?? meta?.[key];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function readVisibilityField(message: ChatMessage, meta: Record<string, unknown> | null): string {
|
|
229
|
+
return readStringField(readRecordField(message, meta, 'visibility'));
|
|
189
230
|
}
|
|
190
231
|
|
|
191
|
-
function
|
|
232
|
+
function readTranscriptVisibilityField(message: ChatMessage, meta: Record<string, unknown> | null): string {
|
|
192
233
|
const record = message as ChatMessage & Record<string, unknown>;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|| source === 'runtime_status'
|
|
204
|
-
|| source === 'runtime_activity'
|
|
205
|
-
|| source === 'provider_chrome'
|
|
206
|
-
|| source === 'control'
|
|
207
|
-
|| record.internal === true
|
|
208
|
-
|| record.isInternal === true
|
|
209
|
-
|| record.debug === true
|
|
210
|
-
|| meta?.internal === true
|
|
211
|
-
|| meta?.isInternal === true
|
|
212
|
-
|| meta?.debug === true
|
|
213
|
-
|| meta?.statusOnly === true
|
|
214
|
-
|| meta?.controlOnly === true;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function isExplicitlyVisibleInTranscript(message: ChatMessage, meta: Record<string, unknown> | null): boolean {
|
|
234
|
+
return readStringField(record.transcriptVisibility ?? meta?.transcriptVisibility ?? record.visibility ?? meta?.visibility);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const EXPLICIT_HIDDEN_VISIBILITIES = new Set(['hidden', 'debug', 'internal']);
|
|
238
|
+
const EXPLICIT_VISIBLE_VISIBILITIES = new Set(['visible', 'user', 'chat']);
|
|
239
|
+
const HIDDEN_AUDIENCES = new Set(['debug', 'trace', 'internal']);
|
|
240
|
+
const ACTIVITY_SOURCE_SET = new Set<string>(CHAT_MESSAGE_ACTIVITY_SOURCES);
|
|
241
|
+
const INTERNAL_SOURCE_SET = new Set<string>(CHAT_MESSAGE_INTERNAL_SOURCES);
|
|
242
|
+
|
|
243
|
+
function hasBooleanMarker(message: ChatMessage, meta: Record<string, unknown> | null, keys: string[]): boolean {
|
|
218
244
|
const record = message as ChatMessage & Record<string, unknown>;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
245
|
+
return keys.some((key) => record[key] === true || meta?.[key] === true);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function isActivityKind(kind: ChatMessageKind): boolean {
|
|
249
|
+
return kind === 'thought' || kind === 'tool' || kind === 'terminal';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function isOrdinaryVisibleTurn(message: ChatMessage, role: string, kind: ChatMessageKind): boolean {
|
|
253
|
+
if (role === 'user' || role === 'human') return kind === 'standard' || kind === '';
|
|
254
|
+
if (role === 'assistant') return kind === 'standard' || kind === '';
|
|
255
|
+
return false;
|
|
227
256
|
}
|
|
228
257
|
|
|
229
258
|
/**
|
|
230
|
-
*
|
|
259
|
+
* Shared transcript visibility protocol for all ADHDev provider chat messages.
|
|
231
260
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* marks a non-standard row as user-facing. This keeps internal tool/status/control
|
|
236
|
-
* plumbing out of the ordinary transcript without matching provider-specific text.
|
|
261
|
+
* Producers can stamp visibility/audience/source/userFacing/internal/debug either
|
|
262
|
+
* at the top level or under `meta`. Consumers should use this classifier instead
|
|
263
|
+
* of matching command text, icons, provider names, or terminal UI fragments.
|
|
237
264
|
*/
|
|
238
|
-
export function
|
|
239
|
-
if (!message)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
265
|
+
export function classifyChatMessageVisibility(message: ChatMessage | null | undefined): ChatMessageVisibilityClassification {
|
|
266
|
+
if (!message) {
|
|
267
|
+
return {
|
|
268
|
+
surface: 'internal',
|
|
269
|
+
isUserFacing: false,
|
|
270
|
+
isActivityFacing: false,
|
|
271
|
+
isInternal: true,
|
|
272
|
+
explicitUserFacing: false,
|
|
273
|
+
explicitHidden: true,
|
|
274
|
+
role: '',
|
|
275
|
+
kind: 'standard',
|
|
276
|
+
visibility: '',
|
|
277
|
+
transcriptVisibility: '',
|
|
278
|
+
audience: '',
|
|
279
|
+
source: '',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
243
282
|
|
|
283
|
+
const meta = readMessageMeta(message);
|
|
244
284
|
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
245
285
|
const kind = resolveChatMessageKind(message);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
286
|
+
const visibility = readVisibilityField(message, meta);
|
|
287
|
+
const transcriptVisibility = readTranscriptVisibilityField(message, meta);
|
|
288
|
+
const audience = readStringField(readRecordField(message, meta, 'audience'));
|
|
289
|
+
const source = readStringField(readRecordField(message, meta, 'source'));
|
|
290
|
+
const explicitHidden = EXPLICIT_HIDDEN_VISIBILITIES.has(visibility)
|
|
291
|
+
|| EXPLICIT_HIDDEN_VISIBILITIES.has(transcriptVisibility)
|
|
292
|
+
|| HIDDEN_AUDIENCES.has(audience)
|
|
293
|
+
|| hasBooleanMarker(message, meta, ['internal', 'isInternal', 'debug', 'statusOnly', 'controlOnly']);
|
|
294
|
+
const explicitUserFacing = EXPLICIT_VISIBLE_VISIBILITIES.has(visibility)
|
|
295
|
+
|| EXPLICIT_VISIBLE_VISIBILITIES.has(transcriptVisibility)
|
|
296
|
+
|| audience === 'chat'
|
|
297
|
+
|| hasBooleanMarker(message, meta, ['userFacing']);
|
|
298
|
+
|
|
299
|
+
if (explicitHidden) {
|
|
300
|
+
const activityLike = isActivityKind(kind) || ACTIVITY_SOURCE_SET.has(source);
|
|
301
|
+
return {
|
|
302
|
+
surface: activityLike ? 'activity' : 'internal',
|
|
303
|
+
isUserFacing: false,
|
|
304
|
+
isActivityFacing: activityLike,
|
|
305
|
+
isInternal: !activityLike,
|
|
306
|
+
explicitUserFacing,
|
|
307
|
+
explicitHidden,
|
|
308
|
+
role,
|
|
309
|
+
kind,
|
|
310
|
+
visibility,
|
|
311
|
+
transcriptVisibility,
|
|
312
|
+
audience,
|
|
313
|
+
source,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (explicitUserFacing) {
|
|
318
|
+
return {
|
|
319
|
+
surface: 'chat',
|
|
320
|
+
isUserFacing: true,
|
|
321
|
+
isActivityFacing: false,
|
|
322
|
+
isInternal: false,
|
|
323
|
+
explicitUserFacing,
|
|
324
|
+
explicitHidden,
|
|
325
|
+
role,
|
|
326
|
+
kind,
|
|
327
|
+
visibility,
|
|
328
|
+
transcriptVisibility,
|
|
329
|
+
audience,
|
|
330
|
+
source,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (INTERNAL_SOURCE_SET.has(source) || role === 'system' || kind === 'system') {
|
|
335
|
+
return {
|
|
336
|
+
surface: 'internal',
|
|
337
|
+
isUserFacing: false,
|
|
338
|
+
isActivityFacing: false,
|
|
339
|
+
isInternal: true,
|
|
340
|
+
explicitUserFacing,
|
|
341
|
+
explicitHidden,
|
|
342
|
+
role,
|
|
343
|
+
kind,
|
|
344
|
+
visibility,
|
|
345
|
+
transcriptVisibility,
|
|
346
|
+
audience,
|
|
347
|
+
source,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (ACTIVITY_SOURCE_SET.has(source) || isActivityKind(kind)) {
|
|
352
|
+
return {
|
|
353
|
+
surface: 'activity',
|
|
354
|
+
isUserFacing: false,
|
|
355
|
+
isActivityFacing: true,
|
|
356
|
+
isInternal: false,
|
|
357
|
+
explicitUserFacing,
|
|
358
|
+
explicitHidden,
|
|
359
|
+
role,
|
|
360
|
+
kind,
|
|
361
|
+
visibility,
|
|
362
|
+
transcriptVisibility,
|
|
363
|
+
audience,
|
|
364
|
+
source,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const isUserFacing = isOrdinaryVisibleTurn(message, role, kind);
|
|
369
|
+
return {
|
|
370
|
+
surface: isUserFacing ? 'chat' : 'internal',
|
|
371
|
+
isUserFacing,
|
|
372
|
+
isActivityFacing: false,
|
|
373
|
+
isInternal: !isUserFacing,
|
|
374
|
+
explicitUserFacing,
|
|
375
|
+
explicitHidden,
|
|
376
|
+
role,
|
|
377
|
+
kind,
|
|
378
|
+
visibility,
|
|
379
|
+
transcriptVisibility,
|
|
380
|
+
audience,
|
|
381
|
+
source,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function isUserFacingChatMessage(message: ChatMessage | null | undefined): boolean {
|
|
386
|
+
return classifyChatMessageVisibility(message).isUserFacing;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function isActivityChatMessage(message: ChatMessage | null | undefined): boolean {
|
|
390
|
+
return classifyChatMessageVisibility(message).isActivityFacing;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function isInternalChatMessage(message: ChatMessage | null | undefined): boolean {
|
|
394
|
+
return classifyChatMessageVisibility(message).isInternal;
|
|
249
395
|
}
|
|
250
396
|
|
|
251
397
|
export function filterUserFacingChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[] {
|
|
252
398
|
return (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
253
399
|
}
|
|
400
|
+
|
|
401
|
+
export function filterActivityChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[] {
|
|
402
|
+
return (Array.isArray(messages) ? messages : []).filter((message) => isActivityChatMessage(message));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function filterInternalChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[] {
|
|
406
|
+
return (Array.isArray(messages) ? messages : []).filter((message) => isInternalChatMessage(message));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function filterChatMessagesByVisibility<T extends ChatMessage>(
|
|
410
|
+
messages: T[] | null | undefined,
|
|
411
|
+
surface: ChatMessageTranscriptSurface,
|
|
412
|
+
): T[] {
|
|
413
|
+
return (Array.isArray(messages) ? messages : []).filter((message) => classifyChatMessageVisibility(message).surface === surface);
|
|
414
|
+
}
|
|
@@ -10,8 +10,8 @@ import * as path from 'path';
|
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
|
-
import { normalizeInputEnvelope, type ProviderModule, flattenContent } from './contracts.js';
|
|
14
|
-
import {
|
|
13
|
+
import { normalizeInputEnvelope, type ProviderModule, flattenContent, type InputEnvelope, type InputPart } from './contracts.js';
|
|
14
|
+
import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport } from './provider-input-support.js';
|
|
15
15
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, ProviderErrorReason, HotChatSessionState, SessionModalState } from './provider-instance.js';
|
|
16
16
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
17
17
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
@@ -35,6 +35,95 @@ type PersistableCliHistoryMessage = {
|
|
|
35
35
|
receivedAt?: number;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
const IMAGE_MIME_EXTENSIONS: Record<string, string> = {
|
|
39
|
+
'image/png': '.png',
|
|
40
|
+
'image/jpeg': '.jpg',
|
|
41
|
+
'image/jpg': '.jpg',
|
|
42
|
+
'image/gif': '.gif',
|
|
43
|
+
'image/webp': '.webp',
|
|
44
|
+
'image/bmp': '.bmp',
|
|
45
|
+
'image/tiff': '.tiff',
|
|
46
|
+
'image/svg+xml': '.svg',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function filePathFromUri(uri: string): string | null {
|
|
50
|
+
if (!uri) return null;
|
|
51
|
+
if (uri.startsWith('file://')) {
|
|
52
|
+
try {
|
|
53
|
+
return decodeURIComponent(new URL(uri).pathname);
|
|
54
|
+
} catch {
|
|
55
|
+
return uri.slice('file://'.length);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (path.isAbsolute(uri)) return uri;
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function extensionForImageMime(mimeType: string): string {
|
|
63
|
+
return IMAGE_MIME_EXTENSIONS[mimeType.toLowerCase()] || '.img';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function safeInputImageBasename(index: number, mimeType: string): string {
|
|
67
|
+
const extension = extensionForImageMime(mimeType);
|
|
68
|
+
const suffix = crypto.randomBytes(6).toString('hex');
|
|
69
|
+
return `adhdev-input-image-${Date.now()}-${index}-${suffix}${extension}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function materializeImageDataPart(part: Extract<InputPart, { type: 'image' }>, index: number, dir: string): string | null {
|
|
73
|
+
if (!part.data) return null;
|
|
74
|
+
const rawData = part.data.includes(',') ? part.data.split(',').pop() || '' : part.data;
|
|
75
|
+
if (!rawData) return null;
|
|
76
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
77
|
+
const filePath = path.join(dir, safeInputImageBasename(index, part.mimeType));
|
|
78
|
+
fs.writeFileSync(filePath, Buffer.from(rawData, 'base64'));
|
|
79
|
+
return filePath;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function buildCliStructuredInputPrompt(
|
|
83
|
+
input: InputEnvelope,
|
|
84
|
+
options: { materializeDir?: string } = {},
|
|
85
|
+
): string {
|
|
86
|
+
const promptParts: string[] = [];
|
|
87
|
+
const imageRefs: string[] = [];
|
|
88
|
+
const resourceRefs: string[] = [];
|
|
89
|
+
const materializeDir = options.materializeDir || path.join(os.tmpdir(), 'adhdev-input-media');
|
|
90
|
+
|
|
91
|
+
input.parts.forEach((part, index) => {
|
|
92
|
+
if (part.type === 'text' && part.text.trim()) {
|
|
93
|
+
promptParts.push(part.text.trim());
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (part.type === 'image') {
|
|
98
|
+
const localPath = typeof part.uri === 'string' ? filePathFromUri(part.uri) : null;
|
|
99
|
+
const materializedPath = !localPath && part.data ? materializeImageDataPart(part, index, materializeDir) : null;
|
|
100
|
+
const ref = localPath || materializedPath || part.uri || '';
|
|
101
|
+
if (ref) imageRefs.push(ref);
|
|
102
|
+
if (part.alt?.trim()) promptParts.push(part.alt.trim());
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (part.type === 'resource_link') {
|
|
107
|
+
resourceRefs.push([part.title, part.name, part.description, part.uri].filter(Boolean).join('\n'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (part.type === 'resource') {
|
|
112
|
+
resourceRefs.push([part.name, part.text, part.uri].filter(Boolean).join('\n'));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (input.textFallback.trim()) promptParts.push(input.textFallback.trim());
|
|
117
|
+
|
|
118
|
+
const ordered = [
|
|
119
|
+
...imageRefs,
|
|
120
|
+
...promptParts,
|
|
121
|
+
...resourceRefs,
|
|
122
|
+
].filter((value, index, values) => value.trim().length > 0 && values.indexOf(value) === index);
|
|
123
|
+
|
|
124
|
+
return ordered.join('\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
38
127
|
function normalizePersistableCliHistoryContent(content: unknown): string {
|
|
39
128
|
return flattenContent(content as any).replace(/\s+/g, ' ').trim();
|
|
40
129
|
}
|
|
@@ -476,6 +565,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
476
565
|
resume: this.provider.resume,
|
|
477
566
|
controlValues: surface.controlValues,
|
|
478
567
|
providerControls: this.provider.controls,
|
|
568
|
+
messageInput: getEffectiveMessageInputSupport(this.provider),
|
|
479
569
|
summaryMetadata: surface.summaryMetadata as any,
|
|
480
570
|
errorMessage: this.errorMessage,
|
|
481
571
|
errorReason: this.errorReason,
|
|
@@ -532,9 +622,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
532
622
|
onEvent(event: string, data?: any): void {
|
|
533
623
|
if (event === 'send_message') {
|
|
534
624
|
const input = normalizeInputEnvelope(data);
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
625
|
+
assertProviderSupportsDeclaredInput(this.provider, input);
|
|
626
|
+
const promptText = buildCliStructuredInputPrompt(input);
|
|
627
|
+
if (promptText) {
|
|
628
|
+
void this.adapter.sendMessage(promptText).catch((e: any) => {
|
|
538
629
|
LOG.warn('CLI', `[${this.type}] send_message failed: ${e?.message || e}`);
|
|
539
630
|
});
|
|
540
631
|
}
|