@tangle-network/agent-app 0.1.3 → 0.1.4

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.
@@ -0,0 +1,208 @@
1
+ import { KnowledgeResearchLoopContext, AddSourceTextInput, SourceAdapter, RunKnowledgeResearchLoopOptions, KnowledgeResearchLoopResult } from '@tangle-network/agent-knowledge';
2
+ import { KnowledgeSourceSpec, AgentKnowledgeConfig } from '../config/index.js';
3
+ import '../knowledge/index.js';
4
+ import '@tangle-network/agent-eval';
5
+ import '../model-BOP69mVu.js';
6
+
7
+ /**
8
+ * `@tangle-network/agent-app/knowledge-loop` — wire the declarative
9
+ * `AgentKnowledgeConfig` to a running, source-grounded, eval-gated knowledge
10
+ * acquisition loop.
11
+ *
12
+ * This module does NOT implement a loop. `@tangle-network/agent-knowledge`
13
+ * already ships `runKnowledgeResearchLoop` — the source-grounded,
14
+ * propose-don't-apply primitive (sources become immutable records first; only
15
+ * accepted `---FILE: knowledge/...---` write blocks are applied; lint +
16
+ * validation + optional readiness gate every iteration). It is pluggable on two
17
+ * seams: a `SourceAdapter[]` (how raw bytes/text become curated source records —
18
+ * text by default, audio/video/image adapters added by the consumer) and a
19
+ * `step` decider (the per-iteration policy: an agentic judge, a sandbox run, or
20
+ * a deterministic gate — the loop deliberately bakes none).
21
+ *
22
+ * `createKnowledgeLoop(config.knowledge, deps)` is the thin mapper between the
23
+ * two:
24
+ *
25
+ * - `config.knowledge.sources` → adapter selection. The text adapter is the
26
+ * default; `deps.adapters` is the multimodal seam (prepend an audio/video/
27
+ * image `SourceAdapter` and the loop ingests that media). agent-app bakes no
28
+ * media handler — it's a parameter.
29
+ * - `config.knowledge.loop` → `runKnowledgeResearchLoop` options. `goal` maps
30
+ * to the loop goal, `minConfidence` to the gate threshold, `freshness` is
31
+ * threaded to the decider so a per-source-freshness policy can read it.
32
+ * - `deps.decide` → the pluggable gate. DEFAULT is a reviewer policy
33
+ * (agent-knowledge's propose-don't-apply posture): a candidate carrying
34
+ * confidence below `minConfidence` is gated OUT (its proposal text is
35
+ * dropped — sources are still recorded, because grounding is never the thing
36
+ * we gate); at/above threshold it is accepted and the loop applies the write
37
+ * blocks. Swap in an agentic judge or a sandbox run by passing your own
38
+ * `decide`.
39
+ * - `deps.driver` → the agent-runtime turn driver (this repo's `../runtime`
40
+ * `runAppToolLoop` seam, or any compatible driver) the decider invokes for
41
+ * the loop's agent turns. Optional; a deterministic / sandbox decider needs
42
+ * no model turns and omits it.
43
+ *
44
+ * Layering: agent-knowledge and agent-runtime are PEER dependencies, never
45
+ * bundled. This module imports only TYPES + the loop entry from agent-knowledge
46
+ * and stays substrate-free behind the `decide` / `driver` / `adapters` seams.
47
+ */
48
+
49
+ /**
50
+ * A research candidate the decider evaluates before the loop applies it. This is
51
+ * the propose-don't-apply unit: notes + the sources discovered this iteration +
52
+ * the proposed write blocks, each carrying a confidence the gate scores against
53
+ * the configured `minConfidence`.
54
+ */
55
+ interface KnowledgeCandidate {
56
+ /** Human-readable research transcript for this iteration. */
57
+ notes?: string;
58
+ /**
59
+ * Textual source artifacts to register as immutable sources BEFORE any
60
+ * proposal is applied. Recording these is grounding, not the gated step — a
61
+ * rejected candidate still keeps its sources (so the next iteration is better
62
+ * grounded). The decider may not strip these.
63
+ */
64
+ sourceTexts?: AddSourceTextInput[];
65
+ /** Local files to register as immutable sources (same grounding posture). */
66
+ sourcePaths?: string[];
67
+ /**
68
+ * Safe-write-protocol text (`---FILE: knowledge/...---` blocks). This IS the
69
+ * gated step: the default decider drops it when `confidence < minConfidence`.
70
+ */
71
+ proposalText?: string;
72
+ /**
73
+ * Aggregate confidence in this candidate's proposal, in [0, 1]. The default
74
+ * reviewer gate compares this to `minConfidence`. A candidate with no
75
+ * `proposalText` needs no confidence (nothing is gated).
76
+ */
77
+ confidence?: number;
78
+ /** The researcher's signal that the goal is met; ends the loop. */
79
+ done?: boolean;
80
+ metadata?: Record<string, unknown>;
81
+ }
82
+ /** The verdict a gate returns for one candidate. */
83
+ interface KnowledgeGateVerdict {
84
+ /** Whether the candidate's proposal is accepted (applied) or gated out. */
85
+ accepted: boolean;
86
+ /** Why — surfaced in the decision metadata for audit. */
87
+ reason: string;
88
+ /** The confidence the gate scored (echoed for telemetry). */
89
+ confidence: number;
90
+ /** The threshold it was scored against. */
91
+ minConfidence: number;
92
+ }
93
+ /**
94
+ * The pluggable acquisition policy. Given the loop context (current index, lint,
95
+ * validation, freshness target, the driver), produce a candidate AND a gate
96
+ * verdict on it. This is the seam an agentic judge or a sandbox run plugs into;
97
+ * the default {@link createReviewerDecider} is a confidence gate.
98
+ */
99
+ interface KnowledgeDecider {
100
+ (input: KnowledgeDeciderInput): Promise<KnowledgeDecision> | KnowledgeDecision;
101
+ }
102
+ interface KnowledgeDeciderInput {
103
+ /** The agent-knowledge loop context for this iteration. */
104
+ context: KnowledgeResearchLoopContext;
105
+ /** The acquisition goal (from `config.loop.goal` or a deps fallback). */
106
+ goal: string;
107
+ /** The confidence threshold a proposal must clear to be applied. */
108
+ minConfidence: number;
109
+ /** The freshness target (`config.loop.freshness`), if set. */
110
+ freshness?: string;
111
+ /** The configured sources, for a decider that fetches/selects among them. */
112
+ sources: KnowledgeSourceSpec[];
113
+ /** The agent-runtime turn driver, if one was supplied to the loop. */
114
+ driver?: KnowledgeLoopDriver;
115
+ }
116
+ interface KnowledgeDecision {
117
+ /** The candidate the policy produced (may be empty to end the loop). */
118
+ candidate: KnowledgeCandidate;
119
+ /** The gate's verdict on the candidate's proposal. */
120
+ verdict: KnowledgeGateVerdict;
121
+ }
122
+ /**
123
+ * The agent-runtime turn driver seam. This is exactly `../runtime`'s
124
+ * `runAppToolLoop` shape (a bounded, awaitable tool-driving turn loop over a
125
+ * model). Typed structurally so a decider can drive the loop's agent turns
126
+ * without this module importing the runtime engine. A deterministic / sandbox
127
+ * decider may omit it.
128
+ */
129
+ interface KnowledgeLoopDriver {
130
+ (opts: {
131
+ systemPrompt: string;
132
+ userMessage: string;
133
+ }): Promise<{
134
+ finalText: string;
135
+ }>;
136
+ }
137
+ interface CreateKnowledgeLoopDeps {
138
+ /**
139
+ * The knowledge-base root the loop reads/writes (an agent-knowledge layout).
140
+ * Required — agent-knowledge owns disk; agent-app owns only the wiring.
141
+ */
142
+ root: string;
143
+ /**
144
+ * The per-iteration policy. Defaults to {@link createReviewerDecider} keyed on
145
+ * the config's `minConfidence`. Pass your own to use an agentic judge or a
146
+ * sandbox run.
147
+ */
148
+ decide?: KnowledgeDecider;
149
+ /**
150
+ * Extra source adapters (audio/video/image/PDF/...). The text adapter is
151
+ * always present as the fallback; these are tried first so a multimodal
152
+ * source is claimed by its adapter. This is the multimodal seam.
153
+ */
154
+ adapters?: SourceAdapter[];
155
+ /** The agent-runtime turn driver for the loop's agent turns. */
156
+ driver?: KnowledgeLoopDriver;
157
+ /**
158
+ * Fallback goal when `config.loop.goal` is unset. agent-knowledge requires a
159
+ * goal; this keeps the loop runnable for a config with only requirement specs.
160
+ */
161
+ defaultGoal?: string;
162
+ /** Default confidence threshold when `config.loop.minConfidence` is unset. */
163
+ defaultMinConfidence?: number;
164
+ /** Max research iterations (forwarded to agent-knowledge). Default 3. */
165
+ maxIterations?: number;
166
+ /** Actor stamped on the loop's knowledge events. */
167
+ actor?: string;
168
+ /** Abort the loop. */
169
+ signal?: AbortSignal;
170
+ /** Per-step hook (forwarded to agent-knowledge's `onStep`). */
171
+ onStep?: RunKnowledgeResearchLoopOptions['onStep'];
172
+ }
173
+ /** The handle `createKnowledgeLoop` returns. */
174
+ interface KnowledgeLoop {
175
+ /** Run the acquisition loop to completion and return the agent-knowledge result. */
176
+ run(): Promise<KnowledgeResearchLoopResult>;
177
+ /** The resolved goal the loop pursues. */
178
+ readonly goal: string;
179
+ /** The resolved confidence gate threshold. */
180
+ readonly minConfidence: number;
181
+ /** The adapters the loop uses (consumer extras first, text last). */
182
+ readonly adapters: SourceAdapter[];
183
+ }
184
+ /**
185
+ * The default gate — agent-knowledge's propose-don't-apply reviewer posture as a
186
+ * confidence threshold. A candidate's `proposalText` is applied only when its
187
+ * `confidence` is at/above `minConfidence`; otherwise the proposal is gated out
188
+ * (sources are still recorded). A candidate with no `proposalText` is trivially
189
+ * accepted (nothing to gate). This is the floor policy; swap in an agentic judge
190
+ * or a sandbox-run decider via `deps.decide` for richer review.
191
+ */
192
+ declare function reviewCandidate(candidate: KnowledgeCandidate, minConfidence: number): KnowledgeGateVerdict;
193
+ /**
194
+ * Wrap a candidate-producing policy in the default reviewer gate. The policy
195
+ * decides WHAT to propose (notes, sources, proposalText, confidence); the gate
196
+ * decides whether the proposal is APPLIED. Use this when you have a proposer but
197
+ * want the standard confidence gate; pass a full {@link KnowledgeDecider} to
198
+ * `deps.decide` to own the gate too.
199
+ */
200
+ declare function createReviewerDecider(propose: (input: KnowledgeDeciderInput) => Promise<KnowledgeCandidate> | KnowledgeCandidate): KnowledgeDecider;
201
+ /**
202
+ * Build a runnable knowledge-acquisition loop from the product's
203
+ * `AgentKnowledgeConfig` and a small set of seams. Maps config → agent-knowledge
204
+ * `runKnowledgeResearchLoop` options; never reimplements the loop.
205
+ */
206
+ declare function createKnowledgeLoop(knowledge: AgentKnowledgeConfig, deps: CreateKnowledgeLoopDeps): KnowledgeLoop;
207
+
208
+ export { type CreateKnowledgeLoopDeps, type KnowledgeCandidate, type KnowledgeDecider, type KnowledgeDeciderInput, type KnowledgeDecision, type KnowledgeGateVerdict, type KnowledgeLoop, type KnowledgeLoopDriver, createKnowledgeLoop, createReviewerDecider, reviewCandidate };
@@ -0,0 +1,11 @@
1
+ import {
2
+ createKnowledgeLoop,
3
+ createReviewerDecider,
4
+ reviewCandidate
5
+ } from "../chunk-EEPJGZJW.js";
6
+ export {
7
+ createKnowledgeLoop,
8
+ createReviewerDecider,
9
+ reviewCandidate
10
+ };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Resolve the model config a Tangle agent's sandbox/runtime runs on.
3
+ *
4
+ * Every Tangle agent product resolves the SAME thing from env: the Tangle Router
5
+ * (OpenAI-compatible, metered at the platform markup against a single
6
+ * `TANGLE_API_KEY`) by default, with a direct-Anthropic BYOK escape hatch. The
7
+ * shape feeds the sandbox SDK's `backend.model`. Lifted here so no product
8
+ * hand-rolls the env parsing + the router default.
9
+ */
10
+ interface TangleModelConfig {
11
+ /** The Tangle Router is OpenAI-compatible → driven via `openai-compat`.
12
+ * `anthropic` is the BYOK escape hatch. */
13
+ provider: 'openai-compat' | 'anthropic';
14
+ model: string;
15
+ apiKey: string;
16
+ baseUrl: string;
17
+ }
18
+ interface ResolveModelOptions {
19
+ /** Env to read (defaults to process.env). */
20
+ env?: Record<string, string | undefined>;
21
+ /** Router base URL default when `TANGLE_ROUTER_BASE_URL` is unset. */
22
+ defaultRouterBaseUrl?: string;
23
+ }
24
+ declare const DEFAULT_TANGLE_ROUTER_BASE_URL = "https://router.tangle.tools/v1";
25
+ /**
26
+ * Resolve the model config from env. DEFAULT path (`MODEL_PROVIDER` unset or
27
+ * `openai-compat`/`tangle-router`/`tcloud`): the Tangle Router, authenticated
28
+ * with `TANGLE_API_KEY`, model from `MODEL_NAME`. BYOK path
29
+ * (`MODEL_PROVIDER=anthropic`): direct Anthropic with `ANTHROPIC_API_KEY` +
30
+ * `ANTHROPIC_BASE_URL`. Throws (fail-loud) on a missing required var so a
31
+ * misconfigured deploy fails at boot, not mid-turn.
32
+ */
33
+ declare function resolveTangleModelConfig(opts?: ResolveModelOptions): TangleModelConfig;
34
+
35
+ export { DEFAULT_TANGLE_ROUTER_BASE_URL as D, type ResolveModelOptions as R, type TangleModelConfig as T, resolveTangleModelConfig as r };
@@ -0,0 +1,244 @@
1
+ import { KeyProvisioner, KeyCrypto, WorkspaceKeyManager, WorkspaceKeyStore } from '../billing/index.js';
2
+ import { KnowledgeStateAccessor } from '../knowledge/index.js';
3
+ import { c as AppToolHandlers } from '../types-CeWor4bQ.js';
4
+ import { KvLike } from '../web/index.js';
5
+ import '@tangle-network/agent-eval';
6
+
7
+ /**
8
+ * `@tangle-network/agent-app/preset-cloudflare` — the batteries-included default
9
+ * stack.
10
+ *
11
+ * Every fleet agent runs the SAME backend: Cloudflare D1 (SQLite) through
12
+ * Drizzle for state, a KV namespace as the artifact vault, AES-GCM field crypto
13
+ * for PII, and per-workspace budget-capped model keys. The other agent-app
14
+ * modules are pure SEAMS — `./tools` needs an `AppToolHandlers`, `./knowledge`
15
+ * needs a `KnowledgeStateAccessor`, `./billing` needs a `WorkspaceKeyStore` +
16
+ * `KeyCrypto`. This module is the ONE implementation of those seams against the
17
+ * house stack, so a consumer that runs D1 + KV stands the whole shell up with
18
+ * config + bindings and ZERO handler code.
19
+ *
20
+ * Layering:
21
+ * - Drizzle is a PEER (the consumer installs `drizzle-orm`, never bundled). The
22
+ * schema is therefore expressed two ways that need no import here: the plain
23
+ * DDL ({@link PRESET_MIGRATION_SQL}) a consumer runs to create the tables, and
24
+ * a {@link createPresetDrizzleSchema} factory that takes the consumer's
25
+ * `drizzle-orm/sqlite-core` builder module and returns the typed tables. The
26
+ * column names in {@link PRESET_TABLES} are the contract the handlers,
27
+ * accessor, and DDL all agree on.
28
+ * - D1 + KV are STRUCTURAL: {@link D1Like} (Cloudflare `D1Database` satisfies it)
29
+ * and `KvLike` from `../web` (Cloudflare `KVNamespace` satisfies it). No
30
+ * `@cloudflare/workers-types` dependency.
31
+ * - Crypto/billing reuse `../crypto` + `../billing` exactly — this only wires
32
+ * them to the D1 key table.
33
+ */
34
+
35
+ /** A prepared, bound D1 statement. */
36
+ interface D1PreparedLike {
37
+ bind(...values: unknown[]): D1PreparedLike;
38
+ first<T = Record<string, unknown>>(colName?: string): Promise<T | null>;
39
+ run(): Promise<unknown>;
40
+ all<T = Record<string, unknown>>(): Promise<{
41
+ results: T[];
42
+ }>;
43
+ }
44
+ /** The D1 surface the preset needs. Cloudflare `D1Database` satisfies it. */
45
+ interface D1Like {
46
+ prepare(query: string): D1PreparedLike;
47
+ }
48
+ /** The preset table + column names — the contract the DDL, Drizzle schema,
49
+ * handlers, and accessor share. Exposed so a consumer can reference a column
50
+ * without a string literal. */
51
+ declare const PRESET_TABLES: {
52
+ readonly proposals: {
53
+ readonly name: "proposals";
54
+ readonly columns: {
55
+ readonly id: "id";
56
+ readonly workspaceId: "workspace_id";
57
+ readonly threadId: "thread_id";
58
+ readonly type: "type";
59
+ readonly title: "title";
60
+ readonly description: "description";
61
+ readonly status: "status";
62
+ readonly createdBy: "created_by";
63
+ readonly createdAt: "created_at";
64
+ };
65
+ };
66
+ readonly threads: {
67
+ readonly name: "threads";
68
+ readonly columns: {
69
+ readonly id: "id";
70
+ readonly workspaceId: "workspace_id";
71
+ readonly title: "title";
72
+ readonly createdAt: "created_at";
73
+ };
74
+ };
75
+ readonly knowledge: {
76
+ readonly name: "knowledge";
77
+ readonly columns: {
78
+ readonly id: "id";
79
+ readonly workspaceId: "workspace_id";
80
+ readonly path: "path";
81
+ readonly kind: "kind";
82
+ readonly label: "label";
83
+ readonly content: "content";
84
+ readonly createdAt: "created_at";
85
+ };
86
+ };
87
+ readonly deadlines: {
88
+ readonly name: "deadlines";
89
+ readonly columns: {
90
+ readonly id: "id";
91
+ readonly workspaceId: "workspace_id";
92
+ readonly threadId: "thread_id";
93
+ readonly title: "title";
94
+ readonly dueDate: "due_date";
95
+ readonly priority: "priority";
96
+ readonly status: "status";
97
+ readonly createdAt: "created_at";
98
+ };
99
+ };
100
+ readonly workspaceKeys: {
101
+ readonly name: "workspace_keys";
102
+ readonly columns: {
103
+ readonly id: "id";
104
+ readonly workspaceId: "workspace_id";
105
+ readonly keyId: "key_id";
106
+ readonly keyEncrypted: "key_encrypted";
107
+ readonly budgetUsd: "budget_usd";
108
+ readonly expiresAt: "expires_at";
109
+ readonly revokedAt: "revoked_at";
110
+ readonly createdAt: "created_at";
111
+ };
112
+ };
113
+ };
114
+ /**
115
+ * Plain DDL for the preset schema — run by a consumer to create the tables with
116
+ * ZERO drizzle (`for (const sql of PRESET_MIGRATION_SQL) await db.prepare(sql).run()`,
117
+ * or paste into a `.sql` migration). One statement per table so D1's
118
+ * single-statement `prepare` accepts each. Matches {@link PRESET_TABLES} exactly.
119
+ */
120
+ declare const PRESET_MIGRATION_SQL: readonly string[];
121
+ /** A chainable column builder — every modifier returns the builder so calls
122
+ * like `.notNull().default('pending')` typecheck. The concrete drizzle builders
123
+ * satisfy this structurally. */
124
+ interface DrizzleColumnLike {
125
+ primaryKey: () => DrizzleColumnLike;
126
+ notNull: () => DrizzleColumnLike;
127
+ default: (v: unknown) => DrizzleColumnLike;
128
+ }
129
+ /** The shape of a `drizzle-orm/sqlite-core` module — the few builders the
130
+ * preset schema uses. The consumer passes the real module; agent-app never
131
+ * imports it (it stays a peer). */
132
+ interface DrizzleSqliteCoreLike {
133
+ sqliteTable: (name: string, columns: Record<string, DrizzleColumnLike>) => unknown;
134
+ text: (name?: string) => DrizzleColumnLike;
135
+ integer: (name?: string, config?: unknown) => DrizzleColumnLike;
136
+ real: (name?: string) => DrizzleColumnLike;
137
+ }
138
+ /**
139
+ * Build the typed Drizzle schema for the preset, given the consumer's
140
+ * `drizzle-orm/sqlite-core` module. Returns one table object per
141
+ * {@link PRESET_TABLES} entry — pass to `drizzle(db, { schema })` for typed
142
+ * queries, or to drizzle-kit for migration generation. agent-app never imports
143
+ * drizzle; the builder module is the seam.
144
+ *
145
+ * ```ts
146
+ * import * as d from 'drizzle-orm/sqlite-core'
147
+ * const schema = createPresetDrizzleSchema(d)
148
+ * ```
149
+ */
150
+ declare function createPresetDrizzleSchema(d: DrizzleSqliteCoreLike): {
151
+ threads: unknown;
152
+ proposals: unknown;
153
+ knowledge: unknown;
154
+ deadlines: unknown;
155
+ workspaceKeys: unknown;
156
+ };
157
+ /** The KV-backed vault. `KvLike` (from `../web`) is the structural KV contract;
158
+ * Cloudflare `KVNamespace` satisfies it. Artifacts are stored under their path. */
159
+ type VaultKv = KvLike;
160
+ interface PresetToolHandlerOptions {
161
+ /** The D1 database (Cloudflare `D1Database` satisfies {@link D1Like}). */
162
+ db: D1Like;
163
+ /** The KV namespace used as the artifact vault. */
164
+ vault: VaultKv;
165
+ /** Id generator. Default `crypto.randomUUID`. Injectable for deterministic tests. */
166
+ newId?: () => string;
167
+ /** Clock (epoch ms). Default `Date.now`. Injectable for deterministic tests. */
168
+ now?: () => number;
169
+ /** Vault path prefix for `render_ui` artifacts. Default `'ui'`. */
170
+ uiPathPrefix?: string;
171
+ /** Vault path prefix for `add_citation` artifacts. Default `'citations'`. */
172
+ citationPathPrefix?: string;
173
+ }
174
+ /**
175
+ * The default {@link AppToolHandlers} for the house stack:
176
+ * - `submit_proposal` → insert a `proposals` row (`status='pending'`), deduped
177
+ * on (workspace, title) so a retried turn doesn't double-queue.
178
+ * - `schedule_followup` → insert a `deadlines` row, deduped on (workspace, title, due_date).
179
+ * - `render_ui` → write the schema JSON as a `ui/<thread>/<slug>.json`
180
+ * vault artifact AND a `knowledge` row pointing at it.
181
+ * - `add_citation` → write the quote as a `citations/<slug>.json` artifact AND
182
+ * a `knowledge` row.
183
+ *
184
+ * Returns the EXACT persisted content from `render_ui` (per the seam contract) so
185
+ * a completion oracle sees real bytes. Pure seam wiring: a consumer that runs
186
+ * D1 + KV gets all four tools with no handler code.
187
+ */
188
+ declare function createPresetToolHandlers(opts: PresetToolHandlerOptions): AppToolHandlers;
189
+ interface PresetKnowledgeAccessorOptions {
190
+ db: D1Like;
191
+ /** The active workspace — every `count` is scoped to it. */
192
+ workspaceId: string;
193
+ /** Workspace config the `satisfiedBy: { config }` rules read. A resolved
194
+ * object (dot-path lookup), or a function the accessor calls per path. */
195
+ config: Record<string, unknown> | ((path: string) => unknown);
196
+ /** The default workspace fk column a `count` rule scopes on when its rule
197
+ * omits `where`. Default `'workspace_id'` (the preset schema convention). */
198
+ defaultWhereColumn?: string;
199
+ }
200
+ /**
201
+ * The {@link KnowledgeStateAccessor} over the preset D1 schema — the seam that
202
+ * lets the declarative `satisfiedBy` rules resolve with ZERO consumer code:
203
+ * - `config(path)` reads the supplied workspace config by dot-path.
204
+ * - `count({ table, where, statusIn })` runs `SELECT count(*)` scoped to the
205
+ * active workspace (the rule's `where` column, default `workspace_id`),
206
+ * optionally filtered to `statusIn` via a parameterized `IN (...)`.
207
+ *
208
+ * Identifiers (table/column) are validated against a safe pattern before
209
+ * interpolation — they originate from the product's own config, never model
210
+ * input, but we fail loud rather than build a malformed/injectable query.
211
+ */
212
+ declare function createD1KnowledgeStateAccessor(opts: PresetKnowledgeAccessorOptions): KnowledgeStateAccessor;
213
+ /** Build the {@link KeyCrypto} the billing key store uses — AES-256-GCM field
214
+ * crypto bound to the product's 64-char-hex `ENCRYPTION_KEY` (or a resolver).
215
+ * This is the concrete impl behind the `../billing` `KeyCrypto` seam. */
216
+ declare function createPresetFieldCrypto(key: string | (() => string)): KeyCrypto;
217
+ /**
218
+ * The {@link WorkspaceKeyStore} over the preset `workspace_keys` table — the
219
+ * persistence seam the per-workspace key manager needs. "Active" = a row with a
220
+ * null `revoked_at`. Pure D1 wiring; no key minting (that's the provisioner).
221
+ */
222
+ declare function createPresetWorkspaceKeyStore(db: D1Like): WorkspaceKeyStore;
223
+ interface PresetBillingOptions {
224
+ db: D1Like;
225
+ /** The key provisioner (`@tangle-network/tcloud`'s client satisfies it structurally). */
226
+ provisioner: KeyProvisioner;
227
+ /** Field-crypto key (64-char hex) or resolver — encrypts the minted key at rest. */
228
+ encryptionKey: string | (() => string);
229
+ /** Default monthly USD allowance when a call doesn't specify one. */
230
+ defaultBudgetUsd: number;
231
+ /** Injectable clock. */
232
+ now?: () => Date;
233
+ /** tcloud product the key is scoped to. Default `'router'`. */
234
+ product?: string;
235
+ }
236
+ /**
237
+ * Stand up the per-workspace budget-capped {@link WorkspaceKeyManager} on the
238
+ * house stack: the preset `workspace_keys` D1 store + AES-GCM field crypto +
239
+ * the consumer's tcloud provisioner. The mint/rotate/rollover/usage LOGIC lives
240
+ * in `../billing`; this only binds it to the preset table + crypto.
241
+ */
242
+ declare function createPresetWorkspaceKeyManager(opts: PresetBillingOptions): WorkspaceKeyManager;
243
+
244
+ export { type D1Like, type D1PreparedLike, type DrizzleColumnLike, type DrizzleSqliteCoreLike, PRESET_MIGRATION_SQL, PRESET_TABLES, type PresetBillingOptions, type PresetKnowledgeAccessorOptions, type PresetToolHandlerOptions, type VaultKv, createD1KnowledgeStateAccessor, createPresetDrizzleSchema, createPresetFieldCrypto, createPresetToolHandlers, createPresetWorkspaceKeyManager, createPresetWorkspaceKeyStore };
@@ -0,0 +1,23 @@
1
+ import {
2
+ PRESET_MIGRATION_SQL,
3
+ PRESET_TABLES,
4
+ createD1KnowledgeStateAccessor,
5
+ createPresetDrizzleSchema,
6
+ createPresetFieldCrypto,
7
+ createPresetToolHandlers,
8
+ createPresetWorkspaceKeyManager,
9
+ createPresetWorkspaceKeyStore
10
+ } from "../chunk-EZXN67KE.js";
11
+ import "../chunk-EAJSWUU5.js";
12
+ import "../chunk-ZJGY7OMZ.js";
13
+ export {
14
+ PRESET_MIGRATION_SQL,
15
+ PRESET_TABLES,
16
+ createD1KnowledgeStateAccessor,
17
+ createPresetDrizzleSchema,
18
+ createPresetFieldCrypto,
19
+ createPresetToolHandlers,
20
+ createPresetWorkspaceKeyManager,
21
+ createPresetWorkspaceKeyStore
22
+ };
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,39 +1,6 @@
1
+ export { D as DEFAULT_TANGLE_ROUTER_BASE_URL, R as ResolveModelOptions, T as TangleModelConfig, r as resolveTangleModelConfig } from '../model-BOP69mVu.js';
1
2
  import { d as AppToolOutcome } from '../types-CeWor4bQ.js';
