@odla-ai/ai 0.1.2 → 0.2.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/dist/index.d.cts CHANGED
@@ -924,6 +924,38 @@ interface OdlaDbKeyResolverOptions {
924
924
  * odla-ai throws its own ConfigError) rather than surfacing odla-db's 404. */
925
925
  declare function odlaDbKeyResolver(db: OdlaDbClient, opts?: OdlaDbKeyResolverOptions): KeyResolver;
926
926
 
927
+ interface InitFromPlatformOptions {
928
+ /** Platform base URL (e.g. "https://odla.ai"). */
929
+ platform: string;
930
+ /** The REGISTRY app id — not the env's tenant id. */
931
+ appId: string;
932
+ /** Which environment's config to use ("dev", "prod", …). */
933
+ env: string;
934
+ /** The app's own tenant client — key reads go through `db.secrets.get`. */
935
+ db: OdlaDbClient;
936
+ /** Transport override (tests / non-global fetch). */
937
+ fetch?: typeof fetch;
938
+ /** public-config cache TTL in ms (default 60s; 0 disables caching). */
939
+ ttlMs?: number;
940
+ /** Extra init() options (catalog, injected providers, …). `resolveKey` is
941
+ * owned by this helper; `defaultModel` is used only when the platform
942
+ * config sets no model. */
943
+ initOptions?: Omit<InitOptions, "resolveKey">;
944
+ }
945
+ interface PlatformAi {
946
+ ai: Ai;
947
+ provider: ProviderId;
948
+ model?: string;
949
+ }
950
+ /** Drop all cached platform configs (tests, or to force an immediate refetch). */
951
+ declare function clearPlatformAiCache(): void;
952
+ /** Build an `Ai` from the app's platform registration: provider/model from
953
+ * public-config (cached ~60s), API key from the tenant's odla-db secrets at
954
+ * call time. On cache refresh the existing `Ai` (and its warm key-resolver
955
+ * cache) is reused unless provider/model changed. Note the first call's
956
+ * `db`/`initOptions` are captured for the cache entry's lifetime. */
957
+ declare function initFromPlatform(opts: InitFromPlatformOptions): Promise<PlatformAi>;
958
+
927
959
  type FetchLike = typeof fetch;
