@capybara-hq/kernel 0.1.0-rc.5 → 0.1.0-rc.6

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 (63) hide show
  1. package/dist/admin/admin.controller.d.ts +13 -2
  2. package/dist/admin/admin.controller.js +29 -4
  3. package/dist/agents/agents.service.d.ts +3 -1
  4. package/dist/agents/agents.service.js +8 -6
  5. package/dist/agents/from-template.service.js +3 -3
  6. package/dist/agents/models.controller.d.ts +34 -5
  7. package/dist/agents/models.controller.js +50 -6
  8. package/dist/agents/readiness.controller.d.ts +5 -5
  9. package/dist/agents/readiness.controller.js +6 -6
  10. package/dist/catalog/marketplaces.service.d.ts +1 -1
  11. package/dist/config/schema.d.ts +4 -0
  12. package/dist/config/schema.js +24 -0
  13. package/dist/conversations/conversations.service.d.ts +4 -3
  14. package/dist/conversations/conversations.service.js +9 -8
  15. package/dist/ctx/create-ctx.d.ts +15 -1
  16. package/dist/ctx/create-ctx.js +23 -2
  17. package/dist/db/kernel-schema.d.ts +24 -0
  18. package/dist/errors.d.ts +3 -0
  19. package/dist/errors.js +3 -0
  20. package/dist/evals/run-replay.js +4 -2
  21. package/dist/evals/runs.service.d.ts +2 -1
  22. package/dist/evals/runs.service.js +13 -12
  23. package/dist/evals/step-replay.d.ts +2 -1
  24. package/dist/evals/step-replay.js +3 -2
  25. package/dist/extensions/config-hash.d.ts +10 -0
  26. package/dist/extensions/config-hash.js +38 -0
  27. package/dist/extensions/extensions.module.js +6 -12
  28. package/dist/extensions/host.d.ts +6 -10
  29. package/dist/extensions/host.js +22 -50
  30. package/dist/extensions/state.module.d.ts +9 -0
  31. package/dist/extensions/state.module.js +38 -0
  32. package/dist/http/api.module.js +2 -0
  33. package/dist/http/plugin-dispatch.d.ts +4 -1
  34. package/dist/http/plugin-dispatch.js +14 -2
  35. package/dist/infra/config/kernel-config.service.js +3 -0
  36. package/dist/infra/events/kernel-events.d.ts +8 -0
  37. package/dist/models/embeddings.d.ts +82 -0
  38. package/dist/models/embeddings.js +326 -0
  39. package/dist/models/enablement.controller.d.ts +40 -0
  40. package/dist/models/enablement.controller.js +74 -0
  41. package/dist/models/enablement.d.ts +99 -0
  42. package/dist/models/enablement.js +263 -0
  43. package/dist/models/models.module.d.ts +5 -1
  44. package/dist/models/models.module.js +16 -4
  45. package/dist/models/purposes.d.ts +18 -4
  46. package/dist/models/purposes.js +46 -9
  47. package/dist/platform/exclusions.js +18 -0
  48. package/dist/runtime/hook-calls.d.ts +8 -1
  49. package/dist/runtime/hook-calls.js +40 -9
  50. package/dist/runtime/model-call.d.ts +8 -3
  51. package/dist/runtime/model-call.js +28 -5
  52. package/dist/runtime/providers.d.ts +146 -32
  53. package/dist/runtime/providers.js +321 -67
  54. package/dist/runtime/run-worker.d.ts +1 -1
  55. package/dist/runtime/run-worker.js +4 -4
  56. package/dist/runtime/subagents/definition.js +3 -3
  57. package/dist/runtime/usage-report-jobs.d.ts +88 -0
  58. package/dist/runtime/usage-report-jobs.js +158 -0
  59. package/dist/runtime/usage-report.d.ts +33 -38
  60. package/dist/runtime/usage-report.js +152 -84
  61. package/dist/tools/pipeline.js +6 -0
  62. package/package.json +3 -3
  63. package/src/migrations/0089_model_kinds_usage.sql +38 -0
@@ -8,6 +8,8 @@ import { type Principal } from "../identity/principal.js";
8
8
  import { SessionService } from "../identity/sessions.js";
9
9
  import { type DeploymentSettings, SettingsService } from "../identity/settings.js";
10
10
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
11
+ import { type ModelDisabledPatch, type ModelProviderParams } from "../models/enablement.controller.js";
12
+ import { type ModelDisableResult, ModelEnablement } from "../models/enablement.js";
11
13
  import { type ModelPrice, type ModelPricePut, PricesService } from "../models/prices.js";
12
14
  import { ModelPurposesService, type PurposeListing, type PurposePut } from "../models/purposes.js";
13
15
  import { type Org, OrgsService } from "../orgs/orgs.service.js";
