@rimori/client 2.5.48 → 2.5.49
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/cli/scripts/release/release-config-upload.d.ts +9 -0
- package/dist/cli/scripts/release/release-config-upload.js +63 -0
- package/dist/cli/scripts/release/release.js +4 -0
- package/dist/cli/types/PromptTypes.d.ts +3 -0
- package/dist/cli/types/PromptTypes.js +0 -2
- package/dist/fromRimori/PluginTypes.d.ts +16 -0
- package/dist/plugin/CommunicationHandler.d.ts +3 -1
- package/dist/plugin/RimoriClient.js +4 -2
- package/dist/plugin/module/AIModule.d.ts +13 -5
- package/dist/plugin/module/AIModule.js +19 -14
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Config } from './release';
|
|
2
|
+
/**
|
|
3
|
+
* Read `rimori/rimori.config.ts` and store it on the plugin's `plugin` row, so the backend has
|
|
4
|
+
* a live source of truth for the plugin's pages and actions (see planning/plugin-config-in-db.md).
|
|
5
|
+
*
|
|
6
|
+
* Unlike db.config.ts / prompts.config.ts (both optional and skipped when absent),
|
|
7
|
+
* rimori.config.ts is mandatory — a plugin without one cannot be released.
|
|
8
|
+
*/
|
|
9
|
+
export default function configUpload(config: Config): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
/**
|
|
5
|
+
* Read `rimori/rimori.config.ts` and store it on the plugin's `plugin` row, so the backend has
|
|
6
|
+
* a live source of truth for the plugin's pages and actions (see planning/plugin-config-in-db.md).
|
|
7
|
+
*
|
|
8
|
+
* Unlike db.config.ts / prompts.config.ts (both optional and skipped when absent),
|
|
9
|
+
* rimori.config.ts is mandatory — a plugin without one cannot be released.
|
|
10
|
+
*/
|
|
11
|
+
export default async function configUpload(config) {
|
|
12
|
+
const configPath = path.resolve('./rimori/rimori.config.ts');
|
|
13
|
+
try {
|
|
14
|
+
await fs.promises.access(configPath);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error('Could not find rimori.config.ts in ./rimori/ directory');
|
|
18
|
+
}
|
|
19
|
+
const configContent = await fs.promises.readFile(configPath, 'utf8');
|
|
20
|
+
// The `import type { RimoriPluginConfig } from '@rimori/client'` at the top of every plugin
|
|
21
|
+
// config is elided by ts.transpile (type-only), which is why importing the transpiled file
|
|
22
|
+
// works without resolving @rimori/client from the plugin's directory.
|
|
23
|
+
const transpiled = ts.transpile(configContent, {
|
|
24
|
+
target: ts.ScriptTarget.ES2020,
|
|
25
|
+
module: ts.ModuleKind.ES2020,
|
|
26
|
+
});
|
|
27
|
+
const tempFile = path.join(process.cwd(), 'temp_rimori_config.js');
|
|
28
|
+
await fs.promises.writeFile(tempFile, transpiled);
|
|
29
|
+
let configObject;
|
|
30
|
+
try {
|
|
31
|
+
const imported = await import(`file://${tempFile}`);
|
|
32
|
+
configObject = imported.default;
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await fs.promises.unlink(tempFile).catch(() => { });
|
|
36
|
+
}
|
|
37
|
+
if (!configObject) {
|
|
38
|
+
throw new Error('rimori.config.ts has no default export');
|
|
39
|
+
}
|
|
40
|
+
console.log('⚙️ Sending plugin configuration...');
|
|
41
|
+
const response = await fetch(`${config.domain}/release/config`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
Authorization: `Bearer ${config.token}`,
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
plugin_id: config.plugin_id,
|
|
49
|
+
config: configObject,
|
|
50
|
+
version: config.version,
|
|
51
|
+
}),
|
|
52
|
+
}).catch((e) => {
|
|
53
|
+
console.log('error', e);
|
|
54
|
+
throw new Error('Error sending plugin configuration');
|
|
55
|
+
});
|
|
56
|
+
const responseText = await response.text();
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
// The release-time validation lives in syncConfig() and its message names the offending
|
|
59
|
+
// page/action, so surface it verbatim rather than a generic failure.
|
|
60
|
+
throw new Error(`Plugin configuration rejected (${response.status}): ${responseText}`);
|
|
61
|
+
}
|
|
62
|
+
console.log('✅ Plugin configuration deployed successfully!');
|
|
63
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import 'dotenv/config';
|
|
14
14
|
import fs from 'fs';
|
|
15
15
|
import path from 'path';
|
|
16
|
+
import configUpload from './release-config-upload.js';
|
|
16
17
|
import dbUpdate from './release-db-update.js';
|
|
17
18
|
import promptsUpload from './release-prompts-upload.js';
|
|
18
19
|
// Read version from package.json
|
|
@@ -59,6 +60,9 @@ async function releaseProcess() {
|
|
|
59
60
|
console.log(`🚀 Releasing ${config.plugin_id} to ${config.release_channel}...`);
|
|
60
61
|
}
|
|
61
62
|
console.log(`📡 Deploying to: ${config.domain}`);
|
|
63
|
+
// Upload rimori.config.ts first: it upserts the `plugin` row, so the db sync below is
|
|
64
|
+
// guaranteed to find a row to write `db_schema` onto. Mandatory — hard-fails if missing.
|
|
65
|
+
await configUpload(config);
|
|
62
66
|
// Upload prompts (if prompts.config.ts exists)
|
|
63
67
|
await promptsUpload(config);
|
|
64
68
|
// Migrate tables (if db.config.ts exists)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SubscriptionTier } from '../../plugin/module/PluginModule';
|
|
1
2
|
/**
|
|
2
3
|
* Formatting options for string array variables when rendered into prompts.
|
|
3
4
|
*/
|
|
@@ -75,4 +76,6 @@ export interface PromptDefinition {
|
|
|
75
76
|
schema?: Record<string, any>;
|
|
76
77
|
tools?: any[];
|
|
77
78
|
model?: string;
|
|
79
|
+
/** Minimum subscription tier allowed to invoke this prompt. Defaults to 'free' when unset. */
|
|
80
|
+
minTier?: SubscriptionTier;
|
|
78
81
|
}
|
|
@@ -27,6 +27,22 @@ export interface PluginPage {
|
|
|
27
27
|
action?: {
|
|
28
28
|
key: string;
|
|
29
29
|
parameters: ObjectTool;
|
|
30
|
+
/**
|
|
31
|
+
* Minimum subscription tier a user must hold to run this exercise. Defaults to 'free'.
|
|
32
|
+
*
|
|
33
|
+
* UI / pre-flight only — the enforcement boundary is the server-side `minTier` on the
|
|
34
|
+
* prompts this action invokes (checked in the backend's ai.controller). Keep the two
|
|
35
|
+
* consistent: this must never be *above* the prompts' minTier, or the UI would block an
|
|
36
|
+
* exercise the backend would happily run.
|
|
37
|
+
*
|
|
38
|
+
* Drives two things in rimori-main: the "account required" badge in the create-exercise
|
|
39
|
+
* picker, and whether the public (anonymous) share toggle is offered — anonymous visitors
|
|
40
|
+
* are granted 'free', so only 'free' actions can be shared publicly.
|
|
41
|
+
*
|
|
42
|
+
* Union is inlined rather than importing SubscriptionTier from plugin/module/PluginModule,
|
|
43
|
+
* which imports this file (circular).
|
|
44
|
+
*/
|
|
45
|
+
min_tier?: 'anonymous' | 'free' | 'standard' | 'premium' | 'early_access';
|
|
30
46
|
supportive_tools?: {
|
|
31
47
|
key: string;
|
|
32
48
|
event: string;
|
|
@@ -38,8 +38,10 @@ export interface RimoriInfo {
|
|
|
38
38
|
* Determined by rimori-main based on release channel:
|
|
39
39
|
* - 'plugins_alpha' for alpha release channel
|
|
40
40
|
* - 'plugins' for stable release channel
|
|
41
|
+
* - 'public' for rimori-main's own natively-absorbed features (e.g. studyplan), which own
|
|
42
|
+
* plain `public` tables namespaced by `tablePrefix` instead of a plugin-scoped schema.
|
|
41
43
|
*/
|
|
42
|
-
dbSchema: 'plugins' | 'plugins_alpha';
|
|
44
|
+
dbSchema: 'plugins' | 'plugins_alpha' | 'public';
|
|
43
45
|
/**
|
|
44
46
|
* Whether text-to-speech is enabled globally (set in rimori-main navbar).
|
|
45
47
|
*/
|
|
@@ -31,8 +31,10 @@ export class RimoriClient {
|
|
|
31
31
|
this.eventBus = eventBus ?? EventBus;
|
|
32
32
|
this.sharedContent = new SharedContentController(supabase, this);
|
|
33
33
|
this.ai = new AIModule(controller);
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Fallback for gated prompts even when a plugin's own useTierGate check is missed or
|
|
35
|
+
// bypassed — mirrors PluginModule.requestUpgrade so both paths open the same modal.
|
|
36
|
+
this.ai.setOnTierGateBlocked((requiredTier) => {
|
|
37
|
+
this.eventBus.emit(info.pluginId, 'global.subscription.triggerUpgrade', { requiredTier });
|
|
36
38
|
});
|
|
37
39
|
this.event = new EventModule(info.pluginId, this.ai, this.eventBus);
|
|
38
40
|
this.db = new DbModule(supabase, controller, info);
|
|
@@ -50,7 +50,7 @@ export type OnLLMResponse = (id: string, response: string, finished: boolean, to
|
|
|
50
50
|
export declare class AIModule {
|
|
51
51
|
private controller;
|
|
52
52
|
private sessionTokenId;
|
|
53
|
-
private
|
|
53
|
+
private onTierGateBlockedCb?;
|
|
54
54
|
constructor(controller: RimoriCommunicationHandler);
|
|
55
55
|
/**
|
|
56
56
|
* Resolves a prompt name following the event naming convention:
|
|
@@ -67,8 +67,12 @@ export declare class AIModule {
|
|
|
67
67
|
/** Clears the stored session token. */
|
|
68
68
|
clear: () => void;
|
|
69
69
|
};
|
|
70
|
-
/**
|
|
71
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Registers a callback invoked whenever the backend refuses a prompt call with 403 because
|
|
72
|
+
* the caller's subscription tier is below the prompt's `minTier`. This is the fallback path
|
|
73
|
+
* for gated prompts even when a plugin's own UI gate (useTierGate) was missed or bypassed.
|
|
74
|
+
*/
|
|
75
|
+
setOnTierGateBlocked(cb: (requiredTier: string) => void): void;
|
|
72
76
|
/**
|
|
73
77
|
* Generate text from messages using AI.
|
|
74
78
|
* @param params.messages The messages to generate text from.
|
|
@@ -109,7 +113,9 @@ export declare class AIModule {
|
|
|
109
113
|
* @param speed The speed of the voice (default: 1).
|
|
110
114
|
* @param language Optional language for the voice.
|
|
111
115
|
* @param cache Whether to cache the result (default: false).
|
|
112
|
-
* @returns The generated audio as a Blob.
|
|
116
|
+
* @returns The generated audio as a Blob. Free/anonymous tier gets voice silently truncated
|
|
117
|
+
* to a few words server-side (see voice.service.ts truncateForFreeTier) — the response shape
|
|
118
|
+
* here stays byte-identical for every tier, no flag or signal is exposed to the frontend.
|
|
113
119
|
*
|
|
114
120
|
* **Empty input:** If `text` is empty or whitespace-only, no network request is
|
|
115
121
|
* made and an empty `Blob` is returned immediately. This prevents a 400 error
|
|
@@ -137,7 +143,9 @@ export declare class AIModule {
|
|
|
137
143
|
* Convert voice audio to text using AI.
|
|
138
144
|
* @param file The audio file to convert.
|
|
139
145
|
* @param language Optional language for the voice.
|
|
140
|
-
* @returns The transcribed text.
|
|
146
|
+
* @returns The transcribed text. Free/anonymous tier gets the transcript silently truncated
|
|
147
|
+
* to a few words server-side (see voice.service.ts truncateForFreeTier) — no flag or signal
|
|
148
|
+
* is exposed to the frontend.
|
|
141
149
|
*/
|
|
142
150
|
getTextFromVoice(file: Blob, language?: Language): Promise<string>;
|
|
143
151
|
/**
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export class AIModule {
|
|
6
6
|
controller;
|
|
7
7
|
sessionTokenId = null;
|
|
8
|
-
|
|
8
|
+
onTierGateBlockedCb;
|
|
9
9
|
constructor(controller) {
|
|
10
10
|
this.controller = controller;
|
|
11
11
|
}
|
|
@@ -36,9 +36,13 @@ export class AIModule {
|
|
|
36
36
|
this.sessionTokenId = null;
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Registers a callback invoked whenever the backend refuses a prompt call with 403 because
|
|
41
|
+
* the caller's subscription tier is below the prompt's `minTier`. This is the fallback path
|
|
42
|
+
* for gated prompts even when a plugin's own UI gate (useTierGate) was missed or bypassed.
|
|
43
|
+
*/
|
|
44
|
+
setOnTierGateBlocked(cb) {
|
|
45
|
+
this.onTierGateBlockedCb = cb;
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
44
48
|
* Generate text from messages using AI.
|
|
@@ -90,7 +94,9 @@ export class AIModule {
|
|
|
90
94
|
* @param speed The speed of the voice (default: 1).
|
|
91
95
|
* @param language Optional language for the voice.
|
|
92
96
|
* @param cache Whether to cache the result (default: false).
|
|
93
|
-
* @returns The generated audio as a Blob.
|
|
97
|
+
* @returns The generated audio as a Blob. Free/anonymous tier gets voice silently truncated
|
|
98
|
+
* to a few words server-side (see voice.service.ts truncateForFreeTier) — the response shape
|
|
99
|
+
* here stays byte-identical for every tier, no flag or signal is exposed to the frontend.
|
|
94
100
|
*
|
|
95
101
|
* **Empty input:** If `text` is empty or whitespace-only, no network request is
|
|
96
102
|
* made and an empty `Blob` is returned immediately. This prevents a 400 error
|
|
@@ -143,7 +149,9 @@ export class AIModule {
|
|
|
143
149
|
* Convert voice audio to text using AI.
|
|
144
150
|
* @param file The audio file to convert.
|
|
145
151
|
* @param language Optional language for the voice.
|
|
146
|
-
* @returns The transcribed text.
|
|
152
|
+
* @returns The transcribed text. Free/anonymous tier gets the transcript silently truncated
|
|
153
|
+
* to a few words server-side (see voice.service.ts truncateForFreeTier) — no flag or signal
|
|
154
|
+
* is exposed to the frontend.
|
|
147
155
|
*/
|
|
148
156
|
async getTextFromVoice(file, language) {
|
|
149
157
|
const formData = new FormData();
|
|
@@ -159,10 +167,7 @@ export class AIModule {
|
|
|
159
167
|
body: formData,
|
|
160
168
|
})
|
|
161
169
|
.then((r) => r.json())
|
|
162
|
-
.then((r) =>
|
|
163
|
-
// console.log("STT response: ", r);
|
|
164
|
-
return r.text;
|
|
165
|
-
});
|
|
170
|
+
.then((r) => r.text);
|
|
166
171
|
}
|
|
167
172
|
/**
|
|
168
173
|
* Generate a structured object from a request using AI.
|
|
@@ -227,11 +232,11 @@ export class AIModule {
|
|
|
227
232
|
body: JSON.stringify(payload),
|
|
228
233
|
});
|
|
229
234
|
if (!response.ok) {
|
|
230
|
-
if (response.status ===
|
|
235
|
+
if (response.status === 403) {
|
|
231
236
|
const body = await response.json().catch(() => ({}));
|
|
232
|
-
const
|
|
233
|
-
this.
|
|
234
|
-
throw new Error(`
|
|
237
|
+
const requiredTier = body.required_tier ?? 'standard';
|
|
238
|
+
this.onTierGateBlockedCb?.(requiredTier);
|
|
239
|
+
throw new Error(`Upgrade required: ${body.error ?? 'Upgrade required for this feature'}. required_tier: ${requiredTier}`);
|
|
235
240
|
}
|
|
236
241
|
throw new Error(`Failed to stream object: ${response.status} ${response.statusText}`);
|
|
237
242
|
}
|