@linkshell/wire 0.2.7 → 0.2.9
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/README.md +7 -1
- package/dist/acp.d.ts +510 -0
- package/dist/acp.js +39 -0
- package/dist/acp.js.map +1 -0
- package/dist/async-questions.d.ts +40 -0
- package/dist/async-questions.js +66 -0
- package/dist/async-questions.js.map +1 -0
- package/dist/computer-preview.d.ts +2 -2
- package/dist/content.d.ts +465 -0
- package/dist/content.js +58 -0
- package/dist/content.js.map +1 -0
- package/dist/editor.d.ts +702 -0
- package/dist/editor.js +23 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +1413 -85
- package/dist/model.js +23 -1
- package/dist/model.js.map +1 -1
- package/dist/peer.d.ts +1 -0
- package/dist/peer.js +22 -6
- package/dist/peer.js.map +1 -1
- package/dist/questions.d.ts +3 -0
- package/dist/questions.js +65 -0
- package/dist/questions.js.map +1 -0
- package/dist/relay.d.ts +6 -6
- package/dist/rpc.d.ts +19941 -3048
- package/dist/rpc.js +41 -3
- package/dist/rpc.js.map +1 -1
- package/dist/updates.d.ts +3981 -1175
- package/dist/updates.js +48 -41
- package/dist/updates.js.map +1 -1
- package/dist/workflow.d.ts +2 -2
- package/package.json +1 -1
- package/src/acp.ts +49 -0
- package/src/async-questions.ts +69 -0
- package/src/content.ts +63 -0
- package/src/editor.ts +27 -0
- package/src/index.ts +4 -0
- package/src/model.ts +23 -1
- package/src/peer.ts +23 -9
- package/src/questions.ts +39 -0
- package/src/rpc.ts +44 -2
- package/src/updates.ts +50 -47
package/src/peer.ts
CHANGED
|
@@ -91,13 +91,21 @@ export class RpcPeer {
|
|
|
91
91
|
receive(text: string): void {
|
|
92
92
|
for (const line of text.split("\n")) {
|
|
93
93
|
if (!line.trim()) continue;
|
|
94
|
-
let message: Record<string, unknown
|
|
94
|
+
let message: Record<string, unknown> | Record<string, unknown>[];
|
|
95
95
|
try {
|
|
96
|
-
message = JSON.parse(line) as
|
|
96
|
+
message = JSON.parse(line) as typeof message;
|
|
97
97
|
} catch {
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
if (Array.isArray(message)) {
|
|
101
|
+
if (!message.length || message.length > 128) {
|
|
102
|
+
this.write({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "invalid JSON-RPC batch" } });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const responses: Record<string, unknown>[] = [];
|
|
106
|
+
void Promise.all(message.map((entry) => Promise.resolve(this.dispatch(entry, (response) => { responses.push(response); }))))
|
|
107
|
+
.then(() => { if (responses.length) this.write(responses); });
|
|
108
|
+
} else void this.dispatch(message);
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
111
|
|
|
@@ -116,7 +124,13 @@ export class RpcPeer {
|
|
|
116
124
|
return this.closed;
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
|
|
127
|
+
hasPendingRequest(id: RpcId): boolean { return this.pending.has(id); }
|
|
128
|
+
|
|
129
|
+
private dispatch(message: Record<string, unknown>, respond = (response: Record<string, unknown>) => this.write(response)): void | Promise<void> {
|
|
130
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) {
|
|
131
|
+
respond({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "invalid JSON-RPC request" } });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
120
134
|
const id = message.id as RpcId | undefined;
|
|
121
135
|
const method = typeof message.method === "string" ? message.method : undefined;
|
|
122
136
|
|
|
@@ -139,27 +153,27 @@ export class RpcPeer {
|
|
|
139
153
|
|
|
140
154
|
const handler = this.options.onRequest;
|
|
141
155
|
if (!handler) {
|
|
142
|
-
|
|
156
|
+
respond({ jsonrpc: "2.0", id, error: { code: RPC_METHOD_NOT_FOUND, message: `no handler for ${method}` } });
|
|
143
157
|
return;
|
|
144
158
|
}
|
|
145
|
-
Promise.resolve()
|
|
159
|
+
return Promise.resolve()
|
|
146
160
|
.then(() => handler(method, message.params, id))
|
|
147
161
|
.then(
|
|
148
162
|
(result) => {
|
|
149
163
|
if (result === ABANDON) return;
|
|
150
|
-
|
|
164
|
+
respond({ jsonrpc: "2.0", id, result: result ?? {} });
|
|
151
165
|
},
|
|
152
166
|
(error: unknown) => {
|
|
153
167
|
const rpcError =
|
|
154
168
|
error instanceof RpcError
|
|
155
169
|
? error
|
|
156
170
|
: new RpcError(RPC_APP_ERROR, error instanceof Error ? error.message : String(error), { code: "internal" });
|
|
157
|
-
|
|
171
|
+
respond({ jsonrpc: "2.0", id, error: rpcError.toObject() });
|
|
158
172
|
},
|
|
159
173
|
);
|
|
160
174
|
}
|
|
161
175
|
|
|
162
|
-
private write(message: Record<string, unknown>): void {
|
|
176
|
+
private write(message: Record<string, unknown> | Record<string, unknown>[]): void {
|
|
163
177
|
if (this.closed) return;
|
|
164
178
|
try {
|
|
165
179
|
this.options.send(JSON.stringify(message));
|
package/src/questions.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Question, QuestionAnswer } from "./model.js";
|
|
2
|
+
|
|
3
|
+
/** Shared validation keeps the phone's feedback and the host's acceptance identical. */
|
|
4
|
+
export function questionAnswerError(question: Question, answer: QuestionAnswer | undefined): string | undefined {
|
|
5
|
+
const label = question.header ?? question.text;
|
|
6
|
+
const values = answer?.values ?? [];
|
|
7
|
+
const c = question.constraints;
|
|
8
|
+
if (values.length > 1 && question.kind !== "choices") return `${label}只能填写一个答案`;
|
|
9
|
+
if (question.options && values.some((value) => !question.options!.some((option) => option.value === value))) return `${label}包含无效选项`;
|
|
10
|
+
const present = c?.type === "array" ? answer !== undefined : c?.type === "string" ? values[0] !== undefined || !!answer?.other?.trim() : values.some((value) => value !== "") || !!answer?.other?.trim();
|
|
11
|
+
if (!present) return question.required ? `还没回答:${label}` : undefined;
|
|
12
|
+
if (!c) return;
|
|
13
|
+
const value = values[0] ?? "";
|
|
14
|
+
if (c.type === "number" || c.type === "integer") {
|
|
15
|
+
const number = Number(value);
|
|
16
|
+
if (!value.trim() || !Number.isFinite(number) || (c.type === "integer" && !Number.isInteger(number))) return `${label}需要填写${c.type === "integer" ? "整数" : "数字"}`;
|
|
17
|
+
if (c.minimum !== undefined && number < c.minimum) return `${label}不能小于 ${c.minimum}`;
|
|
18
|
+
if (c.maximum !== undefined && number > c.maximum) return `${label}不能大于 ${c.maximum}`;
|
|
19
|
+
}
|
|
20
|
+
if (c.type === "array") {
|
|
21
|
+
if (new Set(values).size !== values.length) return `${label}不能重复选择`;
|
|
22
|
+
if (c.minItems !== undefined && values.length < c.minItems) return `${label}至少选择 ${c.minItems} 项`;
|
|
23
|
+
if (c.maxItems !== undefined && values.length > c.maxItems) return `${label}最多选择 ${c.maxItems} 项`;
|
|
24
|
+
}
|
|
25
|
+
if (c.type === "string") {
|
|
26
|
+
const length = [...value].length;
|
|
27
|
+
if (c.minLength !== undefined && length < c.minLength) return `${label}至少需要 ${c.minLength} 个字符`;
|
|
28
|
+
if (c.maxLength !== undefined && length > c.maxLength) return `${label}最多允许 ${c.maxLength} 个字符`;
|
|
29
|
+
if (c.pattern) {
|
|
30
|
+
try { if (!new RegExp(c.pattern, "u").test(value)) return `${label}格式不符合要求`; }
|
|
31
|
+
catch { return `${label}的格式规则无效`; }
|
|
32
|
+
}
|
|
33
|
+
if (c.format === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return `${label}需要有效的邮箱地址`;
|
|
34
|
+
if (c.format === "uri") { try { new URL(value); } catch { return `${label}需要完整的网址`; } }
|
|
35
|
+
if (c.format === "date" && (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value)) return `${label}需要有效日期(YYYY-MM-DD)`;
|
|
36
|
+
if (c.format === "date-time" && (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i.test(value) || !Number.isFinite(Date.parse(value)))) return `${label}需要包含时区的有效日期时间`;
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
package/src/rpc.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { goalChangeSchema, sessionGoalSchema } from "./goal.js";
|
|
3
|
+
import { acpAgentSettingsSchema, acpFeaturesSchema, acpRemoteAgentSchema, acpProviderSchema } from "./acp.js";
|
|
4
|
+
import { editorRequestSchema, editorResultSchema } from "./editor.js";
|
|
3
5
|
import {
|
|
4
6
|
backgroundTaskSchema,
|
|
7
|
+
agentInfoSchema,
|
|
5
8
|
gatewayStatusSchema,
|
|
6
9
|
machineInfoSchema,
|
|
7
10
|
portInfoSchema,
|
|
@@ -13,8 +16,9 @@ import {
|
|
|
13
16
|
gitInfoSchema,
|
|
14
17
|
worktreeEntrySchema,
|
|
15
18
|
questionAnswerSchema,
|
|
19
|
+
pendingPermissionSchema,
|
|
16
20
|
} from "./model.js";
|
|
17
|
-
import { contentBlockSchema, sessionEventSchema } from "./updates.js";
|
|
21
|
+
import { contentBlockSchema, sessionEventSchema, sessionNoticeSchema } from "./updates.js";
|
|
18
22
|
|
|
19
23
|
// ── JSON-RPC 2.0 framing ─────────────────────────────────────────────
|
|
20
24
|
|
|
@@ -72,7 +76,36 @@ export const directoryEntrySchema = z.object({
|
|
|
72
76
|
});
|
|
73
77
|
export type DirectoryEntry = z.infer<typeof directoryEntrySchema>;
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
const acpMethods = {
|
|
80
|
+
"agents.editor": { params: editorRequestSchema, result: editorResultSchema },
|
|
81
|
+
"agents.acp": {
|
|
82
|
+
params: z.object({ agent: z.string().min(1), sessionId: z.string().optional() }),
|
|
83
|
+
result: z.object({ features: acpFeaturesSchema, settings: acpAgentSettingsSchema, interactions: z.array(pendingPermissionSchema) }),
|
|
84
|
+
},
|
|
85
|
+
"agents.pending": { params: empty, result: z.object({ interactions: z.array(z.object({ agent: z.string(), request: pendingPermissionSchema })) }) },
|
|
86
|
+
"agents.configure": {
|
|
87
|
+
params: z.object({ agent: z.string().min(1), settings: acpAgentSettingsSchema, sessionId: z.string().optional() }),
|
|
88
|
+
result: empty,
|
|
89
|
+
},
|
|
90
|
+
"agents.authenticate": {
|
|
91
|
+
params: z.object({ agent: z.string().min(1), methodId: z.string().optional(), logout: z.boolean().optional() }),
|
|
92
|
+
result: z.object({ terminalId: z.string().optional() }),
|
|
93
|
+
},
|
|
94
|
+
"agents.respond": {
|
|
95
|
+
params: z.object({ agent: z.string().min(1), requestId: z.string().min(1), optionId: z.string().optional(), answers: z.array(questionAnswerSchema).optional() }),
|
|
96
|
+
result: empty,
|
|
97
|
+
},
|
|
98
|
+
"agents.providers": {
|
|
99
|
+
params: z.object({ agent: z.string().min(1), operation: z.enum(["list", "set", "disable"]), config: z.object({ providerId: z.string(), apiType: z.string().optional(), baseUrl: z.string().url().optional(), headers: z.record(z.string()).optional() }).optional() }),
|
|
100
|
+
result: z.object({ providers: z.array(acpProviderSchema) }),
|
|
101
|
+
},
|
|
102
|
+
"agents.custom": {
|
|
103
|
+
params: z.object({ save: acpRemoteAgentSchema.optional(), remove: z.string().optional() }),
|
|
104
|
+
result: z.object({ agents: z.array(acpRemoteAgentSchema) }),
|
|
105
|
+
},
|
|
106
|
+
} as const;
|
|
107
|
+
|
|
108
|
+
const coreMethods = {
|
|
76
109
|
"machine.info": {
|
|
77
110
|
params: empty,
|
|
78
111
|
result: machineInfoSchema,
|
|
@@ -152,6 +185,10 @@ export const methods = {
|
|
|
152
185
|
params: z.object({ sessionId: z.string().min(1) }),
|
|
153
186
|
result: z.object({ subagents: z.array(subagentInfoSchema) }),
|
|
154
187
|
},
|
|
188
|
+
"sessions.cancelSubagent": {
|
|
189
|
+
params: z.object({ sessionId: z.string().min(1), nativeSessionId: z.string().min(1) }),
|
|
190
|
+
result: empty,
|
|
191
|
+
},
|
|
155
192
|
/**
|
|
156
193
|
* One sub-agent's conversation: the call that started it and the events
|
|
157
194
|
* under it (the latest 2000), oldest first. Later ones arrive on the
|
|
@@ -575,6 +612,8 @@ export const methods = {
|
|
|
575
612
|
},
|
|
576
613
|
} as const;
|
|
577
614
|
|
|
615
|
+
export const methods: typeof coreMethods & typeof acpMethods = { ...coreMethods, ...acpMethods };
|
|
616
|
+
|
|
578
617
|
export type MethodName = keyof typeof methods;
|
|
579
618
|
export type MethodParams<M extends MethodName> = z.input<(typeof methods)[M]["params"]>;
|
|
580
619
|
export type MethodResult<M extends MethodName> = z.infer<(typeof methods)[M]["result"]>;
|
|
@@ -586,6 +625,9 @@ export function isMethodName(name: string): name is MethodName {
|
|
|
586
625
|
// ── Notifications (host → client) ────────────────────────────────────
|
|
587
626
|
|
|
588
627
|
export const notifications = {
|
|
628
|
+
"agent.changed": z.object({ agent: agentInfoSchema }),
|
|
629
|
+
"agent.interaction": z.object({ agent: z.string(), request: z.union([pendingPermissionSchema, z.object({ requestId: z.string(), resolved: z.literal(true) })]) }),
|
|
630
|
+
"session.notice": z.object({ sessionId: z.string(), notice: sessionNoticeSchema }),
|
|
589
631
|
"session.event": sessionEventSchema,
|
|
590
632
|
/**
|
|
591
633
|
* Sent before a subscription's backlog when it doesn't continue from the
|
package/src/updates.ts
CHANGED
|
@@ -1,54 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { asyncQuestionSchema } from "./async-questions.js";
|
|
2
3
|
import { sessionGoalSchema } from "./goal.js";
|
|
3
4
|
import { backgroundTaskSchema, permissionOptionSchema, questionAnswerSchema, questionSchema, sessionDriverSchema, sessionStateSchema, toolKindSchema } from "./model.js";
|
|
5
|
+
import { contentBlockSchema, toolCallContentSchema, toolCallStatusSchema } from "./content.js";
|
|
6
|
+
export * from "./content.js";
|
|
4
7
|
import { workflowAgentStateSchema, workflowSchema } from "./workflow.js";
|
|
5
8
|
|
|
6
9
|
// Session updates mirror ACP's `SessionUpdate` shapes (discriminated by
|
|
7
10
|
// `sessionUpdate`) so one renderer works for every agent. LinkShell-specific
|
|
8
11
|
// updates use the `ls_` prefix.
|
|
9
12
|
|
|
10
|
-
export const contentBlockSchema = z.discriminatedUnion("type", [
|
|
11
|
-
z.object({ type: z.literal("text"), text: z.string() }),
|
|
12
|
-
z.object({
|
|
13
|
-
type: z.literal("image"),
|
|
14
|
-
mimeType: z.string(),
|
|
15
|
-
data: z.string().optional(),
|
|
16
|
-
uri: z.string().optional(),
|
|
17
|
-
}),
|
|
18
|
-
z.object({
|
|
19
|
-
type: z.literal("resource_link"),
|
|
20
|
-
uri: z.string(),
|
|
21
|
-
name: z.string(),
|
|
22
|
-
/** What the link names: a file (default), a skill the user invoked, or another agent. */
|
|
23
|
-
kind: z.enum(["file", "skill", "agent"]).optional(),
|
|
24
|
-
}),
|
|
25
|
-
]);
|
|
26
|
-
export type ContentBlock = z.infer<typeof contentBlockSchema>;
|
|
27
|
-
|
|
28
|
-
export const toolCallStatusSchema = z.enum(["pending", "in_progress", "completed", "failed"]);
|
|
29
|
-
export type ToolCallStatus = z.infer<typeof toolCallStatusSchema>;
|
|
30
|
-
|
|
31
|
-
export const toolCallContentSchema = z.discriminatedUnion("type", [
|
|
32
|
-
z.object({ type: z.literal("content"), content: contentBlockSchema }),
|
|
33
|
-
/** ACP diff: full before/after text. */
|
|
34
|
-
z.object({
|
|
35
|
-
type: z.literal("diff"),
|
|
36
|
-
path: z.string(),
|
|
37
|
-
oldText: z.string().nullable().optional(),
|
|
38
|
-
newText: z.string(),
|
|
39
|
-
}),
|
|
40
|
-
/** Unified diff, for agents (Codex) that report patches rather than file text. */
|
|
41
|
-
z.object({
|
|
42
|
-
type: z.literal("patch"),
|
|
43
|
-
path: z.string(),
|
|
44
|
-
change: z.enum(["add", "delete", "update"]),
|
|
45
|
-
movePath: z.string().optional(),
|
|
46
|
-
diff: z.string(),
|
|
47
|
-
}),
|
|
48
|
-
z.object({ type: z.literal("terminal"), terminalId: z.string() }),
|
|
49
|
-
]);
|
|
50
|
-
export type ToolCallContent = z.infer<typeof toolCallContentSchema>;
|
|
51
|
-
|
|
52
13
|
export const toolCallLocationSchema = z.object({
|
|
53
14
|
path: z.string(),
|
|
54
15
|
line: z.number().int().optional(),
|
|
@@ -71,6 +32,8 @@ export const toolDetailSchema = z.discriminatedUnion("type", [
|
|
|
71
32
|
agentType: z.string().optional(),
|
|
72
33
|
/** A named agent's address, distinct from its role. */
|
|
73
34
|
name: z.string().optional(),
|
|
35
|
+
nativeSessionId: z.string().optional(),
|
|
36
|
+
canCancel: z.boolean().optional(),
|
|
74
37
|
model: z.string().optional(),
|
|
75
38
|
/** Agent outcome when a tool's coarse completed/failed state is insufficient. */
|
|
76
39
|
state: workflowAgentStateSchema.optional(),
|
|
@@ -94,20 +57,30 @@ export const planEntrySchema = z.object({
|
|
|
94
57
|
});
|
|
95
58
|
export type PlanEntry = z.infer<typeof planEntrySchema>;
|
|
96
59
|
|
|
97
|
-
export const stopReasonSchema = z.enum(["end_turn", "cancelled", "error", "max_tokens", "refusal"]);
|
|
60
|
+
export const stopReasonSchema = z.enum(["end_turn", "cancelled", "error", "max_tokens", "max_turn_requests", "refusal", "unknown"]);
|
|
98
61
|
export type StopReason = z.infer<typeof stopReasonSchema>;
|
|
99
62
|
|
|
100
63
|
export const sessionConfigOptionSchema = z.object({
|
|
101
64
|
/** Agent-defined id; "model" and "mode" are the common ones. */
|
|
102
65
|
id: z.string(),
|
|
103
66
|
name: z.string(),
|
|
104
|
-
|
|
67
|
+
description: z.string().optional(),
|
|
68
|
+
type: z.enum(["select", "boolean"]).optional(),
|
|
69
|
+
category: z.enum(["model", "model_config", "mode", "effort", "other"]).default("other"),
|
|
105
70
|
current: z.string(),
|
|
106
|
-
values: z.array(z.object({ value: z.string(), name: z.string(), description: z.string().optional() })),
|
|
71
|
+
values: z.array(z.object({ value: z.string(), name: z.string(), description: z.string().optional(), group: z.string().optional() })),
|
|
107
72
|
});
|
|
108
73
|
export type SessionConfigOption = z.infer<typeof sessionConfigOptionSchema>;
|
|
109
74
|
|
|
110
75
|
export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
76
|
+
z.object({
|
|
77
|
+
sessionUpdate: z.literal("ls_message"),
|
|
78
|
+
messageId: z.string(), role: z.enum(["user", "agent", "thought", "session"]),
|
|
79
|
+
parentToolCallId: z.string().optional(),
|
|
80
|
+
content: z.array(contentBlockSchema).optional(),
|
|
81
|
+
append: z.boolean().optional(),
|
|
82
|
+
senderSessionId: z.string().optional(), recipientSessionId: z.string().optional(),
|
|
83
|
+
}),
|
|
111
84
|
z.object({
|
|
112
85
|
sessionUpdate: z.literal("user_message_chunk"),
|
|
113
86
|
messageId: z.string().optional(),
|
|
@@ -131,9 +104,11 @@ export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
|
131
104
|
/** Set when a sub-agent made this call: the sub-agent's own (spawn) tool call. */
|
|
132
105
|
parentToolCallId: z.string().optional(),
|
|
133
106
|
title: z.string(),
|
|
107
|
+
name: z.string().optional(),
|
|
134
108
|
kind: toolKindSchema,
|
|
135
109
|
status: toolCallStatusSchema,
|
|
136
110
|
rawInput: z.unknown().optional(),
|
|
111
|
+
rawOutput: z.unknown().optional(),
|
|
137
112
|
content: z.array(toolCallContentSchema).optional(),
|
|
138
113
|
locations: z.array(toolCallLocationSchema).optional(),
|
|
139
114
|
detail: toolDetailSchema.optional(),
|
|
@@ -144,16 +119,28 @@ export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
|
144
119
|
parentToolCallId: z.string().optional(),
|
|
145
120
|
status: toolCallStatusSchema.optional(),
|
|
146
121
|
title: z.string().optional(),
|
|
122
|
+
name: z.string().optional(),
|
|
123
|
+
kind: toolKindSchema.optional(),
|
|
124
|
+
rawInput: z.unknown().optional(),
|
|
125
|
+
locations: z.array(toolCallLocationSchema).optional(),
|
|
147
126
|
detail: toolDetailSchema.optional(),
|
|
148
127
|
/** Replaces the tool call's content when present. */
|
|
149
128
|
content: z.array(toolCallContentSchema).optional(),
|
|
150
129
|
/** Streamed output appended to the tool call's running output. */
|
|
151
130
|
appendOutput: z.string().optional(),
|
|
131
|
+
replaceOutput: z.string().optional(),
|
|
132
|
+
/** ACP v2 null/empty content clears every previous diff and attachment. */
|
|
133
|
+
replaceContent: z.boolean().optional(),
|
|
152
134
|
rawOutput: z.unknown().optional(),
|
|
153
135
|
}),
|
|
154
136
|
z.object({
|
|
155
137
|
sessionUpdate: z.literal("plan"),
|
|
156
138
|
entries: z.array(planEntrySchema),
|
|
139
|
+
planId: z.string().optional(),
|
|
140
|
+
title: z.string().optional(),
|
|
141
|
+
markdown: z.string().nullable().optional(),
|
|
142
|
+
path: z.string().nullable().optional(),
|
|
143
|
+
removed: z.boolean().optional(),
|
|
157
144
|
}),
|
|
158
145
|
z.object({
|
|
159
146
|
sessionUpdate: z.literal("ls_goal"),
|
|
@@ -171,15 +158,22 @@ export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
|
171
158
|
}),
|
|
172
159
|
z.object({
|
|
173
160
|
sessionUpdate: z.literal("session_info_update"),
|
|
174
|
-
title: z.string().optional(),
|
|
161
|
+
title: z.string().nullable().optional(),
|
|
162
|
+
updatedAt: z.string().nullable().optional(),
|
|
175
163
|
model: z.string().optional(),
|
|
176
164
|
}),
|
|
177
165
|
z.object({
|
|
178
166
|
sessionUpdate: z.literal("usage_update"),
|
|
179
167
|
usedTokens: z.number().optional(),
|
|
180
168
|
contextWindow: z.number().optional(),
|
|
169
|
+
cost: z.object({ amount: z.number(), currency: z.string() }).nullable().optional(),
|
|
170
|
+
tokens: z.object({ totalTokens: z.number(), inputTokens: z.number(), outputTokens: z.number(), thoughtTokens: z.number().nullish(), cachedReadTokens: z.number().nullish(), cachedWriteTokens: z.number().nullish() }).optional(),
|
|
181
171
|
}),
|
|
182
172
|
// ── LinkShell extensions ──
|
|
173
|
+
z.object({
|
|
174
|
+
sessionUpdate: z.literal("ls_async_questions"),
|
|
175
|
+
questions: z.array(asyncQuestionSchema),
|
|
176
|
+
}),
|
|
183
177
|
z.object({
|
|
184
178
|
sessionUpdate: z.literal("ls_message_done"),
|
|
185
179
|
parentToolCallId: z.string().optional(),
|
|
@@ -190,7 +184,7 @@ export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
|
190
184
|
* `request_user_input_async`): answered by an ordinary message that quotes
|
|
191
185
|
* them (`asyncQuestionReply`). An app that predates them shows the text only.
|
|
192
186
|
*/
|
|
193
|
-
questions: z.array(
|
|
187
|
+
questions: z.array(asyncQuestionSchema).optional(),
|
|
194
188
|
}),
|
|
195
189
|
/** Selectable session settings (model, permission mode, effort…) as the agent reports them. */
|
|
196
190
|
z.object({
|
|
@@ -214,9 +208,13 @@ export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
|
214
208
|
z.object({
|
|
215
209
|
sessionUpdate: z.literal("ls_permission"),
|
|
216
210
|
requestId: z.string(),
|
|
211
|
+
childSessionId: z.string().optional(),
|
|
217
212
|
toolCallId: z.string().optional(),
|
|
218
213
|
title: z.string(),
|
|
219
214
|
detail: z.string().optional(),
|
|
215
|
+
tool: z.object({ title: z.string().optional(), kind: toolKindSchema.optional(), rawInput: z.unknown().optional(), content: z.array(toolCallContentSchema).optional(), locations: z.array(toolCallLocationSchema).optional() }).optional(),
|
|
216
|
+
/** URL interactions stay out of form answers and are opened only after consent. */
|
|
217
|
+
url: z.object({ url: z.string(), elicitationId: z.string() }).optional(),
|
|
220
218
|
options: z.array(permissionOptionSchema).min(1),
|
|
221
219
|
/**
|
|
222
220
|
* The agent is asking the user something: answer with `sessions.answer`.
|
|
@@ -240,6 +238,7 @@ export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
|
240
238
|
z.object({
|
|
241
239
|
sessionUpdate: z.literal("ls_status"),
|
|
242
240
|
state: sessionStateSchema,
|
|
241
|
+
turnActive: z.boolean().optional(),
|
|
243
242
|
}),
|
|
244
243
|
/** A background task as it is now (the whole record each time: the latest for its id holds). */
|
|
245
244
|
z.object({
|
|
@@ -256,6 +255,10 @@ export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
|
256
255
|
export type SessionUpdate = z.infer<typeof sessionUpdateSchema>;
|
|
257
256
|
export type SessionUpdateKind = SessionUpdate["sessionUpdate"];
|
|
258
257
|
|
|
258
|
+
/** Live ACP notices are not session history and never change execution state. */
|
|
259
|
+
export const sessionNoticeSchema = z.object({ severity: z.string(), title: z.string(), description: z.string().optional() });
|
|
260
|
+
export type SessionNotice = z.infer<typeof sessionNoticeSchema>;
|
|
261
|
+
|
|
259
262
|
/** One entry in a session's append-only event log. */
|
|
260
263
|
export const sessionEventSchema = z.object({
|
|
261
264
|
sessionId: z.string(),
|