@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/README.md +27 -0
- package/dist/index.cjs +102 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -1
- package/dist/index.d.ts +47 -1
- package/dist/index.js +94 -4
- package/dist/index.js.map +1 -1
- package/dist/{openai-Y3OAONAU.js → openai-XZRXMKCC.js} +2 -1
- package/dist/openai-XZRXMKCC.js.map +1 -0
- package/llms.txt +23 -0
- package/package.json +2 -2
- package/dist/openai-Y3OAONAU.js.map +0 -1
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
|
@@ -5,8 +5,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
|
|
8
|
+
var __esm = (fn, res, err) => function __init() {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
10
15
|
};
|
|
11
16
|
var __export = (target, all) => {
|
|
12
17
|
for (var name in all)
|
|
@@ -438,6 +443,7 @@ function buildParams2(spec, req, stream) {
|
|
|
438
443
|
json_schema: { name: req.responseFormat.name ?? "response", schema: req.responseFormat.schema }
|
|
439
444
|
};
|
|
440
445
|
}
|
|
446
|
+
if (req.webSearch) params.web_search_options = { search_context_size: "medium" };
|
|
441
447
|
if (stream) {
|
|
442
448
|
params.stream = true;
|
|
443
449
|
params.stream_options = { include_usage: true };
|
|
@@ -903,6 +909,7 @@ __export(index_exports, {
|
|
|
903
909
|
blocksOf: () => blocksOf,
|
|
904
910
|
buildCatalog: () => buildCatalog,
|
|
905
911
|
chatCapabilities: () => chatCapabilities,
|
|
912
|
+
clearPlatformAiCache: () => clearPlatformAiCache,
|
|
906
913
|
clearProviderCache: () => clearProviderCache,
|
|
907
914
|
composeSkills: () => composeSkills,
|
|
908
915
|
createApp: () => createApp,
|
|
@@ -916,6 +923,7 @@ __export(index_exports, {
|
|
|
916
923
|
getProvider: () => getProvider,
|
|
917
924
|
includes: () => includes,
|
|
918
925
|
init: () => init,
|
|
926
|
+
initFromPlatform: () => initFromPlatform,
|
|
919
927
|
isAudioBlock: () => isAudioBlock,
|
|
920
928
|
isDocumentBlock: () => isDocumentBlock,
|
|
921
929
|
isImageBlock: () => isImageBlock,
|
|
@@ -1069,6 +1077,22 @@ var OPENAI_MODELS = [
|
|
|
1069
1077
|
capabilities: chatCapabilities({ effort: true }),
|
|
1070
1078
|
contextWindow: 4e5
|
|
1071
1079
|
},
|
|
1080
|
+
{
|
|
1081
|
+
id: "gpt-5-nano",
|
|
1082
|
+
provider: "openai",
|
|
1083
|
+
nativeId: "gpt-5-nano",
|
|
1084
|
+
capabilities: chatCapabilities({ effort: true }),
|
|
1085
|
+
contextWindow: 4e5
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
// Search-tuned chat model: the adapter sends web_search_options when a
|
|
1089
|
+
// request sets webSearch. No reasoning-effort knob.
|
|
1090
|
+
id: "gpt-5-search-api",
|
|
1091
|
+
provider: "openai",
|
|
1092
|
+
nativeId: "gpt-5-search-api",
|
|
1093
|
+
capabilities: chatCapabilities({ webSearch: true }),
|
|
1094
|
+
contextWindow: 4e5
|
|
1095
|
+
},
|
|
1072
1096
|
{
|
|
1073
1097
|
// Non-reasoning multimodal chat (image input, no effort knob).
|
|
1074
1098
|
id: "gpt-4o",
|
|
@@ -1390,6 +1414,27 @@ Secrets are read-only from the app key (odlaDbKeyResolver \u2192 db.secrets.get)
|
|
|
1390
1414
|
are WRITTEN only with the operator/dev token via provisionAgentApp/putSecret. Keys
|
|
1391
1415
|
are AES-GCM-encrypted at rest in odla-db and never returned to browser clients.
|
|
1392
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
|
+
|
|
1393
1438
|
## Errors
|
|
1394
1439
|
|
|
1395
1440
|
Every error is an OdlaAIError subclass with a stable \`code\` \u2014 branch on err.code:
|
|
@@ -1826,15 +1871,15 @@ var DEFAULT_SECRET_NAMES = {
|
|
|
1826
1871
|
function odlaDbKeyResolver(db, opts = {}) {
|
|
1827
1872
|
const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
|
|
1828
1873
|
const useCache = opts.cache ?? true;
|
|
1829
|
-
const
|
|
1874
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
1830
1875
|
return async (provider) => {
|
|
1831
1876
|
if (useCache) {
|
|
1832
|
-
const hit =
|
|
1877
|
+
const hit = cache3.get(provider);
|
|
1833
1878
|
if (hit !== void 0) return hit;
|
|
1834
1879
|
}
|
|
1835
1880
|
try {
|
|
1836
1881
|
const key = await db.secrets.get(nameFor(provider));
|
|
1837
|
-
if (useCache)
|
|
1882
|
+
if (useCache) cache3.set(provider, key);
|
|
1838
1883
|
return key;
|
|
1839
1884
|
} catch {
|
|
1840
1885
|
return void 0;
|
|
@@ -1842,6 +1887,52 @@ function odlaDbKeyResolver(db, opts = {}) {
|
|
|
1842
1887
|
};
|
|
1843
1888
|
}
|
|
1844
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
|
+
|
|
1845
1936
|
// src/store/provision.ts
|
|
1846
1937
|
init_shape();
|
|
1847
1938
|
async function post(ctx, path, body) {
|
|
@@ -1885,6 +1976,9 @@ async function pushAgentSchema(ctx, appId, appKey) {
|
|
|
1885
1976
|
async function putSecret(ctx, appId, name, value) {
|
|
1886
1977
|
await post(ctx, `/admin/apps/${encodeURIComponent(appId)}/secrets`, { name, value });
|
|
1887
1978
|
}
|
|
1979
|
+
async function putRules(ctx, appId, rules) {
|
|
1980
|
+
await post(ctx, `/app/${encodeURIComponent(appId)}/admin/rules`, rules);
|
|
1981
|
+
}
|
|
1888
1982
|
async function provisionAgentApp(opts) {
|
|
1889
1983
|
const ctx = { endpoint: opts.endpoint, token: opts.token, fetch: opts.fetch };
|
|
1890
1984
|
const appId = await createApp(ctx, { appId: opts.appId, name: opts.appName });
|
|
@@ -1892,6 +1986,9 @@ async function provisionAgentApp(opts) {
|
|
|
1892
1986
|
if (opts.pushSchema !== false) {
|
|
1893
1987
|
await pushAgentSchema({ endpoint: opts.endpoint, fetch: opts.fetch }, appId, appKey);
|
|
1894
1988
|
}
|
|
1989
|
+
if (opts.rules) {
|
|
1990
|
+
await putRules(ctx, appId, opts.rules);
|
|
1991
|
+
}
|
|
1895
1992
|
const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
|
|
1896
1993
|
for (const [provider, value] of Object.entries(opts.providerKeys ?? {})) {
|
|
1897
1994
|
if (!value) continue;
|