2
3
 
3
- /**
4
- * Resolve the model config a Tangle agent's sandbox/runtime runs on.
5
- *
6
- * Every Tangle agent product resolves the SAME thing from env: the Tangle Router
7
- * (OpenAI-compatible, metered at the platform markup against a single
8
- * `TANGLE_API_KEY`) by default, with a direct-Anthropic BYOK escape hatch. The
9
- * shape feeds the sandbox SDK's `backend.model`. Lifted here so no product
10
- * hand-rolls the env parsing + the router default.
11
- */
12
- interface TangleModelConfig {
13
- /** The Tangle Router is OpenAI-compatible → driven via `openai-compat`.
14
- * `anthropic` is the BYOK escape hatch. */
15
- provider: 'openai-compat' | 'anthropic';
16
- model: string;
17
- apiKey: string;
18
- baseUrl: string;
19
- }
20
- interface ResolveModelOptions {
21
- /** Env to read (defaults to process.env). */
22
- env?: Record<string, string | undefined>;
23
- /** Router base URL default when `TANGLE_ROUTER_BASE_URL` is unset. */
24
- defaultRouterBaseUrl?: string;
25
- }
26
- declare const DEFAULT_TANGLE_ROUTER_BASE_URL = "https://router.tangle.tools/v1";
27
- /**
28
- * Resolve the model config from env. DEFAULT path (`MODEL_PROVIDER` unset or
29
- * `openai-compat`/`tangle-router`/`tcloud`): the Tangle Router, authenticated
30
- * with `TANGLE_API_KEY`, model from `MODEL_NAME`. BYOK path
31
- * (`MODEL_PROVIDER=anthropic`): direct Anthropic with `ANTHROPIC_API_KEY` +
32
- * `ANTHROPIC_BASE_URL`. Throws (fail-loud) on a missing required var so a
33
- * misconfigured deploy fails at boot, not mid-turn.
34
- */
35
- declare function resolveTangleModelConfig(opts?: ResolveModelOptions): TangleModelConfig;
36
-
37
4
  /**
38
5
  * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.
39
6
  *
@@ -216,4 +183,4 @@ interface StreamAppToolLoopOptions<Raw> {
216
183
  */
