@adecore/agents 0.31.6 → 0.31.8
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/chat/acp-backend.js +10 -3
- package/dist/chat/acp-protocol.d.ts +3 -0
- package/dist/chat/acp-protocol.js +26 -2
- package/dist/chat/chat-session.js +5 -2
- package/dist/chat/fixtures/copilot-1.0.101-turn.json +677 -0
- package/dist/testing/fake-acp-capture.d.ts +11 -0
- package/dist/testing/fake-acp-capture.js +50 -0
- package/package.json +3 -3
- package/src/chat/acp-backend.ts +9 -3
- package/src/chat/acp-protocol.ts +30 -2
- package/src/chat/chat-session.ts +5 -2
- package/src/chat/fixtures/copilot-1.0.101-turn.json +677 -0
- package/src/testing/fake-acp-capture.ts +70 -0
package/dist/chat/acp-backend.js
CHANGED
|
@@ -277,7 +277,9 @@ export class AcpBackend {
|
|
|
277
277
|
const named = input.attachments.filter((attachment) => !images.includes(attachment));
|
|
278
278
|
const parts = [this.contextPending, input.preamble, input.text, attachmentNote(named)].filter((part) => part !== null && part !== '');
|
|
279
279
|
this.contextPending = null;
|
|
280
|
-
|
|
280
|
+
const text = parts.join('\n\n');
|
|
281
|
+
this.protocol.prompted(text);
|
|
282
|
+
return [{ type: 'text', text }, ...blocks, ...this.mentionBlocks(input.mentions)];
|
|
281
283
|
}
|
|
282
284
|
/*
|
|
283
285
|
* The text of each mentioned file as a `resource` block, for an agent that takes embedded context. Only a text file
|
|
@@ -486,13 +488,18 @@ export class AcpBackend {
|
|
|
486
488
|
this.protocol.cancelPermissions();
|
|
487
489
|
this.emit({ type: 'exit', exitCode, ...(stderr === null ? {} : { stderr }) });
|
|
488
490
|
}
|
|
489
|
-
/* A cancelled turn still spent what it reports. */
|
|
491
|
+
/* A cancelled turn still spent what it reports. A host that fails to keep it costs the record, never the end of the turn. */
|
|
490
492
|
report(result) {
|
|
491
493
|
const usage = this.spec.onUsage === undefined ? null : promptUsageOf(result);
|
|
492
494
|
if (usage === null) {
|
|
493
495
|
return;
|
|
494
496
|
}
|
|
495
|
-
|
|
497
|
+
try {
|
|
498
|
+
this.spec.onUsage?.({ ...usage, sessionId: this.sessionId, cwd: this.launch.cwd, model: this.model ?? this.launch.selection.model });
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
console.warn(`Could not keep the usage of a ${this.spec.name} turn:`, errorText(error));
|
|
502
|
+
}
|
|
496
503
|
}
|
|
497
504
|
emitAll(events) {
|
|
498
505
|
for (const event of events) {
|
|
@@ -133,8 +133,11 @@ export declare class AcpProtocol {
|
|
|
133
133
|
private thought;
|
|
134
134
|
private counter;
|
|
135
135
|
private costUsd;
|
|
136
|
+
private readonly prompts;
|
|
136
137
|
constructor(generation: number, modes: AcpModeMap, words: AcpStopWords);
|
|
137
138
|
update(sessionId: string, params: unknown): BackendEvent[];
|
|
139
|
+
prompted(text: string): void;
|
|
140
|
+
private restatesPrompt;
|
|
138
141
|
modeEvent(modeId: string): BackendEvent;
|
|
139
142
|
permission(rpcId: number | string, sessionId: string, params: unknown): AcpPermission | null;
|
|
140
143
|
answer(requestId: string, decision: ApprovalDecision): {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isRecord } from '@adecore/agent-contracts';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { MODE_ORDER } from '../modes.js';
|
|
4
|
+
import { cleanTitle } from '../title-file.js';
|
|
4
5
|
/*
|
|
5
6
|
* The Agent Client Protocol (https://agentclientprotocol.com), version 1, as far as a chat needs it.
|
|
6
7
|
* Every schema is loose: an agent may add fields, and a field or an update this version does not
|
|
@@ -309,6 +310,11 @@ const KIND_NAMES = {
|
|
|
309
310
|
switch_mode: 'Switch mode',
|
|
310
311
|
other: 'Tool'
|
|
311
312
|
};
|
|
313
|
+
// A session's title comes from its opening prompts; later ones are not kept.
|
|
314
|
+
const KEPT_PROMPTS = 3;
|
|
315
|
+
function foldSpace(text) {
|
|
316
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
317
|
+
}
|
|
312
318
|
function str(value) {
|
|
313
319
|
return typeof value === 'string' && value !== '' ? value : null;
|
|
314
320
|
}
|
|
@@ -409,6 +415,8 @@ export class AcpProtocol {
|
|
|
409
415
|
thought = null;
|
|
410
416
|
counter = 0;
|
|
411
417
|
costUsd = 0;
|
|
418
|
+
// The opening prompts of the process, whitespace folded, which an agent may hand back as the session's title.
|
|
419
|
+
prompts = [];
|
|
412
420
|
constructor(generation, modes, words) {
|
|
413
421
|
this.generation = generation;
|
|
414
422
|
this.modes = modes;
|
|
@@ -456,12 +464,28 @@ export class AcpProtocol {
|
|
|
456
464
|
}
|
|
457
465
|
return [{ type: 'usage', contextTokens: usage.data.used, contextWindow: usage.data.size, ...(cost === undefined ? {} : { costUsd: cost }) }];
|
|
458
466
|
}
|
|
459
|
-
case 'session_info_update':
|
|
460
|
-
|
|
467
|
+
case 'session_info_update': {
|
|
468
|
+
const title = cleanTitle(update.title);
|
|
469
|
+
return title === null || this.restatesPrompt(title) ? [] : [{ type: 'title', title }];
|
|
470
|
+
}
|
|
461
471
|
default:
|
|
462
472
|
return [];
|
|
463
473
|
}
|
|
464
474
|
}
|
|
475
|
+
/* Notes a prompt sent to the agent, so a title that only repeats its start is not taken for a name. */
|
|
476
|
+
prompted(text) {
|
|
477
|
+
if (this.prompts.length < KEPT_PROMPTS) {
|
|
478
|
+
this.prompts.push(foldSpace(text));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/*
|
|
482
|
+
* Whether a title is only the start of a prompt, as Copilot CLI 1.0.101 names a session: with a host's instructions in
|
|
483
|
+
* front of the message that names every chat after them, and it keeps the host from naming the chat itself.
|
|
484
|
+
*/
|
|
485
|
+
restatesPrompt(title) {
|
|
486
|
+
const start = foldSpace(title.endsWith('…') ? title.slice(0, -1) : title);
|
|
487
|
+
return start !== '' && this.prompts.some((prompt) => prompt.startsWith(start));
|
|
488
|
+
}
|
|
465
489
|
modeEvent(modeId) {
|
|
466
490
|
const runtimeMode = runtimeModeOf(modeId, this.modes);
|
|
467
491
|
return { type: 'permissions', permissionMode: modeId, ...(runtimeMode === null ? {} : { effectiveRuntimeMode: runtimeMode }) };
|
|
@@ -10,6 +10,7 @@ import { ChatThread } from './thread.js';
|
|
|
10
10
|
import { UiReplies } from './ui-replies.js';
|
|
11
11
|
import { errorText } from '../error-text.js';
|
|
12
12
|
import { systemClock } from '../outbox/outbox-worker.js';
|
|
13
|
+
import { cleanTitle } from '../title-file.js';
|
|
13
14
|
// Claude Code names a session about six seconds after its first prompt, and a first turn can run for minutes.
|
|
14
15
|
const TITLE_RECHECK_MS = 10_000;
|
|
15
16
|
// Well inside the 60 seconds a client gives a request; the steer itself goes on after it.
|
|
@@ -1906,8 +1907,10 @@ export class ChatSession {
|
|
|
1906
1907
|
})
|
|
1907
1908
|
.catch(() => undefined);
|
|
1908
1909
|
}
|
|
1909
|
-
applyTitle(
|
|
1910
|
-
|
|
1910
|
+
applyTitle(raw) {
|
|
1911
|
+
// Every info a client reads carries the title, so one it would refuse would leave the chat stuck there.
|
|
1912
|
+
const title = cleanTitle(raw);
|
|
1913
|
+
if (title === null || title === this.thread.info.suggestedTitle) {
|
|
1911
1914
|
return;
|
|
1912
1915
|
}
|
|
1913
1916
|
this.emit([this.thread.patchInfo({ suggestedTitle: title })]);
|