deepseek-agent-acp 0.2.1 → 0.2.3
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 +45 -0
- package/dist/telemetry.d.ts +17 -2
- package/dist/telemetry.js +71 -11
- package/package.json +1 -1
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) {
|
|
@@ -335,6 +345,7 @@ async function handleSessionPrompt(id, params) {
|
|
|
335
345
|
session.messages.push({ role: 'user', content: promptText });
|
|
336
346
|
const nostrMeta = parseNostrContext(promptText);
|
|
337
347
|
const turnHandle = telemetryService?.startTurnSpan(sessionId, promptText, MODEL, nostrMeta);
|
|
348
|
+
session.activeTurnHandle = turnHandle ?? null;
|
|
338
349
|
const maxIterations = getIterationLimit(MODEL);
|
|
339
350
|
let iteration = 0;
|
|
340
351
|
let lastToolError = null;
|
|
@@ -659,7 +670,41 @@ async function handleSessionPrompt(id, params) {
|
|
|
659
670
|
finally {
|
|
660
671
|
session.abortController = null;
|
|
661
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
|
+
});
|
|
662
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' });
|
|
663
708
|
}
|
|
664
709
|
// ============================================================================
|
|
665
710
|
// 6. PROCESS STDIN LISTENER
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -44,18 +44,33 @@ export interface NostrMetadata {
|
|
|
44
44
|
*/
|
|
45
45
|
export declare function parseNostrContext(rawText: unknown): NostrMetadata;
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
47
|
+
* Regex matching posted Nostr event ID across text, JSON, and escaped JSON formats:
|
|
48
|
+
* - buzz messages send / relay stdout: {"accepted":true,"event_id":"<64 hex>",...}
|
|
49
|
+
* - escaped JSON stdout: {\"accepted\":true,\"event_id\":\"<64 hex>\"}
|
|
50
|
+
* - reply tools: `posted (N characters) · event_id <64 hex>`
|
|
51
|
+
* - shell assignments: `event_id=<64 hex>` or `event_id: <64 hex>`
|
|
52
|
+
*/
|
|
53
|
+
export declare const POSTED_OUTPUT_PATTERN: RegExp;
|
|
54
|
+
/**
|
|
55
|
+
* Extract the posted Buzz event ID from a reply tool result or shell output.
|
|
49
56
|
*/
|
|
50
57
|
export declare function extractPostedEventId(output: unknown): string | undefined;
|
|
51
58
|
export interface LlmSpanHandle {
|
|
52
59
|
recordUsage(usage: DeepSeekUsage): void;
|
|
53
60
|
end(outputValue?: string, errorMessage?: string): void;
|
|
54
61
|
}
|
|
62
|
+
export interface SteerSpanParams {
|
|
63
|
+
eventId?: string;
|
|
64
|
+
text: string;
|
|
65
|
+
senderName?: string;
|
|
66
|
+
senderPubkey?: string;
|
|
67
|
+
priority?: 'now' | 'later';
|
|
68
|
+
}
|
|
55
69
|
export interface TurnSpanHandle {
|
|
56
70
|
startLlmSpan(modelName: string, iteration?: number): LlmSpanHandle;
|
|
57
71
|
startToolSpan(toolCallId: string, toolName: string, toolParams: unknown): void;
|
|
58
72
|
endToolSpan(toolCallId: string, output?: unknown, isError?: boolean, toolName?: string): void;
|
|
73
|
+
recordSteerSpan(params: SteerSpanParams): void;
|
|
59
74
|
recordUsage(usage: DeepSeekUsage): void;
|
|
60
75
|
end(status: 'OK' | 'ERROR' | 'CANCELLED', outputOrError?: string, failureKind?: string, errorMessage?: string): void;
|
|
61
76
|
}
|
package/dist/telemetry.js
CHANGED
|
@@ -195,26 +195,43 @@ export function parseNostrContext(rawText) {
|
|
|
195
195
|
return result;
|
|
196
196
|
}
|
|
197
197
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
198
|
+
* Regex matching posted Nostr event ID across text, JSON, and escaped JSON formats:
|
|
199
|
+
* - buzz messages send / relay stdout: {"accepted":true,"event_id":"<64 hex>",...}
|
|
200
|
+
* - escaped JSON stdout: {\"accepted\":true,\"event_id\":\"<64 hex>\"}
|
|
201
|
+
* - reply tools: `posted (N characters) · event_id <64 hex>`
|
|
202
|
+
* - shell assignments: `event_id=<64 hex>` or `event_id: <64 hex>`
|
|
203
|
+
*/
|
|
204
|
+
export const POSTED_OUTPUT_PATTERN = /\bevent_id\\?["']?\s*[:=]?\s*\\?["']?([0-9a-f]{64})(?![0-9a-f])/i;
|
|
205
|
+
/**
|
|
206
|
+
* Extract the posted Buzz event ID from a reply tool result or shell output.
|
|
200
207
|
*/
|
|
201
208
|
export function extractPostedEventId(output) {
|
|
202
209
|
let text = '';
|
|
203
210
|
if (typeof output === 'string') {
|
|
204
211
|
text = output;
|
|
212
|
+
try {
|
|
213
|
+
const parsed = JSON.parse(output);
|
|
214
|
+
if (parsed && typeof parsed.stdout === 'string') {
|
|
215
|
+
text += ` ${parsed.stdout}`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Ignored
|
|
220
|
+
}
|
|
205
221
|
}
|
|
206
222
|
else if (output && typeof output === 'object') {
|
|
223
|
+
const rec = output;
|
|
207
224
|
text = JSON.stringify(output);
|
|
225
|
+
if (typeof rec.stdout === 'string') {
|
|
226
|
+
text += ` ${rec.stdout}`;
|
|
227
|
+
}
|
|
208
228
|
}
|
|
209
229
|
if (!text) {
|
|
210
230
|
return undefined;
|
|
211
231
|
}
|
|
212
|
-
const match =
|
|
232
|
+
const match = POSTED_OUTPUT_PATTERN.exec(text.slice(0, 8192));
|
|
213
233
|
return match?.[1] ? match[1].toLowerCase() : undefined;
|
|
214
234
|
}
|
|
215
|
-
function isReplyTool(toolName) {
|
|
216
|
-
return toolName === 'mcp__buzz-thread__reply' || toolName === 'reply' || toolName.endsWith('reply');
|
|
217
|
-
}
|
|
218
235
|
// ============================================================================
|
|
219
236
|
// 4. FLUSH COALESCING & ERROR THROTTLING
|
|
220
237
|
// ============================================================================
|
|
@@ -309,7 +326,16 @@ export function initTelemetry(options) {
|
|
|
309
326
|
const tracer = trace.getTracer('deepseek-agent-acp', getPackageVersion());
|
|
310
327
|
return {
|
|
311
328
|
startTurnSpan(sessionId, promptText, modelName, nostrMeta) {
|
|
312
|
-
|
|
329
|
+
// Extract the event message block if present for cleaner prompt preview in Phoenix & buzz-trace
|
|
330
|
+
let promptForSpan = promptText;
|
|
331
|
+
if (promptText.includes('\n') && promptText.includes('Event ID:')) {
|
|
332
|
+
const blocks = promptText.split(/\n{2,}/);
|
|
333
|
+
const eventBlock = blocks.find((b) => b.includes('Event ID:'));
|
|
334
|
+
if (eventBlock) {
|
|
335
|
+
promptForSpan = eventBlock;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const sanitizedPrompt = truncatePayload(redactSecrets(promptForSpan), 8192);
|
|
313
339
|
const resolvedSessionId = nostrMeta?.threadRootId ?? sessionId;
|
|
314
340
|
const rootSpan = tracer.startSpan('agent_turn', {
|
|
315
341
|
attributes: {
|
|
@@ -429,18 +455,26 @@ export function initTelemetry(options) {
|
|
|
429
455
|
}
|
|
430
456
|
return;
|
|
431
457
|
}
|
|
432
|
-
const { span: toolSpan
|
|
458
|
+
const { span: toolSpan } = entry;
|
|
433
459
|
const outputBytes = computeOutputBytes(output);
|
|
434
460
|
toolSpan.setAttribute('tool.output_bytes', outputBytes);
|
|
435
|
-
// Extract posted_event_id for reply
|
|
436
|
-
|
|
461
|
+
// Extract posted_event_id for any posting tool (reply, buzz-thread__reply, shell, etc.)
|
|
462
|
+
let hasPostedId = false;
|
|
463
|
+
if (!isError) {
|
|
437
464
|
const postedId = extractPostedEventId(output);
|
|
438
465
|
if (postedId) {
|
|
466
|
+
hasPostedId = true;
|
|
439
467
|
toolSpan.setAttribute('nostr.posted_event_id', postedId);
|
|
440
468
|
rootSpan.setAttribute('nostr.posted_event_id', postedId);
|
|
469
|
+
// Ensure output.value and tool.output are captured on the posting span for trace fidelity
|
|
470
|
+
const rawStr = typeof output === 'string' ? output : JSON.stringify(output);
|
|
471
|
+
const sanitizedOut = truncatePayload(redactSecrets(rawStr), 8192);
|
|
472
|
+
toolSpan.setAttribute('output.value', sanitizedOut);
|
|
473
|
+
toolSpan.setAttribute('tool.output', sanitizedOut);
|
|
474
|
+
toolSpan.setAttribute('output.mime_type', 'text/plain');
|
|
441
475
|
}
|
|
442
476
|
}
|
|
443
|
-
if (process.env.OTEL_CAPTURE_TOOL_OUTPUT === 'true' && output !== undefined) {
|
|
477
|
+
if (process.env.OTEL_CAPTURE_TOOL_OUTPUT === 'true' && output !== undefined && !hasPostedId) {
|
|
444
478
|
const sanitizedOut = truncatePayload(redactSecrets(output), 8192);
|
|
445
479
|
toolSpan.setAttribute('output.value', sanitizedOut);
|
|
446
480
|
toolSpan.setAttribute('output.mime_type', 'text/plain');
|
|
@@ -458,6 +492,32 @@ export function initTelemetry(options) {
|
|
|
458
492
|
toolSpan.end();
|
|
459
493
|
activeToolSpans.delete(toolCallId);
|
|
460
494
|
},
|
|
495
|
+
recordSteerSpan(steerParams) {
|
|
496
|
+
const sanitizedText = truncatePayload(redactSecrets(steerParams.text), 8192);
|
|
497
|
+
const steerSpan = tracer.startSpan('steer', {
|
|
498
|
+
attributes: {
|
|
499
|
+
'openinference.span.kind': 'CHAIN',
|
|
500
|
+
'session.id': resolvedSessionId,
|
|
501
|
+
...(steerParams.eventId ? { 'nostr.event_id': steerParams.eventId.toLowerCase() } : {}),
|
|
502
|
+
'input.value': sanitizedText,
|
|
503
|
+
'input.mime_type': 'text/plain',
|
|
504
|
+
...(steerParams.senderName ? { 'nostr.sender_name': steerParams.senderName } : {}),
|
|
505
|
+
...(steerParams.senderPubkey ? { 'nostr.sender_pubkey': steerParams.senderPubkey.toLowerCase() } : {}),
|
|
506
|
+
'steer.priority': steerParams.priority ?? 'now',
|
|
507
|
+
'steer.delivered': true,
|
|
508
|
+
'steer.end_reason': 'echo'
|
|
509
|
+
}
|
|
510
|
+
}, rootContext);
|
|
511
|
+
steerSpan.setStatus({ code: SpanStatusCode.OK });
|
|
512
|
+
steerSpan.end();
|
|
513
|
+
rootSpan.addEvent('steer', {
|
|
514
|
+
...(steerParams.eventId ? { 'nostr.event_id': steerParams.eventId.toLowerCase() } : {}),
|
|
515
|
+
'input.value': sanitizedText,
|
|
516
|
+
...(steerParams.senderName ? { 'nostr.sender_name': steerParams.senderName } : {}),
|
|
517
|
+
'steer.priority': steerParams.priority ?? 'now',
|
|
518
|
+
'steer.delivered': true
|
|
519
|
+
});
|
|
520
|
+
},
|
|
461
521
|
recordUsage(usage) {
|
|
462
522
|
const promptTokens = usage.prompt_tokens || 0;
|
|
463
523
|
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.3",
|
|
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",
|