@nseng-ai/kernel 0.1.2

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.
@@ -0,0 +1,67 @@
1
+ import type { ExplicitUndefined } from "@nseng-ai/foundation/primitives";
2
+
3
+ import type { ClinkrFormat, RenderCapabilities } from "./command.ts";
4
+ import type { NsCommandIo, NsProgress } from "./services.ts";
5
+ import type { TextGenerator } from "./text-generation.ts";
6
+
7
+ export interface ExecResult {
8
+ stdout: string;
9
+ stderr: string;
10
+ code: number;
11
+ killed: boolean;
12
+ startupError?: string;
13
+ }
14
+
15
+ export interface NsExecOptions {
16
+ timeoutMs?: number;
17
+ stdin?: ExplicitUndefined<"public-api-compatibility", string>;
18
+ onStdout?: ExplicitUndefined<"public-api-compatibility", (text: string) => void>;
19
+ onStderr?: ExplicitUndefined<"public-api-compatibility", (text: string) => void>;
20
+ }
21
+
22
+ export type NsOutputStream = "stdout" | "stderr";
23
+ export interface NsConfirmOptions {
24
+ defaultAnswer?: "yes" | "no";
25
+ }
26
+
27
+ export type NsConfirmPrompt = (
28
+ title: string,
29
+ message: string,
30
+ options?: NsConfirmOptions,
31
+ ) => Promise<boolean> | boolean;
32
+
33
+ export interface NsExtensionApi {
34
+ /** Current repository working directory for command-entry execution. */
35
+ cwd: string;
36
+ /** Environment visible to ns commands and shell execution. */
37
+ env: Record<string, string | undefined>;
38
+ /** Kernel-resolved user home directory for user-scope operations, when available. */
39
+ homeDir?: string;
40
+ /** Low-level argv execution hook. Project commands own the exact commands they run. */
41
+ exec(command: string, args: string[], options?: NsExecOptions): Promise<ExecResult>;
42
+ /** Text-generation capability; ns commands own prompts, validation, and repair policy. */
43
+ textGenerator: TextGenerator;
44
+ /** Higher-level human command-output service provided by the host/kernel. */
45
+ commandIo: NsCommandIo;
46
+ /** Structured phase progress sink provided by the host/kernel. */
47
+ progress: NsProgress;
48
+ /** Host terminal rendering capabilities for human output and previews. */
49
+ renderCapabilities: RenderCapabilities;
50
+ /** Host-selected command output format. Useful only for commands streaming durable output before returning. */
51
+ outputFormat?: ClinkrFormat;
52
+ /** Durable output for commands that need to stream multiple chunks before returning. */
53
+ stdout?: ExplicitUndefined<"public-api-compatibility", (text: string) => void>;
54
+ /** Durable error output for commands that need to stream multiple chunks before returning. */
55
+ stderr?: ExplicitUndefined<"public-api-compatibility", (text: string) => void>;
56
+ /** Optional full stdin reader for commands that consume a finite payload. */
57
+ stdin?: ExplicitUndefined<"public-api-compatibility", () => Promise<string>>;
58
+ /** Transient live-progress output for UI bridges. */
59
+ onOutput?: ExplicitUndefined<
60
+ "public-api-compatibility",
61
+ (stream: NsOutputStream, text: string) => void
62
+ >;
63
+ /** Optional UI confirmation hook for interactive ns commands. */
64
+ confirm?: ExplicitUndefined<"public-api-compatibility", NsConfirmPrompt>;
65
+ /** Project-local extension bag. ns commands own any values they read from it. */
66
+ extensions?: ExplicitUndefined<"public-api-compatibility", Readonly<Record<string, unknown>>>;
67
+ }
@@ -0,0 +1,38 @@
1
+ import { z } from "./schema.ts";
2
+
3
+ export const nsExtensionPointAcceptsValues = ["hook", "prompt"] as const;
4
+ export const nsExtensionPointSemanticsValues = ["additive", "override"] as const;
5
+
6
+ export const nsExtensionManifestCommandSchema = z.looseObject({
7
+ name: z.string().optional(),
8
+ path: z.array(z.string()).optional(),
9
+ group: z.string().optional(),
10
+ description: z.string().optional(),
11
+ fullDescription: z.string().optional(),
12
+ entry: z.string().optional(),
13
+ });
14
+
15
+ export const nsExtensionManifestPointSchema = z.looseObject({
16
+ path: z.array(z.string()).optional(),
17
+ accepts: z.enum(nsExtensionPointAcceptsValues).optional(),
18
+ semantics: z.enum(nsExtensionPointSemanticsValues).optional(),
19
+ default: z.string().optional(),
20
+ description: z.string().optional(),
21
+ });
22
+
23
+ export const nsExtensionManifestSchema = z.looseObject({
24
+ description: z.string().optional(),
25
+ group: z.string().optional(),
26
+ commands: z.array(z.unknown()).optional(),
27
+ points: z.array(z.unknown()).optional(),
28
+ });
29
+
30
+ export const nsExtensionPackageManifestSchema = z.looseObject({
31
+ description: z.string().optional(),
32
+ ns: nsExtensionManifestSchema.optional(),
33
+ });
34
+
35
+ export type NsExtensionManifestCommand = z.infer<typeof nsExtensionManifestCommandSchema>;
36
+ export type NsExtensionManifestPoint = z.infer<typeof nsExtensionManifestPointSchema>;
37
+ export type NsExtensionManifest = z.infer<typeof nsExtensionManifestSchema>;
38
+ export type NsExtensionPackageManifest = z.infer<typeof nsExtensionPackageManifestSchema>;
@@ -0,0 +1,73 @@
1
+ // Public author API for ns extensions.
2
+ // Keep ts/packages/kernel/docs/sdk-reference.md in sync when changing these exports.
3
+ export { defineExtension } from "./command.ts";
4
+ export {
5
+ defineRepoLocalNsExtensionDescriptor,
6
+ repoLocalNsCommandDescriptor,
7
+ } from "./repo-local-ns-extension.ts";
8
+ export {
9
+ nsExtensionManifestCommandSchema,
10
+ nsExtensionManifestSchema,
11
+ nsExtensionPackageManifestSchema,
12
+ } from "./extension-manifest.ts";
13
+ export type {
14
+ NsExtensionManifest,
15
+ NsExtensionManifestCommand,
16
+ NsExtensionPackageManifest,
17
+ } from "./extension-manifest.ts";
18
+ export type {
19
+ ClinkrCompletionCandidate,
20
+ ClinkrCompletionResult,
21
+ ClinkrDynamicCompletionRequest,
22
+ ClinkrExit,
23
+ ClinkrFormat,
24
+ PositionalSpec,
25
+ RenderCapabilities,
26
+ NsCommand,
27
+ NsCommandCompletionProvider,
28
+ NsCommandRequest,
29
+ NsCommandSchema,
30
+ NsExtension,
31
+ } from "./command.ts";
32
+ export type {
33
+ RepoLocalNsCommandDescriptorOptions,
34
+ RepoLocalNsExtensionCommandDescriptor,
35
+ RepoLocalNsExtensionDescriptor,
36
+ } from "./repo-local-ns-extension.ts";
37
+ export type {
38
+ ExecResult,
39
+ NsConfirmOptions,
40
+ NsConfirmPrompt,
41
+ NsExecOptions,
42
+ NsExtensionApi,
43
+ NsOutputStream,
44
+ } from "./execution.ts";
45
+ export {
46
+ normalizeTextOutput,
47
+ stripOuterCodeFence,
48
+ trimOuterBlankLines,
49
+ } from "@nseng-ai/foundation/text-normalization";
50
+ export { truncateTextHead, truncateTextHeadTail } from "@nseng-ai/foundation/text-truncation";
51
+ export type {
52
+ HeadTailTextTruncationOptions,
53
+ HeadTextTruncationOptions,
54
+ } from "@nseng-ai/foundation/text-truncation";
55
+ export { failed, ok } from "./result.ts";
56
+ export type { NsResult } from "./result.ts";
57
+ export { noopNsCommandIo, noopNsProgress } from "./services.ts";
58
+ export type {
59
+ NsCommandIo,
60
+ NsCommandMessageOptions,
61
+ NsNotifyLevel,
62
+ NsProgress,
63
+ NsProgressPhaseEvent,
64
+ NsProgressPhaseInfo,
65
+ NsProgressPhaseListener,
66
+ } from "./services.ts";
67
+ export { z } from "./schema.ts";
68
+ export type {
69
+ TextGenerationRequest,
70
+ TextGenerationResult,
71
+ TextGenerationUsage,
72
+ TextGenerator,
73
+ } from "./text-generation.ts";
@@ -0,0 +1,352 @@
1
+ import type { NsProgressPhaseEvent, NsProgressPhaseInfo } from "./services.ts";
2
+
3
+ export type ProgressPhaseState = "pending" | "active" | "done" | "skipped" | "failed";
4
+
5
+ export interface ProgressPhaseSpec extends NsProgressPhaseInfo {
6
+ readonly substeps?: readonly ProgressPhaseSpec[];
7
+ }
8
+
9
+ export interface ProgressPhaseView {
10
+ readonly key: string;
11
+ readonly name: string;
12
+ readonly detail?: string;
13
+ readonly state: ProgressPhaseState;
14
+ readonly label: string | undefined;
15
+ readonly history: readonly string[];
16
+ readonly substeps: readonly ProgressPhaseView[];
17
+ }
18
+
19
+ export type ProgressPhaseUnknownKeyPolicy = "ignore" | "append";
20
+
21
+ export interface ProgressPhaseStateStoreOptions {
22
+ readonly phases?: readonly ProgressPhaseSpec[];
23
+ readonly unknownKeyPolicy?: ProgressPhaseUnknownKeyPolicy;
24
+ }
25
+
26
+ export interface ProgressPhaseStateStore {
27
+ views(): readonly ProgressPhaseView[];
28
+ title(): string | undefined;
29
+ apply(event: NsProgressPhaseEvent): ProgressPhaseView | undefined;
30
+ failActive(): void;
31
+ settleOpenPhases(): void;
32
+ }
33
+
34
+ interface PhaseRecord {
35
+ spec: ProgressPhaseSpec;
36
+ runtimeDetail: string | undefined;
37
+ state: ProgressPhaseState;
38
+ label: string | undefined;
39
+ history: string[];
40
+ substeps: PhaseRecord[];
41
+ }
42
+
43
+ type PhaseLocation =
44
+ | { type: "top"; index: number }
45
+ | { type: "substep"; parentIndex: number; index: number };
46
+
47
+ export function createProgressPhaseStateStore(
48
+ options: ProgressPhaseStateStoreOptions = {},
49
+ ): ProgressPhaseStateStore {
50
+ const unknownKeyPolicy = options.unknownKeyPolicy ?? "ignore";
51
+ let records = (options.phases ?? []).map(createRecord);
52
+ let indexByKey = indexRecords(records);
53
+ let activeLocation: PhaseLocation | undefined;
54
+ let currentTitle: string | undefined;
55
+
56
+ function views(): readonly ProgressPhaseView[] {
57
+ return records.map(viewForRecord);
58
+ }
59
+
60
+ function title(): string | undefined {
61
+ return currentTitle;
62
+ }
63
+
64
+ function apply(event: NsProgressPhaseEvent): ProgressPhaseView | undefined {
65
+ switch (event.type) {
66
+ case "phases-declared":
67
+ currentTitle = event.title;
68
+ records = event.phases.map(createRecord);
69
+ indexByKey = indexRecords(records);
70
+ activeLocation = undefined;
71
+ return undefined;
72
+ case "title-changed":
73
+ currentTitle = event.title;
74
+ return undefined;
75
+ case "phase-started":
76
+ return applyStarted(event.phaseKey, event.label);
77
+ case "phase-progress":
78
+ return applyProgress(event.phaseKey, event.label);
79
+ case "phase-done":
80
+ return applyDone(event.phaseKey, event.detail);
81
+ case "phase-failed":
82
+ return applyFailed(event.phaseKey, event.detail);
83
+ }
84
+ }
85
+
86
+ function applyStarted(
87
+ phaseKey: string,
88
+ label: string | undefined,
89
+ ): ProgressPhaseView | undefined {
90
+ return updateRecord(phaseKey, {
91
+ prepare: setActive,
92
+ update: (record) => withLabel(record, label ?? record.spec.label),
93
+ });
94
+ }
95
+
96
+ function applyProgress(phaseKey: string, label: string): ProgressPhaseView | undefined {
97
+ return updateRecord(phaseKey, {
98
+ prepare: ensureProgressTargetActive,
99
+ update: (record) => withLabel(record, label),
100
+ });
101
+ }
102
+
103
+ function applyDone(phaseKey: string, detail: string | undefined): ProgressPhaseView | undefined {
104
+ return updateRecord(phaseKey, { update: (record) => completeRecord(record, detail) });
105
+ }
106
+
107
+ function applyFailed(phaseKey: string, detail: string): ProgressPhaseView | undefined {
108
+ return updateRecord(phaseKey, {
109
+ update: (record) => withLabel(record, detail, { state: "failed" }),
110
+ after: failParentForSubstep,
111
+ });
112
+ }
113
+
114
+ function updateRecord(
115
+ phaseKey: string,
116
+ options: {
117
+ prepare?(location: PhaseLocation): void;
118
+ update(record: PhaseRecord): PhaseRecord;
119
+ after?(location: PhaseLocation): void;
120
+ },
121
+ ): ProgressPhaseView | undefined {
122
+ const location = ensureLocation(phaseKey);
123
+ if (location === undefined) return undefined;
124
+ options.prepare?.(location);
125
+ const record = recordAt(location);
126
+ if (record === undefined) return undefined;
127
+ const nextRecord = options.update(record);
128
+ replaceRecord(location, nextRecord);
129
+ options.after?.(location);
130
+ return viewForRecord(nextRecord);
131
+ }
132
+
133
+ function ensureLocation(phaseKey: string): PhaseLocation | undefined {
134
+ const existing = indexByKey.get(phaseKey);
135
+ if (existing !== undefined) return existing;
136
+ if (unknownKeyPolicy === "ignore") return undefined;
137
+ const record = createRecord({ key: phaseKey, name: phaseKey });
138
+ records = [...records, record];
139
+ const location = { type: "top", index: records.length - 1 } satisfies PhaseLocation;
140
+ indexByKey = indexRecords(records);
141
+ return location;
142
+ }
143
+
144
+ function recordAt(location: PhaseLocation): PhaseRecord | undefined {
145
+ if (location.type === "top") return records[location.index];
146
+ return records[location.parentIndex]?.substeps[location.index];
147
+ }
148
+
149
+ function replaceRecord(location: PhaseLocation, record: PhaseRecord): void {
150
+ if (location.type === "top") {
151
+ records = replaceAt(records, location.index, record);
152
+ return;
153
+ }
154
+ const parent = records[location.parentIndex];
155
+ if (parent === undefined) return;
156
+ records = replaceAt(records, location.parentIndex, {
157
+ ...parent,
158
+ substeps: replaceAt(parent.substeps, location.index, record),
159
+ });
160
+ }
161
+
162
+ function setActive(location: PhaseLocation): void {
163
+ if (location.type === "top") {
164
+ records = markEarlierDone(records, location.index);
165
+ const record = recordAt(location);
166
+ if (record !== undefined) replaceRecord(location, { ...record, state: "active" });
167
+ } else {
168
+ activateSubstep(location);
169
+ }
170
+ activeLocation = location;
171
+ }
172
+
173
+ function activateSubstep(location: Extract<PhaseLocation, { type: "substep" }>): void {
174
+ const parent = activateParentForSubstep(location);
175
+ if (parent === undefined) return;
176
+ const substeps = markEarlierDone(parent.substeps, location.index);
177
+ const substep = substeps[location.index];
178
+ const nextSubsteps =
179
+ substep === undefined
180
+ ? substeps
181
+ : replaceAt(substeps, location.index, {
182
+ ...substep,
183
+ state: "active",
184
+ } satisfies PhaseRecord);
185
+ records = replaceAt(records, location.parentIndex, {
186
+ ...parent,
187
+ substeps: nextSubsteps,
188
+ } satisfies PhaseRecord);
189
+ }
190
+
191
+ function activateParentForSubstep(
192
+ location: Extract<PhaseLocation, { type: "substep" }>,
193
+ ): PhaseRecord | undefined {
194
+ records = markEarlierDone(records, location.parentIndex);
195
+ const parent = records[location.parentIndex];
196
+ if (parent === undefined) return undefined;
197
+ const activeParent = { ...parent, state: "active" } satisfies PhaseRecord;
198
+ records = replaceAt(records, location.parentIndex, activeParent);
199
+ return activeParent;
200
+ }
201
+
202
+ function ensureProgressTargetActive(location: PhaseLocation): void {
203
+ const record = recordAt(location);
204
+ if (record?.state === "pending") {
205
+ setActive(location);
206
+ return;
207
+ }
208
+ if (location.type === "top") return;
209
+ if (record?.state === "active") {
210
+ activateParentForSubstep(location);
211
+ activeLocation = location;
212
+ }
213
+ }
214
+
215
+ function failActive(): void {
216
+ if (activeLocation === undefined) return;
217
+ const record = recordAt(activeLocation);
218
+ if (record === undefined) return;
219
+ replaceRecord(activeLocation, { ...record, state: "failed" });
220
+ failParentForSubstep(activeLocation);
221
+ }
222
+
223
+ function settleOpenPhases(): void {
224
+ const hasFailure = records.some(
225
+ (record) =>
226
+ record.state === "failed" || record.substeps.some((substep) => substep.state === "failed"),
227
+ );
228
+ if (hasFailure) return;
229
+ records = records.map((record) => {
230
+ if (record.state === "pending" || record.state === "active") return setDone(record);
231
+ return record;
232
+ });
233
+ }
234
+
235
+ function failParentForSubstep(location: PhaseLocation): void {
236
+ if (location.type === "top") return;
237
+ const parent = records[location.parentIndex];
238
+ if (parent !== undefined) {
239
+ records = replaceAt(records, location.parentIndex, { ...parent, state: "failed" });
240
+ }
241
+ }
242
+
243
+ return { views, title, apply, failActive, settleOpenPhases };
244
+ }
245
+
246
+ function createRecord(spec: ProgressPhaseSpec): PhaseRecord {
247
+ return {
248
+ spec: copySpec(spec),
249
+ runtimeDetail: undefined,
250
+ state: "pending",
251
+ label: spec.label,
252
+ history: [],
253
+ substeps: spec.substeps?.map(createRecord) ?? [],
254
+ };
255
+ }
256
+
257
+ function copySpec(spec: ProgressPhaseSpec): ProgressPhaseSpec {
258
+ return {
259
+ key: spec.key,
260
+ name: spec.name,
261
+ ...(spec.label === undefined ? {} : { label: spec.label }),
262
+ ...(spec.detail === undefined ? {} : { detail: spec.detail }),
263
+ ...(spec.substeps === undefined ? {} : { substeps: spec.substeps.map(copySpec) }),
264
+ };
265
+ }
266
+
267
+ function viewForRecord(record: PhaseRecord): ProgressPhaseView {
268
+ const detail = detailForRecord(record);
269
+ return {
270
+ key: record.spec.key,
271
+ name: record.spec.name,
272
+ ...(detail === undefined ? {} : { detail }),
273
+ state: record.state,
274
+ label: record.label,
275
+ history: record.history.slice(),
276
+ substeps: record.substeps.map(viewForRecord),
277
+ };
278
+ }
279
+
280
+ function indexRecords(records: readonly PhaseRecord[]): Map<string, PhaseLocation> {
281
+ const indexByKey = new Map<string, PhaseLocation>();
282
+ records.forEach((record, index) => {
283
+ indexByKey.set(record.spec.key, { type: "top", index });
284
+ record.substeps.forEach((substep, substepIndex) => {
285
+ indexByKey.set(substep.spec.key, {
286
+ type: "substep",
287
+ parentIndex: index,
288
+ index: substepIndex,
289
+ });
290
+ });
291
+ });
292
+ return indexByKey;
293
+ }
294
+
295
+ function replaceAt<T>(items: readonly T[], index: number, item: T): T[] {
296
+ return items.map((current, currentIndex) => (currentIndex === index ? item : current));
297
+ }
298
+
299
+ function withLabel(
300
+ record: PhaseRecord,
301
+ nextLabel: string | undefined,
302
+ extra: Partial<PhaseRecord> = {},
303
+ ): PhaseRecord {
304
+ return { ...withSupersededLabel(record, nextLabel), label: nextLabel, ...extra };
305
+ }
306
+
307
+ function withSupersededLabel(record: PhaseRecord, nextLabel: string | undefined): PhaseRecord {
308
+ const previous = record.label;
309
+ if (previous === undefined || previous === nextLabel) return record;
310
+ return { ...record, history: [...record.history, previous] };
311
+ }
312
+
313
+ function detailForRecord(record: PhaseRecord): string | undefined {
314
+ return record.runtimeDetail ?? record.spec.detail;
315
+ }
316
+
317
+ function settleSubstepsDone(parent: PhaseRecord): PhaseRecord {
318
+ return {
319
+ ...parent,
320
+ substeps: parent.substeps.map((substep) => {
321
+ if (substep.state === "active") {
322
+ return { ...withSupersededLabel(substep, detailForRecord(substep)), state: "done" };
323
+ }
324
+ if (substep.state === "pending") return { ...substep, state: "skipped" };
325
+ return substep;
326
+ }),
327
+ };
328
+ }
329
+
330
+ function setDone(record: PhaseRecord): PhaseRecord {
331
+ const labeled =
332
+ record.state === "active" ? withSupersededLabel(record, detailForRecord(record)) : record;
333
+ return settleSubstepsDone({ ...labeled, state: "done" });
334
+ }
335
+
336
+ function completeRecord(record: PhaseRecord, detail: string | undefined): PhaseRecord {
337
+ const settledDetail = detail ?? detailForRecord(record);
338
+ return settleSubstepsDone({
339
+ ...withSupersededLabel(record, settledDetail),
340
+ runtimeDetail: detail ?? record.runtimeDetail,
341
+ state: "done",
342
+ label: settledDetail ?? record.label,
343
+ });
344
+ }
345
+
346
+ function markEarlierDone(items: readonly PhaseRecord[], index: number): PhaseRecord[] {
347
+ return items.map((record, itemIndex) => {
348
+ if (itemIndex >= index) return record;
349
+ if (record.state === "pending" || record.state === "active") return setDone(record);
350
+ return record;
351
+ });
352
+ }
@@ -0,0 +1,41 @@
1
+ import type { NsCommand } from "./command.ts";
2
+
3
+ export interface RepoLocalNsExtensionCommandDescriptor {
4
+ readonly command: NsCommand;
5
+ readonly manifestEntry: string;
6
+ readonly packageExport: string;
7
+ readonly manifestPath?: readonly string[];
8
+ readonly manifestName?: string;
9
+ }
10
+
11
+ export interface RepoLocalNsExtensionDescriptor {
12
+ readonly group: string;
13
+ readonly description: string;
14
+ readonly commands: readonly RepoLocalNsExtensionCommandDescriptor[];
15
+ }
16
+
17
+ export interface RepoLocalNsCommandDescriptorOptions {
18
+ readonly command: NsCommand;
19
+ readonly packageExportPrefix: string;
20
+ readonly manifestPath?: readonly string[];
21
+ readonly manifestName?: string;
22
+ }
23
+
24
+ export function repoLocalNsCommandDescriptor(
25
+ options: RepoLocalNsCommandDescriptorOptions,
26
+ ): RepoLocalNsExtensionCommandDescriptor {
27
+ const manifestName = options.manifestName ?? options.command.name;
28
+ return {
29
+ command: options.command,
30
+ ...(options.manifestName === undefined ? {} : { manifestName }),
31
+ ...(options.manifestPath === undefined ? {} : { manifestPath: options.manifestPath }),
32
+ manifestEntry: `./src/commands/${manifestName}.ts`,
33
+ packageExport: `${options.packageExportPrefix}/${manifestName}`,
34
+ };
35
+ }
36
+
37
+ export function defineRepoLocalNsExtensionDescriptor(
38
+ descriptor: RepoLocalNsExtensionDescriptor,
39
+ ): RepoLocalNsExtensionDescriptor {
40
+ return descriptor;
41
+ }
@@ -0,0 +1,11 @@
1
+ export type NsResult =
2
+ | { ok: true; message: string }
3
+ | { ok: false; exitCode: number; message: string };
4
+
5
+ export function ok(message: string): NsResult {
6
+ return { ok: true, message };
7
+ }
8
+
9
+ export function failed(message: string, exitCode = 1): NsResult {
10
+ return { ok: false, exitCode, message };
11
+ }
@@ -0,0 +1,3 @@
1
+ import { z } from "zod";
2
+
3
+ export { z };
@@ -0,0 +1,68 @@
1
+ export type NsNotifyLevel = "info" | "warning" | "error";
2
+
3
+ export interface NsCommandMessageOptions {
4
+ /** Notification level for text-only fallback sinks. Defaults to "info". */
5
+ level?: NsNotifyLevel;
6
+ /**
7
+ * Opaque structured presentation payload for rich sinks (e.g. a Pi custom
8
+ * scrollback message's `details`). The SDK never inspects this value.
9
+ */
10
+ details?: unknown;
11
+ /**
12
+ * When true, emit only to a rich sink. Text-only sinks drop the message.
13
+ * Use for content already delivered through another durable channel (e.g. a
14
+ * CLI summary printed via notify) so it is not duplicated in fallback output.
15
+ */
16
+ isRichOnly?: boolean;
17
+ }
18
+
19
+ export interface NsCommandIo {
20
+ /** Transient, human-facing phase text. Non-contractual wording; never stdout in machine mode. */
21
+ phase(message: string): void;
22
+ /** Terminal human notification (success/warning/error). */
23
+ notify(message: string, level?: NsNotifyLevel): void;
24
+ /**
25
+ * Durable, human-facing scrollback message. Rich sinks (e.g. Pi custom
26
+ * messages) receive `details`; text-only sinks render the message as transient
27
+ * phase text, or drop it entirely when `isRichOnly` is set.
28
+ */
29
+ message(message: string, options?: NsCommandMessageOptions): void;
30
+ /** Clears any sticky transient phase (no-op for append-only sinks). */
31
+ clearPhase(): void;
32
+ }
33
+
34
+ export const noopNsCommandIo: NsCommandIo = {
35
+ phase: () => {},
36
+ notify: () => {},
37
+ message: () => {},
38
+ clearPhase: () => {},
39
+ };
40
+
41
+ /** Presentation metadata for a declared phase checklist. */
42
+ export interface NsProgressPhaseInfo {
43
+ key: string;
44
+ name: string;
45
+ label?: string;
46
+ detail?: string;
47
+ }
48
+
49
+ export type NsProgressPhaseEvent =
50
+ | { type: "phases-declared"; title: string; phases: readonly NsProgressPhaseInfo[] }
51
+ | { type: "title-changed"; title: string }
52
+ | { type: "phase-started"; phaseKey: string; label?: string }
53
+ | { type: "phase-progress"; phaseKey: string; label: string }
54
+ | { type: "phase-done"; phaseKey: string; detail?: string }
55
+ | { type: "phase-failed"; phaseKey: string; detail: string };
56
+
57
+ export type NsProgressPhaseListener = (event: NsProgressPhaseEvent) => void;
58
+
59
+ export interface NsProgress {
60
+ /** True when a host listener consumes phase events; false for the noop sink. */
61
+ readonly isLive: boolean;
62
+ phase(event: NsProgressPhaseEvent): void;
63
+ }
64
+
65
+ export const noopNsProgress: NsProgress = {
66
+ isLive: false,
67
+ phase: () => {},
68
+ };
@@ -0,0 +1,21 @@
1
+ export interface TextGenerationRequest {
2
+ modelRef: string;
3
+ system: string;
4
+ prompt: string;
5
+ maxTokens?: number;
6
+ reasoning?: "minimal" | "low";
7
+ operation?: string;
8
+ }
9
+
10
+ export interface TextGenerationUsage {
11
+ inputTokens: number;
12
+ outputTokens: number;
13
+ }
14
+
15
+ export type TextGenerationResult =
16
+ | { ok: true; text: string; usage?: TextGenerationUsage }
17
+ | { ok: false; error: string };
18
+
19
+ export interface TextGenerator {
20
+ generateText(request: TextGenerationRequest): Promise<TextGenerationResult>;
21
+ }