@adecore/agents 0.39.0 → 0.40.1
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/async.d.ts +5 -0
- package/dist/async.js +13 -5
- package/dist/chat/acp-protocol.js +3 -5
- package/dist/chat/chat-core.js +0 -2
- package/dist/chat/chat-process.d.ts +1 -0
- package/dist/chat/chat-process.js +1 -1
- package/dist/chat/chat-session.js +6 -8
- package/dist/chat/claude-protocol.js +1 -6
- package/dist/chat/claude-transcript.js +1 -3
- package/dist/chat/codex-protocol.js +1 -6
- package/dist/chat/projector.js +1 -3
- package/dist/chat/request-summary.js +1 -3
- package/dist/coded-error.js +1 -5
- package/dist/context/argv.js +1 -2
- package/dist/context/verb.js +1 -3
- package/dist/error-text.d.ts +1 -0
- package/dist/error-text.js +4 -0
- package/dist/fields.d.ts +3 -0
- package/dist/fields.js +12 -0
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +8 -4
- package/dist/lineage.js +11 -5
- package/dist/messages/notice-store.js +3 -4
- package/dist/providers/accounts/acp-account.js +3 -20
- package/dist/providers/accounts/service.d.ts +1 -2
- package/dist/providers/accounts/service.js +5 -9
- package/dist/providers/accounts/status.js +1 -3
- package/dist/providers/acp-agent-state.js +1 -3
- package/dist/providers/claude.js +0 -1
- package/dist/providers/codex-models.json +3 -2
- package/dist/providers/codex.js +0 -1
- package/dist/providers/detect.js +1 -1
- package/dist/providers/shell-commands.js +1 -2
- package/dist/run-process.js +1 -3
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -8
- package/dist/tasks/background-limit.js +3 -2
- package/dist/tasks/task-coordinator.js +11 -17
- package/dist/testing/watch.js +0 -1
- package/dist/usage/limits/monitor.js +1 -2
- package/dist/usage/prices-snapshot.json +8 -0
- package/dist/usage/readers/claude.js +3 -10
- package/dist/usage/readers/codex.js +2 -9
- package/dist/usage/readers/grok.js +3 -10
- package/dist/usage/readers/host-log.js +3 -10
- package/dist/usage/record.d.ts +2 -0
- package/dist/usage/record.js +13 -0
- package/package.json +6 -6
- package/src/async.ts +23 -5
- package/src/chat/acp-backend.ts +121 -0
- package/src/chat/acp-protocol.ts +172 -6
- package/src/chat/attachment-store.ts +21 -0
- package/src/chat/backend.ts +0 -2
- package/src/chat/bookmark-store.ts +23 -0
- package/src/chat/chat-core.ts +208 -7
- package/src/chat/chat-log.ts +19 -0
- package/src/chat/chat-process.ts +52 -1
- package/src/chat/chat-session.ts +401 -10
- package/src/chat/chat-store.ts +50 -0
- package/src/chat/chat-title.ts +9 -0
- package/src/chat/claude-backend.ts +50 -0
- package/src/chat/claude-protocol.ts +155 -8
- package/src/chat/claude-title.ts +10 -0
- package/src/chat/claude-transcript.ts +47 -4
- package/src/chat/codex-backend.ts +131 -0
- package/src/chat/codex-protocol.ts +180 -8
- package/src/chat/codex-thread.ts +23 -0
- package/src/chat/composer-preferences.ts +6 -0
- package/src/chat/context-breakdown.ts +11 -0
- package/src/chat/delta-coalescer.ts +8 -0
- package/src/chat/external-sessions.ts +28 -0
- package/src/chat/generated-image.ts +25 -0
- package/src/chat/index.ts +5 -0
- package/src/chat/input.ts +13 -0
- package/src/chat/json-rpc-transport.ts +28 -0
- package/src/chat/limit-resume.ts +13 -0
- package/src/chat/projector.ts +160 -4
- package/src/chat/readable-text.ts +3 -0
- package/src/chat/request-summary.ts +18 -4
- package/src/chat/subagent-projection.ts +3 -0
- package/src/chat/subagent-reader.ts +76 -0
- package/src/chat/subagent-settlement.ts +24 -0
- package/src/chat/thread-search.ts +13 -0
- package/src/chat/thread.ts +31 -0
- package/src/chat/ui-queries.ts +122 -0
- package/src/chat/ui-replies.ts +34 -0
- package/src/chat/visual-store.ts +49 -0
- package/src/chat/wake-chat.ts +3 -0
- package/src/client-sinks.ts +2 -0
- package/src/coded-error.ts +1 -5
- package/src/context/argv.ts +18 -2
- package/src/context/index.ts +1 -0
- package/src/context/refusal.ts +3 -0
- package/src/context/verb.ts +32 -4
- package/src/coordination/index.ts +10 -0
- package/src/error-text.ts +7 -0
- package/src/fields.ts +14 -0
- package/src/fs.ts +20 -4
- package/src/host/agent-host.ts +20 -0
- package/src/host/environment.ts +6 -0
- package/src/host/handlers.ts +18 -0
- package/src/host/memory-port.ts +4 -0
- package/src/host/wiring.ts +10 -0
- package/src/index.ts +1 -0
- package/src/lineage.ts +24 -5
- package/src/messages/deliver-message.ts +6 -0
- package/src/messages/deliver-notice.ts +13 -0
- package/src/messages/notice-store.ts +16 -4
- package/src/modes.ts +1 -0
- package/src/outbox/outbox-worker.ts +31 -0
- package/src/outbox/outbox.ts +5 -0
- package/src/providers/accounts/accounts.ts +36 -0
- package/src/providers/accounts/acp-account.ts +22 -27
- package/src/providers/accounts/launch.ts +4 -0
- package/src/providers/accounts/service.ts +110 -12
- package/src/providers/accounts/shadow-home.ts +10 -0
- package/src/providers/accounts/status.ts +13 -4
- package/src/providers/accounts/store.ts +25 -0
- package/src/providers/accounts/variables.ts +11 -1
- package/src/providers/acp-agent-state.ts +24 -3
- package/src/providers/acp-provider.ts +5 -0
- package/src/providers/acp-usage-log.ts +2 -0
- package/src/providers/catalog.ts +17 -0
- package/src/providers/claude.ts +10 -1
- package/src/providers/codex-models.json +3 -2
- package/src/providers/codex-rules.ts +3 -0
- package/src/providers/codex.ts +2 -1
- package/src/providers/detect.ts +6 -1
- package/src/providers/provider.ts +1 -1
- package/src/providers/registry.ts +18 -2
- package/src/providers/shell-commands.ts +37 -2
- package/src/record-directory.ts +19 -0
- package/src/run-process.ts +24 -3
- package/src/serializer.ts +3 -0
- package/src/skills.ts +73 -10
- package/src/tasks/background-limit.ts +9 -2
- package/src/tasks/end-children.ts +9 -0
- package/src/tasks/give-task.ts +8 -0
- package/src/tasks/task-coordinator.ts +81 -17
- package/src/tasks/task-store.ts +28 -0
- package/src/tasks/waiting-child.ts +21 -0
- package/src/tasks/wake-parent.ts +26 -0
- package/src/tasks/wiring.ts +22 -0
- package/src/testing/accounts.ts +2 -0
- package/src/testing/fake-acp-capture.ts +16 -0
- package/src/testing/fake-acp.ts +66 -0
- package/src/testing/fake-claude.ts +177 -0
- package/src/testing/fake-cli.ts +29 -0
- package/src/testing/fake-codex.ts +122 -0
- package/src/testing/manual-clock.ts +2 -0
- package/src/testing/watch.ts +7 -1
- package/src/title-file.ts +12 -0
- package/src/usage/accounts.ts +6 -0
- package/src/usage/aggregate.ts +19 -0
- package/src/usage/exchange.ts +12 -0
- package/src/usage/index-file.ts +27 -0
- package/src/usage/limits/monitor.ts +31 -3
- package/src/usage/limits/normalize.ts +30 -0
- package/src/usage/limits/probe.ts +17 -0
- package/src/usage/prices-snapshot.json +8 -0
- package/src/usage/pricing.ts +35 -0
- package/src/usage/projects.ts +8 -0
- package/src/usage/readers/claude.ts +12 -9
- package/src/usage/readers/codex.ts +40 -8
- package/src/usage/readers/grok.ts +17 -9
- package/src/usage/readers/host-log.ts +9 -9
- package/src/usage/record.ts +22 -0
- package/src/usage/roots.ts +6 -0
- package/src/usage/scanner.ts +61 -0
- package/src/usage/usage-service.ts +14 -0
- package/src/watch-seam.ts +8 -0
- package/src/windows-command.ts +12 -0
package/src/chat/acp-protocol.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isRecord, type AgentKind, type ChatFileChange, type ChatQuestion, type ChatTurnLimit, type RuntimeMode } from '@adecore/agent-contracts';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { filledStr } from '../fields.ts';
|
|
3
4
|
import { MODE_ORDER } from '../modes.ts';
|
|
4
5
|
import { cleanTitle } from '../title-file.ts';
|
|
5
6
|
import type { ApprovalDecision, BackendEvent } from './backend.ts';
|
|
@@ -113,17 +114,21 @@ export interface AcpTurnUsage {
|
|
|
113
114
|
*/
|
|
114
115
|
export function promptUsageOf(result: unknown): AcpTurnUsage | null {
|
|
115
116
|
const response = z.looseObject({ usage: PromptUsageSchema }).safeParse(result);
|
|
117
|
+
|
|
116
118
|
if (!response.success) {
|
|
117
119
|
return null;
|
|
118
120
|
}
|
|
121
|
+
|
|
119
122
|
const usage = response.data.usage;
|
|
120
123
|
const cacheRead = usage.cachedReadTokens ?? 0;
|
|
121
124
|
const cacheWrite = usage.cachedWriteTokens ?? 0;
|
|
122
125
|
const apart = usage.inputTokens + usage.outputTokens + cacheRead + cacheWrite <= usage.totalTokens;
|
|
123
126
|
const input = apart ? usage.inputTokens : Math.max(0, usage.inputTokens - cacheRead - cacheWrite);
|
|
127
|
+
|
|
124
128
|
if (input + cacheRead + cacheWrite + usage.outputTokens === 0) {
|
|
125
129
|
return null;
|
|
126
130
|
}
|
|
131
|
+
|
|
127
132
|
return { input, cacheRead, cacheWrite, output: usage.outputTokens, reasoning: usage.thoughtTokens ?? 0 };
|
|
128
133
|
}
|
|
129
134
|
|
|
@@ -156,6 +161,7 @@ const ModelEntrySchema = z.looseObject({ modelId: z.string(), name: z.string(),
|
|
|
156
161
|
/* The context window an agent names for a model in the model's `_meta` (`totalContextTokens`, not part of the protocol); null where it names none. */
|
|
157
162
|
function contextWindowOf(meta: unknown): number | null {
|
|
158
163
|
const tokens = isRecord(meta) ? meta.totalContextTokens : undefined;
|
|
164
|
+
|
|
159
165
|
return typeof tokens === 'number' && Number.isSafeInteger(tokens) && tokens > 0 ? tokens : null;
|
|
160
166
|
}
|
|
161
167
|
|
|
@@ -165,6 +171,7 @@ function contextWindowOf(meta: unknown): number | null {
|
|
|
165
171
|
*/
|
|
166
172
|
function vendorContextTokens(meta: unknown): number | null {
|
|
167
173
|
const tokens = isRecord(meta) ? meta.totalTokens : undefined;
|
|
174
|
+
|
|
168
175
|
return typeof tokens === 'number' && Number.isSafeInteger(tokens) && tokens > 0 ? tokens : null;
|
|
169
176
|
}
|
|
170
177
|
|
|
@@ -195,6 +202,7 @@ const SessionResponseSchema = z.looseObject({ sessionId: z.string().optional() }
|
|
|
195
202
|
function validItems<T>(schema: z.ZodType<T>, items: readonly unknown[] | null | undefined): T[] {
|
|
196
203
|
return (items ?? []).flatMap((item) => {
|
|
197
204
|
const parsed = schema.safeParse(item);
|
|
205
|
+
|
|
198
206
|
return parsed.success ? [parsed.data] : [];
|
|
199
207
|
});
|
|
200
208
|
}
|
|
@@ -222,12 +230,15 @@ export interface AcpAgentCapabilities {
|
|
|
222
230
|
|
|
223
231
|
export function parseInitialize(result: unknown): AcpAgentCapabilities {
|
|
224
232
|
const parsed = InitializeResponseSchema.safeParse(result);
|
|
233
|
+
|
|
225
234
|
if (!parsed.success) {
|
|
226
235
|
throw new Error('initialize failed: the agent answered with something that is not an initialize response');
|
|
227
236
|
}
|
|
237
|
+
|
|
228
238
|
const capabilities = parsed.data.agentCapabilities;
|
|
229
239
|
const sessions = capabilities?.sessionCapabilities;
|
|
230
240
|
const agent = parsed.data.agentInfo;
|
|
241
|
+
|
|
231
242
|
return {
|
|
232
243
|
protocolVersion: parsed.data.protocolVersion,
|
|
233
244
|
loadSession: capabilities?.loadSession === true,
|
|
@@ -296,6 +307,7 @@ type ConfigOption = z.infer<typeof ConfigOptionSchema>;
|
|
|
296
307
|
/* The values of a select option, grouped or not. */
|
|
297
308
|
function selectValues(option: ConfigOption): Array<z.infer<typeof SelectValueSchema>> {
|
|
298
309
|
const flat = (option.options ?? []).flatMap((entry) => (isRecord(entry) && Array.isArray(entry.options) ? entry.options : [entry]));
|
|
310
|
+
|
|
299
311
|
return validItems(SelectValueSchema, flat);
|
|
300
312
|
}
|
|
301
313
|
|
|
@@ -316,12 +328,14 @@ export function thoughtLevelOf(result: unknown): AcpThoughtLevel | null | undefi
|
|
|
316
328
|
if (!isRecord(result) || !Array.isArray(result.configOptions)) {
|
|
317
329
|
return undefined;
|
|
318
330
|
}
|
|
331
|
+
|
|
319
332
|
return sessionState(validItems(ConfigOptionSchema, result.configOptions), null, null).thoughtLevel;
|
|
320
333
|
}
|
|
321
334
|
|
|
322
335
|
export function parseSessionInfo(result: unknown, sessionId: string, agent: AcpAgentInfo | null): AcpSessionInfo {
|
|
323
336
|
const record = isRecord(result) ? result : {};
|
|
324
337
|
const options = validItems(ConfigOptionSchema, Array.isArray(record.configOptions) ? record.configOptions : []);
|
|
338
|
+
|
|
325
339
|
return { sessionId, agent, ...sessionState(options, record.modes, record.models) };
|
|
326
340
|
}
|
|
327
341
|
|
|
@@ -334,6 +348,7 @@ function sessionState(options: ConfigOption[], modes: unknown, models: unknown):
|
|
|
334
348
|
const entries = modelState.success ? validItems(ModelEntrySchema, modelState.data.availableModels) : [];
|
|
335
349
|
// Grok CLI 1.0.62 offers its models as a config option too, but names their windows only in the older field.
|
|
336
350
|
const windows = new Map(entries.map((model) => [model.modelId, contextWindowOf(model._meta)]));
|
|
351
|
+
|
|
337
352
|
return {
|
|
338
353
|
models: modelOption
|
|
339
354
|
? {
|
|
@@ -388,10 +403,13 @@ function commandNames(commands: unknown): string[] {
|
|
|
388
403
|
/* The session a `session/update` notification is for, and the commands it lists when it is an `available_commands_update`; null when it does not parse. */
|
|
389
404
|
export function sessionUpdateOf(params: unknown): { sessionId: string; commands: string[] | null } | null {
|
|
390
405
|
const notification = SessionNotificationSchema.safeParse(params);
|
|
406
|
+
|
|
391
407
|
if (!notification.success) {
|
|
392
408
|
return null;
|
|
393
409
|
}
|
|
410
|
+
|
|
394
411
|
const update = notification.data.update;
|
|
412
|
+
|
|
395
413
|
return {
|
|
396
414
|
sessionId: notification.data.sessionId,
|
|
397
415
|
commands: update.sessionUpdate === 'available_commands_update' ? commandNames(update.availableCommands) : null
|
|
@@ -400,6 +418,7 @@ export function sessionUpdateOf(params: unknown): { sessionId: string; commands:
|
|
|
400
418
|
|
|
401
419
|
export function sessionIdOf(result: unknown): string | null {
|
|
402
420
|
const parsed = SessionResponseSchema.safeParse(result);
|
|
421
|
+
|
|
403
422
|
return parsed.success ? (parsed.data.sessionId ?? null) : null;
|
|
404
423
|
}
|
|
405
424
|
|
|
@@ -414,10 +433,12 @@ export function resolveAcpMode(requested: RuntimeMode, map: AcpModeMap, offered:
|
|
|
414
433
|
for (let index = MODE_ORDER.indexOf(requested); index >= 0; index--) {
|
|
415
434
|
const runtimeMode = MODE_ORDER[index]!;
|
|
416
435
|
const modeId = (map[runtimeMode] ?? []).find((candidate) => offered.includes(candidate));
|
|
436
|
+
|
|
417
437
|
if (modeId !== undefined) {
|
|
418
438
|
return { modeId, runtimeMode };
|
|
419
439
|
}
|
|
420
440
|
}
|
|
441
|
+
|
|
421
442
|
return null;
|
|
422
443
|
}
|
|
423
444
|
|
|
@@ -425,10 +446,12 @@ export function resolveAcpMode(requested: RuntimeMode, map: AcpModeMap, offered:
|
|
|
425
446
|
export function runtimeModeOf(modeId: string, map: AcpModeMap): RuntimeMode | null {
|
|
426
447
|
for (let index = MODE_ORDER.length - 1; index >= 0; index--) {
|
|
427
448
|
const runtimeMode = MODE_ORDER[index]!;
|
|
449
|
+
|
|
428
450
|
if ((map[runtimeMode] ?? []).includes(modeId)) {
|
|
429
451
|
return runtimeMode;
|
|
430
452
|
}
|
|
431
453
|
}
|
|
454
|
+
|
|
432
455
|
return null;
|
|
433
456
|
}
|
|
434
457
|
|
|
@@ -442,6 +465,7 @@ const AUTO_ALLOWED: Record<RuntimeMode, ReadonlySet<string> | 'all'> = {
|
|
|
442
465
|
|
|
443
466
|
export function autoAllows(mode: RuntimeMode, kind: string | null): boolean {
|
|
444
467
|
const allowed = AUTO_ALLOWED[mode];
|
|
468
|
+
|
|
445
469
|
return allowed === 'all' || (kind !== null && allowed.has(kind));
|
|
446
470
|
}
|
|
447
471
|
|
|
@@ -449,27 +473,37 @@ export function autoAllows(mode: RuntimeMode, kind: string | null): boolean {
|
|
|
449
473
|
export function unifiedDiff(oldText: string, newText: string): string | null {
|
|
450
474
|
const lines = (text: string): string[] => {
|
|
451
475
|
const split = text === '' ? [] : text.split('\n');
|
|
476
|
+
|
|
452
477
|
if (split.at(-1) === '') {
|
|
453
478
|
split.pop();
|
|
454
479
|
}
|
|
480
|
+
|
|
455
481
|
return split;
|
|
456
482
|
};
|
|
483
|
+
|
|
457
484
|
const before = lines(oldText);
|
|
458
485
|
const after = lines(newText);
|
|
459
486
|
let prefix = 0;
|
|
487
|
+
|
|
460
488
|
while (prefix < before.length && prefix < after.length && before[prefix] === after[prefix]) {
|
|
461
489
|
prefix++;
|
|
462
490
|
}
|
|
491
|
+
|
|
463
492
|
let suffix = 0;
|
|
493
|
+
|
|
464
494
|
while (suffix < before.length - prefix && suffix < after.length - prefix && before[before.length - 1 - suffix] === after[after.length - 1 - suffix]) {
|
|
465
495
|
suffix++;
|
|
466
496
|
}
|
|
497
|
+
|
|
467
498
|
const removed = before.slice(prefix, before.length - suffix);
|
|
468
499
|
const added = after.slice(prefix, after.length - suffix);
|
|
500
|
+
|
|
469
501
|
if (removed.length === 0 && added.length === 0) {
|
|
470
502
|
return null;
|
|
471
503
|
}
|
|
504
|
+
|
|
472
505
|
const range = (count: number): string => `${count === 0 ? prefix : prefix + 1},${count}`;
|
|
506
|
+
|
|
473
507
|
return (
|
|
474
508
|
[`@@ -${range(removed.length)} +${range(added.length)} @@`, ...removed.map((line) => `-${line}`), ...added.map((line) => `+${line}`)].join('\n') + '\n'
|
|
475
509
|
);
|
|
@@ -525,27 +559,27 @@ function foldSpace(text: string): string {
|
|
|
525
559
|
return text.replace(/\s+/g, ' ').trim();
|
|
526
560
|
}
|
|
527
561
|
|
|
528
|
-
function str(value: unknown): string | null {
|
|
529
|
-
return typeof value === 'string' && value !== '' ? value : null;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/* A field of the agent's own input, which no schema promises; the first one that is a non-empty string. */
|
|
533
562
|
// Where agents put the command a call runs; Antigravity names it `CommandLine`.
|
|
534
563
|
const COMMAND_KEYS = ['command', 'cmd', 'CommandLine'];
|
|
535
564
|
|
|
565
|
+
/* A field of the agent's own input, which no schema promises; the first one that is a non-empty string. */
|
|
536
566
|
function rawField(raw: unknown, ...keys: string[]): string | null {
|
|
537
567
|
if (!isRecord(raw)) {
|
|
538
568
|
return null;
|
|
539
569
|
}
|
|
570
|
+
|
|
540
571
|
for (const key of keys) {
|
|
541
572
|
const value = raw[key];
|
|
573
|
+
|
|
542
574
|
if (Array.isArray(value) && value.every((part) => typeof part === 'string') && value.length > 0) {
|
|
543
575
|
return value.join(' ');
|
|
544
576
|
}
|
|
545
|
-
|
|
577
|
+
|
|
578
|
+
if (filledStr(value) !== null) {
|
|
546
579
|
return value as string;
|
|
547
580
|
}
|
|
548
581
|
}
|
|
582
|
+
|
|
549
583
|
return null;
|
|
550
584
|
}
|
|
551
585
|
|
|
@@ -557,28 +591,38 @@ export function toolRow(tool: Pick<ToolState, 'title' | 'name' | 'kind' | 'rawIn
|
|
|
557
591
|
} {
|
|
558
592
|
const description = tool.title;
|
|
559
593
|
const path = tool.locations[0] ?? rawField(tool.rawInput, 'file_path', 'path', 'filePath') ?? null;
|
|
594
|
+
|
|
560
595
|
if (tool.diffs.length > 0) {
|
|
561
596
|
const paths = new Set(tool.diffs.map((diff) => diff.path));
|
|
597
|
+
|
|
562
598
|
if (paths.size === 1) {
|
|
563
599
|
const [first] = tool.diffs;
|
|
600
|
+
|
|
564
601
|
if (tool.diffs.length === 1 && (first!.oldText ?? null) === null) {
|
|
565
602
|
return { name: 'Write', input: { file_path: first!.path, content: first!.newText, description } };
|
|
566
603
|
}
|
|
604
|
+
|
|
567
605
|
if (tool.diffs.length === 1) {
|
|
568
606
|
return { name: 'Edit', input: { file_path: first!.path, old_string: first!.oldText, new_string: first!.newText, description } };
|
|
569
607
|
}
|
|
608
|
+
|
|
570
609
|
return {
|
|
571
610
|
name: 'MultiEdit',
|
|
572
611
|
input: { file_path: first!.path, edits: tool.diffs.map((diff) => ({ old_string: diff.oldText ?? '', new_string: diff.newText })), description }
|
|
573
612
|
};
|
|
574
613
|
}
|
|
614
|
+
|
|
575
615
|
const changes = tool.diffs.flatMap((diff): ChatFileChange[] => {
|
|
576
616
|
const text = unifiedDiff(diff.oldText ?? '', diff.newText);
|
|
617
|
+
|
|
577
618
|
return text === null ? [] : [{ path: diff.path, kind: (diff.oldText ?? null) === null ? 'add' : 'update', diff: text }];
|
|
578
619
|
});
|
|
620
|
+
|
|
579
621
|
return { name: 'ApplyPatch', input: { summary: [...paths].join(', '), description }, changes };
|
|
580
622
|
}
|
|
623
|
+
|
|
581
624
|
const raw = isRecord(tool.rawInput) ? tool.rawInput : {};
|
|
625
|
+
|
|
582
626
|
switch (tool.kind) {
|
|
583
627
|
case 'execute':
|
|
584
628
|
return { name: 'Bash', input: { ...raw, description, command: rawField(tool.rawInput, ...COMMAND_KEYS) ?? description } };
|
|
@@ -588,6 +632,7 @@ export function toolRow(tool: Pick<ToolState, 'title' | 'name' | 'kind' | 'rawIn
|
|
|
588
632
|
return { name: 'Grep', input: { ...raw, description, pattern: rawField(tool.rawInput, 'pattern', 'query', 'regex') ?? description } };
|
|
589
633
|
case 'fetch': {
|
|
590
634
|
const url = rawField(tool.rawInput, 'url', 'uri');
|
|
635
|
+
|
|
591
636
|
return url === null
|
|
592
637
|
? { name: 'WebSearch', input: { ...raw, description, query: rawField(tool.rawInput, 'query') ?? description } }
|
|
593
638
|
: { name: 'WebFetch', input: { ...raw, description, url } };
|
|
@@ -604,13 +649,17 @@ function toolOutput(tool: ToolState): string | null {
|
|
|
604
649
|
if (tool.text.length > 0) {
|
|
605
650
|
return tool.text.join('\n');
|
|
606
651
|
}
|
|
652
|
+
|
|
607
653
|
if (typeof tool.rawOutput === 'string') {
|
|
608
654
|
return tool.rawOutput;
|
|
609
655
|
}
|
|
656
|
+
|
|
610
657
|
if (isRecord(tool.rawOutput)) {
|
|
611
658
|
const text = rawField(tool.rawOutput, 'output', 'stdout', 'content', 'result', 'error', 'message');
|
|
659
|
+
|
|
612
660
|
return text ?? JSON.stringify(tool.rawOutput);
|
|
613
661
|
}
|
|
662
|
+
|
|
614
663
|
return null;
|
|
615
664
|
}
|
|
616
665
|
|
|
@@ -673,14 +722,18 @@ const OVERLOADED_TYPES = /\b(overloaded_error|service_unavailable_error)\b/;
|
|
|
673
722
|
*/
|
|
674
723
|
export function acpErrorLimit(code: number | null, data: unknown, kind: AgentKind | null): ChatTurnLimit | null {
|
|
675
724
|
const text = typeof data === 'string' ? data : isRecord(data) && typeof data.message === 'string' ? data.message : '';
|
|
725
|
+
|
|
676
726
|
if (code === GROK_RATE_LIMITED && kind === 'grok') {
|
|
677
727
|
return text.includes(GROK_FREE_USAGE) ? { kind: 'usage' } : { kind: 'overload' };
|
|
678
728
|
}
|
|
729
|
+
|
|
679
730
|
if (code !== -32603) {
|
|
680
731
|
return null;
|
|
681
732
|
}
|
|
733
|
+
|
|
682
734
|
const status = isRecord(data) && typeof data.http_status === 'number' ? data.http_status : null;
|
|
683
735
|
const overloaded = status === 529 || OVERLOADED_TYPES.test(text) || (status !== null && status >= 500 && /overloaded/i.test(text));
|
|
736
|
+
|
|
684
737
|
return overloaded ? { kind: 'overload' } : null;
|
|
685
738
|
}
|
|
686
739
|
|
|
@@ -699,25 +752,33 @@ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', '
|
|
|
699
752
|
*/
|
|
700
753
|
export function quotaResetOf(text: string, now: number): number | null {
|
|
701
754
|
const relative = /will reset in ([^.]+)\./.exec(text);
|
|
755
|
+
|
|
702
756
|
if (relative) {
|
|
703
757
|
let total = 0;
|
|
704
758
|
let smallest = Infinity;
|
|
759
|
+
|
|
705
760
|
for (const part of relative[1]!.split(',')) {
|
|
706
761
|
const amount = /^(\d+) (second|minute|hour|day)s?$/.exec(part.trim());
|
|
762
|
+
|
|
707
763
|
if (!amount) {
|
|
708
764
|
return null;
|
|
709
765
|
}
|
|
766
|
+
|
|
710
767
|
const unit = UNIT_MS[amount[2]!]!;
|
|
711
768
|
total += Number(amount[1]) * unit;
|
|
712
769
|
smallest = Math.min(smallest, unit);
|
|
713
770
|
}
|
|
771
|
+
|
|
714
772
|
return now + total + smallest;
|
|
715
773
|
}
|
|
774
|
+
|
|
716
775
|
const absolute = /will reset on ([A-Z][a-z]{2}) (\d{1,2}), (\d{4}) (\d{2}):(\d{2}) UTC\./.exec(text);
|
|
717
776
|
const month = absolute ? MONTHS.indexOf(absolute[1]!) : -1;
|
|
777
|
+
|
|
718
778
|
if (!absolute || month === -1) {
|
|
719
779
|
return null;
|
|
720
780
|
}
|
|
781
|
+
|
|
721
782
|
return Date.UTC(Number(absolute[3]), month, Number(absolute[2]), Number(absolute[4]), Number(absolute[5])) + UNIT_MS.minute!;
|
|
722
783
|
}
|
|
723
784
|
|
|
@@ -726,19 +787,24 @@ function pickedLabels(answer: string, labels: readonly string[], multiSelect: bo
|
|
|
726
787
|
if (labels.includes(answer)) {
|
|
727
788
|
return [answer];
|
|
728
789
|
}
|
|
790
|
+
|
|
729
791
|
if (!multiSelect) {
|
|
730
792
|
return null;
|
|
731
793
|
}
|
|
794
|
+
|
|
732
795
|
// A label may hold `, ` itself, so the pieces join up again until they make one.
|
|
733
796
|
const picked: string[] = [];
|
|
734
797
|
let part = '';
|
|
798
|
+
|
|
735
799
|
for (const piece of answer.split(', ')) {
|
|
736
800
|
part = part === '' ? piece : `${part}, ${piece}`;
|
|
801
|
+
|
|
737
802
|
if (labels.includes(part)) {
|
|
738
803
|
picked.push(part);
|
|
739
804
|
part = '';
|
|
740
805
|
}
|
|
741
806
|
}
|
|
807
|
+
|
|
742
808
|
return part === '' && picked.length > 0 ? picked : null;
|
|
743
809
|
}
|
|
744
810
|
|
|
@@ -792,9 +858,11 @@ export class AcpProtocol {
|
|
|
792
858
|
/* One `session/update` notification; empty for another session, an unknown update or one that does not parse. */
|
|
793
859
|
update(sessionId: string, params: unknown): BackendEvent[] {
|
|
794
860
|
const notification = SessionNotificationSchema.safeParse(params);
|
|
861
|
+
|
|
795
862
|
if (!notification.success || notification.data.sessionId !== sessionId) {
|
|
796
863
|
return [];
|
|
797
864
|
}
|
|
865
|
+
|
|
798
866
|
return [...this.mapUpdate(notification.data.update), ...this.vendorUsage(notification.data._meta)];
|
|
799
867
|
}
|
|
800
868
|
|
|
@@ -807,6 +875,7 @@ export class AcpProtocol {
|
|
|
807
875
|
case 'tool_call':
|
|
808
876
|
case 'tool_call_update': {
|
|
809
877
|
const call = ToolCallSchema.safeParse(update);
|
|
878
|
+
|
|
810
879
|
return call.success ? [...this.closeText(), ...this.closeThought(), ...this.toolCall(call.data)] : [];
|
|
811
880
|
}
|
|
812
881
|
case 'plan':
|
|
@@ -818,6 +887,7 @@ export class AcpProtocol {
|
|
|
818
887
|
case 'config_option_update': {
|
|
819
888
|
const options = validItems(ConfigOptionSchema, Array.isArray(update.configOptions) ? update.configOptions : []);
|
|
820
889
|
const state = sessionState(options, null, null);
|
|
890
|
+
|
|
821
891
|
return [
|
|
822
892
|
...(state.models === null ? [] : [{ type: 'model', model: state.models.current } as const]),
|
|
823
893
|
...(state.modes === null ? [] : [this.modeEvent(state.modes.current)])
|
|
@@ -825,21 +895,27 @@ export class AcpProtocol {
|
|
|
825
895
|
}
|
|
826
896
|
case 'usage_update': {
|
|
827
897
|
const usage = UsageSchema.safeParse(update);
|
|
898
|
+
|
|
828
899
|
if (!usage.success) {
|
|
829
900
|
return [];
|
|
830
901
|
}
|
|
902
|
+
|
|
831
903
|
this.standardUsage = true;
|
|
832
904
|
const cost = usage.data.cost?.currency === 'USD' ? usage.data.cost.amount : undefined;
|
|
905
|
+
|
|
833
906
|
if (cost !== undefined) {
|
|
834
907
|
this.costUsd = cost;
|
|
835
908
|
}
|
|
909
|
+
|
|
836
910
|
return [{ type: 'usage', contextTokens: usage.data.used, contextWindow: usage.data.size, ...(cost === undefined ? {} : { costUsd: cost }) }];
|
|
837
911
|
}
|
|
838
912
|
case 'session_info_update': {
|
|
839
913
|
if (!this.titles) {
|
|
840
914
|
return [];
|
|
841
915
|
}
|
|
916
|
+
|
|
842
917
|
const title = cleanTitle(update.title);
|
|
918
|
+
|
|
843
919
|
return title === null || this.restatesPrompt(title) ? [] : [{ type: 'title', title }];
|
|
844
920
|
}
|
|
845
921
|
default:
|
|
@@ -850,10 +926,13 @@ export class AcpProtocol {
|
|
|
850
926
|
/* The fallback for an agent that sends no `usage_update`: its own count of the context, once each time it changes. */
|
|
851
927
|
private vendorUsage(meta: unknown): BackendEvent[] {
|
|
852
928
|
const tokens = this.standardUsage ? null : vendorContextTokens(meta);
|
|
929
|
+
|
|
853
930
|
if (tokens === null || tokens === this.vendorTokens) {
|
|
854
931
|
return [];
|
|
855
932
|
}
|
|
933
|
+
|
|
856
934
|
this.vendorTokens = tokens;
|
|
935
|
+
|
|
857
936
|
return [{ type: 'usage', contextTokens: tokens }];
|
|
858
937
|
}
|
|
859
938
|
|
|
@@ -870,26 +949,31 @@ export class AcpProtocol {
|
|
|
870
949
|
*/
|
|
871
950
|
private restatesPrompt(title: string): boolean {
|
|
872
951
|
const start = foldSpace(title.endsWith('…') ? title.slice(0, -1) : title);
|
|
952
|
+
|
|
873
953
|
return start !== '' && this.prompts.some((prompt) => prompt.startsWith(start));
|
|
874
954
|
}
|
|
875
955
|
|
|
876
956
|
modeEvent(modeId: string): BackendEvent {
|
|
877
957
|
const runtimeMode = runtimeModeOf(modeId, this.modes);
|
|
958
|
+
|
|
878
959
|
return { type: 'permissions', permissionMode: modeId, ...(runtimeMode === null ? {} : { effectiveRuntimeMode: runtimeMode }) };
|
|
879
960
|
}
|
|
880
961
|
|
|
881
962
|
/* A `session/request_permission` request; null when it does not parse, which the caller answers with an error. */
|
|
882
963
|
permission(rpcId: number | string, sessionId: string, params: unknown): AcpPermission | null {
|
|
883
964
|
const request = PermissionRequestSchema.safeParse(params);
|
|
965
|
+
|
|
884
966
|
if (!request.success || request.data.sessionId !== sessionId) {
|
|
885
967
|
return null;
|
|
886
968
|
}
|
|
969
|
+
|
|
887
970
|
const requestId = `acp-${this.generation}-${String(rpcId)}`;
|
|
888
971
|
const events = [...this.closeText(), ...this.closeThought(), ...this.toolCall(request.data.toolCall, false)];
|
|
889
972
|
const tool = this.tools.get(request.data.toolCall.toolCallId)!;
|
|
890
973
|
const row = toolRow(tool);
|
|
891
974
|
const options = request.data.options;
|
|
892
975
|
this.permissions.set(requestId, { rpcId, toolCallId: request.data.toolCall.toolCallId, options });
|
|
976
|
+
|
|
893
977
|
events.push({
|
|
894
978
|
type: 'approval.requested',
|
|
895
979
|
requestId,
|
|
@@ -899,8 +983,10 @@ export class AcpProtocol {
|
|
|
899
983
|
description: tool.title === '' ? null : tool.title,
|
|
900
984
|
canAllowAlways: options.some((option) => option.kind === 'allow_always')
|
|
901
985
|
});
|
|
986
|
+
|
|
902
987
|
const command = rawField(tool.rawInput, ...COMMAND_KEYS);
|
|
903
988
|
const asked = { kind: tool.kind, title: tool.title === '' ? null : tool.title, commands: [...(command === null ? [] : [command]), ...tool.text] };
|
|
989
|
+
|
|
904
990
|
return { requestId, rpcId, kind: tool.kind, request: asked, events };
|
|
905
991
|
}
|
|
906
992
|
|
|
@@ -911,41 +997,53 @@ export class AcpProtocol {
|
|
|
911
997
|
*/
|
|
912
998
|
answer(requestId: string, decision: ApprovalDecision): { rpcId: number | string; result: unknown; events: BackendEvent[] } | null {
|
|
913
999
|
const pending = this.permissions.get(requestId);
|
|
1000
|
+
|
|
914
1001
|
if (!pending) {
|
|
915
1002
|
return null;
|
|
916
1003
|
}
|
|
1004
|
+
|
|
917
1005
|
this.permissions.delete(requestId);
|
|
1006
|
+
|
|
918
1007
|
const byKind = (...kinds: string[]): string | null => {
|
|
919
1008
|
for (const kind of kinds) {
|
|
920
1009
|
const option = pending.options.find((candidate) => candidate.kind === kind);
|
|
1010
|
+
|
|
921
1011
|
if (option) {
|
|
922
1012
|
return option.optionId;
|
|
923
1013
|
}
|
|
924
1014
|
}
|
|
1015
|
+
|
|
925
1016
|
return null;
|
|
926
1017
|
};
|
|
1018
|
+
|
|
927
1019
|
const optionId =
|
|
928
1020
|
decision === 'allow-always'
|
|
929
1021
|
? byKind('allow_always', 'allow_once')
|
|
930
1022
|
: decision === 'allow'
|
|
931
1023
|
? byKind('allow_once', 'allow_always')
|
|
932
1024
|
: byKind('reject_once', 'reject_always');
|
|
1025
|
+
|
|
933
1026
|
const result = { outcome: optionId === null ? { outcome: 'cancelled' } : { outcome: 'selected', optionId } };
|
|
934
1027
|
const tool = this.tools.get(pending.toolCallId);
|
|
1028
|
+
|
|
935
1029
|
if (decision !== 'deny' || tool === undefined) {
|
|
936
1030
|
return { rpcId: pending.rpcId, result, events: [] };
|
|
937
1031
|
}
|
|
1032
|
+
|
|
938
1033
|
tool.refused = true;
|
|
939
1034
|
this.refusalLine = true;
|
|
1035
|
+
|
|
940
1036
|
return { rpcId: pending.rpcId, result, events: this.toolCall({ toolCallId: pending.toolCallId }, false) };
|
|
941
1037
|
}
|
|
942
1038
|
|
|
943
1039
|
/* A Grok `ask_user_question` request; null when it does not parse or asks nothing, which the caller answers with an error. */
|
|
944
1040
|
question(rpcId: number | string, sessionId: string, params: unknown): AcpQuestion | null {
|
|
945
1041
|
const request = GrokQuestionRequestSchema.safeParse(params);
|
|
1042
|
+
|
|
946
1043
|
if (!request.success || request.data.sessionId !== sessionId) {
|
|
947
1044
|
return null;
|
|
948
1045
|
}
|
|
1046
|
+
|
|
949
1047
|
const questions = validItems(GrokQuestionSchema, request.data.questions).map((question, index): ChatQuestion => ({
|
|
950
1048
|
id: String(index),
|
|
951
1049
|
header: question.header ?? '',
|
|
@@ -953,9 +1051,11 @@ export class AcpProtocol {
|
|
|
953
1051
|
choices: validItems(GrokChoiceSchema, question.options).map((choice) => ({ label: choice.label, description: choice.description ?? '' })),
|
|
954
1052
|
multiSelect: question.multiSelect === true
|
|
955
1053
|
}));
|
|
1054
|
+
|
|
956
1055
|
if (questions.length === 0) {
|
|
957
1056
|
return null;
|
|
958
1057
|
}
|
|
1058
|
+
|
|
959
1059
|
return this.ask(rpcId, { shape: 'grok', rpcId, questions });
|
|
960
1060
|
}
|
|
961
1061
|
|
|
@@ -965,15 +1065,19 @@ export class AcpProtocol {
|
|
|
965
1065
|
*/
|
|
966
1066
|
interaction(rpcId: number | string, sessionId: string, params: unknown): AcpQuestion | null {
|
|
967
1067
|
const request = PermissionRequestSchema.safeParse(params);
|
|
1068
|
+
|
|
968
1069
|
if (!request.success || request.data.sessionId !== sessionId) {
|
|
969
1070
|
return null;
|
|
970
1071
|
}
|
|
1072
|
+
|
|
971
1073
|
const call = request.data.toolCall;
|
|
972
1074
|
const raw = call.rawInput;
|
|
973
1075
|
const bare = raw === undefined || raw === null || (isRecord(raw) && Object.keys(raw).length === 0);
|
|
1076
|
+
|
|
974
1077
|
if (!call.toolCallId.startsWith(INTERACTION_PREFIX) || !bare || call.kind || request.data.options.length === 0) {
|
|
975
1078
|
return null;
|
|
976
1079
|
}
|
|
1080
|
+
|
|
977
1081
|
const question: ChatQuestion = {
|
|
978
1082
|
id: '0',
|
|
979
1083
|
header: '',
|
|
@@ -981,6 +1085,7 @@ export class AcpProtocol {
|
|
|
981
1085
|
choices: request.data.options.map((option) => ({ label: option.name, description: '' })),
|
|
982
1086
|
multiSelect: false
|
|
983
1087
|
};
|
|
1088
|
+
|
|
984
1089
|
return this.ask(rpcId, { shape: 'interaction', rpcId, question, options: request.data.options });
|
|
985
1090
|
}
|
|
986
1091
|
|
|
@@ -988,19 +1093,24 @@ export class AcpProtocol {
|
|
|
988
1093
|
const requestId = `acp-${this.generation}-${String(rpcId)}`;
|
|
989
1094
|
this.questions.set(requestId, pending);
|
|
990
1095
|
const questions = pending.shape === 'grok' ? pending.questions : [pending.question];
|
|
1096
|
+
|
|
991
1097
|
return { requestId, rpcId, events: [...this.closeText(), ...this.closeThought(), { type: 'question.requested', requestId, questions }] };
|
|
992
1098
|
}
|
|
993
1099
|
|
|
994
1100
|
/* The answer to a question in the agent's own shape, from the card's answers keyed by question id. */
|
|
995
1101
|
answerQuestion(requestId: string, answers: Record<string, string>): { rpcId: number | string; result: unknown; events: BackendEvent[] } | null {
|
|
996
1102
|
const pending = this.questions.get(requestId);
|
|
1103
|
+
|
|
997
1104
|
if (!pending) {
|
|
998
1105
|
return null;
|
|
999
1106
|
}
|
|
1107
|
+
|
|
1000
1108
|
this.questions.delete(requestId);
|
|
1109
|
+
|
|
1001
1110
|
if (pending.shape === 'interaction') {
|
|
1002
1111
|
const given = (answers[pending.question.id] ?? '').trim();
|
|
1003
1112
|
const option = pending.options.find((candidate) => candidate.name === given) ?? pending.options.find((candidate) => candidate.optionId === given);
|
|
1113
|
+
|
|
1004
1114
|
if (option === undefined) {
|
|
1005
1115
|
return {
|
|
1006
1116
|
rpcId: pending.rpcId,
|
|
@@ -1008,25 +1118,33 @@ export class AcpProtocol {
|
|
|
1008
1118
|
events: [{ type: 'note', level: 'warning', text: this.words.ownAnswer }]
|
|
1009
1119
|
};
|
|
1010
1120
|
}
|
|
1121
|
+
|
|
1011
1122
|
return { rpcId: pending.rpcId, result: { outcome: { outcome: 'selected', optionId: option.optionId } }, events: [] };
|
|
1012
1123
|
}
|
|
1124
|
+
|
|
1013
1125
|
const picked: Record<string, string[]> = {};
|
|
1014
1126
|
const notes: Record<string, { notes: string }> = {};
|
|
1127
|
+
|
|
1015
1128
|
for (const question of pending.questions) {
|
|
1016
1129
|
const given = answers[question.id];
|
|
1130
|
+
|
|
1017
1131
|
if (given === undefined) {
|
|
1018
1132
|
continue;
|
|
1019
1133
|
}
|
|
1134
|
+
|
|
1020
1135
|
const labels = pickedLabels(
|
|
1021
1136
|
given,
|
|
1022
1137
|
question.choices.map((choice) => choice.label),
|
|
1023
1138
|
question.multiSelect
|
|
1024
1139
|
);
|
|
1140
|
+
|
|
1025
1141
|
picked[question.question] = labels ?? [GROK_OWN_ANSWER];
|
|
1142
|
+
|
|
1026
1143
|
if (labels === null) {
|
|
1027
1144
|
notes[question.question] = { notes: given };
|
|
1028
1145
|
}
|
|
1029
1146
|
}
|
|
1147
|
+
|
|
1030
1148
|
return {
|
|
1031
1149
|
rpcId: pending.rpcId,
|
|
1032
1150
|
result: { outcome: 'accepted', answers: picked, ...(Object.keys(notes).length === 0 ? {} : { annotations: notes }) },
|
|
@@ -1037,10 +1155,13 @@ export class AcpProtocol {
|
|
|
1037
1155
|
/* Turns one question down, in the shape its agent takes a refusal in. */
|
|
1038
1156
|
cancelQuestion(requestId: string): { rpcId: number | string; result: unknown } | null {
|
|
1039
1157
|
const pending = this.questions.get(requestId);
|
|
1158
|
+
|
|
1040
1159
|
if (!pending) {
|
|
1041
1160
|
return null;
|
|
1042
1161
|
}
|
|
1162
|
+
|
|
1043
1163
|
this.questions.delete(requestId);
|
|
1164
|
+
|
|
1044
1165
|
return { rpcId: pending.rpcId, result: cancelledAnswer(pending) };
|
|
1045
1166
|
}
|
|
1046
1167
|
|
|
@@ -1054,8 +1175,10 @@ export class AcpProtocol {
|
|
|
1054
1175
|
})),
|
|
1055
1176
|
...[...this.questions].map(([requestId, pending]) => ({ requestId, rpcId: pending.rpcId, result: cancelledAnswer(pending) }))
|
|
1056
1177
|
];
|
|
1178
|
+
|
|
1057
1179
|
this.permissions.clear();
|
|
1058
1180
|
this.questions.clear();
|
|
1181
|
+
|
|
1059
1182
|
return cancelled;
|
|
1060
1183
|
}
|
|
1061
1184
|
|
|
@@ -1081,29 +1204,38 @@ export class AcpProtocol {
|
|
|
1081
1204
|
const base = { ...(promptId === undefined ? {} : { promptId }), costUsd: this.costUsd };
|
|
1082
1205
|
const quota = this.quota;
|
|
1083
1206
|
this.quota = null;
|
|
1207
|
+
|
|
1084
1208
|
if (quota !== null && reason !== 'cancelled') {
|
|
1085
1209
|
events.push({ type: 'turn.done', ...base, state: 'error', error: quota.text, limit: quotaLimit(quota) });
|
|
1210
|
+
|
|
1086
1211
|
return events;
|
|
1087
1212
|
}
|
|
1213
|
+
|
|
1088
1214
|
if (quota !== null) {
|
|
1089
1215
|
events.push({ type: 'text.done', ref: quota.ref, text: quota.text });
|
|
1090
1216
|
}
|
|
1217
|
+
|
|
1091
1218
|
switch (reason) {
|
|
1092
1219
|
case 'cancelled':
|
|
1093
1220
|
events.push({ type: 'turn.done', ...base, state: 'aborted' });
|
|
1221
|
+
|
|
1094
1222
|
break;
|
|
1095
1223
|
case 'refusal':
|
|
1096
1224
|
events.push({ type: 'turn.done', ...base, state: 'error', error: this.words.refusal });
|
|
1225
|
+
|
|
1097
1226
|
break;
|
|
1098
1227
|
case 'max_tokens':
|
|
1099
1228
|
events.push({ type: 'note', level: 'warning', text: this.words.maxTokens }, { type: 'turn.done', ...base, state: 'done' });
|
|
1229
|
+
|
|
1100
1230
|
break;
|
|
1101
1231
|
case 'max_turn_requests':
|
|
1102
1232
|
events.push({ type: 'note', level: 'warning', text: this.words.maxTurnRequests }, { type: 'turn.done', ...base, state: 'done' });
|
|
1233
|
+
|
|
1103
1234
|
break;
|
|
1104
1235
|
default:
|
|
1105
1236
|
events.push({ type: 'turn.done', ...base, state: 'done' });
|
|
1106
1237
|
}
|
|
1238
|
+
|
|
1107
1239
|
return events;
|
|
1108
1240
|
}
|
|
1109
1241
|
|
|
@@ -1114,81 +1246,103 @@ export class AcpProtocol {
|
|
|
1114
1246
|
const events = [...this.closeText(), ...this.closeThought()];
|
|
1115
1247
|
const quota = this.quota;
|
|
1116
1248
|
this.quota = null;
|
|
1249
|
+
|
|
1117
1250
|
if (quota !== null) {
|
|
1118
1251
|
events.push({ type: 'text.done', ref: quota.ref, text: quota.text });
|
|
1119
1252
|
}
|
|
1253
|
+
|
|
1120
1254
|
// A failed request after the limit message still ran into that limit, so it pauses as one when the error names none.
|
|
1121
1255
|
const named = limit ?? (quota === null ? null : quotaLimit(quota));
|
|
1256
|
+
|
|
1122
1257
|
events.push(
|
|
1123
1258
|
error === null
|
|
1124
1259
|
? { type: 'turn.done', ...base, state: 'aborted' }
|
|
1125
1260
|
: { type: 'turn.done', ...base, state: 'error', error, ...(named === null ? {} : { limit: named }) }
|
|
1126
1261
|
);
|
|
1262
|
+
|
|
1127
1263
|
return events;
|
|
1128
1264
|
}
|
|
1129
1265
|
|
|
1130
1266
|
private messageChunk(update: Record<string, unknown>): BackendEvent[] {
|
|
1131
1267
|
const chunk = ContentChunkSchema.safeParse(update);
|
|
1268
|
+
|
|
1132
1269
|
if (!chunk.success || chunk.data.content.type !== 'text' || chunk.data.content.text === undefined) {
|
|
1133
1270
|
return [];
|
|
1134
1271
|
}
|
|
1272
|
+
|
|
1135
1273
|
const messageId = chunk.data.messageId ?? null;
|
|
1136
1274
|
const events = this.closeThought();
|
|
1275
|
+
|
|
1137
1276
|
if (this.text !== null && messageId !== null && this.text.messageId !== messageId) {
|
|
1138
1277
|
events.push(...this.closeText());
|
|
1139
1278
|
}
|
|
1279
|
+
|
|
1140
1280
|
if (this.text === null) {
|
|
1141
1281
|
const ref = messageId === null ? `message-${++this.counter}` : `message-${messageId}`;
|
|
1142
1282
|
this.text = { ref, messageId, text: '', held: true, afterRefusal: this.refusalLine };
|
|
1143
1283
|
this.refusalLine = false;
|
|
1144
1284
|
}
|
|
1285
|
+
|
|
1145
1286
|
const text = this.text;
|
|
1146
1287
|
text.text += chunk.data.content.text;
|
|
1288
|
+
|
|
1147
1289
|
if (text.held && !withheld(text.text, text.afterRefusal)) {
|
|
1148
1290
|
text.held = false;
|
|
1149
1291
|
events.push({ type: 'text.delta', ref: text.ref, text: text.text });
|
|
1150
1292
|
} else if (!text.held) {
|
|
1151
1293
|
events.push({ type: 'text.delta', ref: text.ref, text: chunk.data.content.text });
|
|
1152
1294
|
}
|
|
1295
|
+
|
|
1153
1296
|
return events;
|
|
1154
1297
|
}
|
|
1155
1298
|
|
|
1156
1299
|
private thoughtChunk(update: Record<string, unknown>): BackendEvent[] {
|
|
1157
1300
|
const chunk = ContentChunkSchema.safeParse(update);
|
|
1301
|
+
|
|
1158
1302
|
if (!chunk.success || chunk.data.content.type !== 'text' || chunk.data.content.text === undefined) {
|
|
1159
1303
|
return [];
|
|
1160
1304
|
}
|
|
1305
|
+
|
|
1161
1306
|
const events = this.closeText();
|
|
1307
|
+
|
|
1162
1308
|
if (this.thought === null) {
|
|
1163
1309
|
this.thought = { ref: `thought-${++this.counter}`, text: '' };
|
|
1164
1310
|
}
|
|
1311
|
+
|
|
1165
1312
|
this.thought.text += chunk.data.content.text;
|
|
1166
1313
|
events.push({ type: 'thinking.delta', ref: this.thought.ref, text: chunk.data.content.text });
|
|
1314
|
+
|
|
1167
1315
|
return events;
|
|
1168
1316
|
}
|
|
1169
1317
|
|
|
1170
1318
|
private closeText(): BackendEvent[] {
|
|
1171
1319
|
const text = this.text;
|
|
1172
1320
|
this.text = null;
|
|
1321
|
+
|
|
1173
1322
|
if (text === null || (text.held && text.afterRefusal && text.text === REFUSAL_LINE)) {
|
|
1174
1323
|
return [];
|
|
1175
1324
|
}
|
|
1325
|
+
|
|
1176
1326
|
if (text.held && text.text.startsWith(QUOTA_HEAD)) {
|
|
1177
1327
|
this.quota = { ref: text.ref, text: text.text.trim(), resetsAt: quotaResetOf(text.text, this.now()) };
|
|
1328
|
+
|
|
1178
1329
|
return [];
|
|
1179
1330
|
}
|
|
1331
|
+
|
|
1180
1332
|
return [{ type: 'text.done', ref: text.ref, text: text.text }];
|
|
1181
1333
|
}
|
|
1182
1334
|
|
|
1183
1335
|
private closeThought(): BackendEvent[] {
|
|
1184
1336
|
const thought = this.thought;
|
|
1185
1337
|
this.thought = null;
|
|
1338
|
+
|
|
1186
1339
|
return thought === null ? [] : [{ type: 'thinking.done', ref: thought.ref, text: thought.text }];
|
|
1187
1340
|
}
|
|
1188
1341
|
|
|
1189
1342
|
/* A tool call or an update to one; fields an update leaves out keep what the call said before, and a list it sends replaces the old one. */
|
|
1190
1343
|
private toolCall(call: z.infer<typeof ToolCallSchema>, settle = true): BackendEvent[] {
|
|
1191
1344
|
const known = this.tools.get(call.toolCallId);
|
|
1345
|
+
|
|
1192
1346
|
const tool: ToolState = known ?? {
|
|
1193
1347
|
title: '',
|
|
1194
1348
|
name: null,
|
|
@@ -1203,27 +1357,34 @@ export class AcpProtocol {
|
|
|
1203
1357
|
settled: false,
|
|
1204
1358
|
refused: false
|
|
1205
1359
|
};
|
|
1360
|
+
|
|
1206
1361
|
tool.title = call.title ?? tool.title;
|
|
1207
1362
|
tool.name = call.name ?? tool.name;
|
|
1208
1363
|
tool.kind = call.kind ?? tool.kind;
|
|
1209
1364
|
tool.status = call.status ?? tool.status;
|
|
1210
1365
|
tool.rawInput = call.rawInput ?? tool.rawInput;
|
|
1211
1366
|
tool.rawOutput = call.rawOutput ?? tool.rawOutput;
|
|
1367
|
+
|
|
1212
1368
|
if (call.locations) {
|
|
1213
1369
|
tool.locations = validItems(LocationSchema, call.locations).map((location) => location.path);
|
|
1214
1370
|
}
|
|
1371
|
+
|
|
1215
1372
|
if (call.content) {
|
|
1216
1373
|
tool.diffs = validItems(DiffSchema, call.content);
|
|
1374
|
+
|
|
1217
1375
|
tool.text = validItems(ToolContentSchema, call.content).flatMap((entry) =>
|
|
1218
1376
|
entry.content.type === 'text' && entry.content.text ? [entry.content.text] : []
|
|
1219
1377
|
);
|
|
1220
1378
|
}
|
|
1379
|
+
|
|
1221
1380
|
this.tools.set(call.toolCallId, tool);
|
|
1222
1381
|
const events: BackendEvent[] = [];
|
|
1223
1382
|
const row = toolRow(tool.refused ? { ...tool, diffs: [] } : tool);
|
|
1224
1383
|
const shown = JSON.stringify(row);
|
|
1384
|
+
|
|
1225
1385
|
if (shown !== tool.shown) {
|
|
1226
1386
|
tool.shown = shown;
|
|
1387
|
+
|
|
1227
1388
|
events.push({
|
|
1228
1389
|
type: 'tool.started',
|
|
1229
1390
|
ref: call.toolCallId,
|
|
@@ -1233,8 +1394,10 @@ export class AcpProtocol {
|
|
|
1233
1394
|
...(row.changes ? { changes: row.changes } : {})
|
|
1234
1395
|
});
|
|
1235
1396
|
}
|
|
1397
|
+
|
|
1236
1398
|
if (settle && !tool.settled && (tool.status === 'completed' || tool.status === 'failed')) {
|
|
1237
1399
|
tool.settled = true;
|
|
1400
|
+
|
|
1238
1401
|
events.push({
|
|
1239
1402
|
type: 'tool.done',
|
|
1240
1403
|
ref: call.toolCallId,
|
|
@@ -1243,6 +1406,7 @@ export class AcpProtocol {
|
|
|
1243
1406
|
...(row.changes ? { changes: row.changes } : {})
|
|
1244
1407
|
});
|
|
1245
1408
|
}
|
|
1409
|
+
|
|
1246
1410
|
return events;
|
|
1247
1411
|
}
|
|
1248
1412
|
|
|
@@ -1250,12 +1414,14 @@ export class AcpProtocol {
|
|
|
1250
1414
|
private plan(update: Record<string, unknown>): BackendEvent[] {
|
|
1251
1415
|
const entries = validItems(PlanEntrySchema, Array.isArray(update.entries) ? update.entries : []);
|
|
1252
1416
|
const ref = `plan-${++this.counter}`;
|
|
1417
|
+
|
|
1253
1418
|
const todos = entries.map((entry) => ({
|
|
1254
1419
|
content: entry.content,
|
|
1255
1420
|
status: entry.status,
|
|
1256
1421
|
activeForm: entry.content,
|
|
1257
1422
|
...(entry.priority ? { priority: entry.priority } : {})
|
|
1258
1423
|
}));
|
|
1424
|
+
|
|
1259
1425
|
return [
|
|
1260
1426
|
...this.closeText(),
|
|
1261
1427
|
...this.closeThought(),
|