Skip to content

Commit d82f2a7

Browse files
committed
Moar tools
1 parent 3ba4e47 commit d82f2a7

File tree

11 files changed

+698
-1
lines changed

11 files changed

+698
-1
lines changed

packages/lexical/src/state/LexicalAdapter.ts

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ export interface OperationResult {
5050
export class LexicalAdapter {
5151
private _editor: LexicalEditor;
5252
private _defaultBlockType: string = 'paragraph';
53+
private _serviceManager?: any; // ServiceManager from @jupyterlab/services
5354

54-
constructor(editor: LexicalEditor) {
55+
constructor(editor: LexicalEditor, serviceManager?: any) {
5556
this._editor = editor;
57+
this._serviceManager = serviceManager;
5658
}
5759

5860
/**
@@ -562,4 +564,112 @@ export class LexicalAdapter {
562564
return null;
563565
}
564566
}
567+
568+
/**
569+
* Execute code directly in the kernel without creating a block.
570+
*
571+
* This method sends code execution requests directly to the kernel,
572+
* bypassing the lexical document model. Useful for:
573+
* - Variable inspection
574+
* - Environment setup
575+
* - Background tasks
576+
* - Tool introspection
577+
*
578+
* @param code - Code to execute
579+
* @param options - Execution options
580+
* @returns Promise with execution result including outputs
581+
*/
582+
async executeCode(
583+
code: string,
584+
options: {
585+
storeHistory?: boolean;
586+
silent?: boolean;
587+
stopOnError?: boolean;
588+
} = {},
589+
): Promise<{
590+
success: boolean;
591+
outputs?: Array<{
592+
type: 'stream' | 'execute_result' | 'display_data' | 'error';
593+
content: unknown;
594+
}>;
595+
executionCount?: number;
596+
error?: string;
597+
}> {
598+
if (!this._serviceManager) {
599+
return {
600+
success: false,
601+
error:
602+
'No ServiceManager available. LexicalAdapter requires a ServiceManager to execute code.',
603+
};
604+
}
605+
606+
const kernel = this._serviceManager.sessions?.running()?.next()
607+
?.value?.kernel;
608+
609+
if (!kernel) {
610+
return {
611+
success: false,
612+
error: 'No active kernel session',
613+
};
614+
}
615+
616+
try {
617+
const future = kernel.requestExecute({
618+
code,
619+
stop_on_error: options.stopOnError ?? true,
620+
store_history: options.storeHistory ?? false,
621+
silent: options.silent ?? false,
622+
allow_stdin: false,
623+
});
624+
625+
const outputs: Array<{
626+
type: 'stream' | 'execute_result' | 'display_data' | 'error';
627+
content: unknown;
628+
}> = [];
629+
630+
let executionCount: number | undefined;
631+
632+
// Collect outputs
633+
future.onIOPub = (msg: any) => {
634+
const msgType = msg.header.msg_type;
635+
636+
if (msgType === 'stream') {
637+
outputs.push({
638+
type: 'stream',
639+
content: msg.content,
640+
});
641+
} else if (msgType === 'execute_result') {
642+
outputs.push({
643+
type: 'execute_result',
644+
content: msg.content,
645+
});
646+
executionCount = (msg.content as any).execution_count;
647+
} else if (msgType === 'display_data') {
648+
outputs.push({
649+
type: 'display_data',
650+
content: msg.content,
651+
});
652+
} else if (msgType === 'error') {
653+
outputs.push({
654+
type: 'error',
655+
content: msg.content,
656+
});
657+
}
658+
};
659+
660+
// Wait for execution to complete
661+
await future.done;
662+
663+
return {
664+
success: true,
665+
outputs,
666+
executionCount,
667+
};
668+
} catch (error) {
669+
return {
670+
success: false,
671+
error: error instanceof Error ? error.message : String(error),
672+
};
673+
}
674+
}
565675
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) 2021-2023 Datalayer, Inc.
3+
*
4+
* MIT License
5+
*/
6+
7+
/**
8+
* Tool definition for executing code directly in kernel
9+
*
10+
* @module tools/definitions/executeCode
11+
*/
12+
13+
import type { ToolDefinition } from '../core/schema';
14+
15+
/**
16+
* Tool definition for executing code directly in the kernel
17+
*
18+
* Executes code without creating or modifying blocks in the document.
19+
*/
20+
export const executeCodeTool: ToolDefinition = {
21+
name: 'datalayer_executeCode_lexical',
22+
displayName: 'Execute Code in Kernel (Lexical)',
23+
toolReferenceName: 'executeCode',
24+
description:
25+
'Executes code directly in the Jupyter kernel without creating or modifying blocks in the Lexical document. Useful for variable inspection, environment setup, background tasks, and tool introspection. Returns execution outputs including streams, results, and errors.',
26+
27+
parameters: {
28+
type: 'object' as const,
29+
properties: {
30+
code: {
31+
type: 'string',
32+
description: 'Python code to execute in the kernel',
33+
},
34+
storeHistory: {
35+
type: 'boolean',
36+
description:
37+
'Whether to store this execution in kernel history (default: false). Set to true to make this code accessible via In[n].',
38+
},
39+
silent: {
40+
type: 'boolean',
41+
description:
42+
'Silent execution - no output displayed (default: false). Useful for background tasks.',
43+
},
44+
stopOnError: {
45+
type: 'boolean',
46+
description:
47+
'Stop execution if an error occurs (default: true). Set to false to continue execution even on errors.',
48+
},
49+
},
50+
required: ['code'],
51+
},
52+
53+
operation: 'executeCode',
54+
55+
config: {
56+
confirmationMessage: (params: { code: string }) =>
57+
`Execute code: ${params.code.substring(0, 50)}${params.code.length > 50 ? '...' : ''}?`,
58+
invocationMessage: () => 'Executing code in kernel',
59+
requiresConfirmation: false,
60+
canBeReferencedInPrompt: true,
61+
priority: 'high',
62+
},
63+
64+
tags: ['lexical', 'kernel', 'execute', 'code', 'inspection'],
65+
};

packages/lexical/src/tools/definitions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ export * from './readAllBlocks';
2121
export * from './runBlock';
2222
export * from './runAllBlocks';
2323
export * from './listAvailableBlocks';
24+
export * from './executeCode';

packages/lexical/src/tools/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { readAllBlocksTool } from './definitions/readAllBlocks';
2222
import { runBlockTool } from './definitions/runBlock';
2323
import { runAllBlocksTool } from './definitions/runAllBlocks';
2424
import { listAvailableBlocksTool } from './definitions/listAvailableBlocks';
25+
import { executeCodeTool } from './definitions/executeCode';
2526

2627
// Import all operations
2728
import { insertBlockOperation } from './operations/insertBlock';
@@ -33,6 +34,7 @@ import { readAllBlocksOperation } from './operations/readAllBlocks';
3334
import { runBlockOperation } from './operations/runBlock';
3435
import { runAllBlocksOperation } from './operations/runAllBlocks';
3536
import { listAvailableBlocksOperation } from './operations/listAvailableBlocks';
37+
import { executeCodeOperation } from './operations/executeCode';
3638

3739
// Import types
3840
import type { ToolDefinition } from './core/schema';
@@ -51,6 +53,7 @@ export const lexicalToolDefinitions: ToolDefinition[] = [
5153
runBlockTool,
5254
runAllBlocksTool,
5355
listAvailableBlocksTool,
56+
executeCodeTool,
5457
];
5558

5659
/**
@@ -70,6 +73,7 @@ export const lexicalToolOperations: Record<
7073
runBlock: runBlockOperation,
7174
runAllBlocks: runAllBlocksOperation,
7275
listAvailableBlocks: listAvailableBlocksOperation,
76+
executeCode: executeCodeOperation,
7377
};
7478

7579
/**
@@ -92,5 +96,6 @@ export * from './operations/readAllBlocks';
9296
export * from './operations/runBlock';
9397
export * from './operations/runAllBlocks';
9498
export * from './operations/listAvailableBlocks';
99+
export * from './operations/executeCode';
95100
export * from '../state';
96101
export * from './utils';
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright (c) 2021-2023 Datalayer, Inc.
3+
*
4+
* MIT License
5+
*/
6+
7+
/**
8+
* Platform-agnostic code execution operation for Lexical documents.
9+
* Executes code directly in the kernel without creating or modifying blocks.
10+
*
11+
* @module tools/operations/executeCode
12+
*/
13+
14+
import type {
15+
ToolOperation,
16+
LexicalExecutionContext,
17+
} from '../core/interfaces';
18+
import { formatResponse } from '../core/formatter';
19+
20+
/**
21+
* Parameters for executeCode operation.
22+
*/
23+
export interface ExecuteCodeParams {
24+
/** Code to execute */
25+
code: string;
26+
27+
/** Whether to store in kernel history (default: false) */
28+
storeHistory?: boolean;
29+
30+
/** Silent execution - no output displayed (default: false) */
31+
silent?: boolean;
32+
33+
/** Stop execution on error (default: true) */
34+
stopOnError?: boolean;
35+
}
36+
37+
/**
38+
* Validates ExecuteCodeParams at runtime.
39+
*/
40+
function isExecuteCodeParams(params: unknown): params is ExecuteCodeParams {
41+
if (typeof params !== 'object' || params === null) {
42+
return false;
43+
}
44+
45+
const p = params as Record<string, unknown>;
46+
47+
// code is required and must be a string
48+
if (typeof p.code !== 'string') {
49+
return false;
50+
}
51+
52+
// Optional boolean fields
53+
if (p.storeHistory !== undefined && typeof p.storeHistory !== 'boolean') {
54+
return false;
55+
}
56+
if (p.silent !== undefined && typeof p.silent !== 'boolean') {
57+
return false;
58+
}
59+
if (p.stopOnError !== undefined && typeof p.stopOnError !== 'boolean') {
60+
return false;
61+
}
62+
63+
return true;
64+
}
65+
66+
/**
67+
* Result from executeCode operation.
68+
*/
69+
export interface ExecuteCodeResult {
70+
success: boolean;
71+
/** Execution outputs (streams, results, errors) */
72+
outputs?: Array<{
73+
type: 'stream' | 'execute_result' | 'display_data' | 'error';
74+
content: unknown;
75+
}>;
76+
/** Error message if execution failed */
77+
error?: string;
78+
/** Execution count (if stored in history) */
79+
executionCount?: number;
80+
}
81+
82+
/**
83+
* Executes code directly in the kernel without creating a block.
84+
*
85+
* This is useful for:
86+
* - Variable inspection
87+
* - Environment setup
88+
* - Background tasks
89+
* - Tool introspection
90+
*/
91+
export const executeCodeOperation: ToolOperation<
92+
ExecuteCodeParams,
93+
ExecuteCodeResult
94+
> = {
95+
name: 'executeCode',
96+
97+
async execute(
98+
params: unknown,
99+
context: LexicalExecutionContext,
100+
): Promise<ExecuteCodeResult> {
101+
// Validate params using type guard
102+
if (!isExecuteCodeParams(params)) {
103+
throw new Error(
104+
`Invalid parameters for executeCode. Expected { code: string, storeHistory?: boolean, silent?: boolean, stopOnError?: boolean }. ` +
105+
`Received: ${JSON.stringify(params)}`,
106+
);
107+
}
108+
109+
const { lexicalId } = context;
110+
111+
if (!lexicalId) {
112+
return formatResponse(
113+
{
114+
success: false,
115+
error: 'Lexical ID is required for this operation.',
116+
},
117+
context.format,
118+
) as ExecuteCodeResult;
119+
}
120+
121+
// Ensure executeCommand is available
122+
if (!context.executeCommand) {
123+
throw new Error(
124+
'executeCommand callback is required for executeCode operation. ' +
125+
'This should be provided by the platform adapter.',
126+
);
127+
}
128+
129+
try {
130+
// Call internal command to execute code directly in kernel
131+
const result = await context.executeCommand<ExecuteCodeResult>(
132+
'lexical.executeCode',
133+
{
134+
lexicalId,
135+
...params,
136+
},
137+
);
138+
139+
return formatResponse(result, context.format) as ExecuteCodeResult;
140+
} catch (error) {
141+
const errorMessage =
142+
error instanceof Error ? error.message : String(error);
143+
throw new Error(`Failed to execute code: ${errorMessage}`);
144+
}
145+
},
146+
};

0 commit comments

Comments
 (0)