@asterism-app/sdk 0.0.0-stage → 0.47.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/LICENSE +9 -0
- package/README.md +20 -2
- package/docs/data-and-lifecycle.md +77 -0
- package/docs/getting-started.md +37 -0
- package/docs/manual.md +34 -0
- package/docs/minimal-plugin.md +33 -0
- package/docs/project-structure.md +27 -0
- package/docs/resources-and-tools.md +29 -0
- package/docs/views-and-settings.md +35 -0
- package/examples/author.ts +158 -0
- package/examples/consume-usage.ts +29 -0
- package/examples/content-author.ts +29 -0
- package/examples/content-contract.ts +10 -0
- package/examples/content-renderer.ts +26 -0
- package/examples/counter/docs/usage.md +9 -0
- package/examples/counter/package.json +27 -0
- package/examples/counter/src/contracts.ts +31 -0
- package/examples/counter/src/panel.module.css +2 -0
- package/examples/counter/src/renderer.tsx +69 -0
- package/examples/counter/src/runtime.ts +43 -0
- package/examples/counter/src/storage.ts +4 -0
- package/examples/counter/src/styles.d.ts +4 -0
- package/examples/counter/tsconfig.json +16 -0
- package/examples/counter-contract.ts +18 -0
- package/examples/data-codecs.ts +18 -0
- package/examples/events.ts +37 -0
- package/examples/i18n.ts +18 -0
- package/examples/model-completion.ts +15 -0
- package/examples/model-input.ts +16 -0
- package/examples/plugin-calls.ts +22 -0
- package/examples/provider-usage-headers.ts +20 -0
- package/examples/provider-usage.ts +34 -0
- package/examples/renderer.ts +47 -0
- package/examples/session-resources.ts +29 -0
- package/examples/settings-contract.ts +8 -0
- package/examples/settings-renderer.ts +30 -0
- package/examples/settings-runtime.ts +11 -0
- package/examples/storage.ts +26 -0
- package/examples/tool-renderer.ts +47 -0
- package/examples/web-provider.ts +27 -0
- package/package.json +39 -4
- package/src/calls.ts +27 -0
- package/src/card.ts +7 -0
- package/src/channels.ts +20 -0
- package/src/commands.ts +28 -0
- package/src/config.ts +25 -0
- package/src/content-runtime.ts +11 -0
- package/src/content.ts +6 -0
- package/src/contracts.ts +29 -0
- package/src/data-encoding.ts +14 -0
- package/src/error-details.ts +78 -0
- package/src/events.ts +91 -0
- package/src/i18n.ts +58 -0
- package/src/json.ts +4 -0
- package/src/model-cost-estimate.ts +49 -0
- package/src/model-cost.ts +44 -0
- package/src/model-input.ts +19 -0
- package/src/model-media.ts +11 -0
- package/src/model-records.ts +56 -0
- package/src/model-selection.ts +34 -0
- package/src/models.ts +137 -0
- package/src/module-contract.ts +44 -0
- package/src/objects.ts +89 -0
- package/src/plugin.ts +58 -0
- package/src/presentation.ts +21 -0
- package/src/provider-usage.ts +87 -0
- package/src/records.ts +60 -0
- package/src/renderer-calls.ts +11 -0
- package/src/renderer-card.ts +29 -0
- package/src/renderer-content.ts +9 -0
- package/src/renderer-creation.ts +83 -0
- package/src/renderer-i18n.ts +49 -0
- package/src/renderer-island.ts +66 -0
- package/src/renderer-records.ts +49 -0
- package/src/renderer-services.ts +75 -0
- package/src/renderer-tools.ts +103 -0
- package/src/renderer-view-state.ts +33 -0
- package/src/renderer.ts +158 -0
- package/src/runtime-calls.ts +20 -0
- package/src/runtime-channels.ts +24 -0
- package/src/runtime-contracts.ts +188 -0
- package/src/runtime-providers.ts +60 -0
- package/src/runtime-resources.ts +117 -0
- package/src/runtime-tools.ts +31 -0
- package/src/runtime-web.ts +36 -0
- package/src/runtime.ts +44 -0
- package/src/tool-result.ts +45 -0
- package/src/tool-validation.ts +14 -0
- package/src/web.ts +75 -0
- package/src/workspace.ts +43 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ChannelContract, ChannelEnd, ChannelValue } from "./channels.ts";
|
|
2
|
+
import type { ObjectReader } from "./objects.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The backend end of a channel. Messages are delivered in send order; messages arriving before listener registration are buffered and delivered in order when the first listener registers.
|
|
6
|
+
* signal aborts when either end closes, the object leaves the current set, the plugin shuts down, or the connection disconnects; reason is ChannelEnd.
|
|
7
|
+
*/
|
|
8
|
+
export interface ObjectChannel<Up extends ChannelValue = ChannelValue, Down extends ChannelValue = ChannelValue> {
|
|
9
|
+
readonly object: ObjectReader;
|
|
10
|
+
readonly signal: AbortSignal;
|
|
11
|
+
send(data: Down): void;
|
|
12
|
+
onMessage(listener: (data: Up) => void): () => void;
|
|
13
|
+
close(): void;
|
|
14
|
+
}
|
|
15
|
+
export interface ChannelContribution {
|
|
16
|
+
readonly contract: ChannelContract;
|
|
17
|
+
/** Called when the frontend opens a channel; throwing PluginApiError ends the channel with unavailable. */
|
|
18
|
+
open(channel: ObjectChannel): void | Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export function implementChannel<Up extends ChannelValue, Down extends ChannelValue>(contract: ChannelContract<Up, Down>,
|
|
21
|
+
open: (channel: ObjectChannel<Up, Down>) => void | Promise<void>): ChannelContribution {
|
|
22
|
+
return { contract, open: channel => open(channel as unknown as ObjectChannel<Up, Down>) };
|
|
23
|
+
}
|
|
24
|
+
export type { ChannelEnd };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/** Backend interfaces shared by plugins and the host; the host implements storage, transactions, and authorization. */
|
|
2
|
+
import type { UiText } from "./i18n.ts";
|
|
3
|
+
import type { CardAppearance } from "./card.ts";
|
|
4
|
+
import type { ConfigContract } from "./config.ts";
|
|
5
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import type { Json } from "./json.ts";
|
|
7
|
+
import type { PluginNamespace } from "./plugin.ts";
|
|
8
|
+
import type { ResourceContract, ImplementationContract } from "./module-contract.ts";
|
|
9
|
+
import type { DataContract, Presentation } from "./presentation.ts";
|
|
10
|
+
import type { RecordContract, SessionRecord } from "./records.ts";
|
|
11
|
+
import type { EventContract, PluginEvents } from "./events.ts";
|
|
12
|
+
import type { ToolCompletion, ToolInvocationInfo, ToolModelOutput } from "./tool-result.ts";
|
|
13
|
+
import type { PluginConfig, PluginLogger, PluginSecrets, PluginTasks } from "./runtime.ts";
|
|
14
|
+
import type { ModelServices } from "./models.ts";
|
|
15
|
+
import type { ResourceSnapshot, SessionSnapshot, SessionDirectory } from "./workspace.ts";
|
|
16
|
+
import type { ActionContract, CreationContract } from "./commands.ts";
|
|
17
|
+
import type { ObjectReader, ObjectExecutor, SessionExecutor } from "./objects.ts";
|
|
18
|
+
import type { ContentReader, ContentScope } from "./content-runtime.ts";
|
|
19
|
+
import type { PluginCalls } from "./calls.ts";
|
|
20
|
+
import type { CallContribution } from "./runtime-calls.ts";
|
|
21
|
+
import type { ChannelContribution } from "./runtime-channels.ts";
|
|
22
|
+
import type { TopLevelTool } from "./runtime-tools.ts";
|
|
23
|
+
import type { ModelProviderServices, UsageProviderServices, UsageProvider, UsageServices } from "./runtime-providers.ts";
|
|
24
|
+
import type { WebServices, WebProviderServices, WebProviderDefinition } from "./runtime-web.ts";
|
|
25
|
+
import type { ModelProviderPresetContribution } from "./provider-usage.ts";
|
|
26
|
+
export type * from "./objects.ts";
|
|
27
|
+
export type { ResourceRef, ResourceCreationResult, ResourceSnapshot, SessionSnapshot } from "./workspace.ts";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Lifecycle scope: on termination, abort signal first, then await all cleanup in reverse registration order.
|
|
31
|
+
* The host maintains application, session, and object scopes; child scopes end before their parent. Exit, archival, and object removal share the same cleanup path.
|
|
32
|
+
*/
|
|
33
|
+
export interface Lifetime {
|
|
34
|
+
readonly signal: AbortSignal;
|
|
35
|
+
/** Register cleanup; throws unavailable if the scope has ended. Cleanup failures are only logged and do not block remaining cleanup. */
|
|
36
|
+
defer(cleanup: () => void | Promise<void>): void;
|
|
37
|
+
}
|
|
38
|
+
export interface PluginContext {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
/** Plugin-level lifecycle: lasts as long as the host runtime instance; shared plugin services such as connection pools, subscriptions, and public registrations belong here. */
|
|
41
|
+
readonly lifetime: Lifetime;
|
|
42
|
+
readonly sessions: SessionDirectory;
|
|
43
|
+
readonly dir: string;
|
|
44
|
+
readonly log: PluginLogger;
|
|
45
|
+
readonly ipc: { readonly events: PluginEvents; readonly calls: PluginCalls };
|
|
46
|
+
config<T extends Json>(contract: ConfigContract<T>): PluginConfig<T>;
|
|
47
|
+
readonly secrets: PluginSecrets;
|
|
48
|
+
db(): DatabaseSync;
|
|
49
|
+
readonly tasks: PluginTasks;
|
|
50
|
+
readonly models: ModelServices;
|
|
51
|
+
readonly modelProviders: ModelProviderServices;
|
|
52
|
+
readonly usageProviders: UsageProviderServices;
|
|
53
|
+
readonly usages: UsageServices;
|
|
54
|
+
readonly webProviders: WebProviderServices;
|
|
55
|
+
readonly web: WebServices;
|
|
56
|
+
readonly catalogs: { get<T extends Json>(contract: DataContract<T>): T | null };
|
|
57
|
+
session(id: string): SessionExecutor;
|
|
58
|
+
/** Obtain an object handle using a named resource contract; the host validates plugin ownership of the contract, object existence, and matching type, and stamps records accordingly. */
|
|
59
|
+
object<S extends Json>(sessionId: string, contract: ResourceContract<S>, id: string): ObjectExecutor;
|
|
60
|
+
}
|
|
61
|
+
export interface SessionCommitted {
|
|
62
|
+
readonly snapshot: SessionSnapshot;
|
|
63
|
+
readonly records: readonly SessionRecord[];
|
|
64
|
+
readonly session: SessionExecutor;
|
|
65
|
+
}
|
|
66
|
+
/** Fixed snapshot after the execution cursor changes; ordinary appends and explicit cursor selection use the same shape, while panel candidate selection does not trigger it. */
|
|
67
|
+
export interface SessionCursorChanged {
|
|
68
|
+
readonly snapshot: SessionSnapshot;
|
|
69
|
+
readonly session: SessionExecutor;
|
|
70
|
+
}
|
|
71
|
+
export interface SessionObserver {
|
|
72
|
+
readonly id: string;
|
|
73
|
+
committed?(event: SessionCommitted): undefined;
|
|
74
|
+
cursorChanged?(event: SessionCursorChanged): undefined;
|
|
75
|
+
}
|
|
76
|
+
export type Preparation<C extends Json = Json> =
|
|
77
|
+
| { action: "accept" }
|
|
78
|
+
| { action: "replace"; input: C }
|
|
79
|
+
| { action: "reject"; code: string; message: string; display?: UiText };
|
|
80
|
+
/** Preparation context: the resource's own preparation can read and write content in this scope, while external preparers can only read; neither modifies session data. */
|
|
81
|
+
export interface CreationPreparation { readonly signal: AbortSignal; readonly content: ContentScope }
|
|
82
|
+
export interface ActionPreparation extends CreationPreparation { readonly object: ObjectReader }
|
|
83
|
+
export interface ExternalPreparation { readonly signal: AbortSignal; readonly object: ObjectReader; readonly content: ContentReader }
|
|
84
|
+
export interface CommandPreparer<O extends Json = Json, I = unknown> {
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly contract: ActionContract<I, O>;
|
|
87
|
+
prepare(input: O, context: ExternalPreparation): Preparation<I & Json> | Promise<Preparation<I & Json>>;
|
|
88
|
+
}
|
|
89
|
+
export interface ContextFile { readonly file: string; readonly text: string }
|
|
90
|
+
export interface CreationContribution {
|
|
91
|
+
readonly contract: CreationContract<unknown>;
|
|
92
|
+
prepare?(input: Json, context: CreationPreparation): Preparation | Promise<Preparation>;
|
|
93
|
+
create(input: Json, object: ObjectExecutor): { title: string; data: Json };
|
|
94
|
+
}
|
|
95
|
+
export interface ActionContribution {
|
|
96
|
+
readonly contract: ActionContract<unknown>;
|
|
97
|
+
prepare?(input: Json, context: ActionPreparation): Preparation | Promise<Preparation>;
|
|
98
|
+
execute(input: Json, object: ObjectExecutor, signal: AbortSignal): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
export interface ResourceContribution {
|
|
101
|
+
readonly resource: ResourceContract;
|
|
102
|
+
/** Geometry declarations for backend placement, shared with ResourceDescription and cardRenderer. */
|
|
103
|
+
readonly card?: CardAppearance;
|
|
104
|
+
readonly records: readonly RecordContract[];
|
|
105
|
+
/** Opt into templates that copy encoded resource state and only these current-data contracts, preserving their values under new host identities. */
|
|
106
|
+
readonly template?: { readonly current: readonly DataContract[] };
|
|
107
|
+
/** Declare that this resource can receive links and provide the recipient's tool policy; independent of Agent domain types. */
|
|
108
|
+
readonly links?: { receive: boolean; tools?(object: ObjectReader): { allow?: readonly string[]; exclude?: readonly string[] } };
|
|
109
|
+
readonly creation?: CreationContribution;
|
|
110
|
+
readonly actions?: readonly ActionContribution[];
|
|
111
|
+
project(resource: ResourceSnapshot, object: ObjectReader): Presentation;
|
|
112
|
+
inspect(resource: ResourceSnapshot, object: ObjectReader): Promise<Json>;
|
|
113
|
+
context?(resource: ResourceSnapshot, object: ObjectReader): Promise<ContextFile[]>;
|
|
114
|
+
/** Explanation presented to users before ending the current instance; does not block backend removal or wait for external cleanup. */
|
|
115
|
+
disposalWarning?(resource: ResourceSnapshot, object: ObjectReader): UiText | null;
|
|
116
|
+
/**
|
|
117
|
+
* Called synchronously when an object enters the current set through startup, creation, restoration, or rewinding that makes it reappear; lifetime ends when the object leaves the current set,
|
|
118
|
+
* its session is archived, or the application exits. Register cleanup here for external instances owned exclusively by the object.
|
|
119
|
+
*/
|
|
120
|
+
attach?(resource: ResourceSnapshot, object: ObjectReader, lifetime: Lifetime): void;
|
|
121
|
+
}
|
|
122
|
+
type OptionalData<T, D> = T extends unknown ? Omit<T, "data"> & { data?: D | null } : never;
|
|
123
|
+
export type CallResult<D extends Json = Json> =
|
|
124
|
+
| { kind: "succeeded"; data: D }
|
|
125
|
+
| { kind: "partial"; data: D; message: string }
|
|
126
|
+
| OptionalData<Exclude<ToolCompletion, { kind: "succeeded" | "partial" }>, D>;
|
|
127
|
+
export type PreparedResult = CallResult & ToolInvocationInfo;
|
|
128
|
+
/** Tool caller identity captured at the request boundary; carries neither private resource state nor operation permissions. */
|
|
129
|
+
export type ToolCaller = Readonly<Pick<ResourceSnapshot, "ref" | "title">>;
|
|
130
|
+
export interface ToolInvocation extends ToolInvocationInfo {
|
|
131
|
+
effect: "read" | "write";
|
|
132
|
+
execution: "parallel" | "sequential";
|
|
133
|
+
execute(signal: AbortSignal, publish?: (data: Json) => void): Promise<CallResult>;
|
|
134
|
+
toModel?(data: Json): ToolModelOutput;
|
|
135
|
+
}
|
|
136
|
+
export interface ToolContribution {
|
|
137
|
+
readonly contract: ImplementationContract<Json, Json, Json, Json>;
|
|
138
|
+
prepare(resource: ResourceSnapshot, object: ObjectExecutor, target: Json, args: Json, caller: ToolCaller): ToolInvocation | PreparedResult;
|
|
139
|
+
}
|
|
140
|
+
export interface ModuleContribution {
|
|
141
|
+
webProviders?: readonly WebProviderDefinition[];
|
|
142
|
+
usageProviders?: readonly UsageProvider[];
|
|
143
|
+
modelProviderPresets?: readonly ModelProviderPresetContribution[];
|
|
144
|
+
calls?: readonly CallContribution[];
|
|
145
|
+
/** Object-level duplex channel; the contract belongs to this plugin, and routing follows the plugin owning the object type. */
|
|
146
|
+
channels?: readonly ChannelContribution[];
|
|
147
|
+
topLevelTools?: readonly TopLevelTool[];
|
|
148
|
+
events?: readonly EventContract[];
|
|
149
|
+
records?: readonly RecordContract[];
|
|
150
|
+
resources?: readonly ResourceContribution[];
|
|
151
|
+
tools?: readonly ToolContribution[];
|
|
152
|
+
observers?: readonly SessionObserver[];
|
|
153
|
+
preparers?: readonly CommandPreparer[];
|
|
154
|
+
}
|
|
155
|
+
/** Named module entry point: plugins provide contributions, and the host handles assembly and invocation. */
|
|
156
|
+
export interface PluginModule { setup(plugin: PluginContext): ModuleContribution }
|
|
157
|
+
/** Composition of shared plugin services and feature modules; shared services are established first and released after the modules. */
|
|
158
|
+
export interface PluginContribution extends ModuleContribution {
|
|
159
|
+
modules?: readonly PluginModule[];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Format declarations for persistent plugin files; paths are relative to extensions/<pluginId>, and migration key n means n → n+1.
|
|
163
|
+
* Versions are stamped by the writer: JSON uses a `{ version, data }` envelope, and SQLite uses `PRAGMA user_version`.
|
|
164
|
+
*/
|
|
165
|
+
export type PluginStorageDeclaration = {
|
|
166
|
+
kind: "json"; path: string; version: number;
|
|
167
|
+
migrations: Readonly<Record<number, (data: unknown) => Json>>;
|
|
168
|
+
} | {
|
|
169
|
+
kind: "sqlite"; path: string; version: number;
|
|
170
|
+
migrations: Readonly<Record<number, (db: DatabaseSync) => void>>;
|
|
171
|
+
};
|
|
172
|
+
/** Default export of the storage entry point (manifest `asterism.storage`); the host imports it separately before the plugin runtime. */
|
|
173
|
+
export function defineStorage(declarations: readonly PluginStorageDeclaration[]): readonly PluginStorageDeclaration[] {
|
|
174
|
+
for (const item of declarations) if (!Number.isSafeInteger(item.version) || item.version < 1)
|
|
175
|
+
throw new Error(`Storage ${item.path} must declare a positive integer version.`);
|
|
176
|
+
return Object.freeze([...declarations]);
|
|
177
|
+
}
|
|
178
|
+
export interface PluginDefinition {
|
|
179
|
+
namespace: PluginNamespace; modules?: readonly PluginModule[];
|
|
180
|
+
setup?(plugin: PluginContext): PluginContribution;
|
|
181
|
+
}
|
|
182
|
+
export function defineModule(setup: PluginModule["setup"]): PluginModule {
|
|
183
|
+
if (typeof setup !== "function") throw new Error("A plugin module must declare a setup function.");
|
|
184
|
+
return { setup };
|
|
185
|
+
}
|
|
186
|
+
export function definePlugin(definition: PluginDefinition): PluginDefinition & { modules: readonly PluginModule[] } {
|
|
187
|
+
return { ...definition, modules: [...definition.modules ?? []] };
|
|
188
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ModelProviderConnection, ModelProviderPreset, ProviderUsageError, UsageItem, UsageProviderInfo } from "./provider-usage.ts";
|
|
2
|
+
import type { UiText } from "./i18n.ts";
|
|
3
|
+
|
|
4
|
+
/** Consumers explicitly supply the connection and credentials required for the query. */
|
|
5
|
+
export interface UsageQueryContext {
|
|
6
|
+
readonly connection: { id: string; endpoint: string };
|
|
7
|
+
readonly credential: string | null;
|
|
8
|
+
readonly signal: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
interface UsageProviderIdentity {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly label: UiText;
|
|
13
|
+
}
|
|
14
|
+
export interface QueryUsageProvider extends UsageProviderIdentity {
|
|
15
|
+
readonly source?: "query";
|
|
16
|
+
query(context: UsageQueryContext): Promise<UsageItem[]>;
|
|
17
|
+
}
|
|
18
|
+
export interface UsageHeadersContext {
|
|
19
|
+
readonly connection: { id: string; endpoint: string };
|
|
20
|
+
readonly model: string;
|
|
21
|
+
readonly status: number;
|
|
22
|
+
readonly receivedAt: number;
|
|
23
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
24
|
+
}
|
|
25
|
+
/** Consumers supply response metadata they obtained themselves; the parser makes no network requests. */
|
|
26
|
+
export interface HeadersUsageProvider extends UsageProviderIdentity {
|
|
27
|
+
readonly source: "headers";
|
|
28
|
+
/** null means no usable data this time; an empty list explicitly reports no quota items. */
|
|
29
|
+
parseHeaders(context: UsageHeadersContext): UsageItem[] | null;
|
|
30
|
+
}
|
|
31
|
+
export type UsageProvider = QueryUsageProvider | HeadersUsageProvider;
|
|
32
|
+
/** Providers report publicly exposable error categories; raw responses and credentials are not delivered across processes. */
|
|
33
|
+
export class UsageQueryError extends Error {
|
|
34
|
+
constructor(readonly code: ProviderUsageError) { super(`Provider usage query failed: ${code}.`); }
|
|
35
|
+
}
|
|
36
|
+
export interface ModelProviderServices {
|
|
37
|
+
presets(): readonly ModelProviderPreset[];
|
|
38
|
+
list(): readonly ModelProviderInfo[];
|
|
39
|
+
get(id: string): ModelProviderInfo | null;
|
|
40
|
+
/** Backend only; reads the connection's current credentials, returning null for connections without authentication. */
|
|
41
|
+
credential(id: string, options?: { signal?: AbortSignal }): Promise<string | null>;
|
|
42
|
+
/** Connection or preset catalog changes; returns an unsubscribe function and unsubscribes automatically when the plugin closes. */
|
|
43
|
+
subscribe(listener: () => void): () => void;
|
|
44
|
+
}
|
|
45
|
+
export interface ModelProviderInfo extends ModelProviderConnection {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface UsageProviderServices {
|
|
51
|
+
list(): readonly UsageProviderInfo[];
|
|
52
|
+
/** Obtain an implementation bound to the provider lifecycle; does not select connections, read credentials, or save results. */
|
|
53
|
+
get(id: string): UsageProvider | null;
|
|
54
|
+
subscribe(listener: () => void): () => void;
|
|
55
|
+
}
|
|
56
|
+
/** Stateless invocation facade; consumers explicitly supply connections, credentials, and responses. */
|
|
57
|
+
export interface UsageServices {
|
|
58
|
+
query(providerId: string, context: UsageQueryContext): Promise<UsageItem[]>;
|
|
59
|
+
parseHeaders(providerId: string, context: UsageHeadersContext): UsageItem[] | null;
|
|
60
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ActionContract, CreationContract } from "./commands.ts";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import { jsonSchema, type Json } from "./json.ts";
|
|
4
|
+
import { defaultToolModel, type ToolModelOutput } from "./tool-result.ts";
|
|
5
|
+
import type { ResourceContract, ImplementationContract } from "./module-contract.ts";
|
|
6
|
+
import type { RecordContract } from "./records.ts";
|
|
7
|
+
import { reference, present, type DataContract } from "./presentation.ts";
|
|
8
|
+
import { PluginApiError } from "./runtime.ts";
|
|
9
|
+
import type { ContextFile, ObjectReader, ObjectExecutor, CreationContribution, ActionContribution, CommandPreparer, CreationPreparation, ActionPreparation, ExternalPreparation, ResourceContribution, ToolContribution, Preparation, CallResult, Lifetime } from "./runtime-contracts.ts";
|
|
10
|
+
import { validationMessage, jsonArgumentHint } from "./tool-validation.ts";
|
|
11
|
+
import type { ToolCaller } from "./runtime-contracts.ts";
|
|
12
|
+
import { encodeJson } from "./data-encoding.ts";
|
|
13
|
+
|
|
14
|
+
function parse<T>(schema: z.ZodType<T>, input: unknown, subject: string): T {
|
|
15
|
+
const result = schema.safeParse(input);
|
|
16
|
+
if (!result.success) throw new PluginApiError("invalid_argument", validationMessage(result.error, subject));
|
|
17
|
+
return result.data;
|
|
18
|
+
}
|
|
19
|
+
/** Creation and actions are invoked after host validation; types are erased only during contribution assembly. */
|
|
20
|
+
export function implementCreation<I, S extends Json, O extends Json>(contract: CreationContract<I, S, O>, definition: {
|
|
21
|
+
prepare?(input: NoInfer<O>, context: CreationPreparation): Preparation<NoInfer<I> & Json> | Promise<Preparation<NoInfer<I> & Json>>;
|
|
22
|
+
create(input: NoInfer<O>, object: ObjectExecutor): { title: string; data: NoInfer<S> };
|
|
23
|
+
}): CreationContribution {
|
|
24
|
+
return { contract, ...definition } as CreationContribution;
|
|
25
|
+
}
|
|
26
|
+
export function implementAction<I, O extends Json>(contract: ActionContract<I, O>, definition: {
|
|
27
|
+
prepare?(input: NoInfer<O>, context: ActionPreparation): Preparation<NoInfer<I> & Json> | Promise<Preparation<NoInfer<I> & Json>>;
|
|
28
|
+
execute(input: NoInfer<O>, object: ObjectExecutor, signal: AbortSignal): Promise<void>;
|
|
29
|
+
}): ActionContribution {
|
|
30
|
+
return { contract, ...definition } as ActionContribution;
|
|
31
|
+
}
|
|
32
|
+
export function implementPreparer<I, O extends Json>(contract: ActionContract<I, O>, definition: {
|
|
33
|
+
id: string;
|
|
34
|
+
prepare(input: NoInfer<O>, context: ExternalPreparation): Preparation<NoInfer<I> & Json> | Promise<Preparation<NoInfer<I> & Json>>;
|
|
35
|
+
}): CommandPreparer {
|
|
36
|
+
return { contract, ...definition } as CommandPreparer;
|
|
37
|
+
}
|
|
38
|
+
export function implementResource<S extends Json, V extends Json>(
|
|
39
|
+
resource: ResourceContract<S>, view: DataContract<V>, definition: {
|
|
40
|
+
card?: ResourceContribution["card"];
|
|
41
|
+
records?: readonly RecordContract[];
|
|
42
|
+
template?: ResourceContribution["template"];
|
|
43
|
+
links?: ResourceContribution["links"];
|
|
44
|
+
creation?: CreationContribution;
|
|
45
|
+
actions?: readonly ActionContribution[];
|
|
46
|
+
project(data: NoInfer<S>, object: ObjectReader): NoInfer<V>;
|
|
47
|
+
inspect(data: NoInfer<S>, object: ObjectReader): Promise<Json>;
|
|
48
|
+
context?(data: NoInfer<S>, object: ObjectReader): Promise<ContextFile[]>;
|
|
49
|
+
disposalWarning?(data: NoInfer<S>, object: ObjectReader): import("./i18n.ts").UiText | null;
|
|
50
|
+
/** Called when an object enters the current set; external instances owned exclusively by the object register cleanup with lifetime. */
|
|
51
|
+
attach?(data: NoInfer<S>, object: ObjectReader, lifetime: Lifetime): void;
|
|
52
|
+
},
|
|
53
|
+
): ResourceContribution {
|
|
54
|
+
const attach = definition.attach;
|
|
55
|
+
return {
|
|
56
|
+
resource, card: definition.card, records: [...definition.records ?? []], links: definition.links, template: definition.template,
|
|
57
|
+
creation: definition.creation, actions: definition.actions,
|
|
58
|
+
project: (snapshot, object) => present(view, definition.project(parse(resource.stateSchema, snapshot.data, "resource"), object)),
|
|
59
|
+
inspect: async (snapshot, object) => jsonSchema.parse(await definition.inspect(parse(resource.stateSchema, snapshot.data, "resource"), object)),
|
|
60
|
+
context: async (snapshot, object) => definition.context?.(parse(resource.stateSchema, snapshot.data, "resource"), object) ?? [],
|
|
61
|
+
disposalWarning: (snapshot, object) => definition.disposalWarning?.(parse(resource.stateSchema, snapshot.data, "resource"), object) ?? null,
|
|
62
|
+
...(attach ? { attach: (snapshot: Parameters<NonNullable<ResourceContribution["attach"]>>[0], object: ObjectReader, lifetime: Lifetime) =>
|
|
63
|
+
attach(parse(resource.stateSchema, snapshot.data, "resource"), object, lifetime) } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function implementTool<S extends Json, T extends Json, A extends Json, D extends Json>(
|
|
67
|
+
contract: ImplementationContract<S, T, A, D>, definition: {
|
|
68
|
+
call(data: S, target: T, args: A, context: {
|
|
69
|
+
object: ObjectExecutor; caller: ToolCaller; signal: AbortSignal; publish(data: NoInfer<D>): void;
|
|
70
|
+
}): Promise<CallResult<NoInfer<D>>>;
|
|
71
|
+
toModel?(data: D): ToolModelOutput;
|
|
72
|
+
},
|
|
73
|
+
): ToolContribution {
|
|
74
|
+
const identity = reference(contract);
|
|
75
|
+
return {
|
|
76
|
+
contract: contract as unknown as ToolContribution["contract"],
|
|
77
|
+
prepare(resource, object, target, args, caller) {
|
|
78
|
+
const state = contract.resource.stateSchema.safeParse(resource.data);
|
|
79
|
+
if (!state.success) return { kind: "failed", implementation: identity,
|
|
80
|
+
error: { code: "invalid_resource", message: "The resource state does not match its backend contract." } };
|
|
81
|
+
const parsedTarget = contract.targetSchema.safeParse(target);
|
|
82
|
+
const parsedArgs = contract.tool.argsSchema.safeParse(args);
|
|
83
|
+
if (!parsedTarget.success || !parsedArgs.success) return { kind: "rejected", implementation: identity,
|
|
84
|
+
error: { code: "invalid_arguments", message: [
|
|
85
|
+
`Invalid arguments for ${contract.resource.kind}/${contract.tool.name}.`,
|
|
86
|
+
...(!parsedTarget.success ? [validationMessage(parsedTarget.error, "target")] : []),
|
|
87
|
+
...(!parsedArgs.success ? [validationMessage(parsedArgs.error, "args")] : []),
|
|
88
|
+
jsonArgumentHint,
|
|
89
|
+
].join("\n") } };
|
|
90
|
+
return {
|
|
91
|
+
effect: contract.effect, execution: contract.execution, implementation: identity,
|
|
92
|
+
// Model projections consume decoded values.
|
|
93
|
+
toModel: data => {
|
|
94
|
+
const decoded = contract.schema.parse(data);
|
|
95
|
+
return definition.toModel ? definition.toModel(decoded) : defaultToolModel(decoded);
|
|
96
|
+
},
|
|
97
|
+
async execute(signal, publish) {
|
|
98
|
+
let active = true;
|
|
99
|
+
const snapshot = (data: Json) => {
|
|
100
|
+
const encoded = encodeJson<Json>(contract.schema, data, contract.id);
|
|
101
|
+
if (encoded === null && data !== null)
|
|
102
|
+
throw new PluginApiError("invalid_argument", `${contract.id}: non-null tool results require a non-null encoding; null denotes absent output.`);
|
|
103
|
+
return encoded;
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
const result = await definition.call(state.data, parsedTarget.data, parsedArgs.data, { object, caller, signal,
|
|
107
|
+
publish(data) { if (active) publish?.(snapshot(data)); } });
|
|
108
|
+
if (result.kind === "succeeded" || result.kind === "partial") {
|
|
109
|
+
if (result.data === undefined) throw new PluginApiError("invalid_argument", "The tool did not provide final data.");
|
|
110
|
+
} else if (result.data === undefined || result.data === null) return result;
|
|
111
|
+
return { ...result, data: snapshot(result.data) };
|
|
112
|
+
} finally { active = false; }
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -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,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,44 @@
|
|
|
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 * from "./runtime-providers.ts";
|
|
11
|
+
export * from "./runtime-web.ts";
|
|
12
|
+
export type * from "./content-runtime.ts";
|
|
13
|
+
export type * from "./models.ts";
|
|
14
|
+
export { validationMessage, jsonArgumentHint } from "./tool-validation.ts";
|
|
15
|
+
|
|
16
|
+
/** Stable plugin API error: code supports plugin branching, and message is used for logs. */
|
|
17
|
+
export class PluginApiError extends Error {
|
|
18
|
+
constructor(readonly code: "invalid_argument" | "foreign_resource" | "foreign_event" | "unavailable" | "rejected" | "conflict" | "not_found", message: string,
|
|
19
|
+
/** Business reason codes for preparation rejection or model input preflight checks. */
|
|
20
|
+
readonly reason?: string, readonly display?: UiText) { super(message); }
|
|
21
|
+
}
|
|
22
|
+
export interface PluginLogger {
|
|
23
|
+
info(message: string): void;
|
|
24
|
+
warn(message: string): void;
|
|
25
|
+
error(message: string, error?: unknown): void;
|
|
26
|
+
}
|
|
27
|
+
/** 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. */
|
|
28
|
+
export interface PluginConfig<T extends Json> {
|
|
29
|
+
get(): T;
|
|
30
|
+
update(next: (current: T) => T): void;
|
|
31
|
+
subscribe(listener: (value: T) => void): () => void;
|
|
32
|
+
}
|
|
33
|
+
/** Namespaced credentials: plaintext exists only in the system-protected vault, while plugin directories store only references. */
|
|
34
|
+
export interface PluginSecrets {
|
|
35
|
+
get(key: string): Promise<string | null>;
|
|
36
|
+
set(key: string, value: string): Promise<void>;
|
|
37
|
+
delete(key: string): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/** 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. */
|
|
40
|
+
export interface PluginTasks {
|
|
41
|
+
start(key: string, run: (signal: AbortSignal) => Promise<void>, options?: { timeoutMs?: number; object?: ResourceRef }): boolean;
|
|
42
|
+
cancel(key: string): void;
|
|
43
|
+
has(key: string): boolean;
|
|
44
|
+
}
|
|
@@ -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.";
|
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>;
|