@asterism-app/sdk 0.0.0-stage → 1.0.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.
Files changed (105) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +20 -2
  3. package/docs/data-and-lifecycle.md +79 -0
  4. package/docs/getting-started.md +37 -0
  5. package/docs/manual.md +38 -0
  6. package/docs/minimal-plugin.md +35 -0
  7. package/docs/project-structure.md +33 -0
  8. package/docs/resources-and-tools.md +43 -0
  9. package/docs/session-entries.md +31 -0
  10. package/docs/views-and-settings.md +43 -0
  11. package/examples/author.ts +158 -0
  12. package/examples/consume-usage.ts +29 -0
  13. package/examples/content-author.ts +29 -0
  14. package/examples/content-contract.ts +10 -0
  15. package/examples/content-renderer.ts +26 -0
  16. package/examples/counter/docs/usage.md +9 -0
  17. package/examples/counter/package.json +27 -0
  18. package/examples/counter/src/contracts.ts +34 -0
  19. package/examples/counter/src/panel.module.css +2 -0
  20. package/examples/counter/src/renderer.tsx +80 -0
  21. package/examples/counter/src/runtime.ts +49 -0
  22. package/examples/counter/src/storage.ts +4 -0
  23. package/examples/counter/src/styles.d.ts +4 -0
  24. package/examples/counter/tsconfig.json +16 -0
  25. package/examples/counter-contract.ts +18 -0
  26. package/examples/data-codecs.ts +18 -0
  27. package/examples/events.ts +37 -0
  28. package/examples/i18n.ts +18 -0
  29. package/examples/model-completion.ts +15 -0
  30. package/examples/model-input.ts +16 -0
  31. package/examples/plugin-calls.ts +22 -0
  32. package/examples/provider-usage-headers.ts +20 -0
  33. package/examples/provider-usage.ts +34 -0
  34. package/examples/renderer.ts +47 -0
  35. package/examples/session-resources.ts +29 -0
  36. package/examples/settings-contract.ts +8 -0
  37. package/examples/settings-renderer.ts +30 -0
  38. package/examples/settings-runtime.ts +11 -0
  39. package/examples/storage.ts +26 -0
  40. package/examples/tool-renderer.ts +47 -0
  41. package/examples/web-provider.ts +27 -0
  42. package/package.json +39 -4
  43. package/src/calls.ts +28 -0
  44. package/src/card.ts +7 -0
  45. package/src/channels.ts +20 -0
  46. package/src/commands.ts +28 -0
  47. package/src/config.ts +25 -0
  48. package/src/content-runtime.ts +11 -0
  49. package/src/content.ts +6 -0
  50. package/src/contracts.ts +39 -0
  51. package/src/data-encoding.ts +14 -0
  52. package/src/error-details.ts +78 -0
  53. package/src/events.ts +91 -0
  54. package/src/i18n.ts +58 -0
  55. package/src/json.ts +4 -0
  56. package/src/mcp.ts +80 -0
  57. package/src/model-catalog.ts +18 -0
  58. package/src/model-cost-estimate.ts +49 -0
  59. package/src/model-cost.ts +44 -0
  60. package/src/model-input.ts +19 -0
  61. package/src/model-media.ts +11 -0
  62. package/src/model-provider.ts +44 -0
  63. package/src/model-records.ts +56 -0
  64. package/src/model-selection.ts +34 -0
  65. package/src/models.ts +134 -0
  66. package/src/module-contract.ts +44 -0
  67. package/src/objects.ts +89 -0
  68. package/src/plugin.ts +58 -0
  69. package/src/presentation.ts +21 -0
  70. package/src/provider-usage.ts +47 -0
  71. package/src/records.ts +60 -0
  72. package/src/renderer-calls.ts +15 -0
  73. package/src/renderer-card.ts +29 -0
  74. package/src/renderer-content.ts +9 -0
  75. package/src/renderer-creation.ts +83 -0
  76. package/src/renderer-i18n.ts +49 -0
  77. package/src/renderer-island.ts +66 -0
  78. package/src/renderer-records.ts +49 -0
  79. package/src/renderer-services.ts +89 -0
  80. package/src/renderer-tools.ts +103 -0
  81. package/src/renderer-view-state.ts +33 -0
  82. package/src/renderer.ts +174 -0
  83. package/src/runtime-agent-catalog.ts +27 -0
  84. package/src/runtime-application.ts +7 -0
  85. package/src/runtime-calls.ts +22 -0
  86. package/src/runtime-channels.ts +24 -0
  87. package/src/runtime-contracts.ts +207 -0
  88. package/src/runtime-mcp.ts +24 -0
  89. package/src/runtime-model-providers.ts +16 -0
  90. package/src/runtime-resources.ts +117 -0
  91. package/src/runtime-sessions.ts +36 -0
  92. package/src/runtime-shell.ts +18 -0
  93. package/src/runtime-terminal.ts +24 -0
  94. package/src/runtime-tools.ts +31 -0
  95. package/src/runtime-usage.ts +54 -0
  96. package/src/runtime-web.ts +36 -0
  97. package/src/runtime.ts +53 -0
  98. package/src/session-title.ts +8 -0
  99. package/src/shell.ts +76 -0
  100. package/src/terminal.ts +36 -0
  101. package/src/tool-result.ts +45 -0
  102. package/src/tool-validation.ts +14 -0
  103. package/src/usage-state.ts +20 -0
  104. package/src/web.ts +75 -0
  105. package/src/workspace.ts +43 -0