928
960
  interface ProvisionContext {
929
961
  /** The odla-db worker URL — always human-supplied, never hardcoded. */
@@ -946,6 +978,15 @@ declare function pushAgentSchema(ctx: {
946
978
  }, appId: string, appKey: string): Promise<void>;
947
979
  /** Store a named secret (operator/dev token). Value must be a non-`$` string. */
948
980
  declare function putSecret(ctx: ProvisionContext, appId: string, name: string, value: string): Promise<void>;
981
+ /** Per-namespace CEL permission rules (odla-db is DEFAULT-DENY: end-users can't
982
+ * read or write any namespace until its rules are set; the app key bypasses
983
+ * rules, so agent-only apps work without them). */
984
+ type AppRules = Record<string, {
985
+ view?: string;
986
+ create?: string;
987
+ update?: string;
988
+ delete?: string;
989
+ }>;
949
990
  interface ProvisionAgentAppOptions extends ProvisionContext {
950
991
  appId?: string;
951
992
  appName?: string;
@@ -955,6 +996,11 @@ interface ProvisionAgentAppOptions extends ProvisionContext {
955
996
  secretName?: (provider: ProviderId) => string;
956
997
  /** Push the agent schema (default true). */
957
998
  pushSchema?: boolean;
999
+ /** Permission rules to install. odla-db is default-deny, so set these for any
1000
+ * namespace end-users should reach (e.g. own-rows-only:
1001
+ * `{ todos: { view: "auth.id == data.ownerId", create: "auth.id == data.ownerId" } }`).
1002
+ * Omit for agent-only apps — the app key bypasses rules. */
1003
+ rules?: AppRules;
958
1004
  }
959
1005
  interface ProvisionedApp {
960
1006
  appId: string;
@@ -994,4 +1040,4 @@ interface EntityCrudSkillOptions {
994
1040
  /** Build a CRUD Skill for one odla-db entity. */
995
1041
  declare function entityCrudSkill(opts: EntityCrudSkillOptions): Skill;
996
1042
 
997
- export { AGENT_SCHEMA, ANTHROPIC_MODELS, type AgentRun, type AgentRunInput, type Ai, type AudioBlock, type AudioMediaType, type AudioSource, AuthError, type Capabilities, CapabilityError, type Catalog, type ChatInput, type ComposedPersona, ConfigError, type ContentBlockDelta, type ContentBlockDeltaEvent, type ContentBlockStartEvent, type ContentBlockStopEvent, ContextWindowError, DEFAULT_CATALOG, DEFAULT_SECRET_NAMES, type DocumentBlock, type DocumentSource, type Effort, type EntityCrudSkillOptions, type EvalCase, type EvalReport, type EvalResult, type EvaluateOptions, type ExtractCall, type ExtractTool, GOOGLE_MODELS, type GradeContext, type GradeResult, type Grader, type ImageBlock, type ImageMediaType, type ImageSource, InMemoryScope, type Inference, type InitOptions, InvalidRequestError, type KeyMap, type KeyOptions, type KeyResolver, type MemoryScope, type MessageDeltaEvent, type MessageStartEvent, type MessageStopEvent, type ModelKind, type ModelSpec, NS, OPENAI_MODELS, OdlaAIError, type OdlaDbClient, type OdlaDbKeyResolverOptions, OdlaDbMemory, type OdlaDbMemoryOptions, type OdlaEntityRef, type OdlaLookup, type OdlaOp, type OdlaQuery, type OdlaScalar, type OracleContentBlock, type OracleErrorEvent, type OracleErrorShape, type OracleErrorType, type OracleEvent, type OracleMessage, type OracleRequest, type OracleResponse, type OracleRole, type OracleStopReason, type OracleTool, type OracleUsage, type PersistRunOptions, type Persona, type Provider, ProviderError, type ProviderFactory, type ProviderId, type ProvisionAgentAppOptions, type ProvisionContext, type ProvisionedApp, type QueryRunsOptions, RateLimitError, type ResponseFormat, type SearchCall, type Skill, type StoppedReason, TaintError, type TaintLabel, type TaintSet, type TextBlock, type ThinkingBlock, type ThinkingMode, type ToolCallRecord, type ToolChoice, type ToolContext, type ToolDef, type ToolHandler, type ToolOutput, type ToolResultBlock, type ToolUseBlock, addUsage, assertSinkAcceptsTaint, blocksOf, buildCatalog, chatCapabilities, clearProviderCache, composeSkills, createApp, emptyUsage, entityCrudSkill, evaluate, exactMatch, extractText, extractToolUses, generateLlmsTxt, getProvider, includes, init, isAudioBlock, isDocumentBlock, isImageBlock, isTextBlock, isThinkingBlock, isToolResultBlock, isToolUseBlock, llmJudge, looksLikeKey, mintAppKey, normalizeError, odlaDbKeyResolver, persistRun, providersInCatalog, provisionAgentApp, pushAgentSchema, putSecret, queryRuns, redact, registerProvider, resolveModel, runAgent, structuredMatch, taint, toOracleTool, validateRequest };
1043
+ export { AGENT_SCHEMA, ANTHROPIC_MODELS, type AgentRun, type AgentRunInput, type Ai, type AudioBlock, type AudioMediaType, type AudioSource, AuthError, type Capabilities, CapabilityError, type Catalog, type ChatInput, type ComposedPersona, ConfigError, type ContentBlockDelta, type ContentBlockDeltaEvent, type ContentBlockStartEvent, type ContentBlockStopEvent, ContextWindowError, DEFAULT_CATALOG, DEFAULT_SECRET_NAMES, type DocumentBlock, type DocumentSource, type Effort, type EntityCrudSkillOptions, type EvalCase, type EvalReport, type EvalResult, type EvaluateOptions, type ExtractCall, type ExtractTool, GOOGLE_MODELS, type GradeContext, type GradeResult, type Grader, type ImageBlock, type ImageMediaType, type ImageSource, InMemoryScope, type Inference, type InitFromPlatformOptions, type InitOptions, InvalidRequestError, type KeyMap, type KeyOptions, type KeyResolver, type MemoryScope, type MessageDeltaEvent, type MessageStartEvent, type MessageStopEvent, type ModelKind, type ModelSpec, NS, OPENAI_MODELS, OdlaAIError, type OdlaDbClient, type OdlaDbKeyResolverOptions, OdlaDbMemory, type OdlaDbMemoryOptions, type OdlaEntityRef, type OdlaLookup, type OdlaOp, type OdlaQuery, type OdlaScalar, type OracleContentBlock, type OracleErrorEvent, type OracleErrorShape, type OracleErrorType, type OracleEvent, type OracleMessage, type OracleRequest, type OracleResponse, type OracleRole, type OracleStopReason, type OracleTool, type OracleUsage, type PersistRunOptions, type Persona, type PlatformAi, type Provider, ProviderError, type ProviderFactory, type ProviderId, type ProvisionAgentAppOptions, type ProvisionContext, type ProvisionedApp, type QueryRunsOptions, RateLimitError, type ResponseFormat, type SearchCall, type Skill, type StoppedReason, TaintError, type TaintLabel, type TaintSet, type TextBlock, type ThinkingBlock, type ThinkingMode, type ToolCallRecord, type ToolChoice, type ToolContext, type ToolDef, type ToolHandler, type ToolOutput, type ToolResultBlock, type ToolUseBlock, addUsage, assertSinkAcceptsTaint, blocksOf, buildCatalog, chatCapabilities, clearPlatformAiCache, clearProviderCache, composeSkills, createApp, emptyUsage, entityCrudSkill, evaluate, exactMatch, extractText, extractToolUses, generateLlmsTxt, getProvider, includes, init, initFromPlatform, isAudioBlock, isDocumentBlock, isImageBlock, isTextBlock, isThinkingBlock, isToolResultBlock, isToolUseBlock, llmJudge, looksLikeKey, mintAppKey, normalizeError, odlaDbKeyResolver, persistRun, providersInCatalog, provisionAgentApp, pushAgentSchema, putSecret, queryRuns, redact, registerProvider, resolveModel, runAgent, structuredMatch, taint, toOracleTool, validateRequest };
package/dist/index.d.ts CHANGED
@@ -924,6 +924,38 @@ interface OdlaDbKeyResolverOptions {
924
924
  * odla-ai throws its own ConfigError) rather than surfacing odla-db's 404. */
925
925
  declare function odlaDbKeyResolver(db: OdlaDbClient, opts?: OdlaDbKeyResolverOptions): KeyResolver;
926
926
 
927
+ interface InitFromPlatformOptions {
928
+ /** Platform base URL (e.g. "https://odla.ai"). */
929
+ platform: string;
930
+ /** The REGISTRY app id — not the env's tenant id. */
931
+ appId: string;
932
+ /** Which environment's config to use ("dev", "prod", …). */
933
+ env: string;
934
+ /** The app's own tenant client — key reads go through `db.secrets.get`. */
935
+ db: OdlaDbClient;
936
+ /** Transport override (tests / non-global fetch). */
937
+ fetch?: typeof fetch;
938
+ /** public-config cache TTL in ms (default 60s; 0 disables caching). */
939
+ ttlMs?: number;
940
+ /** Extra init() options (catalog, injected providers, …). `resolveKey` is
941
+ * owned by this helper; `defaultModel` is used only when the platform
942
+ * config sets no model. */
943
+ initOptions?: Omit<InitOptions, "resolveKey">;
944
+ }
945
+ interface PlatformAi {
946
+ ai: Ai;
947
+ provider: ProviderId;
948
+ model?: string;
949
+ }
950
+ /** Drop all cached platform configs (tests, or to force an immediate refetch). */
951
+ declare function clearPlatformAiCache(): void;
952
+ /** Build an `Ai` from the app's platform registration: provider/model from
953
+ * public-config (cached ~60s), API key from the tenant's odla-db secrets at
954
+ * call time. On cache refresh the existing `Ai` (and its warm key-resolver
955
+ * cache) is reused unless provider/model changed. Note the first call's
956
+ * `db`/`initOptions` are captured for the cache entry's lifetime. */
957
+ declare function initFromPlatform(opts: InitFromPlatformOptions): Promise<PlatformAi>;
958
+
927
959
  type FetchLike = typeof fetch;
928
960
  interface ProvisionContext {
929
961
  /** The odla-db worker URL — always human-supplied, never hardcoded. */
@@ -946,6 +978,15 @@ declare function pushAgentSchema(ctx: {
946
978
  }, appId: string, appKey: string): Promise<void>;
947
979
  /** Store a named secret (operator/dev token). Value must be a non-`$` string. */
948
980
  declare function putSecret(ctx: ProvisionContext, appId: string, name: string, value: string): Promise<void>;
981
+ /** Per-namespace CEL permission rules (odla-db is DEFAULT-DENY: end-users can't
982
+ * read or write any namespace until its rules are set; the app key bypasses
983
+ * rules, so agent-only apps work without them). */
984
+ type AppRules = Record<string, {
985
+ view?: string;
986
+ create?: string;
987
+ update?: string;
988
+ delete?: string;
989
+ }>;
949
990
  interface ProvisionAgentAppOptions extends ProvisionContext {
950
991
  appId?: string;
951
992
  appName?: string;
@@ -955,6 +996,11 @@ interface ProvisionAgentAppOptions extends ProvisionContext {
955
996
  secretName?: (provider: ProviderId) => string;
956
997
  /** Push the agent schema (default true). */
957
998
  pushSchema?: boolean;
999
+ /** Permission rules to install. odla-db is default-deny, so set these for any
1000
+ * namespace end-users should reach (e.g. own-rows-only:
1001
+ * `{ todos: { view: "auth.id == data.ownerId", create: "auth.id == data.ownerId" } }`).
1002
+ * Omit for agent-only apps — the app key bypasses rules. */
1003
+ rules?: AppRules;
958
1004
  }
959
1005
  interface ProvisionedApp {
960
1006
  appId: string;
@@ -994,4 +1040,4 @@ interface EntityCrudSkillOptions {
994
1040
  /** Build a CRUD Skill for one odla-db entity. */
995
1041
  declare function entityCrudSkill(opts: EntityCrudSkillOptions): Skill;
996
1042
 
997
- export { AGENT_SCHEMA, ANTHROPIC_MODELS, type AgentRun, type AgentRunInput, type Ai, type AudioBlock, type AudioMediaType, type AudioSource, AuthError, type Capabilities, CapabilityError, type Catalog, type ChatInput, type ComposedPersona, ConfigError, type ContentBlockDelta, type ContentBlockDeltaEvent, type ContentBlockStartEvent, type ContentBlockStopEvent, ContextWindowError, DEFAULT_CATALOG, DEFAULT_SECRET_NAMES, type DocumentBlock, type DocumentSource, type Effort, type EntityCrudSkillOptions, type EvalCase, type EvalReport, type EvalResult, type EvaluateOptions, type ExtractCall, type ExtractTool, GOOGLE_MODELS, type GradeContext, type GradeResult, type Grader, type ImageBlock, type ImageMediaType, type ImageSource, InMemoryScope, type Inference, type InitOptions, InvalidRequestError, type KeyMap, type KeyOptions, type KeyResolver, type MemoryScope, type MessageDeltaEvent, type MessageStartEvent, type MessageStopEvent, type ModelKind, type ModelSpec, NS, OPENAI_MODELS, OdlaAIError, type OdlaDbClient, type OdlaDbKeyResolverOptions, OdlaDbMemory, type OdlaDbMemoryOptions, type OdlaEntityRef, type OdlaLookup, type OdlaOp, type OdlaQuery, type OdlaScalar, type OracleContentBlock, type OracleErrorEvent, type OracleErrorShape, type OracleErrorType, type OracleEvent, type OracleMessage, type OracleRequest, type OracleResponse, type OracleRole, type OracleStopReason, type OracleTool, type OracleUsage, type PersistRunOptions, type Persona, type Provider, ProviderError, type ProviderFactory, type ProviderId, type ProvisionAgentAppOptions, type ProvisionContext, type ProvisionedApp, type QueryRunsOptions, RateLimitError, type ResponseFormat, type SearchCall, type Skill, type StoppedReason, TaintError, type TaintLabel, type TaintSet, type TextBlock, type ThinkingBlock, type ThinkingMode, type ToolCallRecord, type ToolChoice, type ToolContext, type ToolDef, type ToolHandler, type ToolOutput, type ToolResultBlock, type ToolUseBlock, addUsage, assertSinkAcceptsTaint, blocksOf, buildCatalog, chatCapabilities, clearProviderCache, composeSkills, createApp, emptyUsage, entityCrudSkill, evaluate, exactMatch, extractText, extractToolUses, generateLlmsTxt, getProvider, includes, init, isAudioBlock, isDocumentBlock, isImageBlock, isTextBlock, isThinkingBlock, isToolResultBlock, isToolUseBlock, llmJudge, looksLikeKey, mintAppKey, normalizeError, odlaDbKeyResolver, persistRun, providersInCatalog, provisionAgentApp, pushAgentSchema, putSecret, queryRuns, redact, registerProvider, resolveModel, runAgent, structuredMatch, taint, toOracleTool, validateRequest };
1043
+ export { AGENT_SCHEMA, ANTHROPIC_MODELS, type AgentRun, type AgentRunInput, type Ai, type AudioBlock, type AudioMediaType, type AudioSource, AuthError, type Capabilities, CapabilityError, type Catalog, type ChatInput, type ComposedPersona, ConfigError, type ContentBlockDelta, type ContentBlockDeltaEvent, type ContentBlockStartEvent, type ContentBlockStopEvent, ContextWindowError, DEFAULT_CATALOG, DEFAULT_SECRET_NAMES, type DocumentBlock, type DocumentSource, type Effort, type EntityCrudSkillOptions, type EvalCase, type EvalReport, type EvalResult, type EvaluateOptions, type ExtractCall, type ExtractTool, GOOGLE_MODELS, type GradeContext, type GradeResult, type Grader, type ImageBlock, type ImageMediaType, type ImageSource, InMemoryScope, type Inference, type InitFromPlatformOptions, type InitOptions, InvalidRequestError, type KeyMap, type KeyOptions, type KeyResolver, type MemoryScope, type MessageDeltaEvent, type MessageStartEvent, type MessageStopEvent, type ModelKind, type ModelSpec, NS, OPENAI_MODELS, OdlaAIError, type OdlaDbClient, type OdlaDbKeyResolverOptions, OdlaDbMemory, type OdlaDbMemoryOptions, type OdlaEntityRef, type OdlaLookup, type OdlaOp, type OdlaQuery, type OdlaScalar, type OracleContentBlock, type OracleErrorEvent, type OracleErrorShape, type OracleErrorType, type OracleEvent, type OracleMessage, type OracleRequest, type OracleResponse, type OracleRole, type OracleStopReason, type OracleTool, type OracleUsage, type PersistRunOptions, type Persona, type PlatformAi, type Provider, ProviderError, type ProviderFactory, type ProviderId, type ProvisionAgentAppOptions, type ProvisionContext, type ProvisionedApp, type QueryRunsOptions, RateLimitError, type ResponseFormat, type SearchCall, type Skill, type StoppedReason, TaintError, type TaintLabel, type TaintSet, type TextBlock, type ThinkingBlock, type ThinkingMode, type ToolCallRecord, type ToolChoice, type ToolContext, type ToolDef, type ToolHandler, type ToolOutput, type ToolResultBlock, type ToolUseBlock, addUsage, assertSinkAcceptsTaint, blocksOf, buildCatalog, chatCapabilities, clearPlatformAiCache, clearProviderCache, composeSkills, createApp, emptyUsage, entityCrudSkill, evaluate, exactMatch, extractText, extractToolUses, generateLlmsTxt, getProvider, includes, init, initFromPlatform, isAudioBlock, isDocumentBlock, isImageBlock, isTextBlock, isThinkingBlock, isToolResultBlock, isToolUseBlock, llmJudge, looksLikeKey, mintAppKey, normalizeError, odlaDbKeyResolver, persistRun, providersInCatalog, provisionAgentApp, pushAgentSchema, putSecret, queryRuns, redact, registerProvider, resolveModel, runAgent, structuredMatch, taint, toOracleTool, validateRequest };
package/dist/index.js CHANGED
@@ -139,6 +139,22 @@ var OPENAI_MODELS = [
139
139
  capabilities: chatCapabilities({ effort: true }),
140
140
  contextWindow: 4e5
141
141
  },
142
+ {
143
+ id: "gpt-5-nano",
144
+ provider: "openai",
145
+ nativeId: "gpt-5-nano",
146
+ capabilities: chatCapabilities({ effort: true }),
147
+ contextWindow: 4e5
148
+ },
149
+ {
150
+ // Search-tuned chat model: the adapter sends web_search_options when a
151
+ // request sets webSearch. No reasoning-effort knob.
152
+ id: "gpt-5-search-api",
153
+ provider: "openai",
154
+ nativeId: "gpt-5-search-api",
155
+ capabilities: chatCapabilities({ webSearch: true }),
156
+ contextWindow: 4e5
157
+ },
142
158
  {
143
159
  // Non-reasoning multimodal chat (image input, no effort knob).
144
160
  id: "gpt-4o",
@@ -239,7 +255,7 @@ async function getProvider(id, apiKey) {
239
255
  break;
240
256
  }
241
257
  case "openai": {
242
- const { OpenAIProvider } = await import("./openai-Y3OAONAU.js");
258
+ const { OpenAIProvider } = await import("./openai-XZRXMKCC.js");
243
259
  provider = new OpenAIProvider(apiKey);
244
260
  break;
245
261
  }
@@ -456,6 +472,27 @@ Secrets are read-only from the app key (odlaDbKeyResolver \u2192 db.secrets.get)
456
472
  are WRITTEN only with the operator/dev token via provisionAgentApp/putSecret. Keys
457
473
  are AES-GCM-encrypted at rest in odla-db and never returned to browser clients.
458
474
 
475
+ ## Platform wiring (odla.ai apps registry)
476
+
477
+ Apps registered on the odla platform get LLM access as an APP SETTING \u2014
478
+ never put provider keys in your Worker's wrangler vars/secrets. Provider +
479
+ default model are per-environment registry config (set in Studio's AI card
480
+ or \`@odla-ai/apps\` setAi); the API key lives in the platform vault (the
481
+ env's odla-db tenant secrets, write-only for operators). One call reads both:
482
+
483
+ import { initFromPlatform } from "@odla-ai/ai";
484
+ import { init as odlaInit } from "@odla-ai/db";
485
+
486
+ const db = odlaInit({ appId: TENANT, adminToken: env.ODLA_API_KEY, endpoint: "https://db.odla.ai" });
487
+ const { ai, provider, model } = await initFromPlatform({
488
+ platform: "https://odla.ai", appId: APP_ID, env: "prod", db });
489
+ // ai.chat / ai.stream / ai.extract \u2014 provider/model from public-config
490
+ // (cached ~60s, so Studio changes apply without redeploys); the key is
491
+ // fetched from the vault at call time via odlaDbKeyResolver.
492
+
493
+ The only secret the Worker carries is its odla-db app key. Rotating the LLM
494
+ key = writing the secret again; switching provider/model = a Studio edit.
495
+
459
496
  ## Errors
460
497
 
461
498
  Every error is an OdlaAIError subclass with a stable \`code\` \u2014 branch on err.code:
@@ -883,15 +920,15 @@ var DEFAULT_SECRET_NAMES = {
883
920
  function odlaDbKeyResolver(db, opts = {}) {
884
921
  const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
885
922
  const useCache = opts.cache ?? true;
886
- const cache2 = /* @__PURE__ */ new Map();
923
+ const cache3 = /* @__PURE__ */ new Map();
887
924
  return async (provider) => {
888
925
  if (useCache) {
889
- const hit = cache2.get(provider);
926
+ const hit = cache3.get(provider);
890
927
  if (hit !== void 0) return hit;
891
928
  }
892
929
  try {
893
930
  const key = await db.secrets.get(nameFor(provider));
894
- if (useCache) cache2.set(provider, key);
931
+ if (useCache) cache3.set(provider, key);
895
932
  return key;
896
933
  } catch {
897
934
  return void 0;
@@ -899,6 +936,51 @@ function odlaDbKeyResolver(db, opts = {}) {
899
936
  };
900
937
  }
901
938
 
939
+ // src/store/platform.ts
940
+ var cache2 = /* @__PURE__ */ new Map();
941
+ function clearPlatformAiCache() {
942
+ cache2.clear();
943
+ }
944
+ async function fetchAiConfig(opts) {
945
+ const doFetch = opts.fetch ?? fetch;
946
+ const base = opts.platform.replace(/\/$/, "");
947
+ const url = `${base}/registry/apps/${encodeURIComponent(opts.appId)}/public-config?env=${encodeURIComponent(opts.env)}`;
948
+ const res = await doFetch(url);
949
+ if (!res.ok) throw new ProviderError(`platform public-config fetch failed: ${res.status} for ${opts.appId}/${opts.env}`);
950
+ const cfg = await res.json();
951
+ const ai = cfg.ai;
952
+ if (!ai || typeof ai.provider !== "string") {
953
+ throw new ConfigError(
954
+ `ai service is not enabled/configured for "${opts.appId}" (${opts.env}) \u2014 enable it in Studio or via @odla-ai/apps setAi(appId, env, { provider }).`
955
+ );
956
+ }
957
+ if (!(ai.provider in DEFAULT_SECRET_NAMES)) {
958
+ throw new ConfigError(`platform ai config names unknown provider "${ai.provider}" for "${opts.appId}" (${opts.env}).`);
959
+ }
960
+ const model = typeof ai.model === "string" && ai.model ? ai.model : void 0;
961
+ return { provider: ai.provider, ...model ? { model } : {} };
962
+ }
963
+ async function initFromPlatform(opts) {
964
+ const ttl = opts.ttlMs ?? 6e4;
965
+ const key = `${opts.platform}|${opts.appId}|${opts.env}`;
966
+ const now = Date.now();
967
+ const hit = ttl > 0 ? cache2.get(key) : void 0;
968
+ if (hit && hit.expiresAt > now) return hit.value;
969
+ const { provider, model } = await fetchAiConfig(opts);
970
+ if (hit && hit.value.provider === provider && hit.value.model === model) {
971
+ hit.expiresAt = now + ttl;
972
+ return hit.value;
973
+ }
974
+ const ai = init({
975
+ ...opts.initOptions,
976
+ resolveKey: odlaDbKeyResolver(opts.db),
977
+ defaultModel: model ?? opts.initOptions?.defaultModel
978
+ });
979
+ const value = { ai, provider, ...model ? { model } : {} };
980
+ if (ttl > 0) cache2.set(key, { value, expiresAt: now + ttl });
981
+ return value;
982
+ }
983
+
902
984
  // src/store/provision.ts
903
985
  async function post(ctx, path, body) {
904
986
  const f = ctx.fetch ?? fetch;
@@ -941,6 +1023,9 @@ async function pushAgentSchema(ctx, appId, appKey) {
941
1023
  async function putSecret(ctx, appId, name, value) {
942
1024
  await post(ctx, `/admin/apps/${encodeURIComponent(appId)}/secrets`, { name, value });
943
1025
  }
1026
+ async function putRules(ctx, appId, rules) {
1027
+ await post(ctx, `/app/${encodeURIComponent(appId)}/admin/rules`, rules);
1028
+ }
944
1029
  async function provisionAgentApp(opts) {
945
1030
  const ctx = { endpoint: opts.endpoint, token: opts.token, fetch: opts.fetch };
946
1031
  const appId = await createApp(ctx, { appId: opts.appId, name: opts.appName });
@@ -948,6 +1033,9 @@ async function provisionAgentApp(opts) {
948
1033
  if (opts.pushSchema !== false) {
949
1034
  await pushAgentSchema({ endpoint: opts.endpoint, fetch: opts.fetch }, appId, appKey);
950
1035
  }
1036
+ if (opts.rules) {
1037
+ await putRules(ctx, appId, opts.rules);
1038
+ }
951
1039
  const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
952
1040
  for (const [provider, value] of Object.entries(opts.providerKeys ?? {})) {
953
1041
  if (!value) continue;
@@ -1102,6 +1190,7 @@ export {
1102
1190
  blocksOf,
1103
1191
  buildCatalog,
1104
1192
  chatCapabilities,
1193
+ clearPlatformAiCache,
1105
1194
  clearProviderCache,
1106
1195
  composeSkills,
1107
1196
  createApp,
@@ -1115,6 +1204,7 @@ export {
1115
1204
  getProvider,
1116
1205
  includes,
1117
1206
  init,
1207
+ initFromPlatform,
1118
1208
  isAudioBlock,
1119
1209
  isDocumentBlock,
1120
1210
  isImageBlock,