mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
✨ feat(onboarding): add generic user interaction builtin tool
This commit is contained in:
15
packages/builtin-tool-user-interaction/package.json
Normal file
15
packages/builtin-tool-user-interaction/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@lobechat/builtin-tool-user-interaction",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./client": "./src/client/index.ts",
|
||||
"./executor": "./src/executor/index.ts",
|
||||
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"devDependencies": {
|
||||
"@lobechat/types": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { UserInteractionExecutionRuntime } from './index';
|
||||
|
||||
describe('UserInteractionExecutionRuntime', () => {
|
||||
it('creates a pending interaction request', async () => {
|
||||
const runtime = new UserInteractionExecutionRuntime();
|
||||
const result = await runtime.askUserQuestion({
|
||||
question: { id: 'q1', mode: 'freeform', prompt: 'What is your name?' },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state).toMatchObject({
|
||||
requestId: 'q1',
|
||||
status: 'pending',
|
||||
question: { id: 'q1', mode: 'freeform', prompt: 'What is your name?' },
|
||||
});
|
||||
});
|
||||
|
||||
it('marks interaction as submitted with response', async () => {
|
||||
const runtime = new UserInteractionExecutionRuntime();
|
||||
await runtime.askUserQuestion({
|
||||
question: { id: 'q2', mode: 'form', prompt: 'Fill this form' },
|
||||
});
|
||||
|
||||
const result = await runtime.submitUserResponse({
|
||||
requestId: 'q2',
|
||||
response: { name: 'Alice' },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state).toMatchObject({
|
||||
requestId: 'q2',
|
||||
status: 'submitted',
|
||||
response: { name: 'Alice' },
|
||||
});
|
||||
});
|
||||
|
||||
it('marks interaction as skipped with reason', async () => {
|
||||
const runtime = new UserInteractionExecutionRuntime();
|
||||
await runtime.askUserQuestion({
|
||||
question: { id: 'q3', mode: 'freeform', prompt: 'Optional question' },
|
||||
});
|
||||
|
||||
const result = await runtime.skipUserResponse({
|
||||
requestId: 'q3',
|
||||
reason: 'Not relevant',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state).toMatchObject({
|
||||
requestId: 'q3',
|
||||
status: 'skipped',
|
||||
skipReason: 'Not relevant',
|
||||
});
|
||||
});
|
||||
|
||||
it('marks interaction as cancelled', async () => {
|
||||
const runtime = new UserInteractionExecutionRuntime();
|
||||
await runtime.askUserQuestion({
|
||||
question: { id: 'q4', mode: 'freeform', prompt: 'Will be cancelled' },
|
||||
});
|
||||
|
||||
const result = await runtime.cancelUserResponse({ requestId: 'q4' });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state).toMatchObject({
|
||||
requestId: 'q4',
|
||||
status: 'cancelled',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets current interaction state', async () => {
|
||||
const runtime = new UserInteractionExecutionRuntime();
|
||||
await runtime.askUserQuestion({
|
||||
question: { id: 'q5', mode: 'freeform', prompt: 'Check state' },
|
||||
});
|
||||
|
||||
const result = await runtime.getInteractionState({ requestId: 'q5' });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state).toMatchObject({
|
||||
requestId: 'q5',
|
||||
status: 'pending',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error for non-existent interaction', async () => {
|
||||
const runtime = new UserInteractionExecutionRuntime();
|
||||
const result = await runtime.getInteractionState({ requestId: 'nonexistent' });
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('prevents submitting a non-pending interaction', async () => {
|
||||
const runtime = new UserInteractionExecutionRuntime();
|
||||
await runtime.askUserQuestion({
|
||||
question: { id: 'q6', mode: 'freeform', prompt: 'Already done' },
|
||||
});
|
||||
await runtime.cancelUserResponse({ requestId: 'q6' });
|
||||
|
||||
const result = await runtime.submitUserResponse({
|
||||
requestId: 'q6',
|
||||
response: { late: true },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
import type { BuiltinServerRuntimeOutput } from '@lobechat/types';
|
||||
|
||||
import type {
|
||||
AskUserQuestionArgs,
|
||||
CancelUserResponseArgs,
|
||||
GetInteractionStateArgs,
|
||||
InteractionState,
|
||||
SkipUserResponseArgs,
|
||||
SubmitUserResponseArgs,
|
||||
} from '../types';
|
||||
|
||||
export class UserInteractionExecutionRuntime {
|
||||
private interactions: Map<string, InteractionState> = new Map();
|
||||
|
||||
async askUserQuestion(args: AskUserQuestionArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
const { question } = args;
|
||||
const requestId = question.id;
|
||||
|
||||
const state: InteractionState = {
|
||||
question,
|
||||
requestId,
|
||||
status: 'pending',
|
||||
};
|
||||
|
||||
this.interactions.set(requestId, state);
|
||||
|
||||
return {
|
||||
content: `Question "${question.prompt}" is now pending user response.`,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async submitUserResponse(args: SubmitUserResponseArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
const { requestId, response } = args;
|
||||
const state = this.interactions.get(requestId);
|
||||
|
||||
if (!state) {
|
||||
return { content: `Interaction not found: ${requestId}`, success: false };
|
||||
}
|
||||
|
||||
if (state.status !== 'pending') {
|
||||
return {
|
||||
content: `Interaction ${requestId} is already ${state.status}, cannot submit.`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
state.status = 'submitted';
|
||||
state.response = response;
|
||||
this.interactions.set(requestId, state);
|
||||
|
||||
return {
|
||||
content: `User response submitted for interaction ${requestId}.`,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async skipUserResponse(args: SkipUserResponseArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
const { requestId, reason } = args;
|
||||
const state = this.interactions.get(requestId);
|
||||
|
||||
if (!state) {
|
||||
return { content: `Interaction not found: ${requestId}`, success: false };
|
||||
}
|
||||
|
||||
if (state.status !== 'pending') {
|
||||
return {
|
||||
content: `Interaction ${requestId} is already ${state.status}, cannot skip.`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
state.status = 'skipped';
|
||||
state.skipReason = reason;
|
||||
this.interactions.set(requestId, state);
|
||||
|
||||
return {
|
||||
content: `Interaction ${requestId} skipped.${reason ? ` Reason: ${reason}` : ''}`,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelUserResponse(args: CancelUserResponseArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
const { requestId } = args;
|
||||
const state = this.interactions.get(requestId);
|
||||
|
||||
if (!state) {
|
||||
return { content: `Interaction not found: ${requestId}`, success: false };
|
||||
}
|
||||
|
||||
if (state.status !== 'pending') {
|
||||
return {
|
||||
content: `Interaction ${requestId} is already ${state.status}, cannot cancel.`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
state.status = 'cancelled';
|
||||
this.interactions.set(requestId, state);
|
||||
|
||||
return {
|
||||
content: `Interaction ${requestId} cancelled.`,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async getInteractionState(args: GetInteractionStateArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
const { requestId } = args;
|
||||
const state = this.interactions.get(requestId);
|
||||
|
||||
if (!state) {
|
||||
return { content: `Interaction not found: ${requestId}`, success: false };
|
||||
}
|
||||
|
||||
return {
|
||||
content: `Interaction ${requestId} is ${state.status}.`,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInterventionProps } from '@lobechat/types';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { AskUserQuestionArgs } from '../../../types';
|
||||
|
||||
const AskUserQuestionIntervention = memo<BuiltinInterventionProps<AskUserQuestionArgs>>(
|
||||
({ args }) => {
|
||||
const { question } = args;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{question.prompt}</p>
|
||||
{question.description && (
|
||||
<p style={{ color: 'var(--lobe-text-secondary)', fontSize: 13 }}>
|
||||
{question.description}
|
||||
</p>
|
||||
)}
|
||||
{question.fields && question.fields.length > 0 && (
|
||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||
{question.fields.map((field) => (
|
||||
<li key={field.key}>
|
||||
{field.label}
|
||||
{field.required && ' *'}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
AskUserQuestionIntervention.displayName = 'AskUserQuestionIntervention';
|
||||
|
||||
export default AskUserQuestionIntervention;
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { BuiltinIntervention } from '@lobechat/types';
|
||||
|
||||
import { UserInteractionApiName } from '../../types';
|
||||
import AskUserQuestionIntervention from './AskUserQuestion';
|
||||
|
||||
export const UserInteractionInterventions: Record<string, BuiltinIntervention> = {
|
||||
[UserInteractionApiName.askUserQuestion]: AskUserQuestionIntervention as BuiltinIntervention,
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { UserInteractionManifest } from '../manifest';
|
||||
export * from '../types';
|
||||
export { UserInteractionInterventions } from './Intervention';
|
||||
63
packages/builtin-tool-user-interaction/src/executor/index.ts
Normal file
63
packages/builtin-tool-user-interaction/src/executor/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { BaseExecutor, type BuiltinToolContext, type BuiltinToolResult } from '@lobechat/types';
|
||||
|
||||
import { UserInteractionExecutionRuntime } from '../ExecutionRuntime';
|
||||
import {
|
||||
type AskUserQuestionArgs,
|
||||
type CancelUserResponseArgs,
|
||||
type GetInteractionStateArgs,
|
||||
type SkipUserResponseArgs,
|
||||
type SubmitUserResponseArgs,
|
||||
UserInteractionApiName,
|
||||
UserInteractionIdentifier,
|
||||
} from '../types';
|
||||
|
||||
export class UserInteractionExecutor extends BaseExecutor<typeof UserInteractionApiName> {
|
||||
readonly identifier = UserInteractionIdentifier;
|
||||
protected readonly apiEnum = UserInteractionApiName;
|
||||
|
||||
private runtime: UserInteractionExecutionRuntime;
|
||||
|
||||
constructor(runtime: UserInteractionExecutionRuntime) {
|
||||
super();
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
askUserQuestion = async (
|
||||
params: AskUserQuestionArgs,
|
||||
_ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
return this.runtime.askUserQuestion(params);
|
||||
};
|
||||
|
||||
submitUserResponse = async (
|
||||
params: SubmitUserResponseArgs,
|
||||
_ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
return this.runtime.submitUserResponse(params);
|
||||
};
|
||||
|
||||
skipUserResponse = async (
|
||||
params: SkipUserResponseArgs,
|
||||
_ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
return this.runtime.skipUserResponse(params);
|
||||
};
|
||||
|
||||
cancelUserResponse = async (
|
||||
params: CancelUserResponseArgs,
|
||||
_ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
return this.runtime.cancelUserResponse(params);
|
||||
};
|
||||
|
||||
getInteractionState = async (
|
||||
params: GetInteractionStateArgs,
|
||||
_ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
return this.runtime.getInteractionState(params);
|
||||
};
|
||||
}
|
||||
|
||||
const fallbackRuntime = new UserInteractionExecutionRuntime();
|
||||
|
||||
export const userInteractionExecutor = new UserInteractionExecutor(fallbackRuntime);
|
||||
18
packages/builtin-tool-user-interaction/src/index.ts
Normal file
18
packages/builtin-tool-user-interaction/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export * from './ExecutionRuntime';
|
||||
export { UserInteractionManifest } from './manifest';
|
||||
export { systemPrompt } from './systemRole';
|
||||
export {
|
||||
type AskUserQuestionArgs,
|
||||
type CancelUserResponseArgs,
|
||||
type GetInteractionStateArgs,
|
||||
type InteractionField,
|
||||
type InteractionFieldOption,
|
||||
type InteractionMode,
|
||||
type InteractionState,
|
||||
type InteractionStatus,
|
||||
type SkipUserResponseArgs,
|
||||
type SubmitUserResponseArgs,
|
||||
UserInteractionApiName,
|
||||
UserInteractionIdentifier,
|
||||
type UserInteractionResult,
|
||||
} from './types';
|
||||
143
packages/builtin-tool-user-interaction/src/manifest.ts
Normal file
143
packages/builtin-tool-user-interaction/src/manifest.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import type { BuiltinToolManifest } from '@lobechat/types';
|
||||
|
||||
import { systemPrompt } from './systemRole';
|
||||
import { UserInteractionApiName, UserInteractionIdentifier } from './types';
|
||||
|
||||
export const UserInteractionManifest: BuiltinToolManifest = {
|
||||
api: [
|
||||
{
|
||||
description:
|
||||
'Present a question to the user with either structured form fields or freeform input. Returns the interaction request in pending state.',
|
||||
name: UserInteractionApiName.askUserQuestion,
|
||||
parameters: {
|
||||
properties: {
|
||||
question: {
|
||||
properties: {
|
||||
description: { type: 'string' },
|
||||
fields: {
|
||||
items: {
|
||||
properties: {
|
||||
key: { type: 'string' },
|
||||
kind: {
|
||||
enum: ['multiselect', 'select', 'text', 'textarea'],
|
||||
type: 'string',
|
||||
},
|
||||
label: { type: 'string' },
|
||||
options: {
|
||||
items: {
|
||||
properties: {
|
||||
label: { type: 'string' },
|
||||
value: { type: 'string' },
|
||||
},
|
||||
required: ['label', 'value'],
|
||||
type: 'object',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
placeholder: { type: 'string' },
|
||||
required: { type: 'boolean' },
|
||||
value: {
|
||||
oneOf: [{ type: 'string' }, { items: { type: 'string' }, type: 'array' }],
|
||||
},
|
||||
},
|
||||
required: ['key', 'kind', 'label'],
|
||||
type: 'object',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
id: { type: 'string' },
|
||||
metadata: {
|
||||
additionalProperties: true,
|
||||
type: 'object',
|
||||
},
|
||||
mode: {
|
||||
enum: ['form', 'freeform'],
|
||||
type: 'string',
|
||||
},
|
||||
prompt: { type: 'string' },
|
||||
},
|
||||
required: ['id', 'mode', 'prompt'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
required: ['question'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Record the user's submitted response for a pending interaction request.",
|
||||
name: UserInteractionApiName.submitUserResponse,
|
||||
parameters: {
|
||||
properties: {
|
||||
requestId: {
|
||||
description: 'The interaction request ID to submit a response for.',
|
||||
type: 'string',
|
||||
},
|
||||
response: {
|
||||
additionalProperties: true,
|
||||
description: "The user's response data.",
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
required: ['requestId', 'response'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Mark a pending interaction request as skipped with an optional reason.',
|
||||
name: UserInteractionApiName.skipUserResponse,
|
||||
parameters: {
|
||||
properties: {
|
||||
reason: {
|
||||
description: 'Optional reason for skipping.',
|
||||
type: 'string',
|
||||
},
|
||||
requestId: {
|
||||
description: 'The interaction request ID to skip.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['requestId'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Cancel a pending interaction request.',
|
||||
name: UserInteractionApiName.cancelUserResponse,
|
||||
parameters: {
|
||||
properties: {
|
||||
requestId: {
|
||||
description: 'The interaction request ID to cancel.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['requestId'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Get the current state of an interaction request.',
|
||||
name: UserInteractionApiName.getInteractionState,
|
||||
parameters: {
|
||||
properties: {
|
||||
requestId: {
|
||||
description: 'The interaction request ID to query.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['requestId'],
|
||||
type: 'object',
|
||||
},
|
||||
renderDisplayControl: 'collapsed',
|
||||
},
|
||||
],
|
||||
identifier: UserInteractionIdentifier,
|
||||
meta: {
|
||||
avatar: '💬',
|
||||
description:
|
||||
'Ask users questions and collect structured responses with submit/skip/cancel semantics',
|
||||
title: 'User Interaction',
|
||||
},
|
||||
systemRole: systemPrompt,
|
||||
type: 'builtin',
|
||||
};
|
||||
29
packages/builtin-tool-user-interaction/src/systemRole.ts
Normal file
29
packages/builtin-tool-user-interaction/src/systemRole.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export const systemPrompt = `You have access to a User Interaction tool for asking users questions and collecting structured responses.
|
||||
|
||||
<core_capabilities>
|
||||
1. Ask question (askUserQuestion) - present a question with form fields or freeform input
|
||||
2. Submit response (submitUserResponse) - record the user's submitted answer
|
||||
3. Skip response (skipUserResponse) - mark a question as skipped with optional reason
|
||||
4. Cancel response (cancelUserResponse) - cancel a pending question
|
||||
5. Get state (getInteractionState) - check the current state of an interaction request
|
||||
</core_capabilities>
|
||||
|
||||
<lifecycle>
|
||||
1. Call askUserQuestion with a question object (id, mode, prompt, optional fields).
|
||||
2. The UI surfaces the question to the user. The interaction enters "pending" state.
|
||||
3. The user responds in one of three ways:
|
||||
- Submit: submitUserResponse is called with the user's answer → status becomes "submitted"
|
||||
- Skip: skipUserResponse is called → status becomes "skipped"
|
||||
- Cancel: cancelUserResponse is called → status becomes "cancelled"
|
||||
4. Use getInteractionState to check the outcome if needed.
|
||||
5. Process the result and continue the conversation accordingly.
|
||||
</lifecycle>
|
||||
|
||||
<best_practices>
|
||||
- Use "form" mode when you need structured data with specific fields.
|
||||
- Use "freeform" mode for open-ended questions where the user types a free response.
|
||||
- Always provide a clear, concise prompt so the user knows what is being asked.
|
||||
- Handle all three outcomes (submit/skip/cancel) gracefully.
|
||||
- Do not ask multiple questions simultaneously; wait for one to resolve before asking another.
|
||||
</best_practices>
|
||||
`;
|
||||
70
packages/builtin-tool-user-interaction/src/types.ts
Normal file
70
packages/builtin-tool-user-interaction/src/types.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
export const UserInteractionIdentifier = 'lobe-user-interaction';
|
||||
|
||||
export const UserInteractionApiName = {
|
||||
askUserQuestion: 'askUserQuestion',
|
||||
cancelUserResponse: 'cancelUserResponse',
|
||||
getInteractionState: 'getInteractionState',
|
||||
skipUserResponse: 'skipUserResponse',
|
||||
submitUserResponse: 'submitUserResponse',
|
||||
} as const;
|
||||
|
||||
export type InteractionMode = 'form' | 'freeform';
|
||||
|
||||
export type InteractionStatus = 'cancelled' | 'pending' | 'skipped' | 'submitted';
|
||||
|
||||
export interface InteractionFieldOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface InteractionField {
|
||||
key: string;
|
||||
kind: 'multiselect' | 'select' | 'text' | 'textarea';
|
||||
label: string;
|
||||
options?: InteractionFieldOption[];
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
value?: string | string[];
|
||||
}
|
||||
|
||||
export interface AskUserQuestionArgs {
|
||||
question: {
|
||||
description?: string;
|
||||
fields?: InteractionField[];
|
||||
id: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
mode: InteractionMode;
|
||||
prompt: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SubmitUserResponseArgs {
|
||||
requestId: string;
|
||||
response: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SkipUserResponseArgs {
|
||||
reason?: string;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export interface CancelUserResponseArgs {
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export interface GetInteractionStateArgs {
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export interface InteractionState {
|
||||
question?: AskUserQuestionArgs['question'];
|
||||
requestId: string;
|
||||
response?: Record<string, unknown>;
|
||||
skipReason?: string;
|
||||
status: InteractionStatus;
|
||||
}
|
||||
|
||||
export type UserInteractionResult =
|
||||
| { requestId: string; response: Record<string, unknown>; type: 'submitted' }
|
||||
| { reason?: string; requestId: string; type: 'skipped' }
|
||||
| { requestId: string; type: 'cancelled' };
|
||||
4
packages/builtin-tool-user-interaction/tsconfig.json
Normal file
4
packages/builtin-tool-user-interaction/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
7
packages/builtin-tool-user-interaction/vitest.config.mts
Normal file
7
packages/builtin-tool-user-interaction/vitest.config.mts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user