@blastlauncher/engine 0.0.0-stage → 3.0.0-alpha.1

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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2023 Yukai Huang
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
- # Temporary Holding Version
1
+ # @blastlauncher/engine
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Blast's engine package.
4
+ Emitted ESM and TypeScript declarations for Node.js 24.20+.
5
+ See https://github.com/BlastLauncher/blast for documentation.
package/dist/ai.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { CapabilityRequestMessage } from "@blastlauncher/protocol";
2
+ import { type CapabilityContext } from "./broker.js";
3
+ import type { StartOptions } from "./session.js";
4
+ /** Own-extension providers share the command's lifetime and need no capability grant. */
5
+ export declare function createAIRouter(options: StartOptions): {
6
+ broker(request: CapabilityRequestMessage, context?: CapabilityContext): Promise<import("@blastlauncher/protocol").CapabilityResultMessage>;
7
+ shutdown(): Promise<void>;
8
+ };
package/dist/ai.js ADDED
@@ -0,0 +1,98 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { rm } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { bundleModelProvider } from "./bundle.js";
5
+ import { startModelProvider } from "./provider.js";
6
+ import { createBroker } from "./broker.js";
7
+ /** Own-extension providers share the command's lifetime and need no capability grant. */
8
+ export function createAIRouter(options) {
9
+ const sessionId = randomUUID();
10
+ let loading;
11
+ let directory;
12
+ let models;
13
+ const provider = () => (loading ??= (async () => {
14
+ const bundled = await bundleModelProvider({
15
+ extensionDir: options.modelProviderDir,
16
+ ...(options.dataDir === undefined ? {} : { dataDir: options.dataDir }),
17
+ });
18
+ directory = dirname(bundled.entry);
19
+ const { environment: _environment, ...inherited } = options;
20
+ return startModelProvider({
21
+ ...inherited,
22
+ ...bundled,
23
+ ...(options.environment
24
+ ? { environment: { ...options.environment, commandName: bundled.commandName } }
25
+ : {}),
26
+ });
27
+ })());
28
+ const broker = createBroker({
29
+ "ai.models.refresh": async () => {
30
+ if (options.modelProviderDir)
31
+ models = await (await provider()).getModels();
32
+ },
33
+ "ai.ask": async (params, context = { signal: new AbortController().signal, progress() { } }) => {
34
+ const { model, prompt, creativity } = params;
35
+ const session = await provider();
36
+ models ??= await session.getModels();
37
+ if (context.signal.aborted)
38
+ throw new DOMException("The operation was aborted", "AbortError");
39
+ const selected = models.find((item) => item.id === model.id);
40
+ if (!selected)
41
+ throw new Error(`Unknown model ${model.id}`);
42
+ const temperature = typeof creativity === "number"
43
+ ? Math.max(0, Math.min(2, creativity))
44
+ : creativity === undefined
45
+ ? undefined
46
+ : { none: 0, low: 0.5, medium: 1, high: 1.5, maximum: 2 }[creativity];
47
+ let text = "";
48
+ for await (const part of session.stream(selected, {
49
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
50
+ ...(temperature === undefined ? {} : { temperature }),
51
+ providerOptions: {
52
+ raycast: {
53
+ locale: "en",
54
+ currentDate: new Date().toISOString(),
55
+ sessionId,
56
+ },
57
+ },
58
+ }, { signal: context.signal })) {
59
+ if (part.type === "error")
60
+ throw new Error(String(part.error));
61
+ const chunk = part.type === "text-delta"
62
+ ? (part.textDelta ?? part.text)
63
+ : part.type === "text"
64
+ ? part.text
65
+ : undefined;
66
+ if (chunk !== undefined) {
67
+ text += chunk;
68
+ context.progress(chunk);
69
+ }
70
+ }
71
+ return text;
72
+ },
73
+ });
74
+ return {
75
+ broker(request, context) {
76
+ const model = request.params?.model;
77
+ return request.name === "ai.models.refresh" ||
78
+ (options.modelProviderDir &&
79
+ request.name === "ai.ask" &&
80
+ model !== null &&
81
+ typeof model === "object")
82
+ ? broker(request, context)
83
+ : options.broker(request, context);
84
+ },
85
+ async shutdown() {
86
+ try {
87
+ await (await loading)?.shutdown({ timeoutMs: 250 });
88
+ }
89
+ catch {
90
+ /* Startup failures still own a bundle directory. */
91
+ }
92
+ finally {
93
+ if (directory)
94
+ await rm(directory, { recursive: true, force: true });
95
+ }
96
+ },
97
+ };
98
+ }
@@ -0,0 +1,8 @@
1
+ import type { CapabilityRequestMessage, CapabilityResultMessage, JsonValue } from "@blastlauncher/protocol";
2
+ export type CapabilityContext = {
3
+ signal: AbortSignal;
4
+ progress(value: JsonValue): void;
5
+ };
6
+ export type CapabilityHandler = (params: JsonValue, context?: CapabilityContext) => JsonValue | void | Promise<JsonValue | void>;
7
+ export type Broker = (request: CapabilityRequestMessage, context?: CapabilityContext) => Promise<CapabilityResultMessage>;
8
+ export declare function createBroker(handlers?: Record<string, CapabilityHandler>): Broker;
package/dist/broker.js ADDED
@@ -0,0 +1,30 @@
1
+ export function createBroker(handlers = {}) {
2
+ const grants = new Map(Object.entries(handlers));
3
+ return async ({ id, name, params }, context = { signal: new AbortController().signal, progress() { } }) => {
4
+ const handler = grants.get(name);
5
+ if (!handler) {
6
+ return {
7
+ type: "capability.result",
8
+ id,
9
+ ok: false,
10
+ error: { code: "denied", message: `Capability denied: ${name}` },
11
+ };
12
+ }
13
+ try {
14
+ return {
15
+ type: "capability.result",
16
+ id,
17
+ ok: true,
18
+ value: (await handler(params, context)) ?? null,
19
+ };
20
+ }
21
+ catch (error) {
22
+ return {
23
+ type: "capability.result",
24
+ id,
25
+ ok: false,
26
+ error: { code: "failed", message: error instanceof Error ? error.message : String(error) },
27
+ };
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,93 @@
1
+ import type { CommandMode } from "@blastlauncher/protocol";
2
+ import { symlink } from "node:fs/promises";
3
+ import { type SharedDependencyLoader } from "./dependencies.js";
4
+ export declare class MissingDependencyError extends Error {
5
+ readonly code = "MISSING_DEPENDENCY";
6
+ readonly packages: string[];
7
+ constructor(packages: Iterable<string>);
8
+ }
9
+ export declare class NativeModuleError extends Error {
10
+ readonly code = "NATIVE_MODULE";
11
+ readonly modules: string[];
12
+ constructor(modules: Iterable<string>);
13
+ }
14
+ export declare class BundleTimeoutError extends Error {
15
+ readonly code = "BUNDLE_TIMEOUT";
16
+ readonly extension: string;
17
+ readonly command: string;
18
+ readonly timeoutMs: number;
19
+ constructor(extension: string, command: string, timeoutMs: number);
20
+ }
21
+ export declare class BundleContainmentError extends Error {
22
+ readonly code = "BUNDLE_OUTSIDE_ROOT";
23
+ readonly specifier: string;
24
+ constructor(specifier: string);
25
+ }
26
+ export type BundleOptions = {
27
+ extensionDir: string;
28
+ command: string;
29
+ /** Bundling deadline in milliseconds; defaults to 60_000, 0 disables it. */
30
+ timeoutMs?: number;
31
+ signal?: AbortSignal;
32
+ /** Override optional-package loading, e.g. for an isolated embedder. */
33
+ sharedDependencies?: SharedDependencyLoader;
34
+ outDir?: string;
35
+ dataDir?: string;
36
+ /** `false` keeps resolution allowlist-only, as corpus reports need (decision 0008). */
37
+ installedDependencies?: boolean;
38
+ };
39
+ /** One `commands[].arguments` entry from an extension manifest. */
40
+ export type CommandArgument = {
41
+ name: string;
42
+ type: string;
43
+ /** A required argument must be supplied before the command starts. */
44
+ required?: boolean;
45
+ placeholder?: string;
46
+ };
47
+ export type BundledCommand = {
48
+ /** Elapsed bundling time, including dependency resolution and assets. */
49
+ bundleMs: number;
50
+ modelProviderDir?: string;
51
+ entry: string;
52
+ extensionName: string;
53
+ commandName: string;
54
+ /** The manifest's `owner`, else `author`; defaults to `""` for bare entries. */
55
+ ownerOrAuthorName?: string;
56
+ mode: CommandMode;
57
+ /** The manifest's argument declarations, in declaration order. */
58
+ arguments: CommandArgument[];
59
+ /**
60
+ * The declared default of every manifest preference (extension-wide, then
61
+ * the command's own overriding it). A caller's explicit values win over
62
+ * these when the session starts.
63
+ */
64
+ preferenceDefaults?: Record<string, string | number | boolean>;
65
+ /** `<outDir>/assets`, resolving to the extension's real assets when it has any. */
66
+ assetsPath?: string;
67
+ /** Shared packages whose supplied major disagrees with the manifest. */
68
+ dependencyWarnings?: string[];
69
+ };
70
+ export type Preference = {
71
+ name: string;
72
+ type: string;
73
+ title: string;
74
+ description?: string;
75
+ required: boolean;
76
+ data: {
77
+ title: string;
78
+ value: string;
79
+ }[];
80
+ default?: string | number | boolean;
81
+ };
82
+ /** Shared manifest parsing for bundling defaults and client preference forms. */
83
+ export declare function parsePreferences(value: unknown, at?: string): Preference[];
84
+ /**
85
+ * The build directory mirrors Raycast's layout: `assets/` sits next to the
86
+ * bundle, so `environment.assetsPath` and `path.join(__dirname, "assets", …)`
87
+ * both resolve to the extension's real assets. The link is a symlink; where
88
+ * the platform refuses symlinks, a read-only copy with a size cap is used.
89
+ * Nothing is ever written to the extension directory.
90
+ */
91
+ export declare function linkAssets(outDir: string, extensionDir: string, link?: typeof symlink): Promise<void>;
92
+ export declare function bundleModelProvider(options: Omit<BundleOptions, "command">): Promise<BundledCommand>;
93
+ export declare function bundleCommand(options: BundleOptions): Promise<BundledCommand>;