@tangle-network/agent-interface 0.11.1 → 0.13.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 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,17 +205,18 @@ export type StreamEvent = MessagePartUpdatedEvent | {
198
205
  created?: number;
199
206
  updated?: number;
200
207
  };
201
- } | {
202
- type: "question";
203
- questionId: string;
204
- questions: Array<{
205
- question: string;
206
- options?: Array<{
207
- label: string;
208
- description?: string;
209
- }>;
210
- multiSelect?: boolean;
211
- }>;
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;
212
220
  };
213
221
  export type ToolInvocation = {
214
222
  toolName: string;
@@ -583,8 +591,14 @@ export interface SdkProviderAdapter {
583
591
  listAgentMessages?(agentId: string, options?: BackendListOptions): Promise<unknown>;
584
592
  listArtifacts?(sessionId: string): Promise<BackendArtifact[]>;
585
593
  downloadArtifact?(sessionId: string, path: string): Promise<Uint8Array>;
586
- submitQuestionAnswer?(answers: Record<string, string[]>): Promise<void>;
594
+ /**
595
+ * Respond to an outstanding interaction (question, permission, …). The
596
+ * generalized inbound channel; the adapter translates the response into the
597
+ * provider's native control call to unblock the agent.
598
+ */
599
+ respondToInteraction?(response: InteractionResponse): Promise<void>;
587
600
  }
601
+ export * from "./interaction.js";
588
602
  export * from "./agent-profile.js";
589
603
  export * from "./harness.js";
590
604
  export * from "./harness-capabilities.js";
package/dist/index.js CHANGED
@@ -122,6 +122,7 @@ export function resolveNativeWebTools(tools) {
122
122
  }
123
123
  return posture;
124
124
  }
125
+ export * from "./interaction.js";
125
126
  export * from "./agent-profile.js";
126
127
  export * from "./harness.js";
127
128
  export * from "./harness-capabilities.js";
@@ -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
+ }
@@ -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>>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",