@tangle-network/agent-interface 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +34 -1
- package/dist/index.js +1 -0
- package/dist/interaction.d.ts +281 -0
- package/dist/interaction.js +250 -0
- package/dist/profile-schema.d.ts +15 -15
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,12 +4,19 @@
|
|
|
4
4
|
* Shared types and interfaces for SDK provider adapters.
|
|
5
5
|
* This package defines the contract between the sidecar and provider implementations.
|
|
6
6
|
*/
|
|
7
|
+
import type { InteractionRequest, InteractionResponse } from "./interaction.js";
|
|
7
8
|
export type BackendCapabilities = {
|
|
8
9
|
streaming: boolean;
|
|
9
10
|
toolUse: boolean;
|
|
10
11
|
reasoning: boolean;
|
|
11
12
|
multimodal: boolean;
|
|
12
13
|
contextWindow: number;
|
|
14
|
+
/**
|
|
15
|
+
* Interaction kinds this provider can originate (e.g. `["question",
|
|
16
|
+
* "permission"]`). Empty/undefined means the provider never asks the user.
|
|
17
|
+
* Consumers use this to decide what human-in-the-loop UI to offer.
|
|
18
|
+
*/
|
|
19
|
+
interactions?: string[];
|
|
13
20
|
};
|
|
14
21
|
/**
|
|
15
22
|
* High-signal feature flags surfaced by each provider package. Routes
|
|
@@ -198,7 +205,22 @@ export type StreamEvent = MessagePartUpdatedEvent | {
|
|
|
198
205
|
created?: number;
|
|
199
206
|
updated?: number;
|
|
200
207
|
};
|
|
201
|
-
}
|
|
208
|
+
}
|
|
209
|
+
/** Agent asks the user; answered via `respondToInteraction`. The generalized
|
|
210
|
+
* human-in-the-loop event (question, permission, plan, …). */
|
|
211
|
+
| {
|
|
212
|
+
type: "interaction";
|
|
213
|
+
request: InteractionRequest;
|
|
214
|
+
}
|
|
215
|
+
/** Agent withdraws an outstanding interaction (no longer needs the answer). */
|
|
216
|
+
| {
|
|
217
|
+
type: "interaction.cancel";
|
|
218
|
+
id: string;
|
|
219
|
+
reason?: string;
|
|
220
|
+
}
|
|
221
|
+
/** @deprecated Use the `interaction` event with `kind: "question"`. Retained
|
|
222
|
+
* so existing emitters/consumers keep working during migration. */
|
|
223
|
+
| {
|
|
202
224
|
type: "question";
|
|
203
225
|
questionId: string;
|
|
204
226
|
questions: Array<{
|
|
@@ -583,8 +605,19 @@ export interface SdkProviderAdapter {
|
|
|
583
605
|
listAgentMessages?(agentId: string, options?: BackendListOptions): Promise<unknown>;
|
|
584
606
|
listArtifacts?(sessionId: string): Promise<BackendArtifact[]>;
|
|
585
607
|
downloadArtifact?(sessionId: string, path: string): Promise<Uint8Array>;
|
|
608
|
+
/**
|
|
609
|
+
* Respond to an outstanding interaction (question, permission, …). The
|
|
610
|
+
* generalized inbound channel; the adapter translates the response into the
|
|
611
|
+
* provider's native control call to unblock the agent.
|
|
612
|
+
*/
|
|
613
|
+
respondToInteraction?(response: InteractionResponse): Promise<void>;
|
|
614
|
+
/**
|
|
615
|
+
* @deprecated Use `respondToInteraction`. Retained for back-compat; an
|
|
616
|
+
* adapter implementing only this still answers `kind: "question"` asks.
|
|
617
|
+
*/
|
|
586
618
|
submitQuestionAnswer?(answers: Record<string, string[]>): Promise<void>;
|
|
587
619
|
}
|
|
620
|
+
export * from "./interaction.js";
|
|
588
621
|
export * from "./agent-profile.js";
|
|
589
622
|
export * from "./harness.js";
|
|
590
623
|
export * from "./harness-capabilities.js";
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction contract — the generalized human-in-the-loop primitive.
|
|
3
|
+
*
|
|
4
|
+
* An agent emits an `InteractionRequest`: a typed ask that carries a
|
|
5
|
+
* self-describing `answerSpec` (the fields and types of a valid answer). A
|
|
6
|
+
* human (or an automated policy) returns an `InteractionResponse` keyed by the
|
|
7
|
+
* same `id`. This subsumes the original question/answer pair and extends it to
|
|
8
|
+
* permissions, plans, and provider-specific asks.
|
|
9
|
+
*
|
|
10
|
+
* Design contract:
|
|
11
|
+
* - The envelope is stable; `kind` is an OPEN label, so new ask types need no
|
|
12
|
+
* change to this contract. Well-known kinds (see `InteractionKind`) get
|
|
13
|
+
* richer rendering and platform handling; unknown kinds render generically
|
|
14
|
+
* from `answerSpec` and still work end-to-end.
|
|
15
|
+
* - `answerSpec` is a small closed set of flat field types, so any consumer can
|
|
16
|
+
* render a form and validate a response without a general schema engine. This
|
|
17
|
+
* mirrors MCP elicitation so MCP-originated asks map onto this 1:1.
|
|
18
|
+
* - `default` + `timeoutMs`/`onTimeout` make unattended resolution explicit and
|
|
19
|
+
* auditable, replacing blanket permission-bypass flags.
|
|
20
|
+
*/
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
export declare const InteractionFieldSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
23
|
+
type: z.ZodLiteral<"text">;
|
|
24
|
+
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
26
|
+
default: z.ZodOptional<z.ZodString>;
|
|
27
|
+
name: z.ZodString;
|
|
28
|
+
label: z.ZodString;
|
|
29
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
30
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
31
|
+
type: z.ZodLiteral<"number">;
|
|
32
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
default: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
name: z.ZodString;
|
|
36
|
+
label: z.ZodString;
|
|
37
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
38
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
39
|
+
type: z.ZodLiteral<"boolean">;
|
|
40
|
+
default: z.ZodOptional<z.ZodBoolean>;
|
|
41
|
+
name: z.ZodString;
|
|
42
|
+
label: z.ZodString;
|
|
43
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
45
|
+
type: z.ZodLiteral<"select">;
|
|
46
|
+
options: z.ZodArray<z.ZodObject<{
|
|
47
|
+
value: z.ZodString;
|
|
48
|
+
label: z.ZodString;
|
|
49
|
+
description: z.ZodOptional<z.ZodString>;
|
|
50
|
+
}, z.core.$strip>>;
|
|
51
|
+
multi: z.ZodOptional<z.ZodBoolean>;
|
|
52
|
+
default: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
53
|
+
name: z.ZodString;
|
|
54
|
+
label: z.ZodString;
|
|
55
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
56
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
57
|
+
type: z.ZodLiteral<"secret">;
|
|
58
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
59
|
+
name: z.ZodString;
|
|
60
|
+
label: z.ZodString;
|
|
61
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
62
|
+
}, z.core.$strip>], "type">;
|
|
63
|
+
export type InteractionField = z.infer<typeof InteractionFieldSchema>;
|
|
64
|
+
export declare const InteractionAnswerSpecSchema: z.ZodObject<{
|
|
65
|
+
fields: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
66
|
+
type: z.ZodLiteral<"text">;
|
|
67
|
+
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
68
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
69
|
+
default: z.ZodOptional<z.ZodString>;
|
|
70
|
+
name: z.ZodString;
|
|
71
|
+
label: z.ZodString;
|
|
72
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
73
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
74
|
+
type: z.ZodLiteral<"number">;
|
|
75
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
76
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
77
|
+
default: z.ZodOptional<z.ZodNumber>;
|
|
78
|
+
name: z.ZodString;
|
|
79
|
+
label: z.ZodString;
|
|
80
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
81
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
82
|
+
type: z.ZodLiteral<"boolean">;
|
|
83
|
+
default: z.ZodOptional<z.ZodBoolean>;
|
|
84
|
+
name: z.ZodString;
|
|
85
|
+
label: z.ZodString;
|
|
86
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
87
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
88
|
+
type: z.ZodLiteral<"select">;
|
|
89
|
+
options: z.ZodArray<z.ZodObject<{
|
|
90
|
+
value: z.ZodString;
|
|
91
|
+
label: z.ZodString;
|
|
92
|
+
description: z.ZodOptional<z.ZodString>;
|
|
93
|
+
}, z.core.$strip>>;
|
|
94
|
+
multi: z.ZodOptional<z.ZodBoolean>;
|
|
95
|
+
default: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
96
|
+
name: z.ZodString;
|
|
97
|
+
label: z.ZodString;
|
|
98
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
99
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
100
|
+
type: z.ZodLiteral<"secret">;
|
|
101
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
102
|
+
name: z.ZodString;
|
|
103
|
+
label: z.ZodString;
|
|
104
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
105
|
+
}, z.core.$strip>], "type">>;
|
|
106
|
+
}, z.core.$strip>;
|
|
107
|
+
export type InteractionAnswerSpec = z.infer<typeof InteractionAnswerSpecSchema>;
|
|
108
|
+
export declare const InteractionSubjectSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
109
|
+
type: z.ZodLiteral<"tool">;
|
|
110
|
+
toolName: z.ZodString;
|
|
111
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
112
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
113
|
+
type: z.ZodLiteral<"command">;
|
|
114
|
+
command: z.ZodString;
|
|
115
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
116
|
+
type: z.ZodLiteral<"file">;
|
|
117
|
+
path: z.ZodString;
|
|
118
|
+
preview: z.ZodOptional<z.ZodString>;
|
|
119
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
120
|
+
type: z.ZodLiteral<"resource">;
|
|
121
|
+
uri: z.ZodString;
|
|
122
|
+
}, z.core.$strip>], "type">;
|
|
123
|
+
export type InteractionSubject = z.infer<typeof InteractionSubjectSchema>;
|
|
124
|
+
export declare const InteractionOutcomeSchema: z.ZodEnum<{
|
|
125
|
+
accepted: "accepted";
|
|
126
|
+
declined: "declined";
|
|
127
|
+
cancelled: "cancelled";
|
|
128
|
+
}>;
|
|
129
|
+
export type InteractionOutcome = z.infer<typeof InteractionOutcomeSchema>;
|
|
130
|
+
/** Field values keyed by `InteractionField.name`. Validated against `answerSpec`. */
|
|
131
|
+
export declare const InteractionDataSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>;
|
|
132
|
+
export type InteractionData = z.infer<typeof InteractionDataSchema>;
|
|
133
|
+
export declare const InteractionResolutionSchema: z.ZodObject<{
|
|
134
|
+
outcome: z.ZodEnum<{
|
|
135
|
+
accepted: "accepted";
|
|
136
|
+
declined: "declined";
|
|
137
|
+
cancelled: "cancelled";
|
|
138
|
+
}>;
|
|
139
|
+
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
140
|
+
}, z.core.$strip>;
|
|
141
|
+
export type InteractionResolution = z.infer<typeof InteractionResolutionSchema>;
|
|
142
|
+
export declare const InteractionRequestSchema: z.ZodObject<{
|
|
143
|
+
id: z.ZodString;
|
|
144
|
+
kind: z.ZodString;
|
|
145
|
+
title: z.ZodString;
|
|
146
|
+
body: z.ZodOptional<z.ZodString>;
|
|
147
|
+
subject: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
148
|
+
type: z.ZodLiteral<"tool">;
|
|
149
|
+
toolName: z.ZodString;
|
|
150
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
151
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
152
|
+
type: z.ZodLiteral<"command">;
|
|
153
|
+
command: z.ZodString;
|
|
154
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
155
|
+
type: z.ZodLiteral<"file">;
|
|
156
|
+
path: z.ZodString;
|
|
157
|
+
preview: z.ZodOptional<z.ZodString>;
|
|
158
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
159
|
+
type: z.ZodLiteral<"resource">;
|
|
160
|
+
uri: z.ZodString;
|
|
161
|
+
}, z.core.$strip>], "type">>;
|
|
162
|
+
answerSpec: z.ZodObject<{
|
|
163
|
+
fields: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
164
|
+
type: z.ZodLiteral<"text">;
|
|
165
|
+
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
166
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
167
|
+
default: z.ZodOptional<z.ZodString>;
|
|
168
|
+
name: z.ZodString;
|
|
169
|
+
label: z.ZodString;
|
|
170
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
171
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
172
|
+
type: z.ZodLiteral<"number">;
|
|
173
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
174
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
175
|
+
default: z.ZodOptional<z.ZodNumber>;
|
|
176
|
+
name: z.ZodString;
|
|
177
|
+
label: z.ZodString;
|
|
178
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
179
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
180
|
+
type: z.ZodLiteral<"boolean">;
|
|
181
|
+
default: z.ZodOptional<z.ZodBoolean>;
|
|
182
|
+
name: z.ZodString;
|
|
183
|
+
label: z.ZodString;
|
|
184
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
185
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
186
|
+
type: z.ZodLiteral<"select">;
|
|
187
|
+
options: z.ZodArray<z.ZodObject<{
|
|
188
|
+
value: z.ZodString;
|
|
189
|
+
label: z.ZodString;
|
|
190
|
+
description: z.ZodOptional<z.ZodString>;
|
|
191
|
+
}, z.core.$strip>>;
|
|
192
|
+
multi: z.ZodOptional<z.ZodBoolean>;
|
|
193
|
+
default: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
194
|
+
name: z.ZodString;
|
|
195
|
+
label: z.ZodString;
|
|
196
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
197
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
198
|
+
type: z.ZodLiteral<"secret">;
|
|
199
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
200
|
+
name: z.ZodString;
|
|
201
|
+
label: z.ZodString;
|
|
202
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
203
|
+
}, z.core.$strip>], "type">>;
|
|
204
|
+
}, z.core.$strip>;
|
|
205
|
+
default: z.ZodOptional<z.ZodObject<{
|
|
206
|
+
outcome: z.ZodEnum<{
|
|
207
|
+
accepted: "accepted";
|
|
208
|
+
declined: "declined";
|
|
209
|
+
cancelled: "cancelled";
|
|
210
|
+
}>;
|
|
211
|
+
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
212
|
+
}, z.core.$strip>>;
|
|
213
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
214
|
+
onTimeout: z.ZodOptional<z.ZodEnum<{
|
|
215
|
+
default: "default";
|
|
216
|
+
fail: "fail";
|
|
217
|
+
wait: "wait";
|
|
218
|
+
}>>;
|
|
219
|
+
}, z.core.$strip>;
|
|
220
|
+
export type InteractionRequest = z.infer<typeof InteractionRequestSchema>;
|
|
221
|
+
export declare const InteractionResponseSchema: z.ZodObject<{
|
|
222
|
+
outcome: z.ZodEnum<{
|
|
223
|
+
accepted: "accepted";
|
|
224
|
+
declined: "declined";
|
|
225
|
+
cancelled: "cancelled";
|
|
226
|
+
}>;
|
|
227
|
+
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
|
|
228
|
+
id: z.ZodString;
|
|
229
|
+
}, z.core.$strip>;
|
|
230
|
+
export type InteractionResponse = z.infer<typeof InteractionResponseSchema>;
|
|
231
|
+
export declare const InteractionKind: {
|
|
232
|
+
/** Agent asks the user to answer/choose. Answer = the chosen field values. */
|
|
233
|
+
readonly Question: "question";
|
|
234
|
+
/** Agent requests approval to run a tool/command. Answer = a `PermissionGrant`. */
|
|
235
|
+
readonly Permission: "permission";
|
|
236
|
+
/** Agent shares a plan/todo list for review/approval. */
|
|
237
|
+
readonly Plan: "plan";
|
|
238
|
+
};
|
|
239
|
+
export type WellKnownInteractionKind = (typeof InteractionKind)[keyof typeof InteractionKind];
|
|
240
|
+
/** Field name carrying the grant on a `permission` interaction's response. */
|
|
241
|
+
export declare const PERMISSION_GRANT_FIELD = "grant";
|
|
242
|
+
/** Optional free-text field carrying the user's reason on a `permission` response. */
|
|
243
|
+
export declare const PERMISSION_FEEDBACK_FIELD = "feedback";
|
|
244
|
+
/** Graduated permission decision — the value of the `grant` field. */
|
|
245
|
+
export declare const PermissionGrantSchema: z.ZodEnum<{
|
|
246
|
+
deny: "deny";
|
|
247
|
+
allow_once: "allow_once";
|
|
248
|
+
allow_session: "allow_session";
|
|
249
|
+
allow_always: "allow_always";
|
|
250
|
+
}>;
|
|
251
|
+
export type PermissionGrant = z.infer<typeof PermissionGrantSchema>;
|
|
252
|
+
/** Build the answer spec for a `permission` interaction (graduated grant + feedback). */
|
|
253
|
+
export declare function permissionAnswerSpec(opts?: {
|
|
254
|
+
allowFeedback?: boolean;
|
|
255
|
+
}): InteractionAnswerSpec;
|
|
256
|
+
/** Shape of one legacy question (kept for the back-compat shim). */
|
|
257
|
+
export type LegacyQuestion = {
|
|
258
|
+
question: string;
|
|
259
|
+
options?: Array<{
|
|
260
|
+
label: string;
|
|
261
|
+
description?: string;
|
|
262
|
+
}>;
|
|
263
|
+
multiSelect?: boolean;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Build an answer spec from the legacy `question` event shape. Each question
|
|
267
|
+
* becomes one select field (free text when it declares no options), so the old
|
|
268
|
+
* question/answer path is expressible as a `question` interaction.
|
|
269
|
+
*/
|
|
270
|
+
export declare function questionAnswerSpec(questions: LegacyQuestion[]): InteractionAnswerSpec;
|
|
271
|
+
export type InteractionValidation = {
|
|
272
|
+
ok: true;
|
|
273
|
+
} | {
|
|
274
|
+
ok: false;
|
|
275
|
+
errors: string[];
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Validate an accepted answer against its spec. Used by the broker before a
|
|
279
|
+
* response reaches the adapter, so malformed answers are rejected centrally.
|
|
280
|
+
*/
|
|
281
|
+
export declare function validateInteractionAnswer(spec: InteractionAnswerSpec, data: InteractionData | undefined): InteractionValidation;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction contract — the generalized human-in-the-loop primitive.
|
|
3
|
+
*
|
|
4
|
+
* An agent emits an `InteractionRequest`: a typed ask that carries a
|
|
5
|
+
* self-describing `answerSpec` (the fields and types of a valid answer). A
|
|
6
|
+
* human (or an automated policy) returns an `InteractionResponse` keyed by the
|
|
7
|
+
* same `id`. This subsumes the original question/answer pair and extends it to
|
|
8
|
+
* permissions, plans, and provider-specific asks.
|
|
9
|
+
*
|
|
10
|
+
* Design contract:
|
|
11
|
+
* - The envelope is stable; `kind` is an OPEN label, so new ask types need no
|
|
12
|
+
* change to this contract. Well-known kinds (see `InteractionKind`) get
|
|
13
|
+
* richer rendering and platform handling; unknown kinds render generically
|
|
14
|
+
* from `answerSpec` and still work end-to-end.
|
|
15
|
+
* - `answerSpec` is a small closed set of flat field types, so any consumer can
|
|
16
|
+
* render a form and validate a response without a general schema engine. This
|
|
17
|
+
* mirrors MCP elicitation so MCP-originated asks map onto this 1:1.
|
|
18
|
+
* - `default` + `timeoutMs`/`onTimeout` make unattended resolution explicit and
|
|
19
|
+
* auditable, replacing blanket permission-bypass flags.
|
|
20
|
+
*/
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Answer specification — describes the shape of a valid answer.
|
|
24
|
+
// =============================================================================
|
|
25
|
+
const FieldBase = {
|
|
26
|
+
/** Stable key the answer is returned under in `InteractionResponse.data`. */
|
|
27
|
+
name: z.string().min(1),
|
|
28
|
+
/** Human-readable label for the form control. */
|
|
29
|
+
label: z.string().min(1),
|
|
30
|
+
/** Whether the answer must supply this field to be `accepted`. */
|
|
31
|
+
required: z.boolean().optional(),
|
|
32
|
+
};
|
|
33
|
+
export const InteractionFieldSchema = z.discriminatedUnion("type", [
|
|
34
|
+
z.object({
|
|
35
|
+
...FieldBase,
|
|
36
|
+
type: z.literal("text"),
|
|
37
|
+
multiline: z.boolean().optional(),
|
|
38
|
+
placeholder: z.string().optional(),
|
|
39
|
+
default: z.string().optional(),
|
|
40
|
+
}),
|
|
41
|
+
z.object({
|
|
42
|
+
...FieldBase,
|
|
43
|
+
type: z.literal("number"),
|
|
44
|
+
min: z.number().optional(),
|
|
45
|
+
max: z.number().optional(),
|
|
46
|
+
default: z.number().optional(),
|
|
47
|
+
}),
|
|
48
|
+
z.object({
|
|
49
|
+
...FieldBase,
|
|
50
|
+
type: z.literal("boolean"),
|
|
51
|
+
default: z.boolean().optional(),
|
|
52
|
+
}),
|
|
53
|
+
z.object({
|
|
54
|
+
...FieldBase,
|
|
55
|
+
type: z.literal("select"),
|
|
56
|
+
options: z
|
|
57
|
+
.array(z.object({
|
|
58
|
+
value: z.string(),
|
|
59
|
+
label: z.string(),
|
|
60
|
+
description: z.string().optional(),
|
|
61
|
+
}))
|
|
62
|
+
.min(1),
|
|
63
|
+
/** When true the user may pick more than one option. */
|
|
64
|
+
multi: z.boolean().optional(),
|
|
65
|
+
default: z.array(z.string()).optional(),
|
|
66
|
+
}),
|
|
67
|
+
/** Like `text` but the value is sensitive (token/key) and must be masked. */
|
|
68
|
+
z.object({
|
|
69
|
+
...FieldBase,
|
|
70
|
+
type: z.literal("secret"),
|
|
71
|
+
placeholder: z.string().optional(),
|
|
72
|
+
}),
|
|
73
|
+
]);
|
|
74
|
+
export const InteractionAnswerSpecSchema = z.object({
|
|
75
|
+
fields: z.array(InteractionFieldSchema),
|
|
76
|
+
});
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// Subject — what the request is about (drives preview/permission UX).
|
|
79
|
+
// =============================================================================
|
|
80
|
+
export const InteractionSubjectSchema = z.discriminatedUnion("type", [
|
|
81
|
+
z.object({ type: z.literal("tool"), toolName: z.string(), input: z.unknown().optional() }),
|
|
82
|
+
z.object({ type: z.literal("command"), command: z.string() }),
|
|
83
|
+
z.object({ type: z.literal("file"), path: z.string(), preview: z.string().optional() }),
|
|
84
|
+
z.object({ type: z.literal("resource"), uri: z.string() }),
|
|
85
|
+
]);
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// Outcome + resolution — the answer.
|
|
88
|
+
// =============================================================================
|
|
89
|
+
export const InteractionOutcomeSchema = z.enum(["accepted", "declined", "cancelled"]);
|
|
90
|
+
/** Field values keyed by `InteractionField.name`. Validated against `answerSpec`. */
|
|
91
|
+
export const InteractionDataSchema = z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]));
|
|
92
|
+
export const InteractionResolutionSchema = z.object({
|
|
93
|
+
outcome: InteractionOutcomeSchema,
|
|
94
|
+
/** Present (and validated) only when `outcome === "accepted"`. */
|
|
95
|
+
data: InteractionDataSchema.optional(),
|
|
96
|
+
});
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// The request envelope.
|
|
99
|
+
// =============================================================================
|
|
100
|
+
export const InteractionRequestSchema = z.object({
|
|
101
|
+
/** Correlation id; unique within a session. The response carries the same id. */
|
|
102
|
+
id: z.string().min(1),
|
|
103
|
+
/**
|
|
104
|
+
* Open label for rendering, handling, and authorization. Well-known values:
|
|
105
|
+
* `question` | `permission` | `plan`. Vendor extensions SHOULD namespace,
|
|
106
|
+
* e.g. `x-pi.choose-extension`.
|
|
107
|
+
*/
|
|
108
|
+
kind: z.string().min(1),
|
|
109
|
+
/** Short human-readable prompt. */
|
|
110
|
+
title: z.string().min(1),
|
|
111
|
+
/** Optional longer context (markdown). */
|
|
112
|
+
body: z.string().optional(),
|
|
113
|
+
subject: InteractionSubjectSchema.optional(),
|
|
114
|
+
answerSpec: InteractionAnswerSpecSchema,
|
|
115
|
+
/** Resolution applied when unattended or timed out — explicit, not a bypass flag. */
|
|
116
|
+
default: InteractionResolutionSchema.optional(),
|
|
117
|
+
/** Wait this long for a human before applying `onTimeout`. */
|
|
118
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
119
|
+
/** On timeout: apply `default`, `fail` the turn, or keep `wait`ing. Default `wait`. */
|
|
120
|
+
onTimeout: z.enum(["default", "fail", "wait"]).optional(),
|
|
121
|
+
});
|
|
122
|
+
export const InteractionResponseSchema = InteractionResolutionSchema.extend({
|
|
123
|
+
id: z.string().min(1),
|
|
124
|
+
});
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// Well-known kinds + helpers.
|
|
127
|
+
// =============================================================================
|
|
128
|
+
export const InteractionKind = {
|
|
129
|
+
/** Agent asks the user to answer/choose. Answer = the chosen field values. */
|
|
130
|
+
Question: "question",
|
|
131
|
+
/** Agent requests approval to run a tool/command. Answer = a `PermissionGrant`. */
|
|
132
|
+
Permission: "permission",
|
|
133
|
+
/** Agent shares a plan/todo list for review/approval. */
|
|
134
|
+
Plan: "plan",
|
|
135
|
+
};
|
|
136
|
+
/** Field name carrying the grant on a `permission` interaction's response. */
|
|
137
|
+
export const PERMISSION_GRANT_FIELD = "grant";
|
|
138
|
+
/** Optional free-text field carrying the user's reason on a `permission` response. */
|
|
139
|
+
export const PERMISSION_FEEDBACK_FIELD = "feedback";
|
|
140
|
+
/** Graduated permission decision — the value of the `grant` field. */
|
|
141
|
+
export const PermissionGrantSchema = z.enum([
|
|
142
|
+
"allow_once",
|
|
143
|
+
"allow_session",
|
|
144
|
+
"allow_always",
|
|
145
|
+
"deny",
|
|
146
|
+
]);
|
|
147
|
+
/** Build the answer spec for a `permission` interaction (graduated grant + feedback). */
|
|
148
|
+
export function permissionAnswerSpec(opts) {
|
|
149
|
+
const fields = [
|
|
150
|
+
{
|
|
151
|
+
type: "select",
|
|
152
|
+
name: PERMISSION_GRANT_FIELD,
|
|
153
|
+
label: "Decision",
|
|
154
|
+
required: true,
|
|
155
|
+
options: [
|
|
156
|
+
{ value: "allow_once", label: "Allow once" },
|
|
157
|
+
{ value: "allow_session", label: "Allow for this session" },
|
|
158
|
+
{ value: "allow_always", label: "Always allow" },
|
|
159
|
+
{ value: "deny", label: "Deny" },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
if (opts?.allowFeedback !== false) {
|
|
164
|
+
fields.push({
|
|
165
|
+
type: "text",
|
|
166
|
+
name: PERMISSION_FEEDBACK_FIELD,
|
|
167
|
+
label: "Feedback (optional)",
|
|
168
|
+
multiline: true,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return { fields };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Build an answer spec from the legacy `question` event shape. Each question
|
|
175
|
+
* becomes one select field (free text when it declares no options), so the old
|
|
176
|
+
* question/answer path is expressible as a `question` interaction.
|
|
177
|
+
*/
|
|
178
|
+
export function questionAnswerSpec(questions) {
|
|
179
|
+
const fields = questions.map((q, i) => {
|
|
180
|
+
const name = `q${i}`;
|
|
181
|
+
if (q.options && q.options.length > 0) {
|
|
182
|
+
return {
|
|
183
|
+
type: "select",
|
|
184
|
+
name,
|
|
185
|
+
label: q.question,
|
|
186
|
+
required: true,
|
|
187
|
+
multi: q.multiSelect === true,
|
|
188
|
+
options: q.options.map((o) => ({ value: o.label, label: o.label, description: o.description })),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return { type: "text", name, label: q.question, required: true };
|
|
192
|
+
});
|
|
193
|
+
return { fields };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Validate an accepted answer against its spec. Used by the broker before a
|
|
197
|
+
* response reaches the adapter, so malformed answers are rejected centrally.
|
|
198
|
+
*/
|
|
199
|
+
export function validateInteractionAnswer(spec, data) {
|
|
200
|
+
const errors = [];
|
|
201
|
+
const d = data ?? {};
|
|
202
|
+
for (const field of spec.fields) {
|
|
203
|
+
const v = d[field.name];
|
|
204
|
+
const present = v !== undefined && v !== null && !(typeof v === "string" && v === "");
|
|
205
|
+
if (!present) {
|
|
206
|
+
if (field.required)
|
|
207
|
+
errors.push(`missing required field "${field.name}"`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
switch (field.type) {
|
|
211
|
+
case "text":
|
|
212
|
+
case "secret":
|
|
213
|
+
if (typeof v !== "string")
|
|
214
|
+
errors.push(`field "${field.name}" must be a string`);
|
|
215
|
+
break;
|
|
216
|
+
case "number":
|
|
217
|
+
if (typeof v !== "number") {
|
|
218
|
+
errors.push(`field "${field.name}" must be a number`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (field.min !== undefined && v < field.min)
|
|
222
|
+
errors.push(`field "${field.name}" below min ${field.min}`);
|
|
223
|
+
if (field.max !== undefined && v > field.max)
|
|
224
|
+
errors.push(`field "${field.name}" above max ${field.max}`);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case "boolean":
|
|
228
|
+
if (typeof v !== "boolean")
|
|
229
|
+
errors.push(`field "${field.name}" must be a boolean`);
|
|
230
|
+
break;
|
|
231
|
+
case "select": {
|
|
232
|
+
if (!Array.isArray(v)) {
|
|
233
|
+
errors.push(`field "${field.name}" must be an array of option values`);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
if (!field.multi && v.length > 1)
|
|
237
|
+
errors.push(`field "${field.name}" accepts a single value`);
|
|
238
|
+
if (field.required && v.length === 0)
|
|
239
|
+
errors.push(`field "${field.name}" requires a selection`);
|
|
240
|
+
const allowed = new Set(field.options.map((o) => o.value));
|
|
241
|
+
for (const choice of v) {
|
|
242
|
+
if (!allowed.has(choice))
|
|
243
|
+
errors.push(`field "${field.name}" has invalid option "${choice}"`);
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
250
|
+
}
|
package/dist/profile-schema.d.ts
CHANGED
|
@@ -3,17 +3,17 @@ import type { AgentProfile } from "./agent-profile.js";
|
|
|
3
3
|
import { type SandboxSizePreset } from "./sandbox-size.js";
|
|
4
4
|
export declare const agentProfilePermissionValueSchema: z.ZodEnum<{
|
|
5
5
|
allow: "allow";
|
|
6
|
-
deny: "deny";
|
|
7
6
|
ask: "ask";
|
|
7
|
+
deny: "deny";
|
|
8
8
|
}>;
|
|
9
9
|
export declare const agentProfilePermissionSchema: z.ZodUnion<readonly [z.ZodEnum<{
|
|
10
10
|
allow: "allow";
|
|
11
|
-
deny: "deny";
|
|
12
11
|
ask: "ask";
|
|
12
|
+
deny: "deny";
|
|
13
13
|
}>, z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
14
14
|
allow: "allow";
|
|
15
|
-
deny: "deny";
|
|
16
15
|
ask: "ask";
|
|
16
|
+
deny: "deny";
|
|
17
17
|
}>>]>;
|
|
18
18
|
export declare const agentProfileResourceRefSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
19
19
|
kind: z.ZodLiteral<"inline">;
|
|
@@ -119,10 +119,10 @@ export declare const agentProfileModelHintsSchema: z.ZodObject<{
|
|
|
119
119
|
small: z.ZodOptional<z.ZodString>;
|
|
120
120
|
provider: z.ZodOptional<z.ZodString>;
|
|
121
121
|
reasoningEffort: z.ZodOptional<z.ZodEnum<{
|
|
122
|
-
medium: "medium";
|
|
123
122
|
none: "none";
|
|
124
123
|
minimal: "minimal";
|
|
125
124
|
low: "low";
|
|
125
|
+
medium: "medium";
|
|
126
126
|
high: "high";
|
|
127
127
|
xhigh: "xhigh";
|
|
128
128
|
ultracode: "ultracode";
|
|
@@ -140,12 +140,12 @@ export declare const agentSubagentProfileSchema: z.ZodObject<{
|
|
|
140
140
|
tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
|
|
141
141
|
permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
|
|
142
142
|
allow: "allow";
|
|
143
|
-
deny: "deny";
|
|
144
143
|
ask: "ask";
|
|
144
|
+
deny: "deny";
|
|
145
145
|
}>, z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
146
146
|
allow: "allow";
|
|
147
|
-
deny: "deny";
|
|
148
147
|
ask: "ask";
|
|
148
|
+
deny: "deny";
|
|
149
149
|
}>>]>>>;
|
|
150
150
|
maxSteps: z.ZodOptional<z.ZodNumber>;
|
|
151
151
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -164,12 +164,12 @@ export declare const agentProfileModeSchema: z.ZodObject<{
|
|
|
164
164
|
tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
|
|
165
165
|
permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
|
|
166
166
|
allow: "allow";
|
|
167
|
-
deny: "deny";
|
|
168
167
|
ask: "ask";
|
|
168
|
+
deny: "deny";
|
|
169
169
|
}>, z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
170
170
|
allow: "allow";
|
|
171
|
-
deny: "deny";
|
|
172
171
|
ask: "ask";
|
|
172
|
+
deny: "deny";
|
|
173
173
|
}>>]>>>;
|
|
174
174
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
175
175
|
}, z.core.$strip>;
|
|
@@ -218,10 +218,10 @@ export declare const agentProfileSchema: z.ZodObject<{
|
|
|
218
218
|
small: z.ZodOptional<z.ZodString>;
|
|
219
219
|
provider: z.ZodOptional<z.ZodString>;
|
|
220
220
|
reasoningEffort: z.ZodOptional<z.ZodEnum<{
|
|
221
|
-
medium: "medium";
|
|
222
221
|
none: "none";
|
|
223
222
|
minimal: "minimal";
|
|
224
223
|
low: "low";
|
|
224
|
+
medium: "medium";
|
|
225
225
|
high: "high";
|
|
226
226
|
xhigh: "xhigh";
|
|
227
227
|
ultracode: "ultracode";
|
|
@@ -230,12 +230,12 @@ export declare const agentProfileSchema: z.ZodObject<{
|
|
|
230
230
|
}, z.core.$strip>>;
|
|
231
231
|
permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
|
|
232
232
|
allow: "allow";
|
|
233
|
-
deny: "deny";
|
|
234
233
|
ask: "ask";
|
|
234
|
+
deny: "deny";
|
|
235
235
|
}>, z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
236
236
|
allow: "allow";
|
|
237
|
-
deny: "deny";
|
|
238
237
|
ask: "ask";
|
|
238
|
+
deny: "deny";
|
|
239
239
|
}>>]>>>;
|
|
240
240
|
tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
|
|
241
241
|
mcp: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -265,12 +265,12 @@ export declare const agentProfileSchema: z.ZodObject<{
|
|
|
265
265
|
tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
|
|
266
266
|
permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
|
|
267
267
|
allow: "allow";
|
|
268
|
-
deny: "deny";
|
|
269
268
|
ask: "ask";
|
|
269
|
+
deny: "deny";
|
|
270
270
|
}>, z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
271
271
|
allow: "allow";
|
|
272
|
-
deny: "deny";
|
|
273
272
|
ask: "ask";
|
|
273
|
+
deny: "deny";
|
|
274
274
|
}>>]>>>;
|
|
275
275
|
maxSteps: z.ZodOptional<z.ZodNumber>;
|
|
276
276
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -362,12 +362,12 @@ export declare const agentProfileSchema: z.ZodObject<{
|
|
|
362
362
|
tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
|
|
363
363
|
permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
|
|
364
364
|
allow: "allow";
|
|
365
|
-
deny: "deny";
|
|
366
365
|
ask: "ask";
|
|
366
|
+
deny: "deny";
|
|
367
367
|
}>, z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
368
368
|
allow: "allow";
|
|
369
|
-
deny: "deny";
|
|
370
369
|
ask: "ask";
|
|
370
|
+
deny: "deny";
|
|
371
371
|
}>>]>>>;
|
|
372
372
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
373
373
|
}, z.core.$strip>>>;
|