@capybara-hq/plugin-sdk 0.1.0-rc.3 → 0.1.0-rc.5

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.
@@ -1,4 +1,4 @@
1
- # Agent templates
1
+ # Authoring: agent templates
2
2
 
3
3
  An agent template describes a whole agent: instructions, tools, skills, connectors and the
4
4
  sub-agents it may dispatch. A member creates a private agent from a template; org admins can also
@@ -2,6 +2,10 @@
2
2
 
3
3
  Read this first. It explains the vocabulary the other topics use.
4
4
 
5
+ The other topics are for *authoring*: writing an extension, building a plugin, skills, templates,
6
+ connector templates, tool naming and extension points. To *use* the platform (install, switch on,
7
+ configure, manage), use the operations: `browse`, `search`, `describe` and `call`.
8
+
5
9
  ## Platform and org
6
10
 
7
11
  - The **platform** is one deployment. The **super admin** runs it: imports plugins into the
@@ -1,4 +1,4 @@
1
- # Connector templates
1
+ # Authoring: connector templates
2
2
 
3
3
  A connector template is a ready-made connection to an external system: a protocol connector
4
4
  (`connector-mcp`, `connector-openapi`), a preset endpoint and the auth shape. An org installs it
@@ -1,4 +1,4 @@
1
- # Extension point: channel
1
+ # Authoring: extension point channel
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `channel` (a ref is `channel@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: config-source
1
+ # Authoring: extension point config-source
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `config-source` (a ref is `config-source@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: connector
1
+ # Authoring: extension point connector
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `connector` (a ref is `connector@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: counter
1
+ # Authoring: extension point counter
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `counter` (a ref is `counter@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: directory
1
+ # Authoring: extension point directory
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `directory` (a ref is `directory@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: file-preview
1
+ # Authoring: extension point file-preview
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `file-preview` (a ref is `file-preview@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: identity-source
1
+ # Authoring: extension point identity-source
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `identity-source` (a ref is `identity-source@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: model-provider
1
+ # Authoring: extension point model-provider
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `model-provider` (a ref is `model-provider@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: object-store
1
+ # Authoring: extension point object-store
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `object-store` (a ref is `object-store@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: sandbox
1
+ # Authoring: extension point sandbox
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `sandbox` (a ref is `sandbox@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: secret-store
1
+ # Authoring: extension point secret-store
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `secret-store` (a ref is `secret-store@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: web-fetch
1
+ # Authoring: extension point web-fetch
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `web-fetch` (a ref is `web-fetch@<major>`)
@@ -1,4 +1,4 @@
1
- # Extension point: web-search
1
+ # Authoring: extension point web-search
2
2
 
3
3
  <!-- generated:extension-point -->
4
4
  - Name: `web-search` (a ref is `web-search@<major>`)
@@ -1,4 +1,4 @@
1
- # Writing an extension
1
+ # Authoring: writing an extension
2
2
 
3
3
  An extension is TypeScript code a plugin ships. It runs in the platform's process with full
4
4
  trust: approval by the super admin is governance, not a sandbox. It sees only the SDK's `ctx`
@@ -1,4 +1,4 @@
1
- # Plugin format
1
+ # Authoring: the plugin format
2
2
 
3
3
  A plugin is a directory, packed into a `.tgz` with `capybara-plugin pack` (the published
4
4
  `@capybara-hq/plugin-tools`; `npx capybara-plugin check` runs the checks).
@@ -1,4 +1,4 @@
1
- # Skills
1
+ # Authoring: skills
2
2
 
3
3
  A skill is a folder with a `SKILL.md` (the Agent Skills format) and any files it needs. The agent
4
4
  sees only the skill's `name` and `description` until it loads the skill, so the description is
@@ -1,4 +1,4 @@
1
- # Tool naming
1
+ # Authoring: tool naming
2
2
 
3
3
  The model-visible tool key grammar:
4
4
 
package/dist/ctx.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Kysely } from "kysely";
2
2
  import type { ExtensionPointRef } from "./extension-point.js";
3
3
  import type { AvailableHook, HookHandler, HookOptions } from "./hooks.js";
4
+ import type { ModelEmbeddingInfo, ModelKind } from "./model.js";
4
5
  import type { ObjectHead, PresignedUpload, PresignGetOptions } from "./storage.js";
5
6
  import type { ToolHandler, ToolSpec } from "./tool.js";
6
7
  import type { EmbedClaims, UiActionArgs, UiActionResult, UiRenderArgs, UiView } from "./ui.js";
@@ -58,6 +59,65 @@ export interface ExtensionStorage {
58
59
  ttlSeconds: number;
59
60
  }): Promise<PresignedUpload>;
60
61
  }
62
+ /** A `ctx.models.embed` request: texts embedded for one org with the model mapped to a purpose. */
63
+ export interface ExtensionEmbedRequest {
64
+ orgId: string;
65
+ /** A purpose this extension declared in `permissions.models` with `kind: "embedding"`. */
66
+ purpose: string;
67
+ /** The texts, one vector each; split into batches by the model's `maxBatch` by the kernel. */
68
+ input: string[];
69
+ /** One of the model's `embedding.dimensions`; the model's default when absent. */
70
+ dimensions?: number;
71
+ inputType?: "query" | "document";
72
+ /**
73
+ * Aborts this call (e.g. the tool call's `signal`); combined with `ctx.signal`. Batches not yet
74
+ * sent are not sent; batches already answered are still recorded as usage.
75
+ */
76
+ signal?: AbortSignal;
77
+ }
78
+ /** A `ctx.models.embed` result. */
79
+ export interface ExtensionEmbedResult {
80
+ /** One vector per input, in input order. */
81
+ vectors: number[][];
82
+ /** The model that made them (a changed model invalidates stored vectors). */
83
+ model: {
84
+ provider: string;
85
+ id: string;
86
+ };
87
+ dimensions: number;
88
+ }
89
+ /** The model a purpose maps to in an org (`ctx.models.describe`). */
90
+ export interface ExtensionModelTarget {
91
+ provider: string;
92
+ id: string;
93
+ kind: ModelKind;
94
+ /** Present for an embedding model. */
95
+ embedding?: ModelEmbeddingInfo;
96
+ }
97
+ /**
98
+ * Models by purpose (`permissions.models`): the purpose → model mapping is chosen by org admins
99
+ * (and the deployment default by the super admin), never by the extension. Only purposes this
100
+ * extension declared with `kind: "embedding"` are accepted (`model.purpose_unknown` otherwise), and
101
+ * only for an org that enabled the extension (`extension.not_enabled`). Usage is recorded by the
102
+ * kernel and reported through `usage.report` (purpose `embedding`).
103
+ */
104
+ export interface ExtensionModels {
105
+ /**
106
+ * Embeds `input` with the org's effective model for `purpose`. Rejects with `model.unconfigured`
107
+ * when the purpose maps to no model the org can use, `validation_failed` for dimensions the model
108
+ * lacks, and a `ModelError` when the provider fails (a result of the wrong count or dimension is a
109
+ * `ModelError` of kind `server`). Not retried by the kernel. Each call fires the `model.request`
110
+ * hook first (`purpose: "embedding"`, every text of the call): when a policy hook denies it, the
111
+ * call rejects with an error whose `code` is `model.hook_denied` (HTTP 403 if it escapes an
112
+ * extension route), and nothing is sent or billed.
113
+ */
114
+ embed(req: ExtensionEmbedRequest): Promise<ExtensionEmbedResult>;
115
+ /** The org's effective model for `purpose`, or null when it maps to no model the org can use. */
116
+ describe(req: {
117
+ orgId: string;
118
+ purpose: string;
119
+ }): Promise<ExtensionModelTarget | null>;
120
+ }
61
121
  /** 传给插件 `setup` 的上下文。不提供定时器与进程内共享状态;kv 写入必须带 TTL。 */
62
122
  export interface ExtensionContext {
63
123
  kv: {
@@ -94,6 +154,8 @@ export interface ExtensionContext {
94
154
  };
95
155
  /** 对象存储(需 `permissions.storage`,见 {@link ExtensionStorage})。 */
96
156
  storage: ExtensionStorage;
157
+ /** Models by purpose (requires `permissions.models`, see {@link ExtensionModels}). */
158
+ models: ExtensionModels;
97
159
  /** 已切到本插件 schema(withSchema)的 Kysely 实例。 */
98
160
  db: Kysely<any>;
99
161
  http: {
package/dist/hooks.d.ts CHANGED
@@ -39,8 +39,12 @@ export interface HookScope {
39
39
  * `WebFetch` tool.
40
40
  */
41
41
  export type ModelCallPurpose = "step" | "compaction" | "title" | "web_fetch";
42
- /** What a reported model call was for (the same values as {@link ModelCallPurpose}). */
43
- export type UsagePurpose = ModelCallPurpose;
42
+ /**
43
+ * What a reported or requested model call was for: the {@link ModelCallPurpose} values, or
44
+ * `embedding` for an embedding call an extension made through `ctx.models` (`usage.report`, and
45
+ * `model.request` with {@link EmbeddingRequestHookPayload}; never `model.response`).
46
+ */
47
+ export type UsagePurpose = ModelCallPurpose | "embedding";
44
48
  /**
45
49
  * An image or document of a `model.request` payload. Hook payloads never carry media bytes or
46
50
  * attachment ids: each image or document block is replaced by this placeholder.
@@ -74,14 +78,15 @@ export type HookModelRequest = Omit<ModelRequest, "messages"> & {
74
78
  messages: HookModelMessage[];
75
79
  };
76
80
  /**
77
- * `model.request`: before a model call is sent. A policy hook may deny it. Fires once per attempt of a
78
- * step (`purpose: "step"`), once for each compaction call (`purpose: "compaction"`, `attempt` 1) and
81
+ * `model.request` of a chat model call (see {@link ModelRequestHookPayload}): once per attempt of a
82
+ * step (`purpose: "step"`), once for each compaction call (`purpose: "compaction"`, `attempt` 1),
79
83
  * once for each conversation title call (`purpose: "title"`, `runId` "", `step` 0, `attempt` 1; a
80
- * denial skips the title and is recorded only in the audit log).
84
+ * denial skips the title and is recorded only in the audit log) and once for each `WebFetch`
85
+ * extraction call (`purpose: "web_fetch"`).
81
86
  * `request` is the request as sent, except that images and documents are placeholders
82
87
  * ({@link HookMediaPlaceholder}): no media bytes, no attachment ids.
83
88
  */
84
- export interface ModelRequestHookPayload extends HookScope {
89
+ export interface ModelCallRequestHookPayload extends HookScope {
85
90
  purpose: ModelCallPurpose;
86
91
  step: number;
87
92
  attempt: number;
@@ -89,6 +94,45 @@ export interface ModelRequestHookPayload extends HookScope {
89
94
  model: string;
90
95
  request: Readonly<HookModelRequest>;
91
96
  }
97
+ /**
98
+ * `model.request` of an embedding call an extension made through `ctx.models.embed`
99
+ * (`purpose: "embedding"`): fired once per `embed` call, before the first batch is sent, with every
100
+ * text of the call (the kernel splits them into batches afterwards). A call with no input sends
101
+ * nothing and fires nothing. There is no conversation: the conversation fields of a chat model call
102
+ * (`accountId`, `agentId`, `agentVersion`, `conversationId`, `runId`, `parent`, `step`, `attempt`,
103
+ * `request`) are absent. A policy denial makes `embed` reject (`model.hook_denied`); nothing is sent
104
+ * to the provider and no usage is recorded.
105
+ */
106
+ export interface EmbeddingRequestHookPayload {
107
+ purpose: "embedding";
108
+ orgId: string;
109
+ /** The extension that called `ctx.models.embed`. */
110
+ extension: string;
111
+ /** The embedding purpose the extension called with (one it declared in `permissions.models`). */
112
+ embeddingPurpose: string;
113
+ provider: string;
114
+ model: string;
115
+ /** The texts of the call, in order (text only). */
116
+ input: readonly string[];
117
+ /** The dimensions the extension asked for, when it asked. */
118
+ dimensions?: number;
119
+ /** The input type the extension gave, when it gave one. */
120
+ inputType?: "query" | "document";
121
+ accountId?: undefined;
122
+ agentId?: undefined;
123
+ agentVersion?: undefined;
124
+ conversationId?: undefined;
125
+ runId?: undefined;
126
+ parent?: undefined;
127
+ }
128
+ /**
129
+ * `model.request`: before a model call is sent. A policy hook may deny it. Either a chat model call
130
+ * ({@link ModelCallRequestHookPayload}: steps, compaction, titles, `WebFetch`) or an extension's
131
+ * embedding call ({@link EmbeddingRequestHookPayload}, `purpose: "embedding"`); tell them apart by
132
+ * `purpose`. Handlers must accept the absent conversation fields of an embedding call (a policy
133
+ * hook that throws on one denies the call). Payloads carry no media bytes.
134
+ */
135
+ export type ModelRequestHookPayload = ModelCallRequestHookPayload | EmbeddingRequestHookPayload;
92
136
  /**
93
137
  * `model.response`: after a sent model call finished or failed, with its usage when known (step
94
138
  * attempts, compaction and title calls, see `purpose`). Not fired for a call the user aborted (the run was
@@ -138,12 +182,18 @@ export interface UsageReportPayload {
138
182
  */
139
183
  purpose: UsagePurpose;
140
184
  orgId: string;
141
- accountId: string;
142
- agentId: string;
143
- conversationId: string;
144
- /** A sub-agent's calls report `chat`; `parent` tells them apart. */
145
- conversationKind: "chat" | "eval";
146
- runId: string;
185
+ /** Absent for `embedding`. */
186
+ accountId?: string;
187
+ /** Absent for `embedding`. */
188
+ agentId?: string;
189
+ /** Absent for `embedding`. */
190
+ conversationId?: string;
191
+ /** A sub-agent's calls report `chat`; `parent` tells them apart. Absent for `embedding`. */
192
+ conversationKind?: "chat" | "eval";
193
+ /** Absent for `embedding`. */
194
+ runId?: string;
195
+ /** The calling extension; set for `embedding`. */
196
+ extension?: string;
147
197
  /** Set when the call belongs to a sub-agent: where it was dispatched from. */
148
198
  parent?: HookParentScope;
149
199
  provider: string;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "0.1.0-rc.3";
1
+ export declare const SDK_VERSION = "0.1.0-rc.5";
2
2
  export * from "./agent-template.js";
3
3
  export * from "./bootstrap.js";
4
4
  export * from "./cards.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export const SDK_VERSION = "0.1.0-rc.3";
1
+ export const SDK_VERSION = "0.1.0-rc.5";
2
2
  export * from "./agent-template.js";
3
3
  export * from "./bootstrap.js";
4
4
  export * from "./cards.js";
package/dist/model.d.ts CHANGED
@@ -4,9 +4,32 @@
4
4
  * 见 `model-params.ts`);旧契约 `model-provider@1`(`ModelProviderV1` 等)由内核经适配层继续支持。
5
5
  */
6
6
  import type { Effort, ModelParameter, ParamValue } from "./model-params.js";
7
+ import type { LocalizedName } from "./plugin-package.js";
8
+ /** What a model does. Absent on a `ModelInfo` means `chat`. */
9
+ export type ModelKind = "chat" | "embedding" | "rerank" | "image_generation" | "video_generation" | "speech_to_text" | "text_to_speech";
10
+ /** Embedding facts of a model whose `kind` is `embedding`. */
11
+ export interface ModelEmbeddingInfo {
12
+ /** The output dimensions the model can produce (a single entry when not adjustable). */
13
+ dimensions: number[];
14
+ defaultDimensions: number;
15
+ /** Most tokens one input may hold. */
16
+ maxInputTokens: number;
17
+ /** Most inputs one `embed` call may carry. */
18
+ maxBatch: number;
19
+ }
7
20
  export interface ModelInfo {
8
21
  id: string;
9
22
  displayName: string;
23
+ /** Defaults to `chat`. Chat-only fields below stay required for every kind. */
24
+ kind?: ModelKind;
25
+ /** Who made the model (display only). */
26
+ publisher?: string;
27
+ /** Display only. */
28
+ description?: LocalizedName | string;
29
+ /** ISO date after which the model is retired (display only). */
30
+ retiresAt?: string;
31
+ /** Set when `kind` is `embedding`. */
32
+ embedding?: ModelEmbeddingInfo;
10
33
  contextWindow: number;
11
34
  maxOutputTokens: number;
12
35
  capabilities: {
@@ -206,14 +229,49 @@ export declare class ModelError extends Error {
206
229
  /** rate_limited / overloaded / server / timeout / network 为 true。 */
207
230
  get retryable(): boolean;
208
231
  }
232
+ /**
233
+ * Options of a model call made for an organization.
234
+ * `config` is the extension's effective deployment config for this org: the platform's deployment
235
+ * config with the org's copy applied to the overridable fields (`orgAccess(…, "override")`), or the
236
+ * platform's config (equal to `ctx.config`) while the org follows it. Use it, not `ctx.config`, for
237
+ * work done for this org. Optional in the type so older providers compile; the kernel always passes
238
+ * both.
239
+ */
240
+ export interface ModelCallOptions {
241
+ orgId?: string;
242
+ config?: unknown;
243
+ }
244
+ export interface EmbedRequest {
245
+ model: string;
246
+ input: string[];
247
+ /** One of the model's `embedding.dimensions`; the model's default when absent. */
248
+ dimensions?: number;
249
+ inputType?: "query" | "document";
250
+ }
251
+ export interface EmbedResult {
252
+ /** One vector per input, in input order. */
253
+ vectors: number[][];
254
+ usage: {
255
+ inputTokens: number;
256
+ };
257
+ }
258
+ /**
259
+ * Checks an embedding result against its request and the model's embedding facts. Returns null when
260
+ * valid, else the reason: wrong vector count, wrong dimension, or a non-finite number.
261
+ */
262
+ export declare function validateEmbedResult(req: EmbedRequest, info: ModelEmbeddingInfo, res: EmbedResult): string | null;
209
263
  /** `model-provider@2` 的实现。 */
210
264
  export interface ModelProvider {
211
265
  /** 每个模型的 `parameters` 须通过 `assertValidDeclarations`,否则内核不使用该供应商。 */
212
- listModels(): Promise<ModelInfo[]>;
266
+ listModels(opts?: ModelCallOptions): Promise<ModelInfo[]>;
213
267
  /** 事件序列必须以且仅以一个 message_complete 结束;出错抛 ModelError。 */
214
268
  stream(req: ModelRequest, opts: {
215
269
  signal: AbortSignal;
216
- }): AsyncIterable<ModelStreamEvent>;
270
+ } & ModelCallOptions): AsyncIterable<ModelStreamEvent>;
271
+ /** Present only on a provider that lists `embedding` models. */
272
+ embed?(req: EmbedRequest, opts: {
273
+ signal: AbortSignal;
274
+ } & ModelCallOptions): Promise<EmbedResult>;
217
275
  }
218
276
  /** `model-provider@1` 的实现(旧契约,内核经适配层接入:effort 由 `capabilities.effort` 推导)。 */
219
277
  export interface ModelProviderV1 {
package/dist/model.js CHANGED
@@ -148,5 +148,27 @@ export class ModelError extends Error {
148
148
  return isRetryableKind(this.kind);
149
149
  }
150
150
  }
151
+ /**
152
+ * Checks an embedding result against its request and the model's embedding facts. Returns null when
153
+ * valid, else the reason: wrong vector count, wrong dimension, or a non-finite number.
154
+ */
155
+ export function validateEmbedResult(req, info, res) {
156
+ if (res.vectors.length !== req.input.length) {
157
+ return `vector count ${res.vectors.length} does not match input count ${req.input.length}`;
158
+ }
159
+ const dims = req.dimensions ?? info.defaultDimensions;
160
+ for (let i = 0; i < res.vectors.length; i++) {
161
+ const v = res.vectors[i];
162
+ if (v.length !== dims) {
163
+ return `vector ${i} has dimension ${v.length}, expected ${dims}`;
164
+ }
165
+ for (const x of v) {
166
+ if (typeof x !== "number" || !Number.isFinite(x)) {
167
+ return `vector ${i} holds a non-finite number`;
168
+ }
169
+ }
170
+ }
171
+ return null;
172
+ }
151
173
  export const MODEL_PROVIDER_EXTENSION_POINT = "model-provider@1";
152
174
  export const MODEL_PROVIDER_EXTENSION_POINT_V2 = "model-provider@2";
@@ -22,7 +22,10 @@ export interface ExtensionPermissions {
22
22
  * on a replica, calls on these mount points fail closed there.
23
23
  */
24
24
  policyHooks?: PolicyHook[];
25
- /** 预留:模型用途,键须为 `<插件名>.<名称>`。 */
25
+ /**
26
+ * Model purposes (`ctx.models`): each key must be `<extension name>.<name>`; an org admin maps
27
+ * each to a model of its `kind`. `ctx.models.embed` / `describe` accept the `embedding` ones.
28
+ */
26
29
  models?: {
27
30
  purpose: string;
28
31
  kind: "chat" | "embedding";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capybara-hq/plugin-sdk",
3
- "version": "0.1.0-rc.3",
3
+ "version": "0.1.0-rc.5",
4
4
  "description": "Capybara plugin SDK: the types and pure functions plugins are written against",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {