@plurnk/plurnk-service 0.44.0 → 0.45.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/.env.example +27 -23
- package/README.md +37 -18
- package/SPEC.md +31 -7
- package/dist/core/ChannelWrite.d.ts +4 -0
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +9 -0
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +69 -0
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +108 -71
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +291 -0
- package/dist/core/ExecutorRegistry.d.ts +4 -2
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +14 -4
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +1 -0
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +6 -0
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +8 -1
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +50 -0
- package/dist/core/plugin-attribution.d.ts +5 -0
- package/dist/core/plugin-attribution.d.ts.map +1 -0
- package/dist/core/plugin-attribution.js +39 -0
- package/dist/core/plugin-attribution.js.map +1 -0
- package/dist/core/run-ops.sql +16 -0
- package/dist/core/session-settings.d.ts +1 -0
- package/dist/core/session-settings.d.ts.map +1 -1
- package/dist/core/session-settings.js +2 -1
- package/dist/core/session-settings.js.map +1 -1
- package/dist/schemes/Exec.d.ts +1 -0
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +17 -5
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +27 -3
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.sql +37 -0
- package/dist/schemes/_entry-crud.sql +88 -0
- package/dist/schemes/_entry-find.sql +31 -0
- package/dist/schemes/_entry-graph.sql +60 -0
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +4 -1
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +20 -0
- package/dist/schemes/_entry-semantic.sql +64 -0
- package/dist/schemes/exec-env.js +1 -1
- package/dist/schemes/exec-env.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +103 -37
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/clientTurn.sql +10 -0
- package/dist/server/drain.sql +82 -0
- package/dist/server/dsl.d.ts.map +1 -1
- package/dist/server/dsl.js +11 -4
- package/dist/server/dsl.js.map +1 -1
- package/dist/server/envelope.sql +75 -0
- package/dist/server/logEntry.sql +10 -0
- package/dist/server/methods/_dispatchAsClient.d.ts.map +1 -1
- package/dist/server/methods/_dispatchAsClient.js +11 -6
- package/dist/server/methods/_dispatchAsClient.js.map +1 -1
- package/dist/server/methods/entry_read.sql +21 -0
- package/dist/server/methods/log_read.sql +11 -0
- package/dist/server/methods/loop_run.sql +9 -0
- package/dist/server/methods/session_create.d.ts.map +1 -1
- package/dist/server/methods/session_create.js +10 -3
- package/dist/server/methods/session_create.js.map +1 -1
- package/dist/service.d.ts +6 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +221 -0
- package/dist/service.js.map +1 -0
- package/package.json +28 -14
- package/bin/plurnk-service.ts +0 -176
- package/dist/core/ProviderRegistry.d.ts +0 -42
- package/dist/core/ProviderRegistry.d.ts.map +0 -1
- package/dist/core/ProviderRegistry.js +0 -72
- package/dist/core/ProviderRegistry.js.map +0 -1
- package/dist/core/line-marker.d.ts +0 -23
- package/dist/core/line-marker.d.ts.map +0 -1
- package/dist/core/line-marker.js +0 -321
- package/dist/core/line-marker.js.map +0 -1
- package/dist/core/matcher.d.ts +0 -12
- package/dist/core/matcher.d.ts.map +0 -1
- package/dist/core/matcher.js +0 -72
- package/dist/core/matcher.js.map +0 -1
- package/dist/core/mimetype-binary.d.ts +0 -6
- package/dist/core/mimetype-binary.d.ts.map +0 -1
- package/dist/core/mimetype-binary.js +0 -82
- package/dist/core/mimetype-binary.js.map +0 -1
- package/dist/core/path-mimetype.d.ts +0 -3
- package/dist/core/path-mimetype.d.ts.map +0 -1
- package/dist/core/path-mimetype.js +0 -47
- package/dist/core/path-mimetype.js.map +0 -1
- package/dist/core/plugin-trust.d.ts +0 -4
- package/dist/core/plugin-trust.d.ts.map +0 -1
- package/dist/core/plugin-trust.js +0 -23
- package/dist/core/plugin-trust.js.map +0 -1
- package/dist/providers/Mock.d.ts +0 -43
- package/dist/providers/Mock.d.ts.map +0 -1
- package/dist/providers/Mock.js +0 -36
- package/dist/providers/Mock.js.map +0 -1
- package/dist/server/methods/op_hide.d.ts +0 -5
- package/dist/server/methods/op_hide.d.ts.map +0 -1
- package/dist/server/methods/op_hide.js +0 -24
- package/dist/server/methods/op_hide.js.map +0 -1
- package/dist/server/methods/op_show.d.ts +0 -5
- package/dist/server/methods/op_show.d.ts.map +0 -1
- package/dist/server/methods/op_show.js +0 -24
- package/dist/server/methods/op_show.js.map +0 -1
- package/dist/server/methods/session_set_persona.d.ts +0 -5
- package/dist/server/methods/session_set_persona.d.ts.map +0 -1
- package/dist/server/methods/session_set_persona.js +0 -29
- package/dist/server/methods/session_set_persona.js.map +0 -1
package/bin/plurnk-service.ts
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { parseArgs } from "node:util";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { dirname, resolve } from "node:path";
|
|
7
|
-
import SqlRite from "@possumtech/sqlrite";
|
|
8
|
-
import type { Db } from "../src/core/Db.ts";
|
|
9
|
-
import Daemon from "../src/server/Daemon.ts";
|
|
10
|
-
import EnvFlags from "../src/core/EnvFlags.ts";
|
|
11
|
-
import ProviderInstantiate from "../src/core/ProviderInstantiate.ts";
|
|
12
|
-
import { resolveActiveAlias } from "@plurnk/plurnk-providers";
|
|
13
|
-
|
|
14
|
-
export default class Cli {
|
|
15
|
-
static #projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
|
-
|
|
17
|
-
static #die(code: number, message: string): never {
|
|
18
|
-
process.stderr.write(`${message}\n`);
|
|
19
|
-
process.exit(code);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
static #loadEnv(path: string, required: boolean): void {
|
|
23
|
-
if (existsSync(path)) {
|
|
24
|
-
try { process.loadEnvFile(path); }
|
|
25
|
-
catch (cause) { Cli.#die(64, `failed to load ${path}: ${cause instanceof Error ? cause.message : String(cause)}`); }
|
|
26
|
-
} else if (required) {
|
|
27
|
-
Cli.#die(64, `${path} does not exist`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// node-style env-file flags: --env-file=<path> (required) / --env-file-if-exists=<path>
|
|
32
|
-
// (skip if missing), repeatable, in command-line order. They layer extra files ABOVE
|
|
33
|
-
// the .env cascade but BELOW shell env (loadEnvFile is set-if-unset) and the --<knob>
|
|
34
|
-
// CLI flags (assigned last). The `=` form only — node's canonical syntax (so it never
|
|
35
|
-
// leaks a positional). NB: node validates these paths from the full argv (and exits on
|
|
36
|
-
// a missing *required* one), but only LOADS pre-script files — the post-script loading
|
|
37
|
-
// a published `plurnk-service --env-file=…` needs is this.
|
|
38
|
-
static #envFileArgs(): Array<{ path: string; required: boolean }> {
|
|
39
|
-
return process.argv.flatMap((a): Array<{ path: string; required: boolean }> => {
|
|
40
|
-
if (a.startsWith("--env-file-if-exists=")) return [{ path: a.slice(a.indexOf("=") + 1), required: false }];
|
|
41
|
-
if (a.startsWith("--env-file=")) return [{ path: a.slice(a.indexOf("=") + 1), required: true }];
|
|
42
|
-
return [];
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// The .env cascade always populates these from .env.example, so absence is
|
|
47
|
-
// a broken config, not a runtime branch — fail hard rather than `?? ""`.
|
|
48
|
-
static #requireEnv(name: string): string {
|
|
49
|
-
const value = process.env[name];
|
|
50
|
-
if (value === undefined || value.length === 0) Cli.#die(78, `missing required env ${name} (declare it in .env.example)`);
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Optional integer sqlite tuning knob — undefined when unset (so it never clobbers
|
|
55
|
-
// sqlrite's default by spreading an explicit `undefined`); fail-hard on a non-integer.
|
|
56
|
-
static #sqliteKnob(name: string): number | undefined {
|
|
57
|
-
const raw = process.env[name];
|
|
58
|
-
if (raw === undefined || raw.trim() === "") return undefined;
|
|
59
|
-
const n = Number(raw);
|
|
60
|
-
if (!Number.isInteger(n)) Cli.#die(78, `${name} must be an integer, got ${JSON.stringify(raw)}`);
|
|
61
|
-
return n;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static async #openDb(dbPath: string): Promise<Db> {
|
|
65
|
-
// Curated sqlite tuning (sqlrite 5.2.0, #7) — pass through ONLY the knobs the
|
|
66
|
-
// operator set, so an unset one keeps sqlrite's default (e.g. busy_timeout=5000).
|
|
67
|
-
const tuning: Record<string, number> = {};
|
|
68
|
-
for (const [env, opt] of [
|
|
69
|
-
["PLURNK_SQLITE_TIMEOUT", "timeout"],
|
|
70
|
-
["PLURNK_SQLITE_CACHE_SIZE", "cacheSize"],
|
|
71
|
-
["PLURNK_SQLITE_MMAP_SIZE", "mmapSize"],
|
|
72
|
-
["PLURNK_SQLITE_MAX_PAGE_COUNT", "maxPageCount"],
|
|
73
|
-
] as const) {
|
|
74
|
-
const v = Cli.#sqliteKnob(env);
|
|
75
|
-
if (v !== undefined) tuning[opt] = v;
|
|
76
|
-
}
|
|
77
|
-
const db = await SqlRite.open({
|
|
78
|
-
path: dbPath,
|
|
79
|
-
dir: [resolve(Cli.#projectRoot, "migrations"), resolve(Cli.#projectRoot, "src")],
|
|
80
|
-
functions: [resolve(Cli.#projectRoot, "src/schemes/cosine.ts")],
|
|
81
|
-
...tuning,
|
|
82
|
-
});
|
|
83
|
-
return db as unknown as Db;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
static async #migrate(): Promise<void> {
|
|
87
|
-
const dbPath = Cli.#requireEnv("PLURNK_DB_PATH");
|
|
88
|
-
const db = await Cli.#openDb(dbPath);
|
|
89
|
-
try { process.stdout.write(`migrated: ${dbPath}\n`); }
|
|
90
|
-
finally { await db.close(); }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
static async #start(): Promise<void> {
|
|
94
|
-
const dbPath = Cli.#requireEnv("PLURNK_DB_PATH");
|
|
95
|
-
const host = Cli.#requireEnv("PLURNK_HOST");
|
|
96
|
-
const port = Number(Cli.#requireEnv("PLURNK_PORT"));
|
|
97
|
-
|
|
98
|
-
const db = await Cli.#openDb(dbPath);
|
|
99
|
-
const alias = resolveActiveAlias();
|
|
100
|
-
const provider = alias === null ? null : await ProviderInstantiate.loadActiveProvider();
|
|
101
|
-
const daemon = new Daemon({ db, provider });
|
|
102
|
-
const addr = await daemon.start({ host, port });
|
|
103
|
-
const aliasStr = alias === null ? "no model" : `${alias.alias}=${alias.provider}/${alias.model}`;
|
|
104
|
-
process.stdout.write(`plurnk-service ws://${addr.host}:${addr.port} db=${dbPath} ${aliasStr}\n`);
|
|
105
|
-
|
|
106
|
-
const shutdown = async (): Promise<void> => { await daemon.stop(); await db.close(); process.exit(0); };
|
|
107
|
-
process.on("SIGINT", shutdown);
|
|
108
|
-
process.on("SIGTERM", shutdown);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
static async main(): Promise<void> {
|
|
112
|
-
// Env cascade, highest precedence loads FIRST (loadEnvFile is set-if-unset,
|
|
113
|
-
// first write wins): --env-file(s) < --config < .env < .env.example, all of them
|
|
114
|
-
// OUTRANKED by pre-set shell env, then by the --<knob> CLI flags (assigned last,
|
|
115
|
-
// below). So --env-file overrides the .env files but never a shell var or a CLI
|
|
116
|
-
// arg — node-idiomatic layering.
|
|
117
|
-
for (const { path: envFile, required } of Cli.#envFileArgs()) Cli.#loadEnv(envFile, required);
|
|
118
|
-
|
|
119
|
-
const configFlagIndex = process.argv.findIndex((a) => a === "--config" || a.startsWith("--config="));
|
|
120
|
-
const configFile = ((): string | null => {
|
|
121
|
-
if (configFlagIndex === -1) return null;
|
|
122
|
-
const arg = process.argv[configFlagIndex];
|
|
123
|
-
if (arg.includes("=")) return arg.slice(arg.indexOf("=") + 1);
|
|
124
|
-
return process.argv[configFlagIndex + 1] ?? null;
|
|
125
|
-
})();
|
|
126
|
-
|
|
127
|
-
if (configFile !== null) Cli.#loadEnv(configFile, true);
|
|
128
|
-
Cli.#loadEnv(".env", false);
|
|
129
|
-
Cli.#loadEnv(resolve(Cli.#projectRoot, ".env.example"), false);
|
|
130
|
-
|
|
131
|
-
const flagDescriptors = await EnvFlags.parseEnvExample(resolve(Cli.#projectRoot, ".env.example"));
|
|
132
|
-
const flagOptions: Record<string, { type: "string" }> = {};
|
|
133
|
-
for (const f of flagDescriptors) {
|
|
134
|
-
flagOptions[f.flagName.replace(/^--/, "")] = { type: "string" };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const usage = `usage: plurnk-service [options] [migrate]
|
|
138
|
-
|
|
139
|
-
${EnvFlags.formatFlagsHelp(flagDescriptors)}
|
|
140
|
-
|
|
141
|
-
--env-file=<path> layer env from <path> (repeatable; errors if missing)
|
|
142
|
-
--env-file-if-exists=<path> layer env from <path> if present (repeatable)
|
|
143
|
-
--config=<path> layer additional env from <path>
|
|
144
|
-
-h, --help show this help
|
|
145
|
-
`;
|
|
146
|
-
|
|
147
|
-
const { positionals, values } = parseArgs({
|
|
148
|
-
allowPositionals: true,
|
|
149
|
-
strict: false,
|
|
150
|
-
options: { help: { type: "boolean", short: "h" }, config: { type: "string" }, ...flagOptions },
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
for (const f of flagDescriptors) {
|
|
154
|
-
const key = f.flagName.replace(/^--/, "");
|
|
155
|
-
const v = values[key];
|
|
156
|
-
if (typeof v === "string") process.env[f.envName] = v;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (values.help) { process.stdout.write(usage); process.exit(0); }
|
|
160
|
-
|
|
161
|
-
const dispatch: Record<string, () => Promise<void>> = { migrate: Cli.#migrate, start: Cli.#start };
|
|
162
|
-
const subcommand = typeof positionals[0] === "string" ? positionals[0] : "start";
|
|
163
|
-
const handler = dispatch[subcommand];
|
|
164
|
-
if (handler === undefined) Cli.#die(64, `unknown subcommand: ${subcommand}\n\n${usage}`);
|
|
165
|
-
if (positionals.length > 1) Cli.#die(64, `unexpected arguments: ${positionals.slice(1).join(" ")}`);
|
|
166
|
-
|
|
167
|
-
try { await handler(); }
|
|
168
|
-
catch (cause) {
|
|
169
|
-
process.stderr.write(`${subcommand}: ${cause instanceof Error ? cause.message : String(cause)}\n`);
|
|
170
|
-
if (cause instanceof Error && cause.cause) process.stderr.write(` cause: ${cause.cause instanceof Error ? cause.cause.message : String(cause.cause)}\n`);
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
await Cli.main();
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
interface ChatMessage {
|
|
2
|
-
role: "system" | "user" | "assistant";
|
|
3
|
-
content: string;
|
|
4
|
-
}
|
|
5
|
-
export interface ProviderUsage {
|
|
6
|
-
readonly prompt: number;
|
|
7
|
-
readonly completion: number;
|
|
8
|
-
readonly cached: number;
|
|
9
|
-
readonly total: number;
|
|
10
|
-
}
|
|
11
|
-
export interface ProviderAssistant {
|
|
12
|
-
readonly content: string;
|
|
13
|
-
readonly reasoning: string | null;
|
|
14
|
-
readonly usage: ProviderUsage;
|
|
15
|
-
readonly finishReason: string | null;
|
|
16
|
-
readonly model: string;
|
|
17
|
-
}
|
|
18
|
-
export interface ProviderResponse {
|
|
19
|
-
readonly assistant: ProviderAssistant;
|
|
20
|
-
readonly assistantRaw: unknown;
|
|
21
|
-
}
|
|
22
|
-
export interface Provider {
|
|
23
|
-
generate(args: {
|
|
24
|
-
messages: ChatMessage[];
|
|
25
|
-
signal?: AbortSignal;
|
|
26
|
-
}): Promise<ProviderResponse>;
|
|
27
|
-
readonly contextSize: number | null;
|
|
28
|
-
readonly model: string;
|
|
29
|
-
countTokens(text: string): number;
|
|
30
|
-
costFor(usage: ProviderUsage): number;
|
|
31
|
-
}
|
|
32
|
-
export interface ProviderAlias {
|
|
33
|
-
readonly alias: string;
|
|
34
|
-
readonly provider: string;
|
|
35
|
-
readonly model: string;
|
|
36
|
-
}
|
|
37
|
-
export declare const parseAliasesFromEnv: (env?: NodeJS.ProcessEnv) => ProviderAlias[];
|
|
38
|
-
export declare const resolveActiveAlias: (env?: NodeJS.ProcessEnv) => ProviderAlias | null;
|
|
39
|
-
export declare const instantiateProvider: (alias: ProviderAlias, env?: NodeJS.ProcessEnv) => Promise<Provider>;
|
|
40
|
-
export declare const loadActiveProvider: (env?: NodeJS.ProcessEnv) => Promise<Provider | null>;
|
|
41
|
-
export {};
|
|
42
|
-
//# sourceMappingURL=ProviderRegistry.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderRegistry.d.ts","sourceRoot":"","sources":["../../src/core/ProviderRegistry.ts"],"names":[],"mappings":"AAWA,UAAU,WAAW;IAAG,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAYhF,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AACD,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AACD,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAClC;AACD,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAK7F,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAMvB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAKlC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,mBAAmB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,aAAa,EAgBvF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,aAAa,GAAG,IAKzF,CAAC;AAcF,eAAO,MAAM,mBAAmB,GAAU,OAAO,aAAa,EAAE,MAAK,MAAM,CAAC,UAAwB,KAAG,OAAO,CAAC,QAAQ,CAmBtH,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAAU,MAAK,MAAM,CAAC,UAAwB,KAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAItG,CAAC"}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// Model alias resolution. Reads PLURNK_MODEL_<alias>=<provider>/<model> env
|
|
2
|
-
// vars; PLURNK_MODEL=<alias> selects which to use at boot. Each alias maps
|
|
3
|
-
// to a provider plugin (`@plurnk/plurnk-providers-<provider>`); the registry
|
|
4
|
-
// imports the matching package and constructs it with provider-specific
|
|
5
|
-
// env conventions.
|
|
6
|
-
//
|
|
7
|
-
// Rummy parallel: RUMMY_MODEL_<alias>="<provider>/<model>" cascade. The
|
|
8
|
-
// first path segment selects the provider plugin; the rest is the
|
|
9
|
-
// provider's own identifier (may contain "/" for tri-level providers like
|
|
10
|
-
// openrouter's publisher/model).
|
|
11
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
12
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
13
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
14
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return path;
|
|
18
|
-
};
|
|
19
|
-
export const parseAliasesFromEnv = (env = process.env) => {
|
|
20
|
-
const out = [];
|
|
21
|
-
for (const [key, value] of Object.entries(env)) {
|
|
22
|
-
if (value === undefined || value.length === 0)
|
|
23
|
-
continue;
|
|
24
|
-
if (!key.startsWith("PLURNK_MODEL_"))
|
|
25
|
-
continue;
|
|
26
|
-
const aliasRaw = key.slice("PLURNK_MODEL_".length);
|
|
27
|
-
if (aliasRaw.length === 0)
|
|
28
|
-
continue;
|
|
29
|
-
const slash = value.indexOf("/");
|
|
30
|
-
if (slash <= 0)
|
|
31
|
-
continue;
|
|
32
|
-
out.push({
|
|
33
|
-
alias: aliasRaw.toLowerCase(),
|
|
34
|
-
provider: value.slice(0, slash),
|
|
35
|
-
model: value.slice(slash + 1),
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
39
|
-
};
|
|
40
|
-
export const resolveActiveAlias = (env = process.env) => {
|
|
41
|
-
const selected = env.PLURNK_MODEL;
|
|
42
|
-
if (selected === undefined || selected.length === 0)
|
|
43
|
-
return null;
|
|
44
|
-
const aliases = parseAliasesFromEnv(env);
|
|
45
|
-
return aliases.find((a) => a.alias === selected.toLowerCase()) ?? null;
|
|
46
|
-
};
|
|
47
|
-
export const instantiateProvider = async (alias, env = process.env) => {
|
|
48
|
-
const packageName = `@plurnk/plurnk-providers-${alias.provider}`;
|
|
49
|
-
let mod;
|
|
50
|
-
try {
|
|
51
|
-
mod = await import(__rewriteRelativeImportExtension(packageName));
|
|
52
|
-
}
|
|
53
|
-
catch (cause) {
|
|
54
|
-
throw new Error(`provider package ${packageName} not installed (alias '${alias.alias}' requires it): ` +
|
|
55
|
-
(cause instanceof Error ? cause.message : String(cause)));
|
|
56
|
-
}
|
|
57
|
-
const factory = mod.default;
|
|
58
|
-
if (typeof factory?.fromEnv !== "function") {
|
|
59
|
-
throw new Error(`${packageName}: default export must have a static \`fromEnv(env, model)\` factory ` +
|
|
60
|
-
`(plurnk-providers#1)`);
|
|
61
|
-
}
|
|
62
|
-
return await factory.fromEnv(env, alias.model);
|
|
63
|
-
};
|
|
64
|
-
// Convenience: resolve + instantiate in one call. Returns null when no
|
|
65
|
-
// PLURNK_MODEL is set (caller decides what 'no provider' means).
|
|
66
|
-
export const loadActiveProvider = async (env = process.env) => {
|
|
67
|
-
const alias = resolveActiveAlias(env);
|
|
68
|
-
if (alias === null)
|
|
69
|
-
return null;
|
|
70
|
-
return instantiateProvider(alias, env);
|
|
71
|
-
};
|
|
72
|
-
//# sourceMappingURL=ProviderRegistry.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderRegistry.js","sourceRoot":"","sources":["../../src/core/ProviderRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { LineMarker } from "@plurnk/plurnk-grammar";
|
|
2
|
-
export interface SliceResult {
|
|
3
|
-
status: number;
|
|
4
|
-
text?: string;
|
|
5
|
-
startLine?: number;
|
|
6
|
-
error?: string;
|
|
7
|
-
}
|
|
8
|
-
export declare const sliceLines: (content: string, marker: LineMarker) => SliceResult;
|
|
9
|
-
export interface JsonSliceResult {
|
|
10
|
-
status: number;
|
|
11
|
-
body?: string;
|
|
12
|
-
error?: string;
|
|
13
|
-
}
|
|
14
|
-
export declare const sliceJsonItems: (content: string, marker: LineMarker) => JsonSliceResult;
|
|
15
|
-
export declare const applyJsonItemEdit: (content: string, marker: LineMarker, body: string) => EditResult;
|
|
16
|
-
export declare const sliceLinesRaw: (content: string, marker: LineMarker) => SliceResult;
|
|
17
|
-
export interface EditResult {
|
|
18
|
-
status: number;
|
|
19
|
-
result?: string;
|
|
20
|
-
error?: string;
|
|
21
|
-
}
|
|
22
|
-
export declare const applyLineMarkerEdit: (content: string, marker: LineMarker, body: string) => EditResult;
|
|
23
|
-
//# sourceMappingURL=line-marker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"line-marker.d.ts","sourceRoot":"","sources":["../../src/core/line-marker.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAkCzD,MAAM,WAAW,WAAW;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AAWlG,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,QAAQ,UAAU,KAAG,WAOhE,CAAC;AAyBF,MAAM,WAAW,eAAe;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AAElF,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,QAAQ,UAAU,KAAG,eAoBpE,CAAC;AA6GF,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,EAAE,QAAQ,UAAU,EAAE,MAAM,MAAM,KAAG,UAerF,CAAC;AAOF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,EAAE,QAAQ,UAAU,KAAG,WAQnE,CAAC;AAEF,MAAM,WAAW,UAAU;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AAS/E,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,QAAQ,UAAU,EAAE,MAAM,MAAM,KAAG,UAiBvF,CAAC"}
|
package/dist/core/line-marker.js
DELETED
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
// `<L>` line-marker semantics (plurnk.md §`<L>`):
|
|
2
|
-
//
|
|
3
|
-
// <N> selects position N (1-indexed)
|
|
4
|
-
// <N,M> selects positions N..M inclusive
|
|
5
|
-
// <0> sentinel: before position 1 (EDIT prepend)
|
|
6
|
-
// <-1> sentinel: after the last position (EDIT append)
|
|
7
|
-
// <1,-1> every position (in range context, -1 normalizes to last line)
|
|
8
|
-
//
|
|
9
|
-
// "N and M are signed integers" — but plurnk.md only documents <0> and
|
|
10
|
-
// <-1> as defined sentinels. Other negatives (<-2>, <-3>) are not
|
|
11
|
-
// specified and rejected as 416. Within a range, -1 as the M endpoint
|
|
12
|
-
// means "include through the last line" (so <1,-1> is whole content).
|
|
13
|
-
const splitLines = (content) => {
|
|
14
|
-
const trailingNewline = content.endsWith("\n");
|
|
15
|
-
if (content === "")
|
|
16
|
-
return { lines: [], trailingNewline: false };
|
|
17
|
-
const lines = content.split("\n");
|
|
18
|
-
if (trailingNewline)
|
|
19
|
-
lines.pop();
|
|
20
|
-
return { lines, trailingNewline };
|
|
21
|
-
};
|
|
22
|
-
const normalize = (marker, totalLines) => {
|
|
23
|
-
const { first, last } = marker;
|
|
24
|
-
if (last === null) {
|
|
25
|
-
if (first === 0)
|
|
26
|
-
return { kind: "before-first", start: 0, end: 0 };
|
|
27
|
-
if (first === -1)
|
|
28
|
-
return { kind: "after-last", start: 0, end: 0 };
|
|
29
|
-
if (first > 0 && first <= totalLines)
|
|
30
|
-
return { kind: "range", start: first, end: first };
|
|
31
|
-
return { error: `line ${first} out of range (1..${totalLines})` };
|
|
32
|
-
}
|
|
33
|
-
let n = first;
|
|
34
|
-
let m = last;
|
|
35
|
-
if (n === 0)
|
|
36
|
-
n = 1;
|
|
37
|
-
if (m === -1)
|
|
38
|
-
m = totalLines;
|
|
39
|
-
if (n < 1 || n > totalLines)
|
|
40
|
-
return { error: `range start ${first} out of range (1..${totalLines})` };
|
|
41
|
-
if (m < 1 || m > totalLines)
|
|
42
|
-
return { error: `range end ${last} out of range (1..${totalLines})` };
|
|
43
|
-
if (n > m)
|
|
44
|
-
return { error: `range start ${first} > end ${last}` };
|
|
45
|
-
return { kind: "range", start: n, end: m };
|
|
46
|
-
};
|
|
47
|
-
// READ a line range. Returns the raw selected lines (no `N:\t` prefix)
|
|
48
|
-
// plus the 1-indexed position of the first selected line. The render
|
|
49
|
-
// layer adds `N:\t` per plurnk.md ("READ output prefixes every line with
|
|
50
|
-
// line numbers, N:\t") starting from `startLine` — keeps numbering as a
|
|
51
|
-
// presentation concern, prevents double-prefixing when the same content
|
|
52
|
-
// passes through the log render.
|
|
53
|
-
//
|
|
54
|
-
// Sentinel positions <0> and <-1> select no content (they're insertion
|
|
55
|
-
// points, not lines) → status 200 with empty text.
|
|
56
|
-
export const sliceLines = (content, marker) => {
|
|
57
|
-
const { lines } = splitLines(content);
|
|
58
|
-
const norm = normalize(marker, lines.length);
|
|
59
|
-
if ("error" in norm)
|
|
60
|
-
return { status: 416, error: norm.error };
|
|
61
|
-
if (norm.kind !== "range")
|
|
62
|
-
return { status: 200, text: "", startLine: undefined };
|
|
63
|
-
const selected = lines.slice(norm.start - 1, norm.end);
|
|
64
|
-
return { status: 200, text: selected.join("\n"), startLine: norm.start };
|
|
65
|
-
};
|
|
66
|
-
// Structural `<L>` slice for JSON sources (plurnk-grammar 0.13.0).
|
|
67
|
-
// "On structured entries, <L> addresses item index, not line number."
|
|
68
|
-
// Every JSON value becomes a list of top-level items:
|
|
69
|
-
// array `[a, b, c]` → items are the array elements
|
|
70
|
-
// object `{k1: v1, ...}` → items are key-value pairs (as single-key objects)
|
|
71
|
-
// scalar `"hello"` / 42 → item is the scalar itself (length-1 list)
|
|
72
|
-
// `<L>` indexes into that list (1-indexed). Result is always a JSON array.
|
|
73
|
-
// Sentinels `<0>` / `<-1>` are insertion points — empty `[]` for READ.
|
|
74
|
-
// Out-of-range positions return 416. Matches the uniform "always JSON
|
|
75
|
-
// array out" shape we settled for matcher results.
|
|
76
|
-
const jsonValueToItems = (parsed) => {
|
|
77
|
-
if (Array.isArray(parsed))
|
|
78
|
-
return parsed;
|
|
79
|
-
if (parsed !== null && typeof parsed === "object") {
|
|
80
|
-
// Object items are single-key {key: value} wrappers, in insertion
|
|
81
|
-
// order. Object.entries preserves spec-guaranteed iteration order
|
|
82
|
-
// for string keys.
|
|
83
|
-
return Object.entries(parsed).map(([k, v]) => ({ [k]: v }));
|
|
84
|
-
}
|
|
85
|
-
// Scalar (string, number, boolean, null): a length-1 list of itself.
|
|
86
|
-
return [parsed];
|
|
87
|
-
};
|
|
88
|
-
export const sliceJsonItems = (content, marker) => {
|
|
89
|
-
let parsed;
|
|
90
|
-
try {
|
|
91
|
-
parsed = JSON.parse(content);
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
return { status: 400, error: `malformed JSON: ${err instanceof Error ? err.message : String(err)}` };
|
|
95
|
-
}
|
|
96
|
-
const items = jsonValueToItems(parsed);
|
|
97
|
-
const total = items.length;
|
|
98
|
-
const { first, last } = marker;
|
|
99
|
-
if (last === null) {
|
|
100
|
-
if (first === 0 || first === -1)
|
|
101
|
-
return { status: 200, body: "[]" };
|
|
102
|
-
if (first > 0 && first <= total)
|
|
103
|
-
return { status: 200, body: JSON.stringify([items[first - 1]], null, 2) };
|
|
104
|
-
return { status: 416, error: `item ${first} out of range (1..${total})` };
|
|
105
|
-
}
|
|
106
|
-
let n = first;
|
|
107
|
-
let m = last;
|
|
108
|
-
if (n === 0)
|
|
109
|
-
n = 1;
|
|
110
|
-
if (m === -1)
|
|
111
|
-
m = total;
|
|
112
|
-
if (n < 1 || n > total)
|
|
113
|
-
return { status: 416, error: `range start ${first} out of range (1..${total})` };
|
|
114
|
-
if (m < 1 || m > total)
|
|
115
|
-
return { status: 416, error: `range end ${last} out of range (1..${total})` };
|
|
116
|
-
if (n > m)
|
|
117
|
-
return { status: 416, error: `range start ${first} > end ${last}` };
|
|
118
|
-
return { status: 200, body: JSON.stringify(items.slice(n - 1, m), null, 2) };
|
|
119
|
-
};
|
|
120
|
-
// Structural `<L>` EDIT for JSON sources (plurnk-grammar 0.13.0/0.14.0).
|
|
121
|
-
// Source-shape rules (matches sliceJsonItems' item definition):
|
|
122
|
-
// array → items are elements
|
|
123
|
-
// object → items are key-value pairs (single-key fragments)
|
|
124
|
-
// scalar → length-1 list of itself; grow markers (<0>,<-1>) reject
|
|
125
|
-
//
|
|
126
|
-
// Body shape (Resolution B):
|
|
127
|
-
// body parses as JSON array → those are the items to splice in
|
|
128
|
-
// body parses as non-array JSON → single item to splice in
|
|
129
|
-
// empty body → delete the selection
|
|
130
|
-
// body fails JSON parse → 400 (path-extension declares intent; honor it)
|
|
131
|
-
//
|
|
132
|
-
// Marker semantics (parallel to line-EDIT):
|
|
133
|
-
// <N> replace item N with body item(s)
|
|
134
|
-
// <N,M> replace items N..M with body item(s)
|
|
135
|
-
// <0> prepend body item(s)
|
|
136
|
-
// <-1> append body item(s)
|
|
137
|
-
// <1,-1> replace whole top-level with body item(s); empty body clears
|
|
138
|
-
// Empty body on a sentinel insertion (<0> or <-1>) → no-op.
|
|
139
|
-
const itemsFromBody = (body) => {
|
|
140
|
-
if (body === "")
|
|
141
|
-
return { items: [] }; // empty body = delete
|
|
142
|
-
let parsed;
|
|
143
|
-
try {
|
|
144
|
-
parsed = JSON.parse(body);
|
|
145
|
-
}
|
|
146
|
-
catch (err) {
|
|
147
|
-
return { error: `malformed JSON body: ${err instanceof Error ? err.message : String(err)}` };
|
|
148
|
-
}
|
|
149
|
-
if (Array.isArray(parsed))
|
|
150
|
-
return { items: parsed };
|
|
151
|
-
return { items: [parsed] };
|
|
152
|
-
};
|
|
153
|
-
const applyJsonArrayEdit = (source, marker, items) => {
|
|
154
|
-
const total = source.length;
|
|
155
|
-
const { first, last } = marker;
|
|
156
|
-
let result;
|
|
157
|
-
if (last === null) {
|
|
158
|
-
if (first === 0)
|
|
159
|
-
result = [...items, ...source];
|
|
160
|
-
else if (first === -1)
|
|
161
|
-
result = [...source, ...items];
|
|
162
|
-
else if (first > 0 && first <= total)
|
|
163
|
-
result = [...source.slice(0, first - 1), ...items, ...source.slice(first)];
|
|
164
|
-
else
|
|
165
|
-
return { status: 416, error: `position ${first} out of range (1..${total})` };
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
let n = first;
|
|
169
|
-
let m = last;
|
|
170
|
-
if (n === 0)
|
|
171
|
-
n = 1;
|
|
172
|
-
if (m === -1)
|
|
173
|
-
m = total;
|
|
174
|
-
if (total === 0 && (first !== 1 || last !== -1))
|
|
175
|
-
return { status: 416, error: `range on empty array` };
|
|
176
|
-
if (n < 1 || (total > 0 && n > total))
|
|
177
|
-
return { status: 416, error: `range start ${first} out of range (1..${total})` };
|
|
178
|
-
if (m < 1 || (total > 0 && m > total))
|
|
179
|
-
return { status: 416, error: `range end ${last} out of range (1..${total})` };
|
|
180
|
-
if (n > m)
|
|
181
|
-
return { status: 416, error: `range start ${first} > end ${last}` };
|
|
182
|
-
result = [...source.slice(0, n - 1), ...items, ...source.slice(m)];
|
|
183
|
-
}
|
|
184
|
-
return { status: 200, result: JSON.stringify(result, null, 2) };
|
|
185
|
-
};
|
|
186
|
-
const applyJsonObjectEdit = (source, marker, items) => {
|
|
187
|
-
// Object items are key-value pairs. Body items must be objects;
|
|
188
|
-
// each object's entries become kv-pairs to splice in. Items that
|
|
189
|
-
// aren't single objects → 400 (model used wrong body shape for an
|
|
190
|
-
// object source).
|
|
191
|
-
const bodyEntries = [];
|
|
192
|
-
for (const item of items) {
|
|
193
|
-
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
194
|
-
return { status: 400, error: "object source requires body items to be JSON objects (key-value pairs)" };
|
|
195
|
-
}
|
|
196
|
-
bodyEntries.push(...Object.entries(item));
|
|
197
|
-
}
|
|
198
|
-
const entries = Object.entries(source);
|
|
199
|
-
const total = entries.length;
|
|
200
|
-
const { first, last } = marker;
|
|
201
|
-
let result;
|
|
202
|
-
if (last === null) {
|
|
203
|
-
if (first === 0)
|
|
204
|
-
result = [...bodyEntries, ...entries];
|
|
205
|
-
else if (first === -1)
|
|
206
|
-
result = [...entries, ...bodyEntries];
|
|
207
|
-
else if (first > 0 && first <= total)
|
|
208
|
-
result = [...entries.slice(0, first - 1), ...bodyEntries, ...entries.slice(first)];
|
|
209
|
-
else
|
|
210
|
-
return { status: 416, error: `position ${first} out of range (1..${total})` };
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
let n = first;
|
|
214
|
-
let m = last;
|
|
215
|
-
if (n === 0)
|
|
216
|
-
n = 1;
|
|
217
|
-
if (m === -1)
|
|
218
|
-
m = total;
|
|
219
|
-
if (total === 0 && (first !== 1 || last !== -1))
|
|
220
|
-
return { status: 416, error: `range on empty object` };
|
|
221
|
-
if (n < 1 || (total > 0 && n > total))
|
|
222
|
-
return { status: 416, error: `range start ${first} out of range (1..${total})` };
|
|
223
|
-
if (m < 1 || (total > 0 && m > total))
|
|
224
|
-
return { status: 416, error: `range end ${last} out of range (1..${total})` };
|
|
225
|
-
if (n > m)
|
|
226
|
-
return { status: 416, error: `range start ${first} > end ${last}` };
|
|
227
|
-
result = [...entries.slice(0, n - 1), ...bodyEntries, ...entries.slice(m)];
|
|
228
|
-
}
|
|
229
|
-
return { status: 200, result: JSON.stringify(Object.fromEntries(result), null, 2) };
|
|
230
|
-
};
|
|
231
|
-
const applyJsonScalarEdit = (source, marker, items) => {
|
|
232
|
-
// Scalar source is a length-1 list of itself. Only `<1>` replace
|
|
233
|
-
// works cleanly; grow markers (<0>,<-1>) and ranges that imply
|
|
234
|
-
// growth/delete would require type promotion (scalar → array),
|
|
235
|
-
// which is the kind of implicit magic that bites later. Reject.
|
|
236
|
-
const { first, last } = marker;
|
|
237
|
-
if (last === null && first === 1) {
|
|
238
|
-
if (items.length === 0)
|
|
239
|
-
return { status: 200, result: "null" }; // delete the scalar
|
|
240
|
-
if (items.length === 1)
|
|
241
|
-
return { status: 200, result: JSON.stringify(items[0], null, 2) };
|
|
242
|
-
return { status: 400, error: "scalar source: <1> body must produce 0 or 1 items (no implicit promotion to array)" };
|
|
243
|
-
}
|
|
244
|
-
if (last === -1 && first === 1) {
|
|
245
|
-
// <1,-1> = whole content. Same constraints as <1> for scalars.
|
|
246
|
-
if (items.length === 0)
|
|
247
|
-
return { status: 200, result: "null" };
|
|
248
|
-
if (items.length === 1)
|
|
249
|
-
return { status: 200, result: JSON.stringify(items[0], null, 2) };
|
|
250
|
-
return { status: 400, error: "scalar source: <1,-1> body must produce 0 or 1 items (no implicit promotion to array)" };
|
|
251
|
-
}
|
|
252
|
-
return { status: 400, error: "scalar JSON source: only <1> or <1,-1> markers supported (no implicit promotion to array via grow markers)" };
|
|
253
|
-
};
|
|
254
|
-
export const applyJsonItemEdit = (content, marker, body) => {
|
|
255
|
-
let parsed;
|
|
256
|
-
try {
|
|
257
|
-
parsed = JSON.parse(content);
|
|
258
|
-
}
|
|
259
|
-
catch (err) {
|
|
260
|
-
return { status: 400, error: `malformed JSON source: ${err instanceof Error ? err.message : String(err)}` };
|
|
261
|
-
}
|
|
262
|
-
const bodyResult = itemsFromBody(body);
|
|
263
|
-
if ("error" in bodyResult)
|
|
264
|
-
return { status: 400, error: bodyResult.error };
|
|
265
|
-
const items = bodyResult.items;
|
|
266
|
-
// Empty-body sentinel insertion is a no-op (model accidentally
|
|
267
|
-
// emitted no items at an insertion point).
|
|
268
|
-
if (items.length === 0 && marker.last === null && (marker.first === 0 || marker.first === -1)) {
|
|
269
|
-
return { status: 200, result: content };
|
|
270
|
-
}
|
|
271
|
-
if (Array.isArray(parsed))
|
|
272
|
-
return applyJsonArrayEdit(parsed, marker, items);
|
|
273
|
-
if (parsed !== null && typeof parsed === "object")
|
|
274
|
-
return applyJsonObjectEdit(parsed, marker, items);
|
|
275
|
-
return applyJsonScalarEdit(parsed, marker, items);
|
|
276
|
-
};
|
|
277
|
-
// COPY-style raw line slice. Returns the selected lines verbatim (no line-
|
|
278
|
-
// number prefix), trailing newline appended if any lines were selected.
|
|
279
|
-
// Used for COPY/MOVE `<L>` per SPEC.md §16.9 (source range, symmetric
|
|
280
|
-
// with READ but without the READ-output prefix that's a render concern,
|
|
281
|
-
// not a data concern).
|
|
282
|
-
export const sliceLinesRaw = (content, marker) => {
|
|
283
|
-
const { lines } = splitLines(content);
|
|
284
|
-
const norm = normalize(marker, lines.length);
|
|
285
|
-
if ("error" in norm)
|
|
286
|
-
return { status: 416, error: norm.error };
|
|
287
|
-
if (norm.kind !== "range")
|
|
288
|
-
return { status: 200, text: "" };
|
|
289
|
-
const selected = lines.slice(norm.start - 1, norm.end);
|
|
290
|
-
const result = selected.length > 0 ? `${selected.join("\n")}\n` : "";
|
|
291
|
-
return { status: 200, text: result };
|
|
292
|
-
};
|
|
293
|
-
// EDIT applies body at the marker position:
|
|
294
|
-
// <0> prepend body before line 1
|
|
295
|
-
// <-1> append body after the last line
|
|
296
|
-
// <N> replace line N with body
|
|
297
|
-
// <N,M> replace lines N..M with body
|
|
298
|
-
// <1,-1> whole content (replace everything); empty body clears.
|
|
299
|
-
// Empty body with <N>/<N,M> deletes those lines.
|
|
300
|
-
export const applyLineMarkerEdit = (content, marker, body) => {
|
|
301
|
-
const { lines, trailingNewline } = splitLines(content);
|
|
302
|
-
const norm = normalize(marker, lines.length);
|
|
303
|
-
if ("error" in norm)
|
|
304
|
-
return { status: 416, error: norm.error };
|
|
305
|
-
const bodyLines = splitLines(body).lines;
|
|
306
|
-
let newLines;
|
|
307
|
-
if (norm.kind === "before-first") {
|
|
308
|
-
newLines = [...bodyLines, ...lines];
|
|
309
|
-
}
|
|
310
|
-
else if (norm.kind === "after-last") {
|
|
311
|
-
newLines = [...lines, ...bodyLines];
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
newLines = [...lines.slice(0, norm.start - 1), ...bodyLines, ...lines.slice(norm.end)];
|
|
315
|
-
}
|
|
316
|
-
let result = newLines.join("\n");
|
|
317
|
-
if (newLines.length > 0 && trailingNewline)
|
|
318
|
-
result += "\n";
|
|
319
|
-
return { status: 200, result };
|
|
320
|
-
};
|
|
321
|
-
//# sourceMappingURL=line-marker.js.map
|