217
184
  declare function streamAppToolLoop<Raw>(opts: StreamAppToolLoopOptions<Raw>): AsyncGenerator<StreamLoopYield<Raw>, void, unknown>;
218
185
 
219
- export { type AppToolLoopOptions, DEFAULT_TANGLE_ROUTER_BASE_URL, type LoopEvent, type LoopToolCall, type OpenAICompatStreamTurnOptions, type OpenAIStreamChunk, type ResolveModelOptions, type StreamAppToolLoopOptions, type StreamLoopYield, type TangleModelConfig, type ToolLoopResult, createOpenAICompatStreamTurn, resolveTangleModelConfig, runAppToolLoop, streamAppToolLoop, toLoopEvents };
186
+ export { type AppToolLoopOptions, type LoopEvent, type LoopToolCall, type OpenAICompatStreamTurnOptions, type OpenAIStreamChunk, type StreamAppToolLoopOptions, type StreamLoopYield, type ToolLoopResult, createOpenAICompatStreamTurn, runAppToolLoop, streamAppToolLoop, toLoopEvents };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -66,6 +66,21 @@
66
66
  "import": "./dist/knowledge/index.js",
67
67
  "default": "./dist/knowledge/index.js"
68
68
  },
69
+ "./config": {
70
+ "types": "./dist/config/index.d.ts",
71
+ "import": "./dist/config/index.js",
72
+ "default": "./dist/config/index.js"
73
+ },
74
+ "./knowledge-loop": {
75
+ "types": "./dist/knowledge-loop/index.d.ts",
76
+ "import": "./dist/knowledge-loop/index.js",
77
+ "default": "./dist/knowledge-loop/index.js"
78
+ },
79
+ "./preset-cloudflare": {
80
+ "types": "./dist/preset-cloudflare/index.d.ts",
81
+ "import": "./dist/preset-cloudflare/index.js",
82
+ "default": "./dist/preset-cloudflare/index.js"
83
+ },
69
84
  "./billing": {
70
85
  "types": "./dist/billing/index.d.ts",
71
86
  "import": "./dist/billing/index.js",
@@ -106,15 +121,26 @@
106
121
  "typecheck": "tsc --noEmit"
107
122
  },
