deepseek-agent-acp 0.2.0 → 0.2.2
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.d.ts +4 -0
- package/dist/index.js +88 -1
- package/dist/telemetry.d.ts +17 -3
- package/dist/telemetry.js +39 -10
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -11,3 +11,7 @@
|
|
|
11
11
|
* - Full Model Context Protocol (MCP) tool execution.
|
|
12
12
|
*/
|
|
13
13
|
export declare function loadSecretKey(secretsPath?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Extract clean prompt text from ACP params supporting string, ContentBlock[], or nested structures.
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractPromptText(params?: Record<string, unknown>): string;
|
package/dist/index.js
CHANGED
|
@@ -147,6 +147,7 @@ class Session {
|
|
|
147
147
|
checkpoint;
|
|
148
148
|
abortController = null;
|
|
149
149
|
activePromptId = null;
|
|
150
|
+
activeTurnHandle = null;
|
|
150
151
|
constructor(sessionId) {
|
|
151
152
|
this.sessionId = sessionId;
|
|
152
153
|
const sysPrompt = getSystemPrompt(MODEL);
|
|
@@ -218,6 +219,11 @@ async function handleRpcMessage(rpc) {
|
|
|
218
219
|
streaming: true,
|
|
219
220
|
tools: true,
|
|
220
221
|
sessionCancellation: true
|
|
222
|
+
},
|
|
223
|
+
_meta: {
|
|
224
|
+
steering: {
|
|
225
|
+
supported: true
|
|
226
|
+
}
|
|
221
227
|
}
|
|
222
228
|
});
|
|
223
229
|
break;
|
|
@@ -244,6 +250,10 @@ async function handleRpcMessage(rpc) {
|
|
|
244
250
|
await handleSessionPrompt(id, params);
|
|
245
251
|
break;
|
|
246
252
|
}
|
|
253
|
+
case '_session/steering': {
|
|
254
|
+
handleSessionSteering(id, params);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
247
257
|
default: {
|
|
248
258
|
console.warn(`Unhandled method: ${method}`);
|
|
249
259
|
if (id !== undefined && id !== null) {
|
|
@@ -267,12 +277,54 @@ async function handleRpcMessage(rpc) {
|
|
|
267
277
|
}
|
|
268
278
|
}
|
|
269
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Extract clean prompt text from ACP params supporting string, ContentBlock[], or nested structures.
|
|
282
|
+
*/
|
|
283
|
+
export function extractPromptText(params) {
|
|
284
|
+
if (!params) {
|
|
285
|
+
return '';
|
|
286
|
+
}
|
|
287
|
+
const prompt = params.prompt ?? params.message ?? params.text;
|
|
288
|
+
if (typeof prompt === 'string') {
|
|
289
|
+
return prompt;
|
|
290
|
+
}
|
|
291
|
+
if (Array.isArray(prompt)) {
|
|
292
|
+
return prompt
|
|
293
|
+
.map((block) => {
|
|
294
|
+
if (typeof block === 'string') {
|
|
295
|
+
return block;
|
|
296
|
+
}
|
|
297
|
+
if (block && typeof block === 'object') {
|
|
298
|
+
const rec = block;
|
|
299
|
+
if (typeof rec.text === 'string') {
|
|
300
|
+
return rec.text;
|
|
301
|
+
}
|
|
302
|
+
if (typeof rec.content === 'string') {
|
|
303
|
+
return rec.content;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return '';
|
|
307
|
+
})
|
|
308
|
+
.filter(Boolean)
|
|
309
|
+
.join('\n');
|
|
310
|
+
}
|
|
311
|
+
if (prompt && typeof prompt === 'object') {
|
|
312
|
+
const rec = prompt;
|
|
313
|
+
if (typeof rec.text === 'string') {
|
|
314
|
+
return rec.text;
|
|
315
|
+
}
|
|
316
|
+
if (typeof rec.content === 'string') {
|
|
317
|
+
return rec.content;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return '';
|
|
321
|
+
}
|
|
270
322
|
/**
|
|
271
323
|
* Handle ACP session/prompt execution with multi-turn tool loops.
|
|
272
324
|
*/
|
|
273
325
|
async function handleSessionPrompt(id, params) {
|
|
274
326
|
const sessionId = params?.sessionId || `session-${Date.now().toString()}`;
|
|
275
|
-
const promptText = params
|
|
327
|
+
const promptText = extractPromptText(params);
|
|
276
328
|
let session = sessions.get(sessionId);
|
|
277
329
|
if (!session) {
|
|
278
330
|
session = new Session(sessionId);
|
|
@@ -293,6 +345,7 @@ async function handleSessionPrompt(id, params) {
|
|
|
293
345
|
session.messages.push({ role: 'user', content: promptText });
|
|
294
346
|
const nostrMeta = parseNostrContext(promptText);
|
|
295
347
|
const turnHandle = telemetryService?.startTurnSpan(sessionId, promptText, MODEL, nostrMeta);
|
|
348
|
+
session.activeTurnHandle = turnHandle ?? null;
|
|
296
349
|
const maxIterations = getIterationLimit(MODEL);
|
|
297
350
|
let iteration = 0;
|
|
298
351
|
let lastToolError = null;
|
|
@@ -617,7 +670,41 @@ async function handleSessionPrompt(id, params) {
|
|
|
617
670
|
finally {
|
|
618
671
|
session.abortController = null;
|
|
619
672
|
session.activePromptId = null;
|
|
673
|
+
session.activeTurnHandle = null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Handle ACP _session/steering requests (mid-turn follow-up injection).
|
|
678
|
+
*/
|
|
679
|
+
function handleSessionSteering(id, params) {
|
|
680
|
+
const sessionId = params?.sessionId || '';
|
|
681
|
+
const session = sessionId ? sessions.get(sessionId) : undefined;
|
|
682
|
+
const promptText = extractPromptText(params);
|
|
683
|
+
if (!session || !session.activePromptId) {
|
|
684
|
+
// Session is idle or has no running turn
|
|
685
|
+
sendResponse(id, {
|
|
686
|
+
outcome: 'promptRequired',
|
|
687
|
+
reason: 'noRunningTurn'
|
|
688
|
+
});
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
// Active turn in flight: parse nostr context and record steer child span
|
|
692
|
+
const nostrMeta = parseNostrContext(promptText);
|
|
693
|
+
if (session.activeTurnHandle) {
|
|
694
|
+
session.activeTurnHandle.recordSteerSpan({
|
|
695
|
+
eventId: nostrMeta.eventId,
|
|
696
|
+
text: promptText,
|
|
697
|
+
senderName: nostrMeta.senderName,
|
|
698
|
+
senderPubkey: nostrMeta.senderPubkey,
|
|
699
|
+
priority: 'now'
|
|
700
|
+
});
|
|
620
701
|
}
|
|
702
|
+
// Inject into active turn messages for the model's next iteration
|
|
703
|
+
const steerHeader = '[New message — arrived while you were working]';
|
|
704
|
+
const steerFormatted = `${steerHeader}\n${promptText}`;
|
|
705
|
+
session.messages.push({ role: 'user', content: steerFormatted });
|
|
706
|
+
console.info(`[STEER] Injected steered message into active session ${sessionId} (event: ${nostrMeta.eventId ?? 'unknown'})`);
|
|
707
|
+
sendResponse(id, { outcome: 'injected' });
|
|
621
708
|
}
|
|
622
709
|
// ============================================================================
|
|
623
710
|
// 6. PROCESS STDIN LISTENER
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -42,20 +42,34 @@ export interface NostrMetadata {
|
|
|
42
42
|
* Isolates the active envelope using lastIndexOf('Event ID:') to prevent
|
|
43
43
|
* capturing cancelled or steered predecessor messages.
|
|
44
44
|
*/
|
|
45
|
-
export declare function parseNostrContext(rawText:
|
|
45
|
+
export declare function parseNostrContext(rawText: unknown): NostrMetadata;
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
47
|
+
* Regex matching posted Nostr event ID across text and JSON formats:
|
|
48
|
+
* - buzz messages send / relay stdout: {"accepted":true,"event_id":"<64 hex>",...}
|
|
49
|
+
* - reply tools: `posted (N characters) · event_id <64 hex>`
|
|
50
|
+
* - shell assignments: `event_id=<64 hex>` or `event_id: <64 hex>`
|
|
51
|
+
*/
|
|
52
|
+
export declare const POSTED_OUTPUT_PATTERN: RegExp;
|
|
53
|
+
/**
|
|
54
|
+
* Extract the posted Buzz event ID from a reply tool result or shell output.
|
|
49
55
|
*/
|
|
50
56
|
export declare function extractPostedEventId(output: unknown): string | undefined;
|
|
51
57
|
export interface LlmSpanHandle {
|
|
52
58
|
recordUsage(usage: DeepSeekUsage): void;
|
|
53
59
|
end(outputValue?: string, errorMessage?: string): void;
|
|
54
60
|
}
|
|
61
|
+
export interface SteerSpanParams {
|
|
62
|
+
eventId?: string;
|
|
63
|
+
text: string;
|
|
64
|
+
senderName?: string;
|
|
65
|
+
senderPubkey?: string;
|
|
66
|
+
priority?: 'now' | 'later';
|
|
67
|
+
}
|
|
55
68
|
export interface TurnSpanHandle {
|
|
56
69
|
startLlmSpan(modelName: string, iteration?: number): LlmSpanHandle;
|
|
57
70
|
startToolSpan(toolCallId: string, toolName: string, toolParams: unknown): void;
|
|
58
71
|
endToolSpan(toolCallId: string, output?: unknown, isError?: boolean, toolName?: string): void;
|
|
72
|
+
recordSteerSpan(params: SteerSpanParams): void;
|
|
59
73
|
recordUsage(usage: DeepSeekUsage): void;
|
|
60
74
|
end(status: 'OK' | 'ERROR' | 'CANCELLED', outputOrError?: string, failureKind?: string, errorMessage?: string): void;
|
|
61
75
|
}
|
package/dist/telemetry.js
CHANGED
|
@@ -153,7 +153,7 @@ export function computeOutputBytes(output) {
|
|
|
153
153
|
*/
|
|
154
154
|
export function parseNostrContext(rawText) {
|
|
155
155
|
const result = {};
|
|
156
|
-
if (!rawText) {
|
|
156
|
+
if (typeof rawText !== 'string' || !rawText) {
|
|
157
157
|
return result;
|
|
158
158
|
}
|
|
159
159
|
const lastEventIdx = rawText.lastIndexOf('Event ID:');
|
|
@@ -195,8 +195,14 @@ export function parseNostrContext(rawText) {
|
|
|
195
195
|
return result;
|
|
196
196
|
}
|
|
197
197
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
198
|
+
* Regex matching posted Nostr event ID across text and JSON formats:
|
|
199
|
+
* - buzz messages send / relay stdout: {"accepted":true,"event_id":"<64 hex>",...}
|
|
200
|
+
* - reply tools: `posted (N characters) · event_id <64 hex>`
|
|
201
|
+
* - shell assignments: `event_id=<64 hex>` or `event_id: <64 hex>`
|
|
202
|
+
*/
|
|
203
|
+
export const POSTED_OUTPUT_PATTERN = /\bevent_id["']?\s*[:=]?\s*["']?([0-9a-f]{64})(?![0-9a-f])/i;
|
|
204
|
+
/**
|
|
205
|
+
* Extract the posted Buzz event ID from a reply tool result or shell output.
|
|
200
206
|
*/
|
|
201
207
|
export function extractPostedEventId(output) {
|
|
202
208
|
let text = '';
|
|
@@ -209,12 +215,9 @@ export function extractPostedEventId(output) {
|
|
|
209
215
|
if (!text) {
|
|
210
216
|
return undefined;
|
|
211
217
|
}
|
|
212
|
-
const match =
|
|
218
|
+
const match = POSTED_OUTPUT_PATTERN.exec(text.slice(0, 8192));
|
|
213
219
|
return match?.[1] ? match[1].toLowerCase() : undefined;
|
|
214
220
|
}
|
|
215
|
-
function isReplyTool(toolName) {
|
|
216
|
-
return toolName === 'mcp__buzz-thread__reply' || toolName === 'reply' || toolName.endsWith('reply');
|
|
217
|
-
}
|
|
218
221
|
// ============================================================================
|
|
219
222
|
// 4. FLUSH COALESCING & ERROR THROTTLING
|
|
220
223
|
// ============================================================================
|
|
@@ -429,11 +432,11 @@ export function initTelemetry(options) {
|
|
|
429
432
|
}
|
|
430
433
|
return;
|
|
431
434
|
}
|
|
432
|
-
const { span: toolSpan
|
|
435
|
+
const { span: toolSpan } = entry;
|
|
433
436
|
const outputBytes = computeOutputBytes(output);
|
|
434
437
|
toolSpan.setAttribute('tool.output_bytes', outputBytes);
|
|
435
|
-
// Extract posted_event_id for reply
|
|
436
|
-
if (!isError
|
|
438
|
+
// Extract posted_event_id for any posting tool (reply, buzz-thread__reply, shell, etc.)
|
|
439
|
+
if (!isError) {
|
|
437
440
|
const postedId = extractPostedEventId(output);
|
|
438
441
|
if (postedId) {
|
|
439
442
|
toolSpan.setAttribute('nostr.posted_event_id', postedId);
|
|
@@ -458,6 +461,32 @@ export function initTelemetry(options) {
|
|
|
458
461
|
toolSpan.end();
|
|
459
462
|
activeToolSpans.delete(toolCallId);
|
|
460
463
|
},
|
|
464
|
+
recordSteerSpan(steerParams) {
|
|
465
|
+
const sanitizedText = truncatePayload(redactSecrets(steerParams.text), 8192);
|
|
466
|
+
const steerSpan = tracer.startSpan('steer', {
|
|
467
|
+
attributes: {
|
|
468
|
+
'openinference.span.kind': 'CHAIN',
|
|
469
|
+
'session.id': resolvedSessionId,
|
|
470
|
+
...(steerParams.eventId ? { 'nostr.event_id': steerParams.eventId.toLowerCase() } : {}),
|
|
471
|
+
'input.value': sanitizedText,
|
|
472
|
+
'input.mime_type': 'text/plain',
|
|
473
|
+
...(steerParams.senderName ? { 'nostr.sender_name': steerParams.senderName } : {}),
|
|
474
|
+
...(steerParams.senderPubkey ? { 'nostr.sender_pubkey': steerParams.senderPubkey.toLowerCase() } : {}),
|
|
475
|
+
'steer.priority': steerParams.priority ?? 'now',
|
|
476
|
+
'steer.delivered': true,
|
|
477
|
+
'steer.end_reason': 'echo'
|
|
478
|
+
}
|
|
479
|
+
}, rootContext);
|
|
480
|
+
steerSpan.setStatus({ code: SpanStatusCode.OK });
|
|
481
|
+
steerSpan.end();
|
|
482
|
+
rootSpan.addEvent('steer', {
|
|
483
|
+
...(steerParams.eventId ? { 'nostr.event_id': steerParams.eventId.toLowerCase() } : {}),
|
|
484
|
+
'input.value': sanitizedText,
|
|
485
|
+
...(steerParams.senderName ? { 'nostr.sender_name': steerParams.senderName } : {}),
|
|
486
|
+
'steer.priority': steerParams.priority ?? 'now',
|
|
487
|
+
'steer.delivered': true
|
|
488
|
+
});
|
|
489
|
+
},
|
|
461
490
|
recordUsage(usage) {
|
|
462
491
|
const promptTokens = usage.prompt_tokens || 0;
|
|
463
492
|
const completionTokens = usage.completion_tokens || 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-agent-acp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Agent Client Protocol (ACP) adapter for DeepSeek reasoning and chat models with Model Context Protocol (MCP) tool execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|