@@ -104,8 +106,9 @@ export declare class AdminController {
104
106
  private readonly registry;
105
107
  private readonly catalogRequests;
106
108
  private readonly displayNames;
109
+ private readonly enablement;
107
110
  private readonly config;
108
- constructor(accounts: AccountsService, sessions: SessionService, log: Logger, orgs: OrgsService, settings: SettingsService, prices: PricesService, purposes: ModelPurposesService, plugins: ExtensionAdmin, registry: ProviderRegistry, catalogRequests: CatalogRequestsService, displayNames: ExtensionDisplayNames, config: KernelConfigService);
111
+ constructor(accounts: AccountsService, sessions: SessionService, log: Logger, orgs: OrgsService, settings: SettingsService, prices: PricesService, purposes: ModelPurposesService, plugins: ExtensionAdmin, registry: ProviderRegistry, catalogRequests: CatalogRequestsService, displayNames: ExtensionDisplayNames, enablement: ModelEnablement, config: KernelConfigService);
109
112
  /** 后台概览的四个数字(只读计数,不含任何对话内容)。 */
110
113
  overview(): Promise<AdminOverview>;
111
114
  /**
@@ -139,7 +142,8 @@ export declare class AdminController {
139
142
  /**
140
143
  * 模型供应商与模型(后台「模型」页):每个供应商带状态,列模型失败的标 `error`,不影响其他供应商;
141
144
  * 一并返回价格表与币种(改价仍走 `PUT /admin/model-prices`)。Each provider carries its extension's
142
- * display name (null when it has none).
145
+ * display name (null when it has none); every model of the platform's list carries `disabled`
146
+ * (the platform's disabled list, spec B §5.3).
143
147
  */
144
148
  getModels(): Promise<{
145
149
  providers: (ProviderStatus & {
@@ -148,6 +152,13 @@ export declare class AdminController {
148
152
  prices: ModelPrice[];
149
153
  currency: string;
150
154
  }>;
155
+ /**
156
+ * The platform's disabled models of one provider (spec B §5.3): disables and enables the ids the
157
+ * body names (others keep their state); such a model is invisible to every org. The response
158
+ * lists the agents and purposes, across orgs, that now point at a newly disabled model (the
159
+ * disable is not refused for them).
160
+ */
161
+ patchModelsDisabled(principal: Principal, params: z.infer<typeof ModelProviderParams>, body: z.infer<typeof ModelDisabledPatch>): Promise<ModelDisableResult>;
151
162
  /** 模型价格表(每百万 token)与币种(部署设置 `priceCurrency`)。 */
152
163
  getModelPrices(): Promise<{
153
164
  prices: ModelPrice[];
@@ -21,7 +21,9 @@ import { DEPLOYMENT_SETTINGS_PATCH, SettingsService, } from "../identity/setting
21
21
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
22
22
  import { KERNEL_LOGGER } from "../infra/tokens.js";
23
23
  import { createZodDto } from "../infra/validation/create-zod-dto.js";
24
- import { ZBody, ZQuery } from "../infra/validation/zod-params.js";
24
+ import { ZBody, ZParam, ZQuery } from "../infra/validation/zod-params.js";
25
+ import { ModelDisabledPatchDto, ModelProviderParamsDto, } from "../models/enablement.controller.js";
26
+ import { ModelEnablement } from "../models/enablement.js";
25
27
  import { MODEL_PRICE_PUT, PricesService, } from "../models/prices.js";
26
28
  import { purposeTargetOf } from "../models/purposes.controller.js";
27
29
  import { ModelPurposesService, PurposePutDto, withDeclaredByNames, } from "../models/purposes.js";
@@ -73,8 +75,9 @@ let AdminController = class AdminController {
73
75
  registry;
74
76
  catalogRequests;
75
77
  displayNames;
78
+ enablement;
76
79
  config;
77
- constructor(accounts, sessions, log, orgs, settings, prices, purposes, plugins, registry, catalogRequests, displayNames, config) {
80
+ constructor(accounts, sessions, log, orgs, settings, prices, purposes, plugins, registry, catalogRequests, displayNames, enablement, config) {
78
81
  this.accounts = accounts;
79
82
  this.sessions = sessions;
80
83
  this.log = log;
@@ -86,6 +89,7 @@ let AdminController = class AdminController {
86
89
  this.registry = registry;
87
90
  this.catalogRequests = catalogRequests;
88
91
  this.displayNames = displayNames;
92
+ this.enablement = enablement;
89
93
  this.config = config;
90
94
  }
91
95
  /** 后台概览的四个数字(只读计数,不含任何对话内容)。 */
@@ -175,7 +179,8 @@ let AdminController = class AdminController {
175
179
  /**
176
180
  * 模型供应商与模型(后台「模型」页):每个供应商带状态,列模型失败的标 `error`,不影响其他供应商;
177
181
  * 一并返回价格表与币种(改价仍走 `PUT /admin/model-prices`)。Each provider carries its extension's
178
- * display name (null when it has none).
182
+ * display name (null when it has none); every model of the platform's list carries `disabled`
183
+ * (the platform's disabled list, spec B §5.3).
179
184
  */
180
185
  async getModels() {
181
186
  const [providers, prices] = await Promise.all([
@@ -188,6 +193,19 @@ let AdminController = class AdminController {
188
193
  ...prices,
189
194
  };
190
195
  }
196
+ /**
197
+ * The platform's disabled models of one provider (spec B §5.3): disables and enables the ids the
198
+ * body names (others keep their state); such a model is invisible to every org. The response
199
+ * lists the agents and purposes, across orgs, that now point at a newly disabled model (the
200
+ * disable is not refused for them).
201
+ */
202
+ async patchModelsDisabled(principal, params, body) {
203
+ const actor = requireUser(principal);
204
+ return this.enablement.setPlatform(params.provider, body, {
205
+ accountId: actor.account.id,
206
+ tokenId: patTokenId(actor),
207
+ });
208
+ }
191
209
  /** 模型价格表(每百万 token)与币种(部署设置 `priceCurrency`)。 */
192
210
  async getModelPrices() {
193
211
  const [prices, settings] = await Promise.all([this.prices.list(), this.settings.get()]);
@@ -244,6 +262,12 @@ __decorate([
244
262
  __decorate([
245
263
  Get("models")
246
264
  ], AdminController.prototype, "getModels", null);
265
+ __decorate([
266
+ Patch("models/:provider/disabled"),
267
+ __param(0, CurrentPrincipal()),
268
+ __param(1, ZParam(ModelProviderParamsDto)),
269
+ __param(2, ZBody(ModelDisabledPatchDto))
270
+ ], AdminController.prototype, "patchModelsDisabled", null);
247
271
  __decorate([
248
272
  Get("model-prices")
249
273
  ], AdminController.prototype, "getModelPrices", null);
@@ -275,6 +299,7 @@ AdminController = __decorate([
275
299
  __param(8, Inject(ProviderRegistry)),
276
300
  __param(9, Inject(CatalogRequestsService)),
277
301
  __param(10, Inject(ExtensionDisplayNames)),
278
- __param(11, Inject(KernelConfigService))
302
+ __param(11, Inject(ModelEnablement)),
303
+ __param(12, Inject(KernelConfigService))
279
304
  ], AdminController);
280
305
  export { AdminController };
@@ -105,7 +105,9 @@ export declare class AgentsService {
105
105
  */
106
106
  private checkTools;
107
107
  /**
108
- * 校验主模型与兜底都可解析,并用各自模型的参数声明严格校验 `modelParams` / `fallbackParams`
108
+ * 校验主模型与兜底都可解析:enabled `chat` models in the org (`resolve`'s default kind; a model the
109
+ * platform or the org disabled, or an embedding model → 400 `agent.model_unavailable`, spec B
110
+ * §5.3),并用各自模型的参数声明严格校验 `modelParams` / `fallbackParams`
109
111
  * (没有兜底时 `fallbackParams` 必须为空);不合法 → `validation_failed`,details 逐键点名。返回提示。
110
112
  */
111
113
  private checkModels;
@@ -305,13 +305,15 @@ let AgentsService = class AgentsService {
305
305
  return keys;
306
306
  }
307
307
  /**
308
- * 校验主模型与兜底都可解析,并用各自模型的参数声明严格校验 `modelParams` / `fallbackParams`
308
+ * 校验主模型与兜底都可解析:enabled `chat` models in the org (`resolve`'s default kind; a model the
309
+ * platform or the org disabled, or an embedding model → 400 `agent.model_unavailable`, spec B
310
+ * §5.3),并用各自模型的参数声明严格校验 `modelParams` / `fallbackParams`
309
311
  * (没有兜底时 `fallbackParams` 必须为空);不合法 → `validation_failed`,details 逐键点名。返回提示。
310
312
  */
311
- async checkModels(def) {
313
+ async checkModels(orgId, def) {
312
314
  const registry = this.providers;
313
- const main = await registry.resolve(def.model);
314
- const fallback = def.fallback === null ? null : await registry.resolve(def.fallback);
315
+ const main = await registry.resolve(orgId, def.model);
316
+ const fallback = def.fallback === null ? null : await registry.resolve(orgId, def.fallback);
315
317
  const details = [
316
318
  ...paramIssues("modelParams", main.info.parameters, def.modelParams),
317
319
  ...paramIssues("fallbackParams", fallback?.info.parameters ?? [], def.fallbackParams),
@@ -369,7 +371,7 @@ let AgentsService = class AgentsService {
369
371
  async createChecked(orgId, actorAccountId, request, template, actorTokenId, auditDetail) {
370
372
  const input = { ...request, definition: deriveInstructions(request.definition, null) };
371
373
  // 先解析模型(可能访问供应商),不在持有组织行锁时做
372
- const warnings = await this.checkModels(input.definition);
374
+ const warnings = await this.checkModels(orgId, input.definition);
373
375
  const toolKeys = await this.checkTools(orgId, input.definition);
374
376
  if (input.visibility !== "members" && (input.memberIds?.length ?? 0) > 0) {
375
377
  throw new ApiError("validation_failed", "memberIds requires visibility 'members'");
@@ -478,7 +480,7 @@ let AgentsService = class AgentsService {
478
480
  */
479
481
  async update(orgId, agentId, actorAccountId, request, actorTokenId, auditDetail) {
480
482
  const patch = request;
481
- const warnings = patch.definition ? await this.checkModels(patch.definition) : [];
483
+ const warnings = patch.definition ? await this.checkModels(orgId, patch.definition) : [];
482
484
  const toolKeys = patch.definition ? await this.checkTools(orgId, patch.definition) : null;
483
485
  const agent = await this.updateIn(orgId, agentId, actorAccountId, patch, toolKeys, actorTokenId, auditDetail);
484
486
  return { agent, warnings };
@@ -62,7 +62,7 @@ let AgentsFromTemplate = class AgentsFromTemplate {
62
62
  enabledPlugins: await this.host.enabledPlugins(orgId),
63
63
  resolveModel: async (ref) => {
64
64
  try {
65
- return (await this.providers.resolve(ref)).info.parameters;
65
+ return (await this.providers.resolve(orgId, ref)).info.parameters;
66
66
  }
67
67
  catch {
68
68
  return null;
@@ -79,8 +79,8 @@ let AgentsFromTemplate = class AgentsFromTemplate {
79
79
  }
80
80
  },
81
81
  defaultModel: async () => {
82
- for (const p of await this.providers.list()) {
83
- const m = p.models[0];
82
+ for (const p of await this.providers.list(orgId)) {
83
+ const m = p.models.find((x) => x.kind === "chat");
84
84
  if (m)
85
85
  return { provider: p.provider, model: m.id };
86
86
  }
@@ -1,19 +1,48 @@
1
- import type { LocalizedName, ModelInfo } from "@capybara-hq/plugin-sdk";
1
+ import type { LocalizedName } from "@capybara-hq/plugin-sdk";
2
+ import { z } from "zod";
2
3
  import { ExtensionDisplayNames } from "../extensions/display-name.js";
3
- import { ProviderRegistry } from "../runtime/providers.js";
4
+ import { type Principal } from "../identity/principal.js";
5
+ import { type OrgContext } from "../orgs/org.guard.js";
6
+ import { type KernelModelInfo, ProviderRegistry, type ProviderStatus } from "../runtime/providers.js";
7
+ declare const ModelsQuery: z.ZodObject<{
8
+ kind: z.ZodOptional<z.ZodEnum<{
9
+ chat: "chat";
10
+ embedding: "embedding";
11
+ image_generation: "image_generation";
12
+ other: "other";
13
+ rerank: "rerank";
14
+ speech_to_text: "speech_to_text";
15
+ text_to_speech: "text_to_speech";
16
+ video_generation: "video_generation";
17
+ }>>;
18
+ }, z.core.$strip>;
4
19
  /**
5
20
  * 可选模型(`/api/v1/orgs/:orgId/models`):已安装的模型供应商及其模型,列表缓存在 Redis。Each provider
6
- * carries its extension's display name (null when it has none).
21
+ * carries its extension's display name (null when it has none). Listed for the org (its effective
22
+ * provider config: a provider needing configuration there is absent); only enabled models (neither
23
+ * the platform nor the org disabled them, spec B §5.3), optionally of one `?kind=`; every model
24
+ * carries its normalised `kind` (`other` for a kind this kernel does not know).
7
25
  */
8
26
  export declare class ModelsController {
9
27
  private readonly registry;
10
28
  private readonly displayNames;
11
29
  constructor(registry: ProviderRegistry, displayNames: ExtensionDisplayNames);
12
- list(): Promise<{
30
+ list(org: OrgContext, q: z.infer<typeof ModelsQuery>): Promise<{
13
31
  providers: {
14
32
  provider: string;
15
33
  displayName: LocalizedName | null;
16
- models: ModelInfo[];
34
+ models: KernelModelInfo[];
17
35
  }[];
18
36
  }>;
37
+ /**
38
+ * The org's model catalog (the settings page; `org.models.manage`, people only): every provider
39
+ * with its status for the org and every model the platform left on, each with `disabled` for the
40
+ * org's own list.
41
+ */
42
+ catalog(principal: Principal, org: OrgContext): Promise<{
43
+ providers: (ProviderStatus & {
44
+ displayName: LocalizedName | null;
45
+ })[];
46
+ }>;
19
47
  }
48
+ export {};
@@ -8,12 +8,35 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { Controller, Get, Inject } from "@nestjs/common";
11
+ import { z } from "zod";
11
12
  import { ExtensionDisplayNames } from "../extensions/display-name.js";
12
- import { OrgAction } from "../orgs/org.guard.js";
13
- import { ProviderRegistry } from "../runtime/providers.js";
13
+ import { CurrentPrincipal, requireUser } from "../identity/principal.js";
14
+ import { createZodDto } from "../infra/validation/create-zod-dto.js";
15
+ import { ZQuery } from "../infra/validation/zod-params.js";
16
+ import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
17
+ import { ProviderRegistry, } from "../runtime/providers.js";
18
+ const ModelsQuery = z.object({
19
+ kind: z
20
+ .enum([
21
+ "chat",
22
+ "embedding",
23
+ "rerank",
24
+ "image_generation",
25
+ "video_generation",
26
+ "speech_to_text",
27
+ "text_to_speech",
28
+ "other",
29
+ ])
30
+ .optional(),
31
+ });
32
+ class ModelsQueryDto extends createZodDto(ModelsQuery, "ModelsQuery") {
33
+ }
14
34
  /**
15
35
  * 可选模型(`/api/v1/orgs/:orgId/models`):已安装的模型供应商及其模型,列表缓存在 Redis。Each provider
16
- * carries its extension's display name (null when it has none).
36
+ * carries its extension's display name (null when it has none). Listed for the org (its effective
37
+ * provider config: a provider needing configuration there is absent); only enabled models (neither
38
+ * the platform nor the org disabled them, spec B §5.3), optionally of one `?kind=`; every model
39
+ * carries its normalised `kind` (`other` for a kind this kernel does not know).
17
40
  */
18
41
  let ModelsController = class ModelsController {
19
42
  registry;
@@ -22,8 +45,8 @@ let ModelsController = class ModelsController {
22
45
  this.registry = registry;
23
46
  this.displayNames = displayNames;
24
47
  }
25
- async list() {
26
- const providers = await this.registry.list();
48
+ async list(org, q) {
49
+ const providers = await this.registry.list(org.orgId, q.kind === undefined ? {} : { kind: q.kind });
27
50
  const names = await this.displayNames.many(providers.map((p) => p.provider));
28
51
  return {
29
52
  providers: providers.map((p) => ({
@@ -33,11 +56,32 @@ let ModelsController = class ModelsController {
33
56
  })),
34
57
  };
35
58
  }
59
+ /**
60
+ * The org's model catalog (the settings page; `org.models.manage`, people only): every provider
61
+ * with its status for the org and every model the platform left on, each with `disabled` for the
62
+ * org's own list.
63
+ */
64
+ async catalog(principal, org) {
65
+ requireUser(principal);
66
+ const providers = await this.registry.catalog(org.orgId);
67
+ const names = await this.displayNames.many(providers.map((p) => p.plugin));
68
+ return {
69
+ providers: providers.map((p) => ({ ...p, displayName: names.get(p.plugin) ?? null })),
70
+ };
71
+ }
36
72
  };
37
73
  __decorate([
38
74
  Get(),
39
- OrgAction("agents.use")
75
+ OrgAction("agents.use"),
76
+ __param(0, CurrentOrg()),
77
+ __param(1, ZQuery(ModelsQueryDto))
40
78
  ], ModelsController.prototype, "list", null);
79
+ __decorate([
80
+ Get("catalog"),
81
+ OrgAction("org.models.manage"),
82
+ __param(0, CurrentPrincipal()),
83
+ __param(1, CurrentOrg())
84
+ ], ModelsController.prototype, "catalog", null);
41
85
  ModelsController = __decorate([
42
86
  Controller("api/v1/orgs/:orgId/models"),
43
87
  __param(0, Inject(ProviderRegistry)),
@@ -9,11 +9,11 @@ import { AgentsService } from "./agents.service.js";
9
9
  * a vendor call; `agents` is the number of agents the caller can see (the agent list's
10
10
  * visibility). Humans and service tokens (`org.read`).
11
11
  *
12
- * `modelProvider` is deployment-wide, not about this org: model providers are not org-scoped
13
- * (an org's model purposes map onto the deployment's providers but cannot add one), so every org
14
- * of the deployment reads the same value. It is read from this replica's provider registry, which
15
- * follows the per-replica extension reconcile: right after a provider is enabled, a replica that
16
- * has not reconciled yet may still answer `none` for a moment.
12
+ * `modelProvider` counts the deployment's providers whose effective config is valid in this org
13
+ * (spec B §5.5: a provider the platform installed without a key needs each org's own). It is read
14
+ * from this replica's provider registry, which follows the per-replica extension reconcile: right
15
+ * after a provider is enabled, a replica that has not reconciled yet may still answer `none` for
16
+ * a moment.
17
17
  */
18
18
  export declare class ReadinessController {
19
19
  private readonly registry;
@@ -21,11 +21,11 @@ const viewerOf = (p) => (p.kind === "user" ? p.account.id : null);
21
21
  * a vendor call; `agents` is the number of agents the caller can see (the agent list's
22
22
  * visibility). Humans and service tokens (`org.read`).
23
23
  *
24
- * `modelProvider` is deployment-wide, not about this org: model providers are not org-scoped
25
- * (an org's model purposes map onto the deployment's providers but cannot add one), so every org
26
- * of the deployment reads the same value. It is read from this replica's provider registry, which
27
- * follows the per-replica extension reconcile: right after a provider is enabled, a replica that
28
- * has not reconciled yet may still answer `none` for a moment.
24
+ * `modelProvider` counts the deployment's providers whose effective config is valid in this org
25
+ * (spec B §5.5: a provider the platform installed without a key needs each org's own). It is read
26
+ * from this replica's provider registry, which follows the per-replica extension reconcile: right
27
+ * after a provider is enabled, a replica that has not reconciled yet may still answer `none` for
28
+ * a moment.
29
29
  */
30
30
  let ReadinessController = class ReadinessController {
31
31
  registry;
@@ -37,7 +37,7 @@ let ReadinessController = class ReadinessController {
37
37
  async get(principal, org) {
38
38
  const visible = await this.agents.listVisible(org.orgId, viewerOf(principal));
39
39
  return {
40
- modelProvider: this.registry.hasProvider() ? "ready" : "none",
40
+ modelProvider: (await this.registry.hasProvider(org.orgId)) ? "ready" : "none",
41
41
  agents: visible.length,
42
42
  };
43
43
  }
@@ -181,7 +181,7 @@ export declare function headsToResolve(entries: readonly MarketplaceEntry[], imp
181
181
  /** The host versions (an entry's `minKernel` is judged against them). */
182
182
  export declare const HOST_VERSIONS: {
183
183
  readonly kernel: string;
184
- readonly sdk: "0.1.0-rc.3";
184
+ readonly sdk: "0.1.0-rc.4";
185
185
  };
186
186
  /** Whether this deployment satisfies the entry's `minKernel`. */
187
187
  export declare const entryCompatible: (e: Pick<MarketplaceEntry, "minKernel">) => boolean;
@@ -232,6 +232,10 @@ export declare const KernelConfigSchema: z.ZodObject<{
232
232
  judgeMaxOutputTokens: z.ZodDefault<z.ZodNumber>;
233
233
  maxConsecutiveErrors: z.ZodDefault<z.ZodNumber>;
234
234
  }, z.core.$strip>>;
235
+ models: z.ZodPrefault<z.ZodObject<{
236
+ listTimeoutMs: z.ZodDefault<z.ZodNumber>;
237
+ embedTimeoutMs: z.ZodDefault<z.ZodNumber>;
238
+ }, z.core.$strip>>;
235
239
  hooks: z.ZodPrefault<z.ZodObject<{
236
240
  timeoutMs: z.ZodDefault<z.ZodNumber>;
237
241
  usageReportConcurrency: z.ZodDefault<z.ZodNumber>;
@@ -1252,6 +1252,30 @@ export const KernelConfigSchema = z.object({
1252
1252
  path: ["defaultTokenCap"],
1253
1253
  })
1254
1254
  .prefault({}),
1255
+ models: z
1256
+ .object({
1257
+ listTimeoutMs: z
1258
+ .number()
1259
+ .int()
1260
+ .positive()
1261
+ .max(300_000)
1262
+ .default(10_000)
1263
+ .meta(meta({
1264
+ hot: true,
1265
+ description: "Time limit in milliseconds for one model provider's model list (listModels, which may page through a large platform catalog); a provider that exceeds it is treated as failing to list",
1266
+ })),
1267
+ embedTimeoutMs: z
1268
+ .number()
1269
+ .int()
1270
+ .positive()
1271
+ .max(600_000)
1272
+ .default(60_000)
1273
+ .meta(meta({
1274
+ hot: true,
1275
+ description: "Time limit in milliseconds for one embed call to a model provider (one batch of an extension's ctx.models.embed); the call is aborted and fails with a timeout model error",
1276
+ })),
1277
+ })
1278
+ .prefault({}),
1255
1279
  hooks: z
1256
1280
  .object({
1257
1281
  timeoutMs: z
@@ -372,9 +372,10 @@ export declare class ConversationsService {
372
372
  * The selection a message switches to, or null when it changes nothing. A model equal to the
373
373
  * agent's means "back to the agent's model" (null); a model change without `effort` resets the
374
374
  * effort. The target model must resolve (`agent.model_unavailable`) and declare the effort
375
- * (`validation_failed`). Resolving is the agent editor's check: providers are deployment-scoped
376
- * and serve chat models only. If per-org provider enablement is added, this check and the run
377
- * worker's `fallBackIfUnavailable` must follow it.
375
+ * (`validation_failed`). Resolving is the agent editor's check, for the org (its effective
376
+ * provider config, spec B §5.5) and its disabled lists: a model the platform or the org disabled,
377
+ * or a non-chat model, is refused (spec B §5.3), as the run worker's `fallBackIfUnavailable` falls
378
+ * back from it.
378
379
  */
379
380
  private planSwitch;
380
381
  /**
@@ -205,7 +205,7 @@ let ConversationsService = class ConversationsService {
205
205
  if (prior)
206
206
  return prior;
207
207
  const environmentId = await this.environments.forNew(orgId, accountId, pick);
208
- const next = await this.planSwitch(agent.definition, NO_SELECTION, input);
208
+ const next = await this.planSwitch(orgId, agent.definition, NO_SELECTION, input);
209
209
  const extra = await this.resolveExtra(orgId, accountId, input);
210
210
  const invoked = await this.prepareSkill(orgId, agentId, undefined, agent.definition, input);
211
211
  const id = randomUUID();
@@ -701,7 +701,7 @@ let ConversationsService = class ConversationsService {
701
701
  : undefined;
702
702
  const next = agent === undefined || (input.model === undefined && input.effort === undefined)
703
703
  ? null
704
- : await this.planSwitch(agent, conv.selection, input);
704
+ : await this.planSwitch(orgId, agent, conv.selection, input);
705
705
  const extra = await this.resolveExtra(orgId, accountId, input);
706
706
  const invoked = agent === undefined
707
707
  ? undefined
@@ -741,16 +741,17 @@ let ConversationsService = class ConversationsService {
741
741
  * The selection a message switches to, or null when it changes nothing. A model equal to the
742
742
  * agent's means "back to the agent's model" (null); a model change without `effort` resets the
743
743
  * effort. The target model must resolve (`agent.model_unavailable`) and declare the effort
744
- * (`validation_failed`). Resolving is the agent editor's check: providers are deployment-scoped
745
- * and serve chat models only. If per-org provider enablement is added, this check and the run
746
- * worker's `fallBackIfUnavailable` must follow it.
744
+ * (`validation_failed`). Resolving is the agent editor's check, for the org (its effective
745
+ * provider config, spec B §5.5) and its disabled lists: a model the platform or the org disabled,
746
+ * or a non-chat model, is refused (spec B §5.3), as the run worker's `fallBackIfUnavailable` falls
747
+ * back from it.
747
748
  */
748
- async planSwitch(agent, current, input) {
749
+ async planSwitch(orgId, agent, current, input) {
749
750
  const next = planSelection(agent.model, current, input);
750
751
  if (next === null)
751
752
  return null;
752
753
  const { model, effort } = next;
753
- const { info } = await this.providers.resolve(model ?? agent.model);
754
+ const { info } = await this.providers.resolve(orgId, model ?? agent.model);
754
755
  if (effort !== null) {
755
756
  const knob = info.parameters.find((p) => p.key === "effort");
756
757
  const values = knob?.schema?.enum ?? [];
@@ -771,7 +772,7 @@ let ConversationsService = class ConversationsService {
771
772
  if (pick.model === undefined && pick.effort === undefined)
772
773
  return;
773
774
  const agent = await this.agents.get(orgId, conv.agentId, accountId);
774
- await this.planSwitch(agent.definition, conv.selection, pick);
775
+ await this.planSwitch(orgId, agent.definition, conv.selection, pick);
775
776
  }
776
777
  /**
777
778
  * Writes the message (preceded by its `model_switched` when it switches) inside `t` and updates
@@ -4,6 +4,8 @@ import { Kysely } from "kysely";
4
4
  import { type RegisteredHook } from "../extensions/hooks.js";
5
5
  import { RegistrationLedger } from "../extensions/ledger.js";
6
6
  import type { LockService } from "../infra/lock/lock.service.js";
7
+ import type { ModelEmbeddings } from "../models/embeddings.js";
8
+ import type { HookRunner } from "../runtime/hook-calls.js";
7
9
  import type { EmbedTokens } from "../ui/embed-token.js";
8
10
  import { type UiActionHandler, type UiRender } from "../ui/registry.js";
9
11
  import { type BusSubscriber } from "./bus.js";
@@ -22,6 +24,17 @@ export interface KernelDeps {
22
24
  keyring: Keyring;
23
25
  /** Verifies embed tokens (`ctx.ui` embed requests; the one `EmbedTokens` provider). */
24
26
  embedTokens: Pick<EmbedTokens, "verify">;
27
+ /**
28
+ * The `ctx.models` broker (the one `ModelEmbeddings` provider). Absent (a ctx built alone in a
29
+ * test): `ctx.models` calls reject.
30
+ */
31
+ models?: Pick<ModelEmbeddings, "embed" | "describe">;
32
+ /**
33
+ * The host's hook registry: `ctx.models.embed` fires `model.request` through it (spec B §5.4).
34
+ * A required key, so no builder leaves policy hooks out by omission (fail closed by
35
+ * construction); only a ctx built alone in a test passes `undefined` (no hooks fire).
36
+ */
37
+ hooks: HookRunner | undefined;
25
38
  runWorkers: boolean;
26
39
  /** 内核 logger(`debug` 可选:插件 Logger 没有它,测试可不给)。 */
27
40
  log: Logger & {
@@ -102,7 +115,8 @@ export declare class LiveConfig {
102
115
  * `deploymentConfig` may be a {@link LiveConfig} (shared by the instance, replaceable in place);
103
116
  * otherwise a fixed value is wrapped.
104
117
  * - Granted automatically: `kv`, `lock`, `bus`, `queue`, `log`, `db`, `config`, `provide`;
105
- * - `secrets` only with `permissions.secrets`, `storage` only with `permissions.storage`; `http.route`
118
+ * - `secrets` only with `permissions.secrets`, `storage` only with `permissions.storage`, `models`
119
+ * only with `permissions.models` (and only its `embedding` purposes, through `deps.models`); `http.route`
106
120
  * only the method + path declared in `permissions.http`; `tools.register` only names declared in
107
121
  * `permissions.tools`; `ui.view` / `ui.action` only ids declared in `permissions.ui.views` (during
108
122
  * setup only); `ui.invalidate` sends a refresh over the kernel bus (namespace `kernel`), across
@@ -164,7 +164,8 @@ export class LiveConfig {
164
164
  * `deploymentConfig` may be a {@link LiveConfig} (shared by the instance, replaceable in place);
165
165
  * otherwise a fixed value is wrapped.
166
166
  * - Granted automatically: `kv`, `lock`, `bus`, `queue`, `log`, `db`, `config`, `provide`;
167
- * - `secrets` only with `permissions.secrets`, `storage` only with `permissions.storage`; `http.route`
167
+ * - `secrets` only with `permissions.secrets`, `storage` only with `permissions.storage`, `models`
168
+ * only with `permissions.models` (and only its `embedding` purposes, through `deps.models`); `http.route`
168
169
  * only the method + path declared in `permissions.http`; `tools.register` only names declared in
169
170
  * `permissions.tools`; `ui.view` / `ui.action` only ids declared in `permissions.ui.views` (during
170
171
  * setup only); `ui.invalidate` sends a refresh over the kernel bus (namespace `kernel`), across
@@ -218,6 +219,24 @@ export function createExtensionContext(deps, manifest, deploymentConfig, extensi
218
219
  plugins: async () => [name],
219
220
  }).forPlugin(name)
220
221
  : undefined;
222
+ // ctx.models: only with permissions.models; the broker accepts only the purposes declared there
223
+ // with kind embedding (spec B §5.4), aborted by this instance's signal
224
+ // a frozen copy: the approved manifest, not an object the extension could change later
225
+ const declaredModels = Object.freeze(structuredClone(perms.models ?? []).map((m) => Object.freeze(m)));
226
+ const modelsApi = declaredModels.length > 0
227
+ ? {
228
+ embed: async (req) => {
229
+ if (!deps.models)
230
+ throw new Error(`plugin ${name}: ctx.models is not available`);
231
+ return deps.models.embed(name, declaredModels, req, lifecycle.signal, deps.hooks);
232
+ },
233
+ describe: async (req) => {
234
+ if (!deps.models)
235
+ throw new Error(`plugin ${name}: ctx.models is not available`);
236
+ return deps.models.describe(name, declaredModels, req);
237
+ },
238
+ }
239
+ : undefined;
221
240
  const declaredRoutes = new Set((perms.http ?? []).map((r) => `${r.method} ${r.path}`));
222
241
  const declaredTools = new Set(perms.tools ?? []);
223
242
  const declaredHooks = new Set(perms.hooks ?? []);
@@ -561,7 +580,8 @@ export function createExtensionContext(deps, manifest, deploymentConfig, extensi
561
580
  return closing;
562
581
  },
563
582
  };
564
- // 声明了 secrets / storage 时是普通属性;未声明时是不可枚举的抛错 getter——展开 / 打印 ctx 不会触发越权与审计
583
+ // secrets / storage / models: a plain property when declared; otherwise a non-enumerable getter
584
+ // that throws, so spreading or printing ctx triggers no denial and no audit
565
585
  const gated = (key, api) => {
566
586
  if (api) {
567
587
  Object.assign(ctx, { [key]: api });
@@ -575,6 +595,7 @@ export function createExtensionContext(deps, manifest, deploymentConfig, extensi
575
595
  };
576
596
  gated("secrets", secretsApi);
577
597
  gated("storage", storageApi);
598
+ gated("models", modelsApi);
578
599
  return ctx;
579
600
  }
580
601
  /**
@@ -1012,6 +1012,28 @@ export interface UsageReportSweepTable {
1012
1012
  watermark: Ts;
1013
1013
  updated_at: ColumnType<Date, Date | string | undefined, Date | string>;
1014
1014
  }
1015
+ /** A model disabled on the platform (`org_id` null) or in one org (0089, spec B §5.3). */
1016
+ export interface ModelDisabledTable {
1017
+ org_id: string | null;
1018
+ provider: string;
1019
+ model: string;
1020
+ disabled_by: string | null;
1021
+ disabled_at: Ts;
1022
+ }
1023
+ /** One model call outside a conversation, e.g. an extension's embed call (0089, spec B §5.4). */
1024
+ export interface ModelUsageTable {
1025
+ id: Generated<string>;
1026
+ org_id: string;
1027
+ extension: string;
1028
+ purpose: string;
1029
+ provider: string;
1030
+ model: string;
1031
+ input_tokens: number;
1032
+ occurred_at: Ts;
1033
+ reported_at: TsNullable;
1034
+ /** Set when the row's `usage.report` job gave up after its last attempt (the sweep skips it). */
1035
+ report_failed_at: TsNullable;
1036
+ }
1015
1037
  export interface KernelDatabase {
1016
1038
  accounts: AccountsTable;
1017
1039
  identities: IdentitiesTable;
@@ -1071,6 +1093,8 @@ export interface KernelDatabase {
1071
1093
  eval_runs: EvalRunsTable;
1072
1094
  eval_results: EvalResultsTable;
1073
1095
  model_purpose_mappings: ModelPurposeMappingsTable;
1096
+ model_disabled: ModelDisabledTable;
1097
+ model_usage: ModelUsageTable;
1074
1098
  extension_packages: ExtensionPackagesTable;
1075
1099
  extension_blobs: ExtensionBlobsTable;
1076
1100
  extension_installs: ExtensionInstallsTable;
package/dist/errors.d.ts CHANGED
@@ -280,6 +280,9 @@ export declare const ERROR_CODES: {
280
280
  readonly "eval.candidate_invalid": 400;
281
281
  readonly "model.unconfigured": 409;
282
282
  readonly "model.purpose_unknown": 404;
283
+ readonly "model.provider_not_found": 404;
284
+ /** An extension's `ctx.models.embed` call was denied by a `model.request` policy hook. */
285
+ readonly "model.hook_denied": 403;
283
286
  readonly "eval.result_not_found": 404;
284
287
  };
285
288
  export type ErrorCode = keyof typeof ERROR_CODES;