Skip to content

Commit 8c8c45f

Browse files
authored
Update secrets manager to >=0.3.0 (#63)
1 parent 830b2c3 commit 8c8c45f

File tree

8 files changed

+155
-77
lines changed

8 files changed

+155
-77
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
"@rjsf/utils": "^5.18.4",
8080
"@rjsf/validator-ajv8": "^5.18.4",
8181
"json5": "^2.2.3",
82-
"jupyter-secrets-manager": "^0.2.0",
82+
"jupyter-secrets-manager": "^0.3.0",
8383
"react": "^18.2.0",
8484
"react-dom": "^18.2.0"
8585
},

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ classifiers = [
2323
"Programming Language :: Python :: 3.12",
2424
]
2525
dependencies = [
26-
"jupyter-secrets-manager"
26+
"jupyter-secrets-manager>=0.3.0"
2727
]
2828
dynamic = ["version", "description", "authors", "urls", "keywords"]
2929

src/index.ts

Lines changed: 62 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,18 @@ import {
2121
} from '@jupyterlab/settingregistry';
2222
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
2323
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
24-
import { ISecretsManager } from 'jupyter-secrets-manager';
24+
import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
2525

2626
import { ChatHandler } from './chat-handler';
2727
import { CompletionProvider } from './completion-provider';
2828
import { defaultProviderPlugins } from './default-providers';
2929
import { AIProviderRegistry } from './provider';
3030
import { aiSettingsRenderer, SettingConnector } from './settings';
31-
import { IAIProviderRegistry } from './tokens';
31+
import { IAIProviderRegistry, PLUGIN_IDS } from './tokens';
3232
import { stopItem } from './components/stop-button';
3333

3434
const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
35-
id: '@jupyterlite/ai:autocompletion-registry',
35+
id: PLUGIN_IDS.chatCommandRegistry,
3636
description: 'Autocompletion registry',
3737
autoStart: true,
3838
provides: IChatCommandRegistry,
@@ -44,7 +44,7 @@ const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
4444
};
4545

4646
const chatPlugin: JupyterFrontEndPlugin<void> = {
47-
id: '@jupyterlite/ai:chat',
47+
id: PLUGIN_IDS.chat,
4848
description: 'LLM chat extension',
4949
autoStart: true,
5050
requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
@@ -141,7 +141,7 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
141141
};
142142

143143
const completerPlugin: JupyterFrontEndPlugin<void> = {
144-
id: '@jupyterlite/ai:completer',
144+
id: PLUGIN_IDS.completer,
145145
autoStart: true,
146146
requires: [IAIProviderRegistry, ICompletionProviderManager],
147147
activate: (
@@ -157,67 +157,72 @@ const completerPlugin: JupyterFrontEndPlugin<void> = {
157157
}
158158
};
159159

160-
const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
161-
id: '@jupyterlite/ai:provider-registry',
162-
autoStart: true,
163-
requires: [IFormRendererRegistry, ISettingRegistry],
164-
optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
165-
provides: IAIProviderRegistry,
166-
activate: (
167-
app: JupyterFrontEnd,
168-
editorRegistry: IFormRendererRegistry,
169-
settingRegistry: ISettingRegistry,
170-
rmRegistry?: IRenderMimeRegistry,
171-
secretsManager?: ISecretsManager,
172-
settingConnector?: ISettingConnector
173-
): IAIProviderRegistry => {
174-
const providerRegistry = new AIProviderRegistry({ secretsManager });
175-
176-
editorRegistry.addRenderer(
177-
'@jupyterlite/ai:provider-registry.AIprovider',
178-
aiSettingsRenderer({
179-
providerRegistry,
180-
rmRegistry,
181-
secretsManager,
182-
settingConnector
183-
})
184-
);
185-
186-
settingRegistry
187-
.load(providerRegistryPlugin.id)
188-
.then(settings => {
189-
const updateProvider = () => {
190-
// Update the settings to the AI providers.
191-
const providerSettings = (settings.get('AIprovider').composite ?? {
192-
provider: 'None'
193-
}) as ReadonlyPartialJSONObject;
194-
providerRegistry.setProvider({
195-
name: providerSettings.provider as string,
196-
settings: providerSettings
197-
});
198-
};
199-
200-
settings.changed.connect(() => updateProvider());
201-
updateProvider();
202-
})
203-
.catch(reason => {
204-
console.error(
205-
`Failed to load settings for ${providerRegistryPlugin.id}`,
206-
reason
207-
);
160+
const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> =
161+
SecretsManager.sign(PLUGIN_IDS.providerRegistry, token => ({
162+
id: PLUGIN_IDS.providerRegistry,
163+
autoStart: true,
164+
requires: [IFormRendererRegistry, ISettingRegistry],
165+
optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
166+
provides: IAIProviderRegistry,
167+
activate: (
168+
app: JupyterFrontEnd,
169+
editorRegistry: IFormRendererRegistry,
170+
settingRegistry: ISettingRegistry,
171+
rmRegistry?: IRenderMimeRegistry,
172+
secretsManager?: ISecretsManager,
173+
settingConnector?: ISettingConnector
174+
): IAIProviderRegistry => {
175+
const providerRegistry = new AIProviderRegistry({
176+
token,
177+
secretsManager
208178
});
209179

210-
return providerRegistry;
211-
}
212-
};
180+
editorRegistry.addRenderer(
181+
`${PLUGIN_IDS.providerRegistry}.AIprovider`,
182+
aiSettingsRenderer({
183+
providerRegistry,
184+
secretsToken: token,
185+
rmRegistry,
186+
secretsManager,
187+
settingConnector
188+
})
189+
);
190+
191+
settingRegistry
192+
.load(providerRegistryPlugin.id)
193+
.then(settings => {
194+
const updateProvider = () => {
195+
// Update the settings to the AI providers.
196+
const providerSettings = (settings.get('AIprovider').composite ?? {
197+
provider: 'None'
198+
}) as ReadonlyPartialJSONObject;
199+
providerRegistry.setProvider({
200+
name: providerSettings.provider as string,
201+
settings: providerSettings
202+
});
203+
};
204+
205+
settings.changed.connect(() => updateProvider());
206+
updateProvider();
207+
})
208+
.catch(reason => {
209+
console.error(
210+
`Failed to load settings for ${providerRegistryPlugin.id}`,
211+
reason
212+
);
213+
});
214+
215+
return providerRegistry;
216+
}
217+
}));
213218

214219
/**
215220
* Provides the settings connector as a separate plugin to allow for alternative
216221
* implementations that may want to fetch settings from a different source or
217222
* endpoint.
218223
*/
219224
const settingsConnector: JupyterFrontEndPlugin<ISettingConnector> = {
220-
id: '@jupyterlite/ai:settings-connector',
225+
id: PLUGIN_IDS.settingsConnector,
221226
description: 'Provides a settings connector which does not save passwords.',
222227
autoStart: true,
223228
provides: ISettingConnector,

src/provider.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@ import { JSONSchema7 } from 'json-schema';
66
import { ISecretsManager } from 'jupyter-secrets-manager';
77

88
import { IBaseCompleter } from './base-completer';
9-
import {
10-
getSecretId,
11-
SECRETS_NAMESPACE,
12-
SECRETS_REPLACEMENT
13-
} from './settings';
9+
import { getSecretId, SECRETS_REPLACEMENT } from './settings';
1410
import {
1511
IAIProvider,
1612
IAIProviderRegistry,
1713
IDict,
18-
ISetProviderOptions
14+
ISetProviderOptions,
15+
PLUGIN_IDS
1916
} from './tokens';
2017

18+
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
19+
2120
export const chatSystemPrompt = (
2221
options: AIProviderRegistry.IPromptOptions
2322
) => `
@@ -54,6 +53,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
5453
*/
5554
constructor(options: AIProviderRegistry.IOptions) {
5655
this._secretsManager = options.secretsManager || null;
56+
Private.setToken(options.token);
5757
}
5858

5959
/**
@@ -171,7 +171,11 @@ export class AIProviderRegistry implements IAIProviderRegistry {
171171
for (const key of Object.keys(settings)) {
172172
if (settings[key] === SECRETS_REPLACEMENT) {
173173
const id = getSecretId(name, key);
174-
const secrets = await this._secretsManager?.get(SECRETS_NAMESPACE, id);
174+
const secrets = await this._secretsManager?.get(
175+
Private.getToken(),
176+
SECRETS_NAMESPACE,
177+
id
178+
);
175179
fullSettings[key] = secrets?.value || settings[key];
176180
continue;
177181
}
@@ -236,6 +240,10 @@ export namespace AIProviderRegistry {
236240
* The secrets manager used in the application.
237241
*/
238242
secretsManager?: ISecretsManager;
243+
/**
244+
* The token used to request the secrets manager.
245+
*/
246+
token: symbol;
239247
}
240248

241249
/**
@@ -290,3 +298,24 @@ export namespace AIProviderRegistry {
290298
});
291299
}
292300
}
301+
302+
namespace Private {
303+
/**
304+
* The token to use with the secrets manager.
305+
*/
306+
let secretsToken: symbol;
307+
308+
/**
309+
* Set of the token.
310+
*/
311+
export function setToken(value: symbol): void {
312+
secretsToken = value;
313+
}
314+
315+
/**
316+
* get the token.
317+
*/
318+
export function getToken(): symbol {
319+
return secretsToken;
320+
}
321+
}

src/settings/panel.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,27 @@ import { JSONSchema7 } from 'json-schema';
1313
import { ISecretsManager } from 'jupyter-secrets-manager';
1414
import React from 'react';
1515

16-
import { getSecretId, SECRETS_NAMESPACE, SettingConnector } from '.';
16+
import { getSecretId, SettingConnector } from '.';
1717
import baseSettings from './base.json';
18-
import { IAIProviderRegistry, IDict } from '../tokens';
18+
import { IAIProviderRegistry, IDict, PLUGIN_IDS } from '../tokens';
1919

2020
const MD_MIME_TYPE = 'text/markdown';
2121
const STORAGE_NAME = '@jupyterlite/ai:settings';
2222
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
23+
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
2324

2425
export const aiSettingsRenderer = (options: {
2526
providerRegistry: IAIProviderRegistry;
27+
secretsToken?: symbol;
2628
rmRegistry?: IRenderMimeRegistry;
2729
secretsManager?: ISecretsManager;
2830
settingConnector?: ISettingConnector;
2931
}): IFormRenderer => {
32+
const { secretsToken } = options;
33+
delete options.secretsToken;
34+
if (secretsToken) {
35+
Private.setToken(secretsToken);
36+
}
3037
return {
3138
fieldRenderer: (props: FieldProps) => {
3239
props.formContext = { ...props.formContext, ...options };
@@ -128,7 +135,7 @@ export class AiSettings extends React.Component<
128135
return;
129136
}
130137

131-
await this._secretsManager.detachAll(SECRETS_NAMESPACE);
138+
await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
132139
this._formInputs = [...inputs];
133140
this._unsavedFields = [];
134141
for (let i = 0; i < inputs.length; i++) {
@@ -137,6 +144,7 @@ export class AiSettings extends React.Component<
137144
if (label) {
138145
const id = getSecretId(this._provider, label);
139146
this._secretsManager.attach(
147+
Private.getToken(),
140148
SECRETS_NAMESPACE,
141149
id,
142150
inputs[i],
@@ -151,6 +159,13 @@ export class AiSettings extends React.Component<
151159
}
152160
}
153161

162+
componentWillUnmount(): void {
163+
if (!this._secretsManager || !this._useSecretsManager) {
164+
return;
165+
}
166+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
167+
}
168+
154169
/**
155170
* Get the current provider from the local storage.
156171
*/
@@ -192,7 +207,7 @@ export class AiSettings extends React.Component<
192207
if (!value) {
193208
// Detach all the password inputs attached to the secrets manager, and save the
194209
// current settings to the local storage to save the password.
195-
this._secretsManager?.detachAll(SECRETS_NAMESPACE);
210+
this._secretsManager?.detachAll(Private.getToken(), SECRETS_NAMESPACE);
196211
this._formInputs = [];
197212
this._unsavedFields = [];
198213
if (this._settingConnector instanceof SettingConnector) {
@@ -359,3 +374,24 @@ export class AiSettings extends React.Component<
359374
private _unsavedFields: string[] = [];
360375
private _formInputs: HTMLInputElement[] = [];
361376
}
377+
378+
namespace Private {
379+
/**
380+
* The token to use with the secrets manager.
381+
*/
382+
let secretsToken: symbol;
383+
384+
/**
385+
* Set of the token.
386+
*/
387+
export function setToken(value: symbol): void {
388+
secretsToken = value;
389+
}
390+
391+
/**
392+
* get the token.
393+
*/
394+
export function getToken(): symbol {
395+
return secretsToken;
396+
}
397+
}

src/settings/utils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export const SECRETS_NAMESPACE = '@jupyterlite/ai';
21
export const SECRETS_REPLACEMENT = '***';
32

43
export function getSecretId(provider: string, label: string) {

src/tokens.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import { JSONSchema7 } from 'json-schema';
55

66
import { IBaseCompleter } from './base-completer';
77

8+
export const PLUGIN_IDS = {
9+
chat: '@jupyterlite/ai:chat',
10+
chatCommandRegistry: '@jupyterlite/ai:autocompletion-registry',
11+
completer: '@jupyterlite/ai:completer',
12+
providerRegistry: '@jupyterlite/ai:provider-registry',
13+
settingsConnector: '@jupyterlite/ai:settings-connector'
14+
};
15+
816
export interface IDict<T = any> {
917
[key: string]: T;
1018
}

0 commit comments

Comments
 (0)