okengine 0.11.1 → 0.11.2
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/package.json +1 -1
- package/site/content/docs/elements/ai.mdx +22 -1
- package/site/content/docs/elements/store.mdx +3 -1
- package/site/content/docs/elements/vault.mdx +19 -11
- package/site/content/docs/get-started/installation.mdx +7 -1
- package/site/content/docs/recipes/llama-cpp.mdx +10 -9
- package/site/content/docs/reference/cli.md +6 -2
- package/site/content/docs/reference/environment-variables.mdx +9 -9
- package/src/cli/ai-setup/ai-setup.test.ts +3 -1
- package/src/cli/ai-setup/apply.ts +61 -1
- package/src/cli/ask-seed.test.ts +4 -3
- package/src/cli/ask-seed.ts +5 -6
- package/src/cli/client-add.test.ts +2 -1
- package/src/cli/dev.test.ts +116 -0
- package/src/cli/dev.ts +107 -18
- package/src/cli/project-state.test.ts +50 -0
- package/src/cli/project-state.ts +123 -0
- package/src/cli/vault-cmd.test.ts +47 -18
- package/src/cli/vault-cmd.ts +2 -1
- package/src/compiler/extract.ts +12 -1
- package/src/console/server/console.test.ts +3 -1
- package/src/console/server/operator-db.test.ts +48 -17
- package/src/console/server/operator-db.ts +5 -1
- package/src/docker/derive.ts +24 -3
- package/src/docker/docker.test.ts +4 -2
- package/src/docker/index.ts +1 -0
- package/src/docker/recipes/index.ts +1 -0
- package/src/docker/recipes/llama-cpp.ts +20 -4
- package/src/drivers/ai-openai-compatible.ts +15 -3
- package/src/drivers/vault-builtin.test.ts +50 -42
- package/src/elements/ai/declare.ts +73 -3
- package/src/elements/ai/errors.test.ts +35 -0
- package/src/elements/ai/errors.ts +139 -0
- package/src/elements/ai/eval.ts +26 -1
- package/src/elements/ai/runtime.ts +140 -80
- package/src/elements/ai/tools.test.ts +1 -1
- package/src/elements/ai.test.ts +99 -2
- package/src/elements/ai.ts +11 -1
- package/src/elements/index.ts +2 -0
- package/src/elements/store/index-boot.test.ts +23 -6
- package/src/elements/store/resource.test.ts +38 -19
- package/src/elements/store/sql-session.test.ts +55 -58
- package/src/elements/vault/builtin-adapter.test.ts +115 -58
- package/src/elements/vault/builtin-adapter.ts +241 -47
- package/src/elements/vault/chaos-child.ts +424 -0
- package/src/elements/vault/chaos.test.ts +651 -0
- package/src/elements/vault/resilience.ts +6 -1
- package/src/elements/vault/security-checklist.test.ts +10 -8
- package/src/elements/vault/storage.ts +130 -27
- package/src/elements/vault/test-helpers.ts +368 -0
- package/src/elements/vault.ts +6 -0
- package/src/index.ts +2 -0
- package/src/kernel/app.ts +83 -10
- package/src/kernel/auto-registry.test.ts +52 -1
- package/src/kernel/element-registries.ts +19 -4
- package/src/kernel/errors.ts +3 -3
- package/src/kernel/fx.test.ts +25 -0
- package/src/kernel/fx.ts +4 -1
- package/src/manifest/types.ts +4 -0
- package/src/test/create-test-app.ts +16 -11
- package/src/test/reset-element-registries.ts +17 -9
package/src/kernel/app.ts
CHANGED
|
@@ -81,6 +81,10 @@ import { releaseInstanceLeases } from "./graceful-shutdown.ts";
|
|
|
81
81
|
import type { JournalRuntime } from "./boot-bind/journal.ts";
|
|
82
82
|
import { listBindings, resetBindings, type Binding } from "./on.ts";
|
|
83
83
|
import {
|
|
84
|
+
aiAgentRegistry,
|
|
85
|
+
aiEmbedRegistry,
|
|
86
|
+
aiModelRegistry,
|
|
87
|
+
aiPromptRegistry,
|
|
84
88
|
channelTemplateRegistry,
|
|
85
89
|
requiredEnvRegistry,
|
|
86
90
|
secretRegistry,
|
|
@@ -461,6 +465,45 @@ function mergeUnique<T>(explicit: readonly T[] | undefined, fromRegistry: readon
|
|
|
461
465
|
return out;
|
|
462
466
|
}
|
|
463
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Merge explicit `oke({ ai })` with auto-drained AI decls. Returns
|
|
470
|
+
* `undefined` when neither side contributes — a defined empty object would
|
|
471
|
+
* flip {@link resolveElementNeeds}'s `ai` flag for every app.
|
|
472
|
+
*
|
|
473
|
+
* @param explicit - Hand-passed AI boot options
|
|
474
|
+
* @param fromRegistry - Models / prompts / embeds / agents from declare registries
|
|
475
|
+
*/
|
|
476
|
+
function mergeAiOptions(
|
|
477
|
+
explicit: BootOptions["ai"] | undefined,
|
|
478
|
+
fromRegistry: {
|
|
479
|
+
readonly models: NonNullable<BootOptions["ai"]>["models"];
|
|
480
|
+
readonly prompts: NonNullable<BootOptions["ai"]>["prompts"];
|
|
481
|
+
readonly embeds: NonNullable<BootOptions["ai"]>["embeds"];
|
|
482
|
+
readonly agents: NonNullable<BootOptions["ai"]>["agents"];
|
|
483
|
+
},
|
|
484
|
+
): BootOptions["ai"] | undefined {
|
|
485
|
+
const models = mergeUnique(explicit?.models, fromRegistry.models ?? []);
|
|
486
|
+
const prompts = mergeUnique(explicit?.prompts, fromRegistry.prompts ?? []);
|
|
487
|
+
const embeds = mergeUnique(explicit?.embeds, fromRegistry.embeds ?? []);
|
|
488
|
+
const agents = mergeUnique(explicit?.agents, fromRegistry.agents ?? []);
|
|
489
|
+
if (
|
|
490
|
+
explicit === undefined &&
|
|
491
|
+
models.length === 0 &&
|
|
492
|
+
prompts.length === 0 &&
|
|
493
|
+
embeds.length === 0 &&
|
|
494
|
+
agents.length === 0
|
|
495
|
+
) {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
...(explicit ?? {}),
|
|
500
|
+
...(models.length > 0 ? { models } : {}),
|
|
501
|
+
...(prompts.length > 0 ? { prompts } : {}),
|
|
502
|
+
...(embeds.length > 0 ? { embeds } : {}),
|
|
503
|
+
...(agents.length > 0 ? { agents } : {}),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
464
507
|
/**
|
|
465
508
|
* Create an application. Adopts bindings registered via {@link on}.
|
|
466
509
|
*
|
|
@@ -476,20 +519,35 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
476
519
|
if (registry === "consume") resetBindings();
|
|
477
520
|
|
|
478
521
|
// Same registry mode drains store.sql/store.files, vault.secret, signal(),
|
|
479
|
-
//
|
|
480
|
-
// mirror `on`'s trigger drain
|
|
481
|
-
// Explicit `options.stores` /
|
|
482
|
-
//
|
|
483
|
-
//
|
|
522
|
+
// channel.<medium>().template(), and ai.model/prompt/embed/agent —
|
|
523
|
+
// module-evaluation registries that mirror `on`'s trigger drain
|
|
524
|
+
// (`listBindings`/`resetBindings` above). Explicit `options.stores` /
|
|
525
|
+
// `secrets` / `signals` / `channel.templates` / `ai` are additive, never
|
|
526
|
+
// silently ignored — deduped by reference so an explicitly-passed decl
|
|
527
|
+
// that is also in the registry is not doubled.
|
|
484
528
|
const registrySnapshot =
|
|
485
529
|
registry === "ignore"
|
|
486
|
-
? {
|
|
530
|
+
? {
|
|
531
|
+
stores: [],
|
|
532
|
+
secrets: [],
|
|
533
|
+
requiredEnv: [],
|
|
534
|
+
signals: [],
|
|
535
|
+
channelTemplates: [],
|
|
536
|
+
aiModels: [],
|
|
537
|
+
aiPrompts: [],
|
|
538
|
+
aiEmbeds: [],
|
|
539
|
+
aiAgents: [],
|
|
540
|
+
}
|
|
487
541
|
: {
|
|
488
542
|
stores: storeRegistry.slice(),
|
|
489
543
|
secrets: secretRegistry.slice(),
|
|
490
544
|
requiredEnv: requiredEnvRegistry.slice(),
|
|
491
545
|
signals: signalRegistry.slice(),
|
|
492
546
|
channelTemplates: channelTemplateRegistry.slice(),
|
|
547
|
+
aiModels: aiModelRegistry.slice(),
|
|
548
|
+
aiPrompts: aiPromptRegistry.slice(),
|
|
549
|
+
aiEmbeds: aiEmbedRegistry.slice(),
|
|
550
|
+
aiAgents: aiAgentRegistry.slice(),
|
|
493
551
|
};
|
|
494
552
|
if (registry === "consume") {
|
|
495
553
|
storeRegistry.length = 0;
|
|
@@ -497,6 +555,10 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
497
555
|
requiredEnvRegistry.length = 0;
|
|
498
556
|
signalRegistry.length = 0;
|
|
499
557
|
channelTemplateRegistry.length = 0;
|
|
558
|
+
aiModelRegistry.length = 0;
|
|
559
|
+
aiPromptRegistry.length = 0;
|
|
560
|
+
aiEmbedRegistry.length = 0;
|
|
561
|
+
aiAgentRegistry.length = 0;
|
|
500
562
|
}
|
|
501
563
|
const effectiveStores = mergeUnique(options.stores, registrySnapshot.stores);
|
|
502
564
|
const effectiveSecrets = mergeUnique(options.secrets, registrySnapshot.secrets);
|
|
@@ -523,6 +585,14 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
523
585
|
...(options.channel ?? {}),
|
|
524
586
|
templates: mergeUnique(options.channel?.templates, registrySnapshot.channelTemplates),
|
|
525
587
|
};
|
|
588
|
+
// Same undefined-when-empty rule for AI — a bare `{}` would force the AI
|
|
589
|
+
// runtime open even when the app never declared models / prompts.
|
|
590
|
+
const effectiveAi = mergeAiOptions(options.ai, {
|
|
591
|
+
models: registrySnapshot.aiModels,
|
|
592
|
+
prompts: registrySnapshot.aiPrompts,
|
|
593
|
+
embeds: registrySnapshot.aiEmbeds,
|
|
594
|
+
agents: registrySnapshot.aiAgents,
|
|
595
|
+
});
|
|
526
596
|
|
|
527
597
|
// Resolve Gate bag early so auth HTTP Bindings join `adopted` + the router
|
|
528
598
|
// before posture audit (same ensureBoot → doBoot path — never a side channel).
|
|
@@ -531,8 +601,11 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
531
601
|
env: options.env,
|
|
532
602
|
});
|
|
533
603
|
|
|
534
|
-
/** Retained for test harness / boot merges. */
|
|
535
|
-
const $options =
|
|
604
|
+
/** Retained for test harness / boot merges (includes auto-drained AI decls). */
|
|
605
|
+
const $options: OkeOptions =
|
|
606
|
+
effectiveAi === undefined || options.ai === effectiveAi
|
|
607
|
+
? options
|
|
608
|
+
: { ...options, ai: effectiveAi };
|
|
536
609
|
|
|
537
610
|
const appHooks: HookMap = {};
|
|
538
611
|
const unitHooks = new Map<string, HookMap>();
|
|
@@ -828,7 +901,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
828
901
|
clocks: overrides?.clocks ?? options.clocks,
|
|
829
902
|
stores: overrides?.stores ?? effectiveStores,
|
|
830
903
|
channel: overrides?.channel ?? effectiveChannel,
|
|
831
|
-
ai: overrides?.ai ??
|
|
904
|
+
ai: overrides?.ai ?? effectiveAi,
|
|
832
905
|
runs: overrides?.runs ?? options.runs,
|
|
833
906
|
bindings: adopted,
|
|
834
907
|
flows: [...flowsByName.values()],
|
|
@@ -907,7 +980,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
907
980
|
(overrides?.config ?? options.config)?.i18n?.default ??
|
|
908
981
|
"en",
|
|
909
982
|
},
|
|
910
|
-
ai: overrides?.ai ??
|
|
983
|
+
ai: overrides?.ai ?? effectiveAi,
|
|
911
984
|
runs: overrides?.runs ?? options.runs,
|
|
912
985
|
now: overrides?.now ?? options.fx?.now,
|
|
913
986
|
instanceId: overrides?.instanceId,
|
|
@@ -20,7 +20,7 @@ import { flow } from "./flow.ts";
|
|
|
20
20
|
import { on, resetBindings } from "./on.ts";
|
|
21
21
|
import { http } from "./triggers.ts";
|
|
22
22
|
|
|
23
|
-
describe("oke() auto-registry — stores/secrets/signals/channel.templates", () => {
|
|
23
|
+
describe("oke() auto-registry — stores/secrets/signals/channel.templates/ai", () => {
|
|
24
24
|
test("before/after: zero explicit arrays boots identically to the explicit-array form", async () => {
|
|
25
25
|
resetBindings();
|
|
26
26
|
|
|
@@ -195,4 +195,55 @@ describe("oke() auto-registry — stores/secrets/signals/channel.templates", ()
|
|
|
195
195
|
expect(appB.bootResult?.signal).toBeUndefined();
|
|
196
196
|
expect(appB.bootResult?.channel?.templates.has("leak-template") ?? false).toBe(false);
|
|
197
197
|
});
|
|
198
|
+
|
|
199
|
+
test("ai.model / .prompt auto-drain into oke options (no explicit oke({ ai }))", async () => {
|
|
200
|
+
const { ai } = await import("../elements/ai/declare.ts");
|
|
201
|
+
const { mockAiDriver } = await import("../drivers/ai-mock.ts");
|
|
202
|
+
const { createAiRuntime } = await import("../elements/ai/runtime.ts");
|
|
203
|
+
|
|
204
|
+
const smart = ai.model("smart", { provider: "mock" });
|
|
205
|
+
const summarizeNote = smart.prompt("summarize-note", {
|
|
206
|
+
version: 1,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const ping = flow("ai.ping", {
|
|
210
|
+
effects: { asks: ["summarize-note"] },
|
|
211
|
+
do: async (_input, fx) => fx.ask("summarize-note", { title: "t", body: "b" }),
|
|
212
|
+
});
|
|
213
|
+
on(http.post("/ai-ping"), ping);
|
|
214
|
+
|
|
215
|
+
const app = oke({
|
|
216
|
+
name: "ai-auto-registry",
|
|
217
|
+
autoBoot: false,
|
|
218
|
+
startScheduler: false,
|
|
219
|
+
gate: { unguardedHttp: "allow" },
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(app.$options.ai?.models?.some((m) => m.name === "smart")).toBe(true);
|
|
223
|
+
expect(app.$options.ai?.prompts?.some((p) => p.name === "summarize-note")).toBe(true);
|
|
224
|
+
|
|
225
|
+
const runtime = createAiRuntime({
|
|
226
|
+
models: [smart],
|
|
227
|
+
prompts: [summarizeNote],
|
|
228
|
+
defaultDriver: {
|
|
229
|
+
id: "mock",
|
|
230
|
+
open: () =>
|
|
231
|
+
mockAiDriver.open({
|
|
232
|
+
mockResponses: { "*": { summary: "one-line summary" } },
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
await app.boot({
|
|
237
|
+
env: "test",
|
|
238
|
+
unguardedHttp: "allow",
|
|
239
|
+
elements: { ai: runtime },
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const res = await app.fetch(
|
|
243
|
+
new Request("http://127.0.0.1/ai-ping", { method: "POST", body: "{}" }),
|
|
244
|
+
);
|
|
245
|
+
expect(res.status).toBe(200);
|
|
246
|
+
const json = (await res.json()) as { data: { summary: string } };
|
|
247
|
+
expect(json.data.summary).toBe("one-line summary");
|
|
248
|
+
});
|
|
198
249
|
});
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module-evaluation registries for `store.sql` / `store.files`, `vault.secret`,
|
|
3
|
-
* `vault.env.required`, `signal()`,
|
|
4
|
-
*
|
|
5
|
-
* (mirrors `on.ts`'s
|
|
3
|
+
* `vault.env.required`, `signal()`, `channel.<medium>().template()`, and
|
|
4
|
+
* `ai.model` / `.prompt` / `ai.embed` / `ai.agent` — the plain arrays behind
|
|
5
|
+
* each declare module's `listX()` / `resetX()` pair (mirrors `on.ts`'s
|
|
6
|
+
* trigger-drain `bindings` array).
|
|
6
7
|
*
|
|
7
8
|
* Lives here, not in the declare modules themselves, so {@link oke} can read
|
|
8
9
|
* every registry with one lightweight import instead of statically pulling
|
|
9
|
-
* in all
|
|
10
|
+
* in all element `declare.ts` modules (and every unrelated export they
|
|
10
11
|
* carry) into every app's bundle — see the `oke()` Store-only bundle-size
|
|
11
12
|
* budget in `boot.test.ts`. Only type imports cross back to the declare
|
|
12
13
|
* modules, so this file has zero runtime dependencies of its own.
|
|
13
14
|
*/
|
|
14
15
|
|
|
16
|
+
import type {
|
|
17
|
+
AiAgentDecl,
|
|
18
|
+
AiEmbedDecl,
|
|
19
|
+
AiModelDecl,
|
|
20
|
+
AiPromptDecl,
|
|
21
|
+
} from "../elements/ai/declare.ts";
|
|
15
22
|
import type { StoreDecl } from "../elements/store/declare.ts";
|
|
16
23
|
import type { VaultSecretDecl } from "../elements/vault/declare.ts";
|
|
17
24
|
import type { SignalDecl } from "../elements/signal/declare.ts";
|
|
@@ -27,3 +34,11 @@ export const requiredEnvRegistry: string[] = [];
|
|
|
27
34
|
export const signalRegistry: SignalDecl[] = [];
|
|
28
35
|
/** Medium-binder `.template()` declarations since the last reset. */
|
|
29
36
|
export const channelTemplateRegistry: ChannelTemplateDecl[] = [];
|
|
37
|
+
/** `ai.model` declarations since the last reset. */
|
|
38
|
+
export const aiModelRegistry: AiModelDecl[] = [];
|
|
39
|
+
/** `model.prompt` declarations since the last reset. */
|
|
40
|
+
export const aiPromptRegistry: AiPromptDecl[] = [];
|
|
41
|
+
/** `ai.embed` declarations since the last reset. */
|
|
42
|
+
export const aiEmbedRegistry: AiEmbedDecl[] = [];
|
|
43
|
+
/** `ai.agent` declarations since the last reset. */
|
|
44
|
+
export const aiAgentRegistry: AiAgentDecl[] = [];
|
package/src/kernel/errors.ts
CHANGED
|
@@ -144,19 +144,19 @@ export const OKE_ERRORS = {
|
|
|
144
144
|
},
|
|
145
145
|
/**
|
|
146
146
|
* Flow has no declared `effects` and no Manifest-derived effects were
|
|
147
|
-
* available to stamp at boot (
|
|
147
|
+
* available to stamp at boot (dev+compose / prod — never a silent open token).
|
|
148
148
|
*/
|
|
149
149
|
NO_EFFECTS_DECLARED: {
|
|
150
150
|
code: 1008,
|
|
151
151
|
cause: 'Flow "{flow}" has no declared effects and no Manifest to derive them from.',
|
|
152
152
|
fix:
|
|
153
153
|
"Add explicit `effects` to this flow, or boot with a Manifest (`oke build`) / " +
|
|
154
|
-
"`rootDir` so effects can be derived.
|
|
154
|
+
"`rootDir` so effects can be derived. dev+compose/prod refuse an open capability token.",
|
|
155
155
|
},
|
|
156
156
|
/**
|
|
157
157
|
* A `src/flows/<unit>` folder exists on disk but no adopted flow carries
|
|
158
158
|
* that unit — the generated `.adopt()` barrel (`src/flows/generated.ts`)
|
|
159
|
-
* is stale or was hand-edited.
|
|
159
|
+
* is stale or was hand-edited. dev+compose / prod — never a silently-incomplete
|
|
160
160
|
* route table in a deploy-shaped environment.
|
|
161
161
|
*/
|
|
162
162
|
ADOPT_BARREL_STALE: {
|
package/src/kernel/fx.test.ts
CHANGED
|
@@ -93,6 +93,31 @@ describe("fx — effect ledger", () => {
|
|
|
93
93
|
},
|
|
94
94
|
ledger,
|
|
95
95
|
secrets: { STRIPE_KEY: "sk_test" },
|
|
96
|
+
aiRuntime: {
|
|
97
|
+
prompts: new Map(),
|
|
98
|
+
agents: new Map(),
|
|
99
|
+
embeds: new Map(),
|
|
100
|
+
autoCacheDisabled: true,
|
|
101
|
+
journalingForced: false,
|
|
102
|
+
denials: [],
|
|
103
|
+
agentRuns: [],
|
|
104
|
+
journal: [],
|
|
105
|
+
async ask() {
|
|
106
|
+
return { ok: true };
|
|
107
|
+
},
|
|
108
|
+
async runAgent() {
|
|
109
|
+
return { ok: true, steps: 0, denials: [], output: {} };
|
|
110
|
+
},
|
|
111
|
+
async *stream() {
|
|
112
|
+
/* no tokens */
|
|
113
|
+
},
|
|
114
|
+
async search() {
|
|
115
|
+
return [];
|
|
116
|
+
},
|
|
117
|
+
async embed() {
|
|
118
|
+
return { vectors: [] };
|
|
119
|
+
},
|
|
120
|
+
} as never,
|
|
96
121
|
});
|
|
97
122
|
|
|
98
123
|
await stub(fx, "sql:bookings").get("x");
|
package/src/kernel/fx.ts
CHANGED
|
@@ -297,6 +297,8 @@ export interface FxDeliverOtpOptions {
|
|
|
297
297
|
/** Options for {@link Fx.ask}. */
|
|
298
298
|
export interface FxAskOptions {
|
|
299
299
|
readonly via?: readonly NamedRef[];
|
|
300
|
+
/** Per-call deadline — overrides prompt `timeout` (`"30s"` or ms). */
|
|
301
|
+
readonly timeout?: string | number;
|
|
300
302
|
/** Flow refs offered as tools — each model call goes through `fx.call`. */
|
|
301
303
|
readonly tools?: readonly NamedRef[];
|
|
302
304
|
/** Bound on tool invocations (default 6). */
|
|
@@ -1587,13 +1589,14 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1587
1589
|
if (options.aiRuntime) {
|
|
1588
1590
|
return options.aiRuntime.ask(name, input, {
|
|
1589
1591
|
via: opts?.via?.map(resolveName),
|
|
1592
|
+
...(opts?.timeout !== undefined ? { timeout: opts.timeout } : {}),
|
|
1590
1593
|
tools: opts?.tools?.map(resolveName),
|
|
1591
1594
|
maxSteps: opts?.maxSteps,
|
|
1592
1595
|
// Host fx.call — same capability / ledger / Runs path as any call.
|
|
1593
1596
|
callTool: (tool, toolInput) => fx.call(tool, toolInput),
|
|
1594
1597
|
});
|
|
1595
1598
|
}
|
|
1596
|
-
|
|
1599
|
+
throw new Error(`fx.ask: AI runtime is not configured for prompt "${name}"`);
|
|
1597
1600
|
});
|
|
1598
1601
|
},
|
|
1599
1602
|
search(embed, query, opts) {
|
package/src/manifest/types.ts
CHANGED
|
@@ -300,6 +300,10 @@ export interface AiPrompt {
|
|
|
300
300
|
version?: number;
|
|
301
301
|
evals?: string;
|
|
302
302
|
budget?: AiBudget;
|
|
303
|
+
/** Ordered recovery chain of logical model names. */
|
|
304
|
+
via?: string[];
|
|
305
|
+
/** Per-command deadline (`"30s"` or milliseconds). */
|
|
306
|
+
timeout?: string | number;
|
|
303
307
|
model?: string;
|
|
304
308
|
in?: JsonSchema;
|
|
305
309
|
out?: JsonSchema;
|
|
@@ -14,9 +14,6 @@
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
18
|
-
import { tmpdir } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
20
17
|
import {
|
|
21
18
|
createMockAiDriver,
|
|
22
19
|
createChannelInbox,
|
|
@@ -215,15 +212,26 @@ export async function createTestApp<App extends OkeApp>(
|
|
|
215
212
|
return out;
|
|
216
213
|
};
|
|
217
214
|
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
215
|
+
// Harness contract is in-process memory SQL (same as vault/channel/ai/runs).
|
|
216
|
+
// Global `STORE_SQL_DEFAULTS.test` is PGLite for real app boots — do not inherit
|
|
217
|
+
// that here, or every insert pays cold WASM+pgvector init (~seconds). Dialects
|
|
218
|
+
// that need PGLite pass `boot.config.drivers.store.sql` explicitly.
|
|
219
|
+
const bootConfig = options.boot?.config;
|
|
220
|
+
const config = {
|
|
221
|
+
...(bootConfig ?? {}),
|
|
222
|
+
drivers: {
|
|
223
|
+
...(bootConfig?.drivers ?? {}),
|
|
224
|
+
store: {
|
|
225
|
+
...(bootConfig?.drivers?.store ?? {}),
|
|
226
|
+
sql: bootConfig?.drivers?.store?.sql ?? { test: "memory" },
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
};
|
|
223
230
|
|
|
224
231
|
await app.boot({
|
|
225
232
|
...(options.boot ?? {}),
|
|
226
233
|
env: "test",
|
|
234
|
+
config,
|
|
227
235
|
// Test-only opt-out (honoured because env is "test" below). Production
|
|
228
236
|
// boots never skip posture via this flag.
|
|
229
237
|
unguardedHttp: options.boot?.unguardedHttp ?? appOpts.gate?.unguardedHttp ?? "allow",
|
|
@@ -331,9 +339,6 @@ export async function createTestApp<App extends OkeApp>(
|
|
|
331
339
|
},
|
|
332
340
|
async close() {
|
|
333
341
|
await app.bootResult?.close();
|
|
334
|
-
if (prevPgliteUrl === undefined) delete process.env.OKE_PGLITE_URL;
|
|
335
|
-
else process.env.OKE_PGLITE_URL = prevPgliteUrl;
|
|
336
|
-
await rm(pgliteDir, { recursive: true, force: true }).catch(() => undefined);
|
|
337
342
|
},
|
|
338
343
|
};
|
|
339
344
|
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global safety net for the `store.sql` / `store.files` / `vault.secret` /
|
|
3
|
-
* `signal()` / `channel.<medium>().template()`
|
|
4
|
-
* (`src/kernel/element-registries.ts`).
|
|
3
|
+
* `signal()` / `channel.<medium>().template()` / `ai.model`·prompt·embed·agent
|
|
4
|
+
* auto-registries (`src/kernel/element-registries.ts`).
|
|
5
5
|
*
|
|
6
6
|
* Unlike `on()` bindings — created almost exclusively to wire a real app —
|
|
7
|
-
* these
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* these factories are called throughout the suite as bare value constructors,
|
|
8
|
+
* completely unrelated to booting an app (hundreds of call sites across
|
|
9
|
+
* `src/elements/*.test.ts`). Since the registries are plain module-level
|
|
10
|
+
* arrays shared by every test file in one `bun test` process, leaving cleanup
|
|
11
|
+
* to per-file discipline (the convention `on.ts` relies on — see
|
|
12
|
+
* `resetBindings()` / `registry: "ignore"`) would let stray decls from one
|
|
13
|
+
* file silently reach a default-registry `oke()` call in a completely
|
|
14
14
|
* unrelated file. Reset after every test, globally, via `bunfig.toml`
|
|
15
15
|
* `[test].preload`.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { afterEach } from "bun:test";
|
|
19
19
|
import {
|
|
20
|
+
aiAgentRegistry,
|
|
21
|
+
aiEmbedRegistry,
|
|
22
|
+
aiModelRegistry,
|
|
23
|
+
aiPromptRegistry,
|
|
20
24
|
channelTemplateRegistry,
|
|
21
25
|
requiredEnvRegistry,
|
|
22
26
|
secretRegistry,
|
|
@@ -30,4 +34,8 @@ afterEach(() => {
|
|
|
30
34
|
requiredEnvRegistry.length = 0;
|
|
31
35
|
signalRegistry.length = 0;
|
|
32
36
|
channelTemplateRegistry.length = 0;
|
|
37
|
+
aiModelRegistry.length = 0;
|
|
38
|
+
aiPromptRegistry.length = 0;
|
|
39
|
+
aiEmbedRegistry.length = 0;
|
|
40
|
+
aiAgentRegistry.length = 0;
|
|
33
41
|
});
|