@jupyterlite/ai 0.16.0 → 0.18.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/lib/agent.d.ts +19 -10
- package/lib/agent.js +82 -46
- package/lib/chat-commands/clear.js +1 -1
- package/lib/chat-model-handler.d.ts +2 -3
- package/lib/chat-model-handler.js +6 -2
- package/lib/chat-model.d.ts +129 -26
- package/lib/chat-model.js +543 -160
- package/lib/components/clear-button.d.ts +1 -1
- package/lib/components/clear-button.js +1 -1
- package/lib/components/save-button.d.ts +2 -2
- package/lib/index.js +224 -59
- package/lib/models/settings-model.js +1 -0
- package/lib/providers/built-in-providers.js +1 -1
- package/lib/providers/{generated-context-windows.d.ts → generated-model-info.d.ts} +2 -2
- package/lib/providers/generated-model-info.js +502 -0
- package/lib/providers/model-info.d.ts +3 -0
- package/lib/providers/model-info.js +33 -0
- package/lib/tokens.d.ts +98 -15
- package/lib/tokens.js +1 -0
- package/lib/widgets/ai-settings.js +5 -0
- package/lib/widgets/main-area-chat.d.ts +3 -3
- package/lib/widgets/main-area-chat.js +9 -5
- package/package.json +3 -3
- package/schema/settings-model.json +6 -0
- package/src/agent.ts +100 -52
- package/src/chat-commands/clear.ts +1 -1
- package/src/chat-model-handler.ts +10 -3
- package/src/chat-model.ts +727 -210
- package/src/components/clear-button.tsx +3 -3
- package/src/components/save-button.tsx +3 -3
- package/src/index.ts +289 -83
- package/src/models/settings-model.ts +1 -0
- package/src/providers/built-in-providers.ts +1 -1
- package/src/providers/generated-model-info.ts +508 -0
- package/src/providers/model-info.ts +57 -0
- package/src/tokens.ts +100 -15
- package/src/widgets/ai-settings.tsx +26 -0
- package/src/widgets/main-area-chat.ts +14 -9
- package/lib/providers/generated-context-windows.js +0 -96
- package/src/providers/generated-context-windows.ts +0 -102
package/lib/tokens.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { ActiveCellManager,
|
|
1
|
+
import { ActiveCellManager, IChatModel, IMessage } from '@jupyter/chat';
|
|
2
2
|
import { VDomRenderer } from '@jupyterlab/apputils';
|
|
3
3
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
4
4
|
import { Token } from '@lumino/coreutils';
|
|
5
5
|
import type { IDisposable } from '@lumino/disposable';
|
|
6
6
|
import { ISignal } from '@lumino/signaling';
|
|
7
|
-
import type { Tool, LanguageModel } from 'ai';
|
|
7
|
+
import type { Tool, LanguageModel, UserContent, ModelMessage } from 'ai';
|
|
8
8
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
9
9
|
import type { IModelOptions } from './providers/models';
|
|
10
|
-
import { AIChatModel } from './chat-model';
|
|
11
10
|
import type { ISkillDefinition, ISkillRegistration, ISkillResourceResult, ISkillSummary } from './skills/types';
|
|
12
11
|
export type { ISkillDefinition, ISkillRegistration, ISkillResourceResult, ISkillSummary } from './skills/types';
|
|
13
12
|
/**
|
|
@@ -17,6 +16,7 @@ export declare namespace CommandIds {
|
|
|
17
16
|
const openSettings = "@jupyterlite/ai:open-settings";
|
|
18
17
|
const reposition = "@jupyterlite/ai:reposition";
|
|
19
18
|
const openChat = "@jupyterlite/ai:open-chat";
|
|
19
|
+
const openOrRevealChat = "@jupyterlite/ai:open-or-reveal-chat";
|
|
20
20
|
const moveChat = "@jupyterlite/ai:move-chat";
|
|
21
21
|
const refreshSkills = "@jupyterlite/ai:refresh-skills";
|
|
22
22
|
const saveChat = "@jupyterlite/ai:save-chat";
|
|
@@ -158,6 +158,18 @@ export interface IProviderModelInfo {
|
|
|
158
158
|
* Default context window for the model in tokens.
|
|
159
159
|
*/
|
|
160
160
|
contextWindow?: number;
|
|
161
|
+
/**
|
|
162
|
+
* Whether the model supports image inputs.
|
|
163
|
+
*/
|
|
164
|
+
supportsImages?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Whether the model supports PDF inputs.
|
|
167
|
+
*/
|
|
168
|
+
supportsPdf?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Whether the model supports audio inputs.
|
|
171
|
+
*/
|
|
172
|
+
supportsAudio?: boolean;
|
|
161
173
|
}
|
|
162
174
|
export interface IProviderInfo {
|
|
163
175
|
/**
|
|
@@ -302,6 +314,7 @@ export interface IAIConfig {
|
|
|
302
314
|
diffDisplayMode: 'split' | 'unified';
|
|
303
315
|
skillsPaths: string[];
|
|
304
316
|
chatBackupDirectory: string;
|
|
317
|
+
autoTitle: boolean;
|
|
305
318
|
}
|
|
306
319
|
export interface IAISettingsModel extends VDomRenderer.IModel {
|
|
307
320
|
readonly config: IAIConfig;
|
|
@@ -400,13 +413,12 @@ export declare namespace IAgentManager {
|
|
|
400
413
|
isError: boolean;
|
|
401
414
|
};
|
|
402
415
|
tool_approval_request: {
|
|
403
|
-
approvalId: string;
|
|
404
416
|
toolCallId: string;
|
|
405
417
|
toolName: string;
|
|
406
418
|
args: unknown;
|
|
407
419
|
};
|
|
408
420
|
tool_approval_resolved: {
|
|
409
|
-
|
|
421
|
+
toolCallId: string;
|
|
410
422
|
approved: boolean;
|
|
411
423
|
};
|
|
412
424
|
error: {
|
|
@@ -465,34 +477,39 @@ export interface IAgentManager {
|
|
|
465
477
|
/**
|
|
466
478
|
* Clears conversation history and resets agent state.
|
|
467
479
|
*/
|
|
468
|
-
clearHistory(): void
|
|
480
|
+
clearHistory(): Promise<void>;
|
|
469
481
|
/**
|
|
470
|
-
* Sets the
|
|
471
|
-
* @param messages
|
|
482
|
+
* Sets the history from already-processed model messages.
|
|
483
|
+
* @param messages Pre-built model messages (may include binary content)
|
|
472
484
|
*/
|
|
473
|
-
setHistory(messages:
|
|
485
|
+
setHistory(messages: ModelMessage[]): void;
|
|
474
486
|
/**
|
|
475
487
|
* Stops the current streaming response by aborting the request.
|
|
476
488
|
*/
|
|
477
489
|
stopStreaming(): void;
|
|
478
490
|
/**
|
|
479
491
|
* Approves a pending tool call.
|
|
480
|
-
* @param
|
|
492
|
+
* @param toolCallId The tool call ID to approve
|
|
481
493
|
* @param reason Optional reason for approval
|
|
482
494
|
*/
|
|
483
|
-
approveToolCall(
|
|
495
|
+
approveToolCall(toolCallId: string, reason?: string): void;
|
|
484
496
|
/**
|
|
485
497
|
* Rejects a pending tool call.
|
|
486
|
-
* @param
|
|
498
|
+
* @param toolCallId The tool call ID to reject
|
|
487
499
|
* @param reason Optional reason for rejection
|
|
488
500
|
*/
|
|
489
|
-
rejectToolCall(
|
|
501
|
+
rejectToolCall(toolCallId: string, reason?: string): void;
|
|
490
502
|
/**
|
|
491
503
|
* Generates AI response to user message using the agent.
|
|
492
504
|
* Handles the complete execution cycle including tool calls.
|
|
493
505
|
* @param message The user message to respond to (may include processed attachment content)
|
|
494
506
|
*/
|
|
495
|
-
generateResponse(message:
|
|
507
|
+
generateResponse(message: UserContent): Promise<void>;
|
|
508
|
+
/**
|
|
509
|
+
* Create a transient language model to request a text response, which won't be added to history.
|
|
510
|
+
* @param messages - the messages sequence to send to the model.
|
|
511
|
+
*/
|
|
512
|
+
textResponse(messages: ModelMessage[]): Promise<string>;
|
|
496
513
|
/**
|
|
497
514
|
* Initializes the AI agent with current settings and tools.
|
|
498
515
|
* Sets up the agent with model configuration, tools, and MCP tools.
|
|
@@ -527,6 +544,68 @@ export interface IAgentManagerFactory {
|
|
|
527
544
|
getMCPTools(): Promise<ToolMap>;
|
|
528
545
|
}
|
|
529
546
|
export declare const IAgentManagerFactory: Token<IAgentManagerFactory>;
|
|
547
|
+
export interface IAIChatModel extends IChatModel {
|
|
548
|
+
/**
|
|
549
|
+
* A signal emitting when the chat name has changed.
|
|
550
|
+
*/
|
|
551
|
+
readonly nameChanged: ISignal<IAIChatModel, string>;
|
|
552
|
+
/**
|
|
553
|
+
* The title of the chat.
|
|
554
|
+
*/
|
|
555
|
+
title: string | null;
|
|
556
|
+
/**
|
|
557
|
+
* A signal emitting when the chat title has changed.
|
|
558
|
+
*/
|
|
559
|
+
readonly titleChanged: ISignal<IAIChatModel, string | null>;
|
|
560
|
+
/**
|
|
561
|
+
* Whether to save the chat automatically.
|
|
562
|
+
*/
|
|
563
|
+
autosave: boolean;
|
|
564
|
+
/**
|
|
565
|
+
* A signal emitting when the autosave flag changed.
|
|
566
|
+
*/
|
|
567
|
+
readonly autosaveChanged: ISignal<IAIChatModel, boolean>;
|
|
568
|
+
/**
|
|
569
|
+
* Whether save/restore is available.
|
|
570
|
+
*/
|
|
571
|
+
readonly saveAvailable: boolean;
|
|
572
|
+
/**
|
|
573
|
+
* A signal emitting when the token usage changed.
|
|
574
|
+
*/
|
|
575
|
+
readonly tokenUsageChanged: ISignal<IAgentManager, ITokenUsage>;
|
|
576
|
+
/**
|
|
577
|
+
* The agent manager used in the model.
|
|
578
|
+
*/
|
|
579
|
+
readonly agentManager: IAgentManager;
|
|
580
|
+
/**
|
|
581
|
+
* Save the chat as json file.
|
|
582
|
+
*/
|
|
583
|
+
save(): Promise<void>;
|
|
584
|
+
/**
|
|
585
|
+
* Restore the chat from a json file.
|
|
586
|
+
*
|
|
587
|
+
* @param silent - Whether a log should be displayed in the console if the
|
|
588
|
+
* restoration is not possible.
|
|
589
|
+
*/
|
|
590
|
+
restore(filepath: string, silent?: boolean): Promise<boolean>;
|
|
591
|
+
/**
|
|
592
|
+
* Request a title to this chat, regarding the message history.
|
|
593
|
+
*/
|
|
594
|
+
requestTitle(): Promise<string>;
|
|
595
|
+
/**
|
|
596
|
+
* Removes a queued message by its ID.
|
|
597
|
+
* @param messageId The ID of the queued message to remove
|
|
598
|
+
*/
|
|
599
|
+
removeQueuedMessage(messageId: string): void;
|
|
600
|
+
/**
|
|
601
|
+
* The current message queue
|
|
602
|
+
*/
|
|
603
|
+
messageQueue: any[];
|
|
604
|
+
/**
|
|
605
|
+
* Whether the chat is currently busy processing a message
|
|
606
|
+
*/
|
|
607
|
+
isBusy: boolean;
|
|
608
|
+
}
|
|
530
609
|
/**
|
|
531
610
|
* The interface for the chat model handler.
|
|
532
611
|
*/
|
|
@@ -534,7 +613,7 @@ export interface IChatModelHandler {
|
|
|
534
613
|
/**
|
|
535
614
|
* The function to create a new model.
|
|
536
615
|
*/
|
|
537
|
-
createModel(options: ICreateChatOptions):
|
|
616
|
+
createModel(options: ICreateChatOptions): IAIChatModel;
|
|
538
617
|
/**
|
|
539
618
|
* The active cell manager (to copy code from chat to cell).
|
|
540
619
|
*/
|
|
@@ -561,6 +640,10 @@ export interface ICreateChatOptions {
|
|
|
561
640
|
* Whether the chat is autosaved or not.
|
|
562
641
|
*/
|
|
563
642
|
autosave?: boolean;
|
|
643
|
+
/**
|
|
644
|
+
* An optional title to the chat.
|
|
645
|
+
*/
|
|
646
|
+
title?: string | null;
|
|
564
647
|
}
|
|
565
648
|
/**
|
|
566
649
|
* Token for the chat model handler.
|
package/lib/tokens.js
CHANGED
|
@@ -7,6 +7,7 @@ export var CommandIds;
|
|
|
7
7
|
CommandIds.openSettings = '@jupyterlite/ai:open-settings';
|
|
8
8
|
CommandIds.reposition = '@jupyterlite/ai:reposition';
|
|
9
9
|
CommandIds.openChat = '@jupyterlite/ai:open-chat';
|
|
10
|
+
CommandIds.openOrRevealChat = '@jupyterlite/ai:open-or-reveal-chat';
|
|
10
11
|
CommandIds.moveChat = '@jupyterlite/ai:move-chat';
|
|
11
12
|
CommandIds.refreshSkills = '@jupyterlite/ai:refresh-skills';
|
|
12
13
|
CommandIds.saveChat = '@jupyterlite/ai:save-chat';
|
|
@@ -482,6 +482,11 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
482
482
|
}), color: "primary" }), label: React.createElement(Box, null,
|
|
483
483
|
React.createElement(Typography, { variant: "body1" }, trans.__('Send with Shift+Enter')),
|
|
484
484
|
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, trans.__('Use Shift+Enter to send messages (Enter creates new line)'))) }),
|
|
485
|
+
React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.autoTitle, onChange: e => handleConfigUpdate({
|
|
486
|
+
autoTitle: e.target.checked
|
|
487
|
+
}), color: "primary" }), label: React.createElement(Box, null,
|
|
488
|
+
React.createElement(Typography, { variant: "body1" }, trans.__('Auto Title')),
|
|
489
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, trans.__('Automatically generate a chat title from the model for every message until there are 5 messages'))) }),
|
|
485
490
|
React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.showTokenUsage, onChange: e => handleConfigUpdate({
|
|
486
491
|
showTokenUsage: e.target.checked
|
|
487
492
|
}), color: "primary" }), label: React.createElement(Box, null,
|
|
@@ -2,8 +2,7 @@ import { ChatWidget } from '@jupyter/chat';
|
|
|
2
2
|
import { MainAreaWidget } from '@jupyterlab/apputils';
|
|
3
3
|
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
4
4
|
import { CommandRegistry } from '@lumino/commands';
|
|
5
|
-
import {
|
|
6
|
-
import { type IAISettingsModel } from '../tokens';
|
|
5
|
+
import { IAIChatModel, type IAISettingsModel } from '../tokens';
|
|
7
6
|
export declare namespace MainAreaChat {
|
|
8
7
|
interface IOptions extends MainAreaWidget.IOptions<ChatWidget> {
|
|
9
8
|
commands: CommandRegistry;
|
|
@@ -20,11 +19,12 @@ export declare class MainAreaChat extends MainAreaWidget<ChatWidget> {
|
|
|
20
19
|
/**
|
|
21
20
|
* Get the model of the chat.
|
|
22
21
|
*/
|
|
23
|
-
get model():
|
|
22
|
+
get model(): IAIChatModel;
|
|
24
23
|
/**
|
|
25
24
|
* Get the area of the chat.
|
|
26
25
|
*/
|
|
27
26
|
get area(): string | undefined;
|
|
28
27
|
private _writersChanged;
|
|
28
|
+
private _titleChanged;
|
|
29
29
|
private _outputAreaCompat;
|
|
30
30
|
}
|
|
@@ -10,7 +10,8 @@ import { CommandIds } from '../tokens';
|
|
|
10
10
|
export class MainAreaChat extends MainAreaWidget {
|
|
11
11
|
constructor(options) {
|
|
12
12
|
super(options);
|
|
13
|
-
this.title.label = this.
|
|
13
|
+
this.title.label = this.model.name;
|
|
14
|
+
this.title.caption = this.model.title ?? this.model.name;
|
|
14
15
|
const { trans } = options;
|
|
15
16
|
// Move to side button.
|
|
16
17
|
this.toolbar.addItem('moveToSide', new CommandToolbarButton({
|
|
@@ -42,13 +43,15 @@ export class MainAreaChat extends MainAreaWidget {
|
|
|
42
43
|
this._outputAreaCompat = new RenderedMessageOutputAreaCompat({
|
|
43
44
|
chatPanel: this.content
|
|
44
45
|
});
|
|
45
|
-
this.model.writersChanged
|
|
46
|
+
this.model.writersChanged?.connect(this._writersChanged);
|
|
47
|
+
this.model.titleChanged.connect(this._titleChanged);
|
|
46
48
|
}
|
|
47
49
|
dispose() {
|
|
48
50
|
super.dispose();
|
|
49
51
|
// Dispose of the approval buttons widget when the chat is disposed.
|
|
50
52
|
this._outputAreaCompat.dispose();
|
|
51
|
-
this.model.writersChanged
|
|
53
|
+
this.model.writersChanged?.disconnect(this._writersChanged);
|
|
54
|
+
this.model.titleChanged.disconnect(this._titleChanged);
|
|
52
55
|
}
|
|
53
56
|
/**
|
|
54
57
|
* Get the model of the chat.
|
|
@@ -66,13 +69,14 @@ export class MainAreaChat extends MainAreaWidget {
|
|
|
66
69
|
// Check if AI is currently writing (streaming)
|
|
67
70
|
const aiWriting = writers.some(writer => writer.user.username === 'ai-assistant');
|
|
68
71
|
if (aiWriting) {
|
|
69
|
-
this.content.inputToolbarRegistry?.hide('send');
|
|
70
72
|
this.content.inputToolbarRegistry?.show('stop');
|
|
71
73
|
}
|
|
72
74
|
else {
|
|
73
75
|
this.content.inputToolbarRegistry?.hide('stop');
|
|
74
|
-
this.content.inputToolbarRegistry?.show('send');
|
|
75
76
|
}
|
|
76
77
|
};
|
|
78
|
+
_titleChanged = () => {
|
|
79
|
+
this.title.caption = this.model.title ?? this.model.name;
|
|
80
|
+
};
|
|
77
81
|
_outputAreaCompat;
|
|
78
82
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"watch:labextension": "jupyter labextension watch .",
|
|
55
55
|
"docs": "jupyter book start",
|
|
56
56
|
"docs:build": "sed -e 's/\\[@/[/g' -e 's/@/\\@/g' CHANGELOG.md > docs/_changelog_content.md && jupyter book build --html",
|
|
57
|
-
"sync:model-
|
|
57
|
+
"sync:model-info": "node scripts/sync-model-info.mjs && prettier --write src/providers/generated-model-info.ts && eslint --fix src/providers/generated-model-info.ts"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@ai-sdk/anthropic": "^3.0.58",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@mui/icons-material": "^7",
|
|
90
90
|
"@mui/material": "^7",
|
|
91
91
|
"ai": "^6.0.116",
|
|
92
|
-
"jupyter-chat-components": "^0.
|
|
92
|
+
"jupyter-chat-components": "^0.5.0",
|
|
93
93
|
"jupyter-secrets-manager": "^0.5.0",
|
|
94
94
|
"yaml": "^2.8.1",
|
|
95
95
|
"zod": "^4.3.6"
|
|
@@ -210,6 +210,12 @@
|
|
|
210
210
|
"type": "boolean",
|
|
211
211
|
"default": false
|
|
212
212
|
},
|
|
213
|
+
"autoTitle": {
|
|
214
|
+
"title": "Auto Title",
|
|
215
|
+
"description": "Automatically request a title from the model for every message until there are 5 messages",
|
|
216
|
+
"type": "boolean",
|
|
217
|
+
"default": false
|
|
218
|
+
},
|
|
213
219
|
"showTokenUsage": {
|
|
214
220
|
"title": "Show Token Usage",
|
|
215
221
|
"description": "Display token usage information in the chat toolbar",
|
package/src/agent.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createMCPClient, type MCPClient } from '@ai-sdk/mcp';
|
|
2
|
-
import type { IMessageContent } from '@jupyter/chat';
|
|
3
2
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
4
3
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
5
4
|
import { ISignal, Signal } from '@lumino/signaling';
|
|
6
5
|
import {
|
|
6
|
+
generateText,
|
|
7
7
|
ToolLoopAgent,
|
|
8
8
|
type ModelMessage,
|
|
9
9
|
type LanguageModel,
|
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
type TypedToolError,
|
|
14
14
|
type TypedToolOutputDenied,
|
|
15
15
|
type TypedToolResult,
|
|
16
|
-
type
|
|
16
|
+
type UserContent,
|
|
17
|
+
type AssistantModelMessage,
|
|
18
|
+
APICallError
|
|
17
19
|
} from 'ai';
|
|
18
20
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
19
21
|
|
|
@@ -487,31 +489,12 @@ export class AgentManager implements IAgentManager {
|
|
|
487
489
|
}
|
|
488
490
|
|
|
489
491
|
/**
|
|
490
|
-
* Sets the history
|
|
491
|
-
* @param messages
|
|
492
|
+
* Sets the history from already-processed model messages.
|
|
493
|
+
* @param messages Pre-built model messages (may include binary content)
|
|
492
494
|
*/
|
|
493
|
-
setHistory(messages:
|
|
494
|
-
|
|
495
|
-
this.
|
|
496
|
-
|
|
497
|
-
for (const [approvalId, pending] of this._pendingApprovals) {
|
|
498
|
-
pending.resolve(false, 'Chat history changed');
|
|
499
|
-
this._agentEvent.emit({
|
|
500
|
-
type: 'tool_approval_resolved',
|
|
501
|
-
data: { approvalId, approved: false }
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
this._pendingApprovals.clear();
|
|
505
|
-
|
|
506
|
-
// Convert chat messages to model messages
|
|
507
|
-
const modelMessages = messages.map(msg => {
|
|
508
|
-
const isAIMessage = msg.sender.username === 'ai-assistant';
|
|
509
|
-
return {
|
|
510
|
-
role: isAIMessage ? 'assistant' : 'user',
|
|
511
|
-
content: msg.body
|
|
512
|
-
} as ModelMessage;
|
|
513
|
-
});
|
|
514
|
-
this._history = Private.sanitizeModelMessages(modelMessages);
|
|
495
|
+
setHistory(messages: ModelMessage[]): void {
|
|
496
|
+
this.stopStreaming('Chat history changed');
|
|
497
|
+
this._history = Private.sanitizeModelMessages(messages);
|
|
515
498
|
}
|
|
516
499
|
|
|
517
500
|
/**
|
|
@@ -522,11 +505,11 @@ export class AgentManager implements IAgentManager {
|
|
|
522
505
|
this._controller?.abort();
|
|
523
506
|
|
|
524
507
|
// Reject any pending approvals
|
|
525
|
-
for (const [
|
|
508
|
+
for (const [toolCallId, pending] of this._pendingApprovals) {
|
|
526
509
|
pending.resolve(false, reason ?? 'Stream ended by user');
|
|
527
510
|
this._agentEvent.emit({
|
|
528
511
|
type: 'tool_approval_resolved',
|
|
529
|
-
data: {
|
|
512
|
+
data: { toolCallId, approved: false }
|
|
530
513
|
});
|
|
531
514
|
}
|
|
532
515
|
this._pendingApprovals.clear();
|
|
@@ -534,34 +517,34 @@ export class AgentManager implements IAgentManager {
|
|
|
534
517
|
|
|
535
518
|
/**
|
|
536
519
|
* Approves a pending tool call.
|
|
537
|
-
* @param
|
|
520
|
+
* @param toolCallId The tool call ID to approve
|
|
538
521
|
* @param reason Optional reason for approval
|
|
539
522
|
*/
|
|
540
|
-
approveToolCall(
|
|
541
|
-
const pending = this._pendingApprovals.get(
|
|
523
|
+
approveToolCall(toolCallId: string, reason?: string): void {
|
|
524
|
+
const pending = this._pendingApprovals.get(toolCallId);
|
|
542
525
|
if (pending) {
|
|
543
526
|
pending.resolve(true, reason);
|
|
544
|
-
this._pendingApprovals.delete(
|
|
527
|
+
this._pendingApprovals.delete(toolCallId);
|
|
545
528
|
this._agentEvent.emit({
|
|
546
529
|
type: 'tool_approval_resolved',
|
|
547
|
-
data: {
|
|
530
|
+
data: { toolCallId, approved: true }
|
|
548
531
|
});
|
|
549
532
|
}
|
|
550
533
|
}
|
|
551
534
|
|
|
552
535
|
/**
|
|
553
536
|
* Rejects a pending tool call.
|
|
554
|
-
* @param
|
|
537
|
+
* @param toolCallId The tool call ID to reject
|
|
555
538
|
* @param reason Optional reason for rejection
|
|
556
539
|
*/
|
|
557
|
-
rejectToolCall(
|
|
558
|
-
const pending = this._pendingApprovals.get(
|
|
540
|
+
rejectToolCall(toolCallId: string, reason?: string): void {
|
|
541
|
+
const pending = this._pendingApprovals.get(toolCallId);
|
|
559
542
|
if (pending) {
|
|
560
543
|
pending.resolve(false, reason);
|
|
561
|
-
this._pendingApprovals.delete(
|
|
544
|
+
this._pendingApprovals.delete(toolCallId);
|
|
562
545
|
this._agentEvent.emit({
|
|
563
546
|
type: 'tool_approval_resolved',
|
|
564
|
-
data: {
|
|
547
|
+
data: { toolCallId, approved: false }
|
|
565
548
|
});
|
|
566
549
|
}
|
|
567
550
|
}
|
|
@@ -571,10 +554,17 @@ export class AgentManager implements IAgentManager {
|
|
|
571
554
|
* Handles the complete execution cycle including tool calls.
|
|
572
555
|
* @param message The user message to respond to (may include processed attachment content)
|
|
573
556
|
*/
|
|
574
|
-
async generateResponse(message:
|
|
557
|
+
async generateResponse(message: UserContent): Promise<void> {
|
|
575
558
|
this._streaming = new PromiseDelegate();
|
|
576
559
|
this._controller = new AbortController();
|
|
577
560
|
const responseHistory: ModelMessage[] = [];
|
|
561
|
+
|
|
562
|
+
// Add user message to history
|
|
563
|
+
responseHistory.push({
|
|
564
|
+
role: 'user',
|
|
565
|
+
content: message
|
|
566
|
+
});
|
|
567
|
+
|
|
578
568
|
try {
|
|
579
569
|
// Ensure we have an agent
|
|
580
570
|
if (!this._agent) {
|
|
@@ -585,12 +575,6 @@ export class AgentManager implements IAgentManager {
|
|
|
585
575
|
throw new Error('Failed to initialize agent');
|
|
586
576
|
}
|
|
587
577
|
|
|
588
|
-
// Add user message to history
|
|
589
|
-
responseHistory.push({
|
|
590
|
-
role: 'user',
|
|
591
|
-
content: message
|
|
592
|
-
});
|
|
593
|
-
|
|
594
578
|
let continueLoop = true;
|
|
595
579
|
while (continueLoop) {
|
|
596
580
|
const result = await this._agent.stream({
|
|
@@ -647,9 +631,32 @@ export class AgentManager implements IAgentManager {
|
|
|
647
631
|
this._history.push(...Private.sanitizeModelMessages(responseHistory));
|
|
648
632
|
} catch (error) {
|
|
649
633
|
if ((error as Error).name !== 'AbortError') {
|
|
634
|
+
let helpMessage = `${(error as Error).message}`;
|
|
635
|
+
|
|
636
|
+
// Remove attachments from history on payload rejection errors
|
|
637
|
+
if (
|
|
638
|
+
APICallError.isInstance(error) &&
|
|
639
|
+
(error.statusCode === 400 ||
|
|
640
|
+
error.statusCode === 404 ||
|
|
641
|
+
error.statusCode === 413 ||
|
|
642
|
+
error.statusCode === 415 ||
|
|
643
|
+
error.statusCode === 422)
|
|
644
|
+
) {
|
|
645
|
+
this._stripAttachments(
|
|
646
|
+
[...this._history, ...responseHistory],
|
|
647
|
+
'_Attachment removed due to error_'
|
|
648
|
+
);
|
|
649
|
+
helpMessage +=
|
|
650
|
+
'\n\nAttachments have been removed from history. Please send your prompt again.';
|
|
651
|
+
}
|
|
650
652
|
this._agentEvent.emit({
|
|
651
653
|
type: 'error',
|
|
652
|
-
data: { error:
|
|
654
|
+
data: { error: new Error(helpMessage) }
|
|
655
|
+
});
|
|
656
|
+
this._history.push(...Private.sanitizeModelMessages(responseHistory));
|
|
657
|
+
this._history.push({
|
|
658
|
+
role: 'assistant',
|
|
659
|
+
content: helpMessage
|
|
653
660
|
});
|
|
654
661
|
}
|
|
655
662
|
} finally {
|
|
@@ -658,6 +665,24 @@ export class AgentManager implements IAgentManager {
|
|
|
658
665
|
}
|
|
659
666
|
}
|
|
660
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Create a transient language model to request a text response which won't be added to history.
|
|
670
|
+
* @param messages - the messages sequence to send to the model.
|
|
671
|
+
*/
|
|
672
|
+
async textResponse(messages: ModelMessage[]): Promise<string> {
|
|
673
|
+
try {
|
|
674
|
+
const model = await this._createModel();
|
|
675
|
+
const result = await generateText({
|
|
676
|
+
model,
|
|
677
|
+
messages
|
|
678
|
+
});
|
|
679
|
+
this._updateTokenUsage(result.totalUsage, result.totalUsage.inputTokens);
|
|
680
|
+
return result.text;
|
|
681
|
+
} catch (e) {
|
|
682
|
+
throw `Error while getting the topic of the chat\n${e}`;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
661
686
|
/**
|
|
662
687
|
* Updates cumulative token usage statistics from a completed model step.
|
|
663
688
|
*/
|
|
@@ -680,6 +705,27 @@ export class AgentManager implements IAgentManager {
|
|
|
680
705
|
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
681
706
|
}
|
|
682
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Removes image and file parts from all user messages in the given list.
|
|
710
|
+
*/
|
|
711
|
+
private _stripAttachments(
|
|
712
|
+
messages: ModelMessage[],
|
|
713
|
+
placeholder: string
|
|
714
|
+
): void {
|
|
715
|
+
for (const msg of messages) {
|
|
716
|
+
if (msg.role === 'user' && Array.isArray(msg.content)) {
|
|
717
|
+
const hasMedia = msg.content.some(p => p.type !== 'text');
|
|
718
|
+
if (hasMedia) {
|
|
719
|
+
const textContent = msg.content
|
|
720
|
+
.filter(p => p.type === 'text')
|
|
721
|
+
.map(p => (p as { text: string }).text)
|
|
722
|
+
.join('\n');
|
|
723
|
+
msg.content = textContent || placeholder;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
683
729
|
/**
|
|
684
730
|
* Gets the configured context window for the active provider.
|
|
685
731
|
*/
|
|
@@ -932,6 +978,9 @@ ${richOutputWorkflowInstruction}`;
|
|
|
932
978
|
await this._handleApprovalRequest(part, processResult);
|
|
933
979
|
break;
|
|
934
980
|
|
|
981
|
+
case 'error':
|
|
982
|
+
throw part.error;
|
|
983
|
+
|
|
935
984
|
case 'finish-step':
|
|
936
985
|
this._updateTokenUsage(part.usage, part.usage.inputTokens);
|
|
937
986
|
break;
|
|
@@ -940,7 +989,7 @@ ${richOutputWorkflowInstruction}`;
|
|
|
940
989
|
processResult.aborted = true;
|
|
941
990
|
break;
|
|
942
991
|
|
|
943
|
-
// Ignore: text-start, text-end, finish,
|
|
992
|
+
// Ignore: text-start, text-end, finish, and others
|
|
944
993
|
default:
|
|
945
994
|
break;
|
|
946
995
|
}
|
|
@@ -1034,14 +1083,13 @@ ${richOutputWorkflowInstruction}`;
|
|
|
1034
1083
|
this._agentEvent.emit({
|
|
1035
1084
|
type: 'tool_approval_request',
|
|
1036
1085
|
data: {
|
|
1037
|
-
approvalId,
|
|
1038
1086
|
toolCallId: toolCall.toolCallId,
|
|
1039
1087
|
toolName: toolCall.toolName,
|
|
1040
1088
|
args: toolCall.input
|
|
1041
1089
|
}
|
|
1042
1090
|
});
|
|
1043
1091
|
|
|
1044
|
-
const approved = await this._waitForApproval(
|
|
1092
|
+
const approved = await this._waitForApproval(toolCall.toolCallId);
|
|
1045
1093
|
|
|
1046
1094
|
result.approvalProcessed = true;
|
|
1047
1095
|
result.approvalResponse = {
|
|
@@ -1058,12 +1106,12 @@ ${richOutputWorkflowInstruction}`;
|
|
|
1058
1106
|
|
|
1059
1107
|
/**
|
|
1060
1108
|
* Waits for user approval of a tool call.
|
|
1061
|
-
* @param
|
|
1109
|
+
* @param toolCallId The tool call ID to wait for approval
|
|
1062
1110
|
* @returns Promise that resolves to true if approved, false if rejected
|
|
1063
1111
|
*/
|
|
1064
|
-
private _waitForApproval(
|
|
1112
|
+
private _waitForApproval(toolCallId: string): Promise<boolean> {
|
|
1065
1113
|
return new Promise(resolve => {
|
|
1066
|
-
this._pendingApprovals.set(
|
|
1114
|
+
this._pendingApprovals.set(toolCallId, {
|
|
1067
1115
|
resolve: (approved: boolean) => {
|
|
1068
1116
|
resolve(approved);
|
|
1069
1117
|
}
|
|
@@ -29,7 +29,7 @@ export class ClearCommandProvider implements IChatCommandProvider {
|
|
|
29
29
|
const context = inputModel.chatContext as
|
|
30
30
|
| AIChatModel.IAIChatContext
|
|
31
31
|
| undefined;
|
|
32
|
-
context?.clearMessages?.();
|
|
32
|
+
await context?.clearMessages?.();
|
|
33
33
|
|
|
34
34
|
inputModel.value = '';
|
|
35
35
|
inputModel.clearAttachments();
|
|
@@ -6,6 +6,7 @@ import { Contents } from '@jupyterlab/services';
|
|
|
6
6
|
import { AIChatModel } from './chat-model';
|
|
7
7
|
import type {
|
|
8
8
|
IAgentManagerFactory,
|
|
9
|
+
IAIChatModel,
|
|
9
10
|
IAISettingsModel,
|
|
10
11
|
IChatModelHandler,
|
|
11
12
|
ICreateChatOptions,
|
|
@@ -28,8 +29,9 @@ export class ChatModelHandler implements IChatModelHandler {
|
|
|
28
29
|
this._contentsManager = options.contentsManager;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
createModel(options: ICreateChatOptions):
|
|
32
|
-
const { name, activeProvider, tokenUsage, messages, autosave } =
|
|
32
|
+
createModel(options: ICreateChatOptions): IAIChatModel {
|
|
33
|
+
const { name, activeProvider, tokenUsage, messages, autosave, title } =
|
|
34
|
+
options;
|
|
33
35
|
|
|
34
36
|
// Create Agent Manager first so it can be shared
|
|
35
37
|
const agentManager = this._agentManagerFactory.createAgent({
|
|
@@ -48,7 +50,8 @@ export class ChatModelHandler implements IChatModelHandler {
|
|
|
48
50
|
agentManager,
|
|
49
51
|
activeCellManager: this._activeCellManager,
|
|
50
52
|
documentManager: this._docManager,
|
|
51
|
-
contentsManager: this._contentsManager
|
|
53
|
+
contentsManager: this._contentsManager,
|
|
54
|
+
providerRegistry: this._providerRegistry
|
|
52
55
|
});
|
|
53
56
|
|
|
54
57
|
messages?.forEach(message => {
|
|
@@ -58,6 +61,10 @@ export class ChatModelHandler implements IChatModelHandler {
|
|
|
58
61
|
|
|
59
62
|
model.name = name;
|
|
60
63
|
|
|
64
|
+
if (title) {
|
|
65
|
+
model.title = title;
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
return model;
|
|
62
69
|
}
|
|
63
70
|
|