@odla-ai/ai 0.1.4 → 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/README.md +27 -0
- package/dist/index.cjs +72 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +71 -3
- package/dist/index.js.map +1 -1
- package/llms.txt +21 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -252,6 +252,33 @@ is a *privileged* action (operator or `odla_dev_` token, via `provisionAgentApp`
|
|
|
252
252
|
never returned to browser clients. `OdlaDbMemory` persists conversation turns and
|
|
253
253
|
`persistRun` writes run traces, both as natural-key upserts (idempotent on retry).
|
|
254
254
|
|
|
255
|
+
### Platform apps (odla.ai)
|
|
256
|
+
|
|
257
|
+
For apps registered on the odla platform, LLM access is an **app setting**:
|
|
258
|
+
provider + default model live in the registry per environment (Studio's AI
|
|
259
|
+
card, or `@odla-ai/apps` `setAi`), and the API key lives in the platform
|
|
260
|
+
vault — the env's odla-db tenant secrets. `initFromPlatform` reads both:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { initFromPlatform } from "@odla-ai/ai";
|
|
264
|
+
import { init as odlaInit } from "@odla-ai/db";
|
|
265
|
+
|
|
266
|
+
const db = odlaInit({ appId: TENANT, adminToken: env.ODLA_API_KEY, endpoint: "https://db.odla.ai" });
|
|
267
|
+
const { ai, provider, model } = await initFromPlatform({
|
|
268
|
+
platform: "https://odla.ai",
|
|
269
|
+
appId: APP_ID, // the registry app id, not the tenant
|
|
270
|
+
env: "prod",
|
|
271
|
+
db,
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The config comes from the anonymous `public-config` endpoint and is cached
|
|
276
|
+
(~60s by default), so switching provider or model in Studio takes effect
|
|
277
|
+
without a redeploy; the key resolves from the vault at call time, so rotation
|
|
278
|
+
is just writing the secret again. **Never put provider keys in an app
|
|
279
|
+
Worker's wrangler vars/secrets** — the worker's only secret is its odla-db
|
|
280
|
+
app key.
|
|
281
|
+
|
|
255
282
|
## Models & capabilities
|
|
256
283
|
|
|
257
284
|
The catalog maps a canonical model id → provider, native id, and capabilities.
|
package/dist/index.cjs
CHANGED
|
@@ -909,6 +909,7 @@ __export(index_exports, {
|
|
|
909
909
|
blocksOf: () => blocksOf,
|
|
910
910
|
buildCatalog: () => buildCatalog,
|
|
911
911
|
chatCapabilities: () => chatCapabilities,
|
|
912
|
+
clearPlatformAiCache: () => clearPlatformAiCache,
|
|
912
913
|
clearProviderCache: () => clearProviderCache,
|
|
913
914
|
composeSkills: () => composeSkills,
|
|
914
915
|
createApp: () => createApp,
|
|
@@ -922,6 +923,7 @@ __export(index_exports, {
|
|
|
922
923
|
getProvider: () => getProvider,
|
|
923
924
|
includes: () => includes,
|
|
924
925
|
init: () => init,
|
|
926
|
+
initFromPlatform: () => initFromPlatform,
|
|
925
927
|
isAudioBlock: () => isAudioBlock,
|
|
926
928
|
isDocumentBlock: () => isDocumentBlock,
|
|
927
929
|
isImageBlock: () => isImageBlock,
|
|
@@ -1412,6 +1414,27 @@ Secrets are read-only from the app key (odlaDbKeyResolver \u2192 db.secrets.get)
|
|
|
1412
1414
|
are WRITTEN only with the operator/dev token via provisionAgentApp/putSecret. Keys
|
|
1413
1415
|
are AES-GCM-encrypted at rest in odla-db and never returned to browser clients.
|
|
1414
1416
|
|
|
1417
|
+
## Platform wiring (odla.ai apps registry)
|
|
1418
|
+
|
|
1419
|
+
Apps registered on the odla platform get LLM access as an APP SETTING \u2014
|
|
1420
|
+
never put provider keys in your Worker's wrangler vars/secrets. Provider +
|
|
1421
|
+
default model are per-environment registry config (set in Studio's AI card
|
|
1422
|
+
or \`@odla-ai/apps\` setAi); the API key lives in the platform vault (the
|
|
1423
|
+
env's odla-db tenant secrets, write-only for operators). One call reads both:
|
|
1424
|
+
|
|
1425
|
+
import { initFromPlatform } from "@odla-ai/ai";
|
|
1426
|
+
import { init as odlaInit } from "@odla-ai/db";
|
|
1427
|
+
|
|
1428
|
+
const db = odlaInit({ appId: TENANT, adminToken: env.ODLA_API_KEY, endpoint: "https://db.odla.ai" });
|
|
1429
|
+
const { ai, provider, model } = await initFromPlatform({
|
|
1430
|
+
platform: "https://odla.ai", appId: APP_ID, env: "prod", db });
|
|
1431
|
+
// ai.chat / ai.stream / ai.extract \u2014 provider/model from public-config
|
|
1432
|
+
// (cached ~60s, so Studio changes apply without redeploys); the key is
|
|
1433
|
+
// fetched from the vault at call time via odlaDbKeyResolver.
|
|
1434
|
+
|
|
1435
|
+
The only secret the Worker carries is its odla-db app key. Rotating the LLM
|
|
1436
|
+
key = writing the secret again; switching provider/model = a Studio edit.
|
|
1437
|
+
|
|
1415
1438
|
## Errors
|
|
1416
1439
|
|
|
1417
1440
|
Every error is an OdlaAIError subclass with a stable \`code\` \u2014 branch on err.code:
|
|
@@ -1848,15 +1871,15 @@ var DEFAULT_SECRET_NAMES = {
|
|
|
1848
1871
|
function odlaDbKeyResolver(db, opts = {}) {
|
|
1849
1872
|
const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
|
|
1850
1873
|
const useCache = opts.cache ?? true;
|
|
1851
|
-
const
|
|
1874
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
1852
1875
|
return async (provider) => {
|
|
1853
1876
|
if (useCache) {
|
|
1854
|
-
const hit =
|
|
1877
|
+
const hit = cache3.get(provider);
|
|
1855
1878
|
if (hit !== void 0) return hit;
|
|
1856
1879
|
}
|
|
1857
1880
|
try {
|
|
1858
1881
|
const key = await db.secrets.get(nameFor(provider));
|
|
1859
|
-
if (useCache)
|
|
1882
|
+
if (useCache) cache3.set(provider, key);
|
|
1860
1883
|
return key;
|
|
1861
1884
|
} catch {
|
|
1862
1885
|
return void 0;
|
|
@@ -1864,6 +1887,52 @@ function odlaDbKeyResolver(db, opts = {}) {
|
|
|
1864
1887
|
};
|
|
1865
1888
|
}
|
|
1866
1889
|
|
|
1890
|
+
// src/store/platform.ts
|
|
1891
|
+
init_shape();
|
|
1892
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
1893
|
+
function clearPlatformAiCache() {
|
|
1894
|
+
cache2.clear();
|
|
1895
|
+
}
|
|
1896
|
+
async function fetchAiConfig(opts) {
|
|
1897
|
+
const doFetch = opts.fetch ?? fetch;
|
|
1898
|
+
const base = opts.platform.replace(/\/$/, "");
|
|
1899
|
+
const url = `${base}/registry/apps/${encodeURIComponent(opts.appId)}/public-config?env=${encodeURIComponent(opts.env)}`;
|
|
1900
|
+
const res = await doFetch(url);
|
|
1901
|
+
if (!res.ok) throw new ProviderError(`platform public-config fetch failed: ${res.status} for ${opts.appId}/${opts.env}`);
|
|
1902
|
+
const cfg = await res.json();
|
|
1903
|
+
const ai = cfg.ai;
|
|
1904
|
+
if (!ai || typeof ai.provider !== "string") {
|
|
1905
|
+
throw new ConfigError(
|
|
1906
|
+
`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 }).`
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
if (!(ai.provider in DEFAULT_SECRET_NAMES)) {
|
|
1910
|
+
throw new ConfigError(`platform ai config names unknown provider "${ai.provider}" for "${opts.appId}" (${opts.env}).`);
|
|
1911
|
+
}
|
|
1912
|
+
const model = typeof ai.model === "string" && ai.model ? ai.model : void 0;
|
|
1913
|
+
return { provider: ai.provider, ...model ? { model } : {} };
|
|
1914
|
+
}
|
|
1915
|
+
async function initFromPlatform(opts) {
|
|
1916
|
+
const ttl = opts.ttlMs ?? 6e4;
|
|
1917
|
+
const key = `${opts.platform}|${opts.appId}|${opts.env}`;
|
|
1918
|
+
const now = Date.now();
|
|
1919
|
+
const hit = ttl > 0 ? cache2.get(key) : void 0;
|
|
1920
|
+
if (hit && hit.expiresAt > now) return hit.value;
|
|
1921
|
+
const { provider, model } = await fetchAiConfig(opts);
|
|
1922
|
+
if (hit && hit.value.provider === provider && hit.value.model === model) {
|
|
1923
|
+
hit.expiresAt = now + ttl;
|
|
1924
|
+
return hit.value;
|
|
1925
|
+
}
|
|
1926
|
+
const ai = init({
|
|
1927
|
+
...opts.initOptions,
|
|
1928
|
+
resolveKey: odlaDbKeyResolver(opts.db),
|
|
1929
|
+
defaultModel: model ?? opts.initOptions?.defaultModel
|
|
1930
|
+
});
|
|
1931
|
+
const value = { ai, provider, ...model ? { model } : {} };
|
|
1932
|
+
if (ttl > 0) cache2.set(key, { value, expiresAt: now + ttl });
|
|
1933
|
+
return value;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1867
1936
|
// src/store/provision.ts
|
|
1868
1937
|
init_shape();
|
|
1869
1938
|
async function post(ctx, path, body) {
|