108
123
  "devDependencies": {
124
+ "@tangle-network/agent-eval": "^0.70.0",
125
+ "@tangle-network/agent-integrations": "^0.32.0",
126
+ "@tangle-network/agent-knowledge": "^1.5.2",
109
127
  "@types/node": "^25.6.0",
110
128
  "tsup": "^8.0.0",
111
129
  "typescript": "^5.7.0",
112
- "vitest": "^3.0.0",
113
- "@tangle-network/agent-integrations": "^0.32.0",
114
- "@tangle-network/agent-eval": "^0.70.0"
130
+ "vitest": "^3.0.0"
115
131
  },
116
132
  "peerDependencies": {
133
+ "@tangle-network/agent-eval": ">=0.50.0",
117
134
  "@tangle-network/agent-integrations": ">=0.32.0",
118
- "@tangle-network/agent-eval": ">=0.50.0"
135
+ "@tangle-network/agent-knowledge": ">=1.5.0",
136
+ "@tangle-network/agent-runtime": ">=0.21.0"
137
+ },
138
+ "peerDependenciesMeta": {
139
+ "@tangle-network/agent-knowledge": {
140
+ "optional": true
141
+ },
142
+ "@tangle-network/agent-runtime": {
143
+ "optional": true
144
+ }
119
145
  }
120
146
  }