@@ -0,0 +1,36 @@
1
+ import type { Json } from "./json.ts";
2
+ import type { ActionContract, CreationContract } from "./commands.ts";
3
+ import type { ResourceContract } from "./module-contract.ts";
4
+ import type { ContentReader } from "./content-runtime.ts";
5
+ import type { ObjectReader, ObjectWriter, SessionReader } from "./objects.ts";
6
+ import type { ResourceCreationResult } from "./workspace.ts";
7
+
8
+ declare const prepared: unique symbol;
9
+ /** Host-issued resource preparation, valid only within one session creation attempt. */
10
+ export interface PreparedCreation { readonly [prepared]: true }
11
+ export interface SessionPreparation {
12
+ readonly signal: AbortSignal;
13
+ prepare<I, S extends Json, O extends Json>(contract: CreationContract<I, S, O>, input: NoInfer<I> & Json): Promise<PreparedCreation>;
14
+ /** Run action parsing and registered preparers for a resource awaiting initialization; this does not execute the action. */
15
+ prepareAction<I, O extends Json>(resource: PreparedCreation, contract: ActionContract<I, O>, input: NoInfer<I> & Json): Promise<O>;
16
+ }
17
+ export interface ObjectInitializer extends ObjectReader {
18
+ commit(work: (writer: ObjectWriter) => undefined): void;
19
+ }
20
+ export interface SessionInitializer extends SessionReader {
21
+ /** Materialize each prepared resource once, preserving its preparation identity. */
22
+ create(prepared: PreparedCreation): ResourceCreationResult;
23
+ object<S extends Json>(contract: ResourceContract<S>, id: string): ObjectInitializer;
24
+ }
25
+ export interface SessionInitialization {
26
+ title: string;
27
+ /** Synchronous initialization commits session identity, resources, and initial records together. */
28
+ initialize(session: SessionInitializer): undefined;
29
+ }
30
+ export interface SessionCreation {
31
+ /** The key identifies one accepted creation within this plugin; retries return its existing session. */
32
+ (key: string, prepare: (context: SessionPreparation) => Promise<SessionInitialization>, options?: {
33
+ signal?: AbortSignal;
34
+ content?: ContentReader;
35
+ }): Promise<string>;
36
+ }
@@ -0,0 +1,18 @@
1
+ import type { Json } from "./json.ts";
2
+ import type { CallResult } from "./runtime-contracts.ts";
3
+ import type { ExecResult, ShellSettings, ShellProfileList } from "./shell.ts";
4
+ import type { TerminalSessions } from "./runtime-terminal.ts";
5
+
6
+ export interface ShellProfiles { get(): ShellSettings; list(): Promise<ShellProfileList> }
7
+ /** One noninteractive shell command with piped output and its own process lifetime. */
8
+ export interface ShellExecution {
9
+ inspect(): Promise<Json>;
10
+ execute(cwd: string, args: { command: string; timeout?: number }, options: {
11
+ signal: AbortSignal; publish(data: ExecResult): void;
12
+ }): Promise<CallResult<ExecResult>>;
13
+ }
14
+ /** Host shell capabilities share configured profiles; plugins own how they compose commands and terminal sessions. */
15
+ export interface ShellServices extends ShellExecution {
16
+ readonly profiles: ShellProfiles;
17
+ readonly terminals: TerminalSessions;
18
+ }
@@ -0,0 +1,24 @@
1
+ import type { TerminalConfig, TerminalDown, TerminalUp, TerminalStatus, TerminalImage } from "./terminal.ts";
2
+
3
+ export interface TerminalViewer { send(frame: TerminalDown): void }
4
+ export class TerminalError extends Error { constructor(readonly code: string, message: string) { super(message); } }
5
+ export interface InteractiveTerminal {
6
+ readonly status: TerminalStatus;
7
+ readonly started: boolean;
8
+ readonly applicationCursor: boolean;
9
+ start(): Promise<void>;
10
+ close(): Promise<void>;
11
+ dispose(): Promise<void>;
12
+ attach(viewer: TerminalViewer): (message: TerminalUp) => void;
13
+ detach(viewer: TerminalViewer): void;
14
+ input(data: string, signal: AbortSignal): Promise<TerminalStatus>;
15
+ read(source: "screen" | "history" | "raw", offset?: number, limit?: number): Promise<TerminalStatus & {
16
+ source: "screen" | "history" | "raw"; text: string; cols: number; rows: number;
17
+ cursor: { x: number; y: number }; offset: number; nextOffset: number; truncated: boolean;
18
+ }>;
19
+ image(): Promise<TerminalImage>;
20
+ waitIdle(timeout: number, signal: AbortSignal): Promise<TerminalStatus & { reason: string; elapsedMs: number }>;
21
+ }
22
+ export interface TerminalSessions {
23
+ create(config: TerminalConfig, events: { failed(error: unknown): void; log(message: string, error: unknown): void }): InteractiveTerminal;
24
+ }
@@ -0,0 +1,31 @@
1
+ import { defineTool, type ToolContract } from "./module-contract.ts";
2
+ import type { Json } from "./json.ts";
3
+ import type { ObjectReader, ToolSession } from "./objects.ts";
4
+ import type { CallResult } from "./runtime-contracts.ts";
5
+ import type { ToolModelOutput } from "./tool-result.ts";
6
+ import type { z } from "zod";
7
+
8
+ export interface TopLevelToolContext<D extends Json = Json> {
9
+ readonly object: ObjectReader;
10
+ readonly session: ToolSession;
11
+ readonly callId: string;
12
+ readonly signal: AbortSignal;
13
+ publish(data: D): void;
14
+ }
15
+ /** Top-level tools declare their definition and implementation together, independently of resource support relationships. */
16
+ export interface TopLevelTool<A extends Json = Json, D extends Json = Json> extends ToolContract<A> {
17
+ /** Encodes D for progress/history and decodes it for views and model projection. */
18
+ readonly resultSchema: z.ZodType<D>;
19
+ readonly effect: "read" | "write";
20
+ readonly execution: "parallel" | "sequential";
21
+ execute(args: A, context: TopLevelToolContext<D>): Promise<CallResult<D>>;
22
+ toModel?(data: D): ToolModelOutput;
23
+ }
24
+ export function defineTopLevelTool<A extends Json, D extends Json>(definition: TopLevelTool<A, D>): TopLevelTool {
25
+ if (!definition.resultSchema.description?.trim()) throw new Error(`Missing result schema description: ${definition.id}`);
26
+ const tool = defineTool(definition);
27
+ return { ...definition, ...tool,
28
+ execute: (args, context) => definition.execute(args as A, context as TopLevelToolContext<D>),
29
+ ...(definition.toModel ? { toModel: (data: Json) => definition.toModel!(data as D) } : {}),
30
+ } as TopLevelTool;
31
+ }
@@ -0,0 +1,54 @@
1
+ import type { ProviderUsageError, UsageItem, UsageProviderInfo } from "./provider-usage.ts";
2
+ import type { UiText } from "./i18n.ts";
3
+ import type { ProviderUsageState } from "./usage-state.ts";
4
+ import type { ModelResponse } from "./models.ts";
5
+
6
+ /** Consumers explicitly supply the connection and credentials required for the query. */
7
+ export interface UsageQueryContext {
8
+ readonly connection: { id: string; endpoint: string };
9
+ readonly credential: string | null;
10
+ readonly signal: AbortSignal;
11
+ }
12
+ interface UsageProviderIdentity {
13
+ readonly id: string;
14
+ readonly label: UiText;
15
+ }
16
+ export interface QueryUsageProvider extends UsageProviderIdentity {
17
+ readonly source?: "query";
18
+ query(context: UsageQueryContext): Promise<UsageItem[]>;
19
+ }
20
+ export interface UsageHeadersContext {
21
+ readonly connection: { id: string; endpoint: string };
22
+ readonly model: string;
23
+ readonly status: number;
24
+ readonly receivedAt: number;
25
+ readonly headers: Readonly<Record<string, string>>;
26
+ }
27
+ /** Consumers supply response metadata they obtained themselves; the parser makes no network requests. */
28
+ export interface HeadersUsageProvider extends UsageProviderIdentity {
29
+ readonly source: "headers";
30
+ /** null means no usable data this time; an empty list explicitly reports no quota items. */
31
+ parseHeaders(context: UsageHeadersContext): UsageItem[] | null;
32
+ }
33
+ export type UsageProvider = QueryUsageProvider | HeadersUsageProvider;
34
+ /** Providers report publicly exposable error categories; raw responses and credentials are not delivered across processes. */
35
+ export class UsageQueryError extends Error {
36
+ constructor(readonly code: ProviderUsageError) { super(`Provider usage query failed: ${code}.`); }
37
+ }
38
+ export interface UsageProviderServices {
39
+ list(): readonly UsageProviderInfo[];
40
+ /** Obtain an implementation bound to the provider lifecycle; does not select connections, read credentials, or save results. */
41
+ get(id: string): UsageProvider | null;
42
+ subscribe(listener: () => void): () => void;
43
+ }
44
+ /** Stateless invocation facade; consumers explicitly supply connections, credentials, and responses. */
45
+ export interface UsageServices {
46
+ query(providerId: string, context: UsageQueryContext): Promise<UsageItem[]>;
47
+ parseHeaders(providerId: string, context: UsageHeadersContext): UsageItem[] | null;
48
+ }
49
+
50
+ /** Host cache and shared queries; consumers supply response headers they obtained themselves. */
51
+ export interface ProviderUsage {
52
+ query(id: string, options?: { signal?: AbortSignal; refresh?: boolean }): Promise<ProviderUsageState>;
53
+ consume(response: ModelResponse): void;
54
+ }
@@ -0,0 +1,36 @@
1
+ import type { UiText } from "./i18n.ts";
2
+ import type { WebCredentialDefinition, WebProviderState, WebProviderType, WebSearchRequest, WebFetchRequest, WebResponse, WebSearchResult, WebFetchResult } from "./web.ts";
3
+
4
+ export interface WebProviderInstance {
5
+ search?(request: WebSearchRequest, options: { signal: AbortSignal }): Promise<WebResponse>;
6
+ fetch?(request: WebFetchRequest, options: { signal: AbortSignal }): Promise<WebResponse>;
7
+ }
8
+ export interface WebProviderDefinition {
9
+ id: string;
10
+ label: UiText;
11
+ search: boolean;
12
+ fetch: boolean;
13
+ /** Glob for the complete URL, with * matching any characters; fetch routing matches in declaration order. */
14
+ urlPatterns: readonly string[];
15
+ defaultBaseUrl: string;
16
+ /** Required credential type; null means no credentials are required. */
17
+ credential: WebCredentialDefinition | null;
18
+ /** Synchronous factory; baseUrl has trailing slashes removed, and credential is null if and only if the credential declaration is null. */
19
+ create(context: { baseUrl: string; credential: string | null }): WebProviderInstance;
20
+ }
21
+ export interface WebProviderServices {
22
+ types(): readonly WebProviderType[];
23
+ list(): readonly WebProviderState[];
24
+ get(id: string): WebProviderState | null;
25
+ /** Return an invocation facade bound to the provider lifecycle; id is the provider type slug. */
26
+ resolve(id: string, options: { signal: AbortSignal }): Promise<WebProviderInstance>;
27
+ subscribe(listener: () => void): () => void;
28
+ }
29
+ export interface WebServices {
30
+ search(request: WebSearchRequest, options: { signal: AbortSignal }): Promise<WebSearchResult>;
31
+ fetch(request: WebFetchRequest, options: { signal: AbortSignal }): Promise<WebFetchResult>;
32
+ }
33
+ export type WebErrorCode = "provider_unavailable" | "credential_unavailable" | "unsupported" | "invalid_request" | "protocol" | "request_failed";
34
+ export class WebError extends Error {
35
+ constructor(readonly code: WebErrorCode, message: string) { super(message); }
36
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,53 @@
1
+ /** SDK runtime entry point: generic service interfaces available to backend plugins; excludes host internal types and domain knowledge. */
2
+ import type { Json } from "./json.ts";
3
+ import type { UiText } from "./i18n.ts";
4
+ import type { ResourceRef } from "./workspace.ts";
5
+ export * from "./runtime-contracts.ts";
6
+ export * from "./runtime-resources.ts";
7
+ export * from "./runtime-calls.ts";
8
+ export * from "./runtime-channels.ts";
9
+ export * from "./runtime-tools.ts";
10
+ export type * from "./runtime-model-providers.ts";
11
+ export * from "./runtime-usage.ts";
12
+ export * from "./runtime-web.ts";
13
+ export * from "./runtime-agent-catalog.ts";
14
+ export * from "./runtime-sessions.ts";
15
+ export type * from "./content-runtime.ts";
16
+ export type * from "./models.ts";
17
+ export { validationMessage, jsonArgumentHint } from "./tool-validation.ts";
18
+
19
+ /** Stable plugin API error: code supports plugin branching, and message is used for logs. */
20
+ export class PluginApiError extends Error {
21
+ constructor(readonly code: "invalid_argument" | "foreign_resource" | "foreign_event" | "unavailable" | "rejected" | "conflict" | "not_found", message: string,
22
+ /** Business reason codes for preparation rejection or model input preflight checks. */
23
+ readonly reason?: string, readonly display?: UiText) { super(message); }
24
+ }
25
+ export interface PluginLogger {
26
+ info(message: string): void;
27
+ warn(message: string): void;
28
+ error(message: string, error?: unknown): void;
29
+ }
30
+ /** Backend handle for host-managed configuration: get reads the current value each time; update uses the latest complete configuration; subscribe receives changes from settings pages or other modules. */
31
+ export interface PluginConfig<T extends Json> {
32
+ get(): T;
33
+ update(next: (current: T) => T): void;
34
+ subscribe(listener: (value: T) => void): () => void;
35
+ }
36
+ /** Namespaced credentials: plaintext exists only in the system-protected vault, while plugin directories store only references. */
37
+ export interface PluginSecrets {
38
+ get(key: string): Promise<string | null>;
39
+ set(key: string, value: string): Promise<void>;
40
+ delete(key: string): Promise<void>;
41
+ }
42
+ /** Host-managed asynchronous tasks within the process: a running key is not started again, and host shutdown cancels tasks; no timeout by default, and tasks are not resumed after restart. */
43
+ export interface PluginTasks {
44
+ start(key: string, run: (signal: AbortSignal) => Promise<void>, options?: { timeoutMs?: number; object?: ResourceRef }): boolean;
45
+ cancel(key: string): void;
46
+ has(key: string): boolean;
47
+ }
48
+
49
+ export type * from "./runtime-application.ts";
50
+
51
+ export * from "./runtime-shell.ts";
52
+ export * from "./runtime-terminal.ts";
53
+ export type * from "./runtime-mcp.ts";
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ import { defineRecord } from "./records.ts";
3
+ import { presentationSchema } from "./presentation.ts";
4
+
5
+ /** Immutable title assignment; optional detail belongs to the plugin that produced it. */
6
+ export const SessionTitleAssigned = defineRecord("asterism.session/title", {
7
+ schema: z.strictObject({ title: z.string().trim().min(1).max(160), detail: presentationSchema.optional() }),
8
+ });
package/src/shell.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { z } from "zod";
2
+ import { defineCall } from "./calls.ts";
3
+ import { defineData } from "./presentation.ts";
4
+
5
+ export const OUTPUT_CHAR_LIMIT = 16_384;
6
+ export const MAX_TIMEOUT_SECONDS = 2_147_483_647 / 1000;
7
+
8
+ /** Startup arguments and command arguments are separate, allowing a profile to be shared across different shell usage modes. */
9
+ export const shellProfileSchema = z.strictObject({
10
+ id: z.string().trim().min(1).max(160).regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/u).refine(value => value !== "auto", "The profile ID 'auto' is reserved."),
11
+ executable: z.string().trim().min(1),
12
+ args: z.array(z.string()),
13
+ commandArgs: z.array(z.string()),
14
+ env: z.record(z.string(), z.string()),
15
+ });
16
+ export type ShellProfile = z.infer<typeof shellProfileSchema>;
17
+
18
+ export const shellPresets: ShellProfile[] = [
19
+ { id: "pwsh", executable: "pwsh", args: [], commandArgs: ["-NonInteractive", "-Command"], env: {} },
20
+ { id: "nu", executable: "nu", args: ["-l"], commandArgs: ["-c"], env: {} },
21
+ { id: "fish", executable: "fish", args: [], commandArgs: ["-c"], env: {} },
22
+ { id: "zsh", executable: "zsh", args: [], commandArgs: ["-c"], env: {} },
23
+ { id: "bash", executable: "bash", args: [], commandArgs: ["-c"], env: {} },
24
+ { id: "sh", executable: "sh", args: [], commandArgs: ["-c"], env: {} },
25
+ ];
26
+ export const defaultShellSettings = { selected: "auto", profiles: shellPresets };
27
+ export const shellSettingsSchema = z.strictObject({
28
+ selected: z.string().min(1),
29
+ profiles: z.array(shellProfileSchema),
30
+ }).superRefine((value, ctx) => {
31
+ const ids = new Set<string>();
32
+ value.profiles.forEach((profile, index) => {
33
+ if (ids.has(profile.id)) ctx.addIssue({ code: "custom", path: ["profiles", index, "id"], message: "Shell profile IDs must be unique." });
34
+ ids.add(profile.id);
35
+ });
36
+ if (value.selected !== "auto" && !ids.has(value.selected))
37
+ ctx.addIssue({ code: "custom", path: ["selected"], message: "Select an existing shell profile or auto." });
38
+ });
39
+ export type ShellSettings = z.infer<typeof shellSettingsSchema>;
40
+ export const ShellProfilesCatalog = defineData("asterism.shell/profiles", shellSettingsSchema);
41
+
42
+ export const shellProfileListSchema = z.strictObject({
43
+ selected: z.string(),
44
+ resolvedProfile: z.string().nullable(),
45
+ profiles: z.array(z.strictObject({
46
+ id: z.string(),
47
+ executable: z.string(),
48
+ args: z.array(z.string()),
49
+ commandArgs: z.array(z.string()),
50
+ available: z.boolean(),
51
+ resolvedExecutable: z.string().nullable(),
52
+ })),
53
+ });
54
+ export type ShellProfileList = z.infer<typeof shellProfileListSchema>;
55
+
56
+ export const shellOutputSchema = z.strictObject({
57
+ output: z.string(),
58
+ truncation: z.strictObject({
59
+ totalChars: z.number().int().nonnegative(),
60
+ retainedChars: z.number().int().nonnegative(),
61
+ file: z.string(),
62
+ }).nullable(),
63
+ });
64
+ export type ShellOutput = z.infer<typeof shellOutputSchema>;
65
+ export const execResultSchema = shellOutputSchema.extend({
66
+ shell: z.string(),
67
+ termination: z.enum(["running", "exited", "timeout", "cancelled", "spawn_error", "io_error"]),
68
+ exitCode: z.number().int().nullable(),
69
+ signal: z.string().nullable(),
70
+ durationMs: z.number().nonnegative(),
71
+ });
72
+ export type ExecResult = z.infer<typeof execResultSchema>;
73
+
74
+ export const ReadShellProfiles = defineCall("core.shell.profiles.read", {
75
+ scope: "plugin", input: z.strictObject({}), output: shellProfileListSchema,
76
+ });
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+
3
+ export const terminalStateSchema = z.enum(["inactive", "starting", "idle", "running", "unknown", "exited", "failed"]);
4
+ export type TerminalState = z.infer<typeof terminalStateSchema>;
5
+ export const terminalConfigSchema = z.strictObject({ cwd: z.string(), profile: z.string() });
6
+ export type TerminalConfig = z.infer<typeof terminalConfigSchema>;
7
+
8
+ export const terminalStatusSchema = z.strictObject({
9
+ state: terminalStateSchema, shell: z.string().nullable(), integration: z.boolean(),
10
+ exitCode: z.number().nullable(), error: z.string().nullable(),
11
+ /** Current directory reported by shell integration; null without integration. */
12
+ cwd: z.string().nullable(),
13
+ /** Exit code of the latest foreground command; null without integration or before any command has run. */
14
+ lastExitCode: z.number().nullable(),
15
+ });
16
+ export type TerminalStatus = z.infer<typeof terminalStatusSchema>;
17
+ export const inactiveStatus: TerminalStatus = { state: "inactive", shell: null, integration: false, exitCode: null, error: null, cwd: null, lastExitCode: null };
18
+
19
+ /** Terminal channel: the frontend sends input, dimensions, and rendered-character acknowledgments; the backend sends screen resets, output, dimensions, and status, all delivered in order. */
20
+ export type TerminalUp =
21
+ | { type: "input"; data: string }
22
+ /** xterm binary input (such as X10 mouse reports), with each character representing one byte. */
23
+ | { type: "binary"; data: string }
24
+ | { type: "resize"; cols: number; rows: number }
25
+ | { type: "ack"; chars: number }
26
+ /** The frontend screen has remounted and requests a current screen snapshot. */
27
+ | { type: "refresh" };
28
+ export type TerminalDown =
29
+ | { type: "reset"; cols: number; rows: number; data: string }
30
+ | { type: "output"; data: string }
31
+ | { type: "resize"; cols: number; rows: number }
32
+ | { type: "status"; status: TerminalStatus };
33
+
34
+ const cellSchema = z.strictObject({ text: z.string(), width: z.number(), fg: z.string(), bg: z.string(), bold: z.boolean(), italic: z.boolean(), underline: z.boolean(), strike: z.boolean() });
35
+ export const terminalImageSchema = z.strictObject({ cols: z.number(), rows: z.number(), cursor: z.strictObject({ x: z.number(), y: z.number(), visible: z.boolean() }), cells: z.array(z.array(cellSchema)) });
36
+ export type TerminalImage = z.infer<typeof terminalImageSchema>;
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import { jsonSchema, type Json } from "./json.ts";
3
+ import { contractReferenceSchema } from "./presentation.ts";
4
+ import { modelInputPartsSchema, type ModelInputPart } from "./model-input.ts";
5
+
6
+ export const toolResourceSchema = z.strictObject({ id: z.string(), kind: z.string(), title: z.string() });
7
+ /** Identity selected for this request; history display must not substitute the current resource or current implementation. */
8
+ export const toolInvocationSchema = z.strictObject({
9
+ implementation: contractReferenceSchema.optional(),
10
+ resource: toolResourceSchema.optional(),
11
+ startedAt: z.number().nonnegative().optional(),
12
+ finishedAt: z.number().nonnegative().optional(),
13
+ });
14
+ export type ToolInvocationInfo = z.infer<typeof toolInvocationSchema>;
15
+
16
+ const error = z.strictObject({ code: z.string(), message: z.string() });
17
+ export const toolCompletionSchema = z.discriminatedUnion("kind", [
18
+ z.strictObject({ kind: z.literal("succeeded"), data: jsonSchema }),
19
+ z.strictObject({ kind: z.literal("partial"), data: jsonSchema, message: z.string() }),
20
+ z.strictObject({ kind: z.literal("rejected"), data: jsonSchema, error }),
21
+ z.strictObject({ kind: z.literal("failed"), data: jsonSchema, error }),
22
+ z.strictObject({ kind: z.literal("cancelled"), data: jsonSchema, message: z.string() }),
23
+ z.strictObject({ kind: z.literal("unknown"), data: jsonSchema, message: z.string() }),
24
+ ]);
25
+ export const toolRunningResultSchema = z.strictObject({ kind: z.literal("running"), data: jsonSchema });
26
+ type WithData<T, D> = T extends unknown ? Omit<T, "data"> & { data: D | null } : never;
27
+ export type ToolCompletion<D = Json> = WithData<z.infer<typeof toolCompletionSchema>, D>;
28
+ /** Progress and terminal states share the data contract; null means no valid output yet. */
29
+ export type ToolResult<D = Json> = { kind: "running"; data: D | null } | ToolCompletion<D>;
30
+ /** Model projection and user input share text | file parts; only success and partial success may carry files. */
31
+ const saved = { ...toolInvocationSchema.shape, model: modelInputPartsSchema };
32
+ function extendSaved<I extends 0 | 1 | 2 | 3 | 4 | 5>(index: I) { return toolCompletionSchema.options[index].extend(saved); }
33
+ export const toolOutcomeSchema = z.discriminatedUnion("kind",
34
+ toolCompletionSchema.options.map(option => option.extend(saved)) as unknown as [
35
+ ReturnType<typeof extendSaved<0>>, ReturnType<typeof extendSaved<1>>, ReturnType<typeof extendSaved<2>>,
36
+ ReturnType<typeof extendSaved<3>>, ReturnType<typeof extendSaved<4>>, ReturnType<typeof extendSaved<5>>,
37
+ ]).refine(outcome => outcome.kind === "succeeded" || outcome.kind === "partial" || outcome.model.every(part => part.kind === "text"),
38
+ "Only succeeded or partial tool results can carry files.");
39
+ export type ToolOutcome = z.infer<typeof toolOutcomeSchema>;
40
+ /** Content passed from a tool to the model: a string becomes one text part; file parts reference immutable content staged during execution or already owned by the object. */
41
+ export type ToolModelOutput = string | ModelInputPart[];
42
+ /** Projection used when a tool declares no toModel: strings are text, other decoded values are JSON text. */
43
+ export function defaultToolModel(data: Json): string {
44
+ return typeof data === "string" ? data : JSON.stringify(data);
45
+ }
@@ -0,0 +1,14 @@
1
+ import type { z } from "zod";
2
+
3
+ /** Preserve field paths and specific validation reasons from union branches so the model can correct the raw JSON arguments. */
4
+ export function validationMessage(error: z.ZodError, field: string): string {
5
+ const format = (issues: readonly z.core.$ZodIssue[]): string => issues.map(issue => {
6
+ const path = [field, ...issue.path.map(String)].filter(Boolean).join(".");
7
+ return issue.code === "invalid_union"
8
+ ? `${path}: must match one of these alternatives: ${issue.errors.map(branch => `(${format(branch)})`).join(" OR ")}`
9
+ : `${path}: ${issue.message}`;
10
+ }).join("; ");
11
+ return format(error.issues);
12
+ }
13
+
14
+ export const jsonArgumentHint = "target and args are JSON values matching their schemas; objects and null retain their JSON types.";
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ import { defineCall } from "./calls.ts";
3
+ import { defineEvent } from "./events.ts";
4
+ import { providerUsageIdSchema, usageItemsSchema, providerUsageErrorSchema } from "./provider-usage.ts";
5
+
6
+ /** Shared provider usage state; response consumption remains explicit. */
7
+ export const providerUsageStateSchema = z.strictObject({
8
+ providerId: z.string(), usage: providerUsageIdSchema.nullable(),
9
+ source: z.enum(["query", "headers"]).optional(), model: z.string().optional(),
10
+ state: z.enum(["disabled", "waiting", "loading", "ready", "error"]),
11
+ items: usageItemsSchema, updatedAt: z.number().nonnegative().nullable(), error: providerUsageErrorSchema.nullable(),
12
+ });
13
+ export type ProviderUsageState = z.infer<typeof providerUsageStateSchema>;
14
+ export const ReadUsage = defineCall("core.usage.read", {
15
+ scope: "plugin", input: z.strictObject({ providerId: z.string(), refresh: z.boolean() }), output: providerUsageStateSchema,
16
+ });
17
+ export const UsageChanged = defineEvent("core.usage.changed", {
18
+ schema: z.strictObject({ providerId: z.string().nullable() }),
19
+ source: "host", semantics: { kind: "snapshot", key: ["providerId"] },
20
+ });
package/src/web.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ import { presentationSchema } from "./presentation.ts";
3
+ import { uiTextSchema } from "./i18n.ts";
4
+
5
+ export const webUrlSchema = z.string().url().refine(value => {
6
+ const url = new URL(value);
7
+ return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
8
+ }, "An HTTP or HTTPS URL without embedded credentials is required.");
9
+ export const webHintSchema = z.object({ code: z.string(), message: z.string(), parameter: z.string().optional() });
10
+ export const webContentPartSchema = z.object({
11
+ kind: z.enum(["snippet", "excerpt", "body", "summary"]), format: z.enum(["text", "markdown"]), text: z.string(),
12
+ truncated: z.boolean().optional().describe("Known content truncation. Omitted when completeness is unknown."),
13
+ });
14
+ /** Dates retain upstream precision and kind; use unknown for an unknown date kind. */
15
+ export const webDateSchema = z.object({ kind: z.enum(["published", "modified", "fetched", "unknown"]), value: z.string() });
16
+ export const webDocumentSchema = z.object({
17
+ url: webUrlSchema, title: z.string().optional(), author: z.string().optional(),
18
+ dates: z.array(webDateSchema).optional(), content: z.array(webContentPartSchema),
19
+ image: webUrlSchema.optional(), favicon: webUrlSchema.optional(),
20
+ links: z.array(z.object({ url: webUrlSchema, title: z.string().optional() })).optional(),
21
+ /** Scores are interpreted by the actual provider; consumers preserve result order. */
22
+ score: z.number().finite().optional(), details: z.array(presentationSchema).optional(),
23
+ });
24
+ export const webUsageSchema = z.object({
25
+ name: z.string(), amount: z.number().finite(), unit: z.string(), currency: z.string().optional(),
26
+ });
27
+ export const webResponseSchema = z.object({
28
+ documents: z.array(webDocumentSchema), hints: z.array(webHintSchema),
29
+ requestId: z.string().optional(), usage: z.array(webUsageSchema).optional(), details: z.array(presentationSchema).optional(),
30
+ });
31
+ const date = z.union([z.iso.date(), z.iso.datetime({ offset: true })]);
32
+ export const webSearchRequestSchema = z.object({
33
+ query: z.string().trim().min(1).describe("Web search query."),
34
+ maxResults: z.number().int().positive().default(10).describe("Maximum number of search results."),
35
+ includeDomains: z.array(z.string().trim().min(1)).optional().describe("Domains to include."),
36
+ excludeDomains: z.array(z.string().trim().min(1)).optional().describe("Domains to exclude."),
37
+ publishedAfter: date.optional().describe("Publication date lower bound, as an ISO date or timestamp."),
38
+ publishedBefore: date.optional().describe("Publication date upper bound, as an ISO date or timestamp."),
39
+ language: z.string().trim().min(1).optional().describe("Preferred content language."),
40
+ region: z.string().regex(/^[A-Z]{2}$/u).optional().describe("Two-letter country code for localized results."),
41
+ }).describe("Search terms and optional filters. Result hints describe conditions that were not applied.");
42
+ export const webFetchRequestSchema = z.object({
43
+ url: webUrlSchema.describe("URL to retrieve."),
44
+ query: z.string().trim().min(1).optional().describe("Optional focus for additional excerpts delivered alongside the page body. Excerpt retrieval may incur additional provider charges."),
45
+ maxCharacters: z.number().int().positive().optional().describe("Maximum body characters per document, counted as Unicode code points across body parts. Excerpts and metadata are separate. Omitted uses provider defaults."),
46
+ }).describe("URL, optional additional excerpt focus and body character limit.");
47
+
48
+ /** Providers declare credential types; the UI renders corresponding inputs by type and does not accept arbitrary JSON. */
49
+ export const webCredentialDefinitionSchema = z.object({
50
+ kind: z.enum(["api_key"]),
51
+ keyUrl: webUrlSchema.optional(),
52
+ });
53
+ /** At most one configuration per provider type; id equals the type slug. */
54
+ export const webProviderStateSchema = z.strictObject({
55
+ id: z.string().trim().min(1),
56
+ enabled: z.boolean(),
57
+ });
58
+ export const webProviderTypeSchema = z.object({
59
+ id: z.string().min(1), label: uiTextSchema, owner: z.string(),
60
+ search: z.boolean(), fetch: z.boolean(), urlPatterns: z.array(z.string().min(1)),
61
+ credential: webCredentialDefinitionSchema.nullable(),
62
+ });
63
+ /** provider in results is the type slug of the provider that actually handled the request. */
64
+ export const webSearchResultSchema = webResponseSchema.extend({ query: z.string(), provider: z.string() });
65
+ export const webFetchResultSchema = webResponseSchema.extend({ requestedUrl: webUrlSchema, provider: z.string() });
66
+ export type WebHint = z.infer<typeof webHintSchema>;
67
+ export type WebDocument = z.infer<typeof webDocumentSchema>;
68
+ export type WebResponse = z.infer<typeof webResponseSchema>;
69
+ export type WebSearchRequest = z.infer<typeof webSearchRequestSchema>;
70
+ export type WebFetchRequest = z.infer<typeof webFetchRequestSchema>;
71
+ export type WebCredentialDefinition = z.infer<typeof webCredentialDefinitionSchema>;
72
+ export type WebProviderState = z.infer<typeof webProviderStateSchema>;
73
+ export type WebProviderType = z.infer<typeof webProviderTypeSchema>;
74
+ export type WebSearchResult = z.infer<typeof webSearchResultSchema>;
75
+ export type WebFetchResult = z.infer<typeof webFetchResultSchema>;
@@ -0,0 +1,43 @@
1
+ /** Shared workbench read contracts: consumable by both backend and frontend, independent of host storage and Node. */
2
+ import type { Json } from "./json.ts";
3
+ import type { ResourceContract } from "./module-contract.ts";
4
+ import type { SessionView } from "./records.ts";
5
+ import { z } from "zod";
6
+
7
+ /** Resource rename input; identity and external location do not change with the name. */
8
+ export const resourceTitleSchema = z.string().max(160).regex(/\S/u, "The title must contain non-whitespace text.");
9
+ export interface ResourceRef { readonly sessionId: string; readonly id: string; readonly kind: string }
10
+ /** Identity and title at committed creation; the title is a result summary and does not participate in resource identity validation. */
11
+ export interface ResourceCreationResult extends ResourceRef { readonly title: string }
12
+ export interface SessionSummary { readonly id: string; readonly title: string; readonly createdAt: string }
13
+ /** Archived session summary; archivedAt is the archival time, and startup cleanup deletes the session after the cooling-off period. */
14
+ export interface ArchivedSessionSummary extends SessionSummary { readonly archivedAt: string }
15
+ /**
16
+ * Read-only session catalog shared within the same host runtime instance; each read returns current summaries.
17
+ * list contains only active sessions; archived lists sessions in the recycle bin. Session IDs absent from both have been deleted, allowing plugins to clean up their own per-session data.
18
+ */
19
+ export interface SessionDirectory { list(): readonly SessionSummary[]; archived(): readonly ArchivedSessionSummary[] }
20
+ /** Source: null means the application itself; plugin-originated sources always include plugin identity, and resource sources always belong to that plugin. Shared by records, events, and others. */
21
+ export type Source = { readonly plugin: string; readonly resource: ResourceRef | null } | null;
22
+ export interface ResourceSnapshot<S extends Json = Json> {
23
+ readonly ref: ResourceRef;
24
+ readonly title: string;
25
+ readonly data: S;
26
+ }
27
+ /** Read-only observation handle with a fixed timeline tip and session read upper bound, distinct from the full tree and workbench projections in host IPC. */
28
+ export interface SessionSnapshot extends SessionView {
29
+ readonly position: { readonly nodeId: string | null };
30
+ readonly resources: {
31
+ all(): readonly ResourceSnapshot[];
32
+ /**
33
+ * Resources directly linked to this receiver on the snapshot's timeline path, with captured titles and public data.
34
+ * Reverse and transitive links are independent. Returns an empty list if the receiver's session, identity, or kind does not match a present resource.
35
+ * Observing links does not expand a model request's captured operation scope.
36
+ */
37
+ linkedTo(receiver: ResourceRef): readonly ResourceSnapshot[];
38
+ get<S extends Json>(contract: ResourceContract<S>, id: string):
39
+ | { status: "known"; resource: ResourceSnapshot<S> }
40
+ | { status: "unknown"; resource: ResourceSnapshot; reason: "invalid_data" }
41
+ | null;
42
+ };
43
+ }