@stackbone/sdk 0.1.0-alpha.2 → 0.1.0-alpha.3
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/CHANGELOG.md +275 -1
- package/README.md +31 -97
- package/db/testing/index.cjs +12 -43
- package/db/testing/index.cjs.map +1 -1
- package/db/testing/index.d.cts +12 -21
- package/db/testing/index.d.ts +12 -21
- package/db/testing/index.js +12 -43
- package/db/testing/index.js.map +1 -1
- package/index.cjs +3849 -19181
- package/index.cjs.map +1 -1
- package/index.d.cts +1466 -878
- package/index.d.ts +1466 -878
- package/index.js +3833 -19170
- package/index.js.map +1 -1
- package/observability/index.cjs +596 -0
- package/observability/index.cjs.map +1 -0
- package/observability/index.d.cts +175 -0
- package/observability/index.d.ts +175 -0
- package/observability/index.js +587 -0
- package/observability/index.js.map +1 -0
- package/package.json +14 -2
- package/rag/migrations/index.cjs +1 -1
- package/rag/migrations/index.cjs.map +1 -1
- package/rag/migrations/index.js +1 -1
- package/rag/migrations/index.js.map +1 -1
- package/rag/schema.cjs +1 -1
- package/rag/schema.cjs.map +1 -1
- package/rag/schema.js +1 -1
- package/rag/schema.js.map +1 -1
- package/stackbone-sdk-0.1.0-alpha.3.tgz +0 -0
- package/stackbone-sdk-0.1.0-alpha.2.tgz +0 -0
package/index.d.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { ContractResponse } from '@stackbone/validators';
|
|
2
|
+
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
3
|
+
import { Sql } from 'postgres';
|
|
2
4
|
import OpenAI from 'openai';
|
|
3
5
|
import { ChatCompletionCreateParamsStreaming, ChatCompletionChunk, ChatCompletionCreateParamsNonStreaming, ChatCompletion, EmbeddingCreateParams, CreateEmbeddingResponse, ChatCompletionMessageParam } from 'openai/resources';
|
|
4
6
|
import { Stream } from 'openai/streaming';
|
|
5
|
-
import {
|
|
6
|
-
import { Sql } from 'postgres';
|
|
7
|
+
import { RunStepsSpanProcessorOptions, RunStepsSpanProcessor, PlatformLoggerOptions, PlatformLogger, AggregateRunCostResult } from './observability/index.js';
|
|
7
8
|
import { S3Client } from '@aws-sdk/client-s3';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
export { z } from 'zod';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Optional overrides accepted by `createClient`. All fields are optional;
|
|
11
|
-
*
|
|
14
|
+
* the resolver applies the precedence rule (`config.xxx` first, env var
|
|
15
|
+
* fallback) and produces a fully typed `ResolvedConfig` so downstream
|
|
16
|
+
* modules never have to know the env-var names or the order of precedence.
|
|
12
17
|
*/
|
|
13
18
|
interface ClientConfig {
|
|
14
19
|
/** Ed25519 JWT signed by the control plane. Falls back to `STACKBONE_AGENT_JWT`. */
|
|
@@ -86,22 +91,245 @@ interface ClientConfig {
|
|
|
86
91
|
protocolRequired?: number;
|
|
87
92
|
}
|
|
88
93
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
94
|
+
* Fully-resolved, typed snapshot the SDK consumes. The resolver is the **only**
|
|
95
|
+
* place in the library that touches `process.env`; every downstream module
|
|
96
|
+
* reads from typed fields here instead of consulting the environment itself.
|
|
97
|
+
*
|
|
98
|
+
* Precedence rule: `config.xxx` (passed to `createClient`) wins over
|
|
99
|
+
* `process.env['XXX']`. Required env vars surface as `string | undefined` —
|
|
100
|
+
* the resolver never throws when a var is missing; whether a missing value
|
|
101
|
+
* is fatal is a per-surface decision (some error at construction, others
|
|
102
|
+
* lazily on first use).
|
|
103
|
+
*
|
|
104
|
+
* Construction is a snapshot. Tests that mutated `process.env` between
|
|
105
|
+
* `createClient()` and the first module access used to observe the new value
|
|
106
|
+
* through the old `env` channel; they now pass overrides via `createClient`
|
|
107
|
+
* or rebuild a new `ResolvedConfig` after mutating `process.env`. Snapshot
|
|
108
|
+
* semantics make the SDK easier to reason about — one read, one source.
|
|
109
|
+
*
|
|
110
|
+
* Note: there is intentionally no `env` channel. Reading
|
|
111
|
+
* `resolved.env['SOMETHING']` is a type error. Adding a new env-driven
|
|
112
|
+
* setting means adding a typed field here and resolving it in
|
|
113
|
+
* `resolveConfig`.
|
|
92
114
|
*/
|
|
93
115
|
interface ResolvedConfig {
|
|
94
|
-
|
|
95
|
-
|
|
116
|
+
/** The frozen creator-supplied options (untouched, for debug introspection). */
|
|
117
|
+
readonly config: Readonly<ClientConfig>;
|
|
118
|
+
/** Resolved from `config.stackboneApiUrl` ?? `STACKBONE_API_URL`. */
|
|
119
|
+
readonly stackboneApiUrl: string | undefined;
|
|
120
|
+
/** Resolved from `config.agentJwt` ?? `STACKBONE_AGENT_JWT`. */
|
|
121
|
+
readonly agentJwt: string | undefined;
|
|
122
|
+
/** Resolved from `config.installationId` ?? `STACKBONE_INSTALLATION_ID`. */
|
|
123
|
+
readonly installationId: string | undefined;
|
|
124
|
+
/** Resolved from `config.approvalSigningKey` ?? `STACKBONE_APPROVAL_SIGNING_KEY`. */
|
|
125
|
+
readonly approvalSigningKey: string | undefined;
|
|
126
|
+
/** Resolved from `config.agentId` ?? `STACKBONE_AGENT_ID`. */
|
|
127
|
+
readonly agentId: string | undefined;
|
|
128
|
+
/** Resolved from `config.databaseUrl` ?? `STACKBONE_POSTGRES_URL`. Shared by `client.database`, `client.rag` and `client.observability`. */
|
|
129
|
+
readonly databaseUrl: string | undefined;
|
|
130
|
+
/** Resolved from `config.openrouterKey` ?? `OPENROUTER_API_KEY`. */
|
|
131
|
+
readonly openrouterKey: string | undefined;
|
|
132
|
+
/** Resolved from `config.openrouterBaseUrl` ?? `OPENROUTER_BASE_URL`. `undefined` means use the OpenRouter default. */
|
|
133
|
+
readonly openrouterBaseUrl: string | undefined;
|
|
134
|
+
/** Resolved from `config.s3.accessKeyId` ?? `STACKBONE_S3_ACCESS_KEY`. */
|
|
135
|
+
readonly s3AccessKeyId: string | undefined;
|
|
136
|
+
/** Resolved from `config.s3.secretAccessKey` ?? `STACKBONE_S3_SECRET_KEY`. */
|
|
137
|
+
readonly s3SecretAccessKey: string | undefined;
|
|
138
|
+
/** Resolved from `config.s3.endpoint` ?? `STACKBONE_S3_ENDPOINT`. */
|
|
139
|
+
readonly s3Endpoint: string | undefined;
|
|
140
|
+
/** Resolved from `config.s3.bucket` ?? `STACKBONE_S3_BUCKET`. */
|
|
141
|
+
readonly s3Bucket: string | undefined;
|
|
142
|
+
/** Resolved from `config.s3.region` ?? `STACKBONE_S3_REGION` ?? `'auto'`. Always defined. */
|
|
143
|
+
readonly s3Region: string;
|
|
144
|
+
/** Resolved from `config.otel.exporterOtlpEndpoint` ?? `OTEL_EXPORTER_OTLP_ENDPOINT`. */
|
|
145
|
+
readonly otelExporterOtlpEndpoint: string | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* `true` unless `STACKBONE_REQUIRE_CONTRACT=0` is set. Consulted by the
|
|
148
|
+
* contract gate to decide whether a `capability_unavailable` /
|
|
149
|
+
* `contract_version_unsupported` error becomes a hard failure or a logged
|
|
150
|
+
* warning (escape-hatch path).
|
|
151
|
+
*/
|
|
152
|
+
readonly requireContract: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Parsed `STACKBONE_CONTRACT_TTL_MS`. `undefined` (default) means cached
|
|
155
|
+
* handshakes never expire within the process lifetime; a positive integer
|
|
156
|
+
* caps the cache entry's `expiresAt`.
|
|
157
|
+
*/
|
|
158
|
+
readonly contractTtlMs: number | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* `true` only when `STACKBONE_DEBUG=1` is set. Consulted by the handshake
|
|
161
|
+
* to emit a one-shot debug line per (store, baseUrl) tuple after a
|
|
162
|
+
* successful negotiation.
|
|
163
|
+
*/
|
|
164
|
+
readonly debug: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Embedding model parsed by the CLI from `agent.yaml.rag.embeddingModel`
|
|
167
|
+
* and forwarded as `config.rag.embeddingModel`. Same value, surfaced
|
|
168
|
+
* directly so consumers don't reach through the nested object.
|
|
169
|
+
*/
|
|
170
|
+
readonly ragEmbeddingModel: string | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* Forwarded from `config.protocolRequired`. The CLI sources it from
|
|
173
|
+
* `agent.yaml.protocol.required`; there is no env-var fallback today.
|
|
174
|
+
*/
|
|
175
|
+
readonly protocolRequired: number | undefined;
|
|
96
176
|
}
|
|
97
177
|
|
|
178
|
+
/**
|
|
179
|
+
* The single source of truth for every `code` an `SdkError` may carry on the
|
|
180
|
+
* `Result` envelope returned from a public SDK method. The README contract
|
|
181
|
+
* (and every creator who pattern-matches on `result.error.code`) is driven by
|
|
182
|
+
* this catalog — adding a new code means editing this file, and **only** this
|
|
183
|
+
* file. The compiler then refuses to ship any `SdkError({ code: '<unknown>' })`
|
|
184
|
+
* downstream.
|
|
185
|
+
*
|
|
186
|
+
* Shape:
|
|
187
|
+
* - `SDK_ERROR_CODE_PREFIXES` declares each prefix family and the suffixes
|
|
188
|
+
* it owns. `<prefix>` + `_<reason>` is the wire-level code.
|
|
189
|
+
* - `SDK_ERROR_CODES_STANDALONE` declares the freestanding codes that don't
|
|
190
|
+
* follow the `<prefix>_<reason>` rule (`not_implemented`, the various
|
|
191
|
+
* `*_missing` setup-bug codes, the `capability_*` / `contract_*` shapes).
|
|
192
|
+
* - `SdkErrorCode` is the union projected from both. Pass it to `err({...})`
|
|
193
|
+
* and the constructor rejects any literal that isn't in this catalog.
|
|
194
|
+
*
|
|
195
|
+
* Stability contract: the wire-level shape of every code in this catalog is
|
|
196
|
+
* part of the README's public error table. Codes are added behind a minor;
|
|
197
|
+
* codes are removed behind a major and the CHANGELOG. Spelling tweaks (even
|
|
198
|
+
* typos) wait for a major — fix the catalog and the call site together.
|
|
199
|
+
*/
|
|
200
|
+
/**
|
|
201
|
+
* Prefix families. Each entry maps a `<prefix>` to the tuple of `<reason>`
|
|
202
|
+
* suffixes the SDK can emit under it. Two-step structure (object of tuples)
|
|
203
|
+
* is what powers the typed projection at the bottom of this file: the
|
|
204
|
+
* `as const` makes both the prefix names and the suffix strings into literal
|
|
205
|
+
* types so `SdkErrorCode` collapses to a finite string union.
|
|
206
|
+
*/
|
|
207
|
+
declare const SDK_ERROR_CODE_PREFIXES: {
|
|
208
|
+
/**
|
|
209
|
+
* `client.ai` — wraps the OpenAI SDK pointed at OpenRouter. Status codes
|
|
210
|
+
* come from `codeForStatus` in `ai.ts` plus the fetch-error mapper.
|
|
211
|
+
*/
|
|
212
|
+
readonly ai: readonly ["aborted", "credits_exhausted", "forbidden", "moderation_blocked", "network_error", "no_image_generated", "provider_error", "rate_limited", "timeout", "unauthorized", "validation_error"];
|
|
213
|
+
/**
|
|
214
|
+
* `client.storage` — wraps `@aws-sdk/client-s3` against R2/MinIO. Most
|
|
215
|
+
* failures collapse into `s3_error` with the AWS metadata on `meta`; the
|
|
216
|
+
* specific codes below are the early validation paths.
|
|
217
|
+
*/
|
|
218
|
+
readonly s3: readonly ["bucket_missing", "credentials_missing", "empty_response", "error", "invalid_argument", "invalid_key"];
|
|
219
|
+
/**
|
|
220
|
+
* `client.rag` — the agent-local retrieval pipeline. `invalid_request` is
|
|
221
|
+
* the validation rail; the rest map specific Postgres / embedder failures
|
|
222
|
+
* the README documents.
|
|
223
|
+
*/
|
|
224
|
+
readonly rag: readonly ["dim_mismatch", "embedding_failed", "embedding_model_unsupported", "error", "ingest_cancelled", "invalid_request", "job_insert_failed", "jobs_error", "schema_missing"];
|
|
225
|
+
/**
|
|
226
|
+
* `client.approval` — control-plane facade plus the HMAC verify helper.
|
|
227
|
+
* The transport-driven codes (`unauthorized`, `forbidden`, ...) come from
|
|
228
|
+
* `HttpClient`'s status→domain remap with `prefix: 'approval'`; the
|
|
229
|
+
* verify-specific codes (`invalid_signature`, etc.) are emitted locally.
|
|
230
|
+
*/
|
|
231
|
+
readonly approval: readonly ["forbidden", "invalid_payload", "invalid_request", "invalid_response", "invalid_signature", "not_found", "rate_limited", "signature_expired", "signing_key_missing", "timeout", "tool_execute_failed", "unauthorized", "unavailable"];
|
|
232
|
+
/**
|
|
233
|
+
* `client.secrets` — control-plane facade. Almost entirely transport-
|
|
234
|
+
* driven (HttpClient remap with `prefix: 'secrets'`); the local codes are
|
|
235
|
+
* the request-shape rail.
|
|
236
|
+
*/
|
|
237
|
+
readonly secrets: readonly ["already_exists", "forbidden", "invalid_request", "invalid_response", "not_found", "rate_limited", "timeout", "unauthorized", "unavailable"];
|
|
238
|
+
/**
|
|
239
|
+
* `client.config` — symmetric to `secrets`, points at `/api/config`.
|
|
240
|
+
*/
|
|
241
|
+
readonly config: readonly ["forbidden", "invalid_request", "invalid_response", "not_found", "rate_limited", "timeout", "unauthorized", "unavailable"];
|
|
242
|
+
/**
|
|
243
|
+
* `client.memory` — reserved. The README documents the prefix; today the
|
|
244
|
+
* pending surface returns `not_implemented` for every method so no
|
|
245
|
+
* `memory_*` codes are emitted yet. Kept as an empty bucket so the
|
|
246
|
+
* catalog matches the README table verbatim.
|
|
247
|
+
*/
|
|
248
|
+
readonly memory: readonly string[];
|
|
249
|
+
/**
|
|
250
|
+
* Transport-level error family — `HttpClient` emits these when no
|
|
251
|
+
* facade-specific `errorMapping.prefix` is configured, or when the
|
|
252
|
+
* failure is pre-/post-HTTP (DNS, abort, body parse).
|
|
253
|
+
*/
|
|
254
|
+
readonly http: readonly ["aborted", "client_error", "forbidden", "invalid_request", "invalid_response", "network_error", "not_found", "parse_error", "rate_limited", "request_failed", "server_error", "timeout", "unauthorized"];
|
|
255
|
+
/**
|
|
256
|
+
* `client.database` (and its cross-surface consumers) — the setup-bug
|
|
257
|
+
* surface stays under one prefix so creators can pattern-match on
|
|
258
|
+
* `database_*` regardless of whether the failure came from the database
|
|
259
|
+
* module, RAG, or the observability run-cost hook.
|
|
260
|
+
*/
|
|
261
|
+
readonly database: readonly ["not_configured"];
|
|
262
|
+
/**
|
|
263
|
+
* `client.observability` — only the run-cost rollup emits a domain code
|
|
264
|
+
* today; everything else either returns ok or surfaces through the
|
|
265
|
+
* `database_*` family above.
|
|
266
|
+
*/
|
|
267
|
+
readonly observability: readonly ["close_run_failed"];
|
|
268
|
+
/**
|
|
269
|
+
* Contract handshake / gating family — emitted by the per-client
|
|
270
|
+
* `ContractStore` and consumed by every gated facade as a hard error.
|
|
271
|
+
* Documented for creators in the gating section of the SDK ADRs.
|
|
272
|
+
*/
|
|
273
|
+
readonly contract: readonly ["malformed", "unreachable", "version_unsupported"];
|
|
274
|
+
/**
|
|
275
|
+
* Capability gating family — the single code is the table-driven block
|
|
276
|
+
* the gate returns when the negotiated contract lacks the required
|
|
277
|
+
* capability. The shape lives in `@stackbone/validators`; the SDK widens
|
|
278
|
+
* it into `SdkError` verbatim.
|
|
279
|
+
*/
|
|
280
|
+
readonly capability: readonly ["unavailable"];
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Freestanding codes — they don't follow the `<prefix>_<reason>` rule, so
|
|
284
|
+
* they live as full literals here. Two families:
|
|
285
|
+
*
|
|
286
|
+
* - `not_implemented` for the pending surfaces' stubbed methods.
|
|
287
|
+
* - `*_missing` for the setup-bug rail every facade checks before its
|
|
288
|
+
* first network call (`STACKBONE_API_URL`, `STACKBONE_AGENT_ID`,
|
|
289
|
+
* `OPENROUTER_API_KEY`, `STACKBONE_POSTGRES_URL`).
|
|
290
|
+
*/
|
|
291
|
+
declare const SDK_ERROR_CODES_STANDALONE: readonly ["agent_id_missing", "database_url_missing", "not_implemented", "openrouter_key_missing", "stackbone_api_url_missing"];
|
|
292
|
+
/**
|
|
293
|
+
* Type-level projection: `<prefix>_<reason>` for every entry of
|
|
294
|
+
* `SDK_ERROR_CODE_PREFIXES`, plus the standalone codes. The trick is the
|
|
295
|
+
* second mapped-tuple step: `(typeof SDK_ERROR_CODE_PREFIXES)[P][number]`
|
|
296
|
+
* collapses the tuple to the union of its suffixes, then template-string
|
|
297
|
+
* inference builds the full code.
|
|
298
|
+
*/
|
|
299
|
+
type Prefix = keyof typeof SDK_ERROR_CODE_PREFIXES;
|
|
300
|
+
type PrefixedCode<P extends Prefix = Prefix> = {
|
|
301
|
+
[K in P]: `${K}_${(typeof SDK_ERROR_CODE_PREFIXES)[K][number]}`;
|
|
302
|
+
}[P];
|
|
303
|
+
type SdkErrorCode = PrefixedCode | (typeof SDK_ERROR_CODES_STANDALONE)[number];
|
|
304
|
+
/**
|
|
305
|
+
* Type guard for callers that receive a raw string from the wire (e.g. a
|
|
306
|
+
* decoded JSON body) and want to narrow it to the catalog. Returns false
|
|
307
|
+
* for any unknown code so the caller can decide whether to re-throw,
|
|
308
|
+
* fall back, or surface the raw string.
|
|
309
|
+
*/
|
|
310
|
+
declare function isSdkErrorCode(code: string): code is SdkErrorCode;
|
|
311
|
+
/**
|
|
312
|
+
* Prefix names exposed as a tuple for the `errorMapping.prefix` slot on
|
|
313
|
+
* `HttpClient.request`. Statically tying the transport's prefix to the
|
|
314
|
+
* catalog keeps `<prefix>_<reason>` emissions inside `SdkErrorCode` —
|
|
315
|
+
* adding a new prefix means adding it here AND a `<prefix>_invalid_request`
|
|
316
|
+
* / `<prefix>_invalid_response` / standard status-mapped suffix to the
|
|
317
|
+
* catalog above.
|
|
318
|
+
*/
|
|
319
|
+
type SdkErrorPrefix = Prefix;
|
|
320
|
+
|
|
98
321
|
/**
|
|
99
322
|
* Uniform error envelope returned by every SDK method. `cause` and `meta` are
|
|
100
323
|
* optional escape hatches so wrappers can attach upstream errors / context
|
|
101
324
|
* without breaking the public shape.
|
|
325
|
+
*
|
|
326
|
+
* `code` is typed against the catalog declared in `errors/codes.ts` — the
|
|
327
|
+
* compiler refuses any literal that isn't part of the documented inventory.
|
|
328
|
+
* Adding a new code is a single-file edit there; ALL throw sites then update
|
|
329
|
+
* automatically through the union.
|
|
102
330
|
*/
|
|
103
331
|
interface SdkError {
|
|
104
|
-
code:
|
|
332
|
+
code: SdkErrorCode;
|
|
105
333
|
message: string;
|
|
106
334
|
cause?: unknown;
|
|
107
335
|
meta?: Record<string, unknown>;
|
|
@@ -126,364 +354,198 @@ type Result<T> = {
|
|
|
126
354
|
* `Result<T>` of the method.
|
|
127
355
|
*
|
|
128
356
|
* Test seam — every gated module exposes an optional `gate` constructor
|
|
129
|
-
* argument that defaults to `createModuleGate(<id>, resolved)`. Specs
|
|
130
|
-
* a stub returning a synthetic `Result<undefined>` to exercise the
|
|
131
|
-
* paths (ok / `contract_version_unsupported` / `capability_unavailable`
|
|
132
|
-
* `contract_unreachable`) without driving a real handshake.
|
|
357
|
+
* argument that defaults to `createModuleGate(<id>, resolved, store)`. Specs
|
|
358
|
+
* inject a stub returning a synthetic `Result<undefined>` to exercise the
|
|
359
|
+
* gating paths (ok / `contract_version_unsupported` / `capability_unavailable`
|
|
360
|
+
* / `contract_unreachable`) without driving a real handshake.
|
|
133
361
|
*/
|
|
134
362
|
type ModuleGate = () => Promise<Result<undefined>>;
|
|
135
363
|
|
|
136
|
-
/** Subset of `RequestInit['body']` we serialize without modification. */
|
|
137
|
-
type SerializedBody = NonNullable<RequestInit['body']>;
|
|
138
|
-
interface RequestOptions$1 extends Omit<RequestInit, 'body' | 'signal'> {
|
|
139
|
-
params?: Record<string, string>;
|
|
140
|
-
body?: SerializedBody | Record<string, unknown> | unknown[] | null;
|
|
141
|
-
signal?: AbortSignal;
|
|
142
|
-
/** Allow retrying non-idempotent requests (POST, PATCH). Off by default to prevent duplicate writes. */
|
|
143
|
-
idempotent?: boolean;
|
|
144
|
-
}
|
|
145
|
-
interface HttpClientOptions {
|
|
146
|
-
/** Default 30_000. Set to 0 to disable. */
|
|
147
|
-
timeout?: number;
|
|
148
|
-
/** Default 3. Set to 0 to disable. */
|
|
149
|
-
retryCount?: number;
|
|
150
|
-
/** Initial backoff in ms; doubles each attempt with ±15% jitter. Default 500. */
|
|
151
|
-
retryDelay?: number;
|
|
152
|
-
/** Override the global fetch (useful for tests). */
|
|
153
|
-
fetch?: typeof fetch;
|
|
154
|
-
}
|
|
155
364
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* envelope so facades never throw at the SDK boundary.
|
|
365
|
+
* Public surface a creator can use through `client.database`. We pin it to the
|
|
366
|
+
* `postgres-js` Drizzle wrapper because that is the only driver `@stackbone/sdk`
|
|
367
|
+
* ships and the only one `STACKBONE_POSTGRES_URL` is contracted to point at.
|
|
160
368
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
369
|
+
* Re-exporting Drizzle's own type instead of inventing a structural alias keeps
|
|
370
|
+
* the surface 1:1 with what `drizzle-orm` documents and what `@stackbone/sdk/db`
|
|
371
|
+
* re-exports — so types flow end to end without translation.
|
|
163
372
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
private readonly retryCount;
|
|
168
|
-
private readonly retryDelay;
|
|
169
|
-
private readonly fetchImpl;
|
|
170
|
-
constructor(resolved: ResolvedConfig, options?: HttpClientOptions);
|
|
171
|
-
get<T>(path: string, options?: RequestOptions$1): Promise<Result<T>>;
|
|
172
|
-
post<T>(path: string, body?: RequestOptions$1['body'], options?: RequestOptions$1): Promise<Result<T>>;
|
|
173
|
-
put<T>(path: string, body?: RequestOptions$1['body'], options?: RequestOptions$1): Promise<Result<T>>;
|
|
174
|
-
patch<T>(path: string, body?: RequestOptions$1['body'], options?: RequestOptions$1): Promise<Result<T>>;
|
|
175
|
-
delete<T>(path: string, options?: RequestOptions$1): Promise<Result<T>>;
|
|
176
|
-
request<T>(method: string, path: string, options?: RequestOptions$1): Promise<Result<T>>;
|
|
177
|
-
private computeBackoff;
|
|
178
|
-
}
|
|
373
|
+
type DrizzleClient = PostgresJsDatabase<Record<string, never>> & {
|
|
374
|
+
$client: Sql;
|
|
375
|
+
};
|
|
179
376
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
377
|
+
/**
|
|
378
|
+
* `client.database` is a lazy wrapper over a `drizzle()` instance bound to
|
|
379
|
+
* the resolved `databaseUrl` (config first, then `STACKBONE_POSTGRES_URL`
|
|
380
|
+
* at `resolveConfig` time). The underlying postgres-js connection and the
|
|
381
|
+
* Drizzle facade are constructed on the first method call so partner SDKs
|
|
382
|
+
* only pay the cost when the agent actually touches the database.
|
|
383
|
+
*
|
|
384
|
+
* Native Drizzle methods (`select`, `insert`, `update`, `delete`, `transaction`,
|
|
385
|
+
* `execute`) are exposed verbatim — we explicitly avoid wrapping them in a
|
|
386
|
+
* Result envelope here. Drizzle already throws on misuse and returns typed rows
|
|
387
|
+
* on success; mirroring its API keeps the contract identical to what the
|
|
388
|
+
* `@stackbone/sdk/db` re-exports document, so creators write idiomatic Drizzle.
|
|
389
|
+
*
|
|
390
|
+
* Missing `STACKBONE_POSTGRES_URL` raises `SdkError('database_not_configured')`
|
|
391
|
+
* with an actionable hint that points to `stackbone dev`. This is a creator
|
|
392
|
+
* setup bug, not something the agent's code can recover from at runtime.
|
|
393
|
+
*
|
|
394
|
+
* Methods are declared with their full Drizzle generic signatures (rather than
|
|
395
|
+
* `(...args) => Drizzle[k](...args)` arrow properties) so the inferred row
|
|
396
|
+
* types from `pgTable(...)` flow end-to-end into the caller, which is the
|
|
397
|
+
* promise the `@stackbone/sdk/db` re-export makes.
|
|
398
|
+
*
|
|
399
|
+
* The actual `postgres()` pool and the Drizzle handle are owned by the
|
|
400
|
+
* module-internal singleton in `./shared-handle.ts`. The shared handle is
|
|
401
|
+
* exposed to other SDK surfaces (RAG today, memory / queues tomorrow)
|
|
402
|
+
* through the `shared()` method below — the **canonical accessor** of
|
|
403
|
+
* the SDK's shared-handles pattern. Same instance every call → one pool
|
|
404
|
+
* per agent process. See `shared-handle.spec.ts` for the invariant.
|
|
405
|
+
*/
|
|
406
|
+
declare class DatabaseModule {
|
|
407
|
+
private readonly _gate;
|
|
408
|
+
private readonly _databaseUrl;
|
|
409
|
+
constructor(resolved: ResolvedConfig,
|
|
185
410
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* `
|
|
189
|
-
*
|
|
411
|
+
* Test seam — see `ModuleGate`. Defaults to the lazy contract gate.
|
|
412
|
+
* The gate is awaited at the terminal `await` of every Drizzle chain
|
|
413
|
+
* (`select/insert/update/delete/execute/transaction`); when it returns
|
|
414
|
+
* an error, a tagged `GateBlockedError` is thrown so callers see the
|
|
415
|
+
* same `Error & { code, message, meta }` surface as the existing
|
|
416
|
+
* `database_not_configured` setup-bug.
|
|
190
417
|
*/
|
|
191
|
-
|
|
192
|
-
|
|
418
|
+
gate?: ModuleGate);
|
|
419
|
+
/**
|
|
420
|
+
* Escape hatch — returns Drizzle's relational query builder verbatim. The
|
|
421
|
+
* contract gate does NOT fire here because the surface is a synchronous
|
|
422
|
+
* accessor; the user can still bypass the gate via `client.database.raw()`
|
|
423
|
+
* for the same reason. Audit: `query` and `raw()` are intentionally
|
|
424
|
+
* ungated escape hatches; the standard `select/insert/update/delete/
|
|
425
|
+
* execute/transaction` path is fully gated.
|
|
426
|
+
*/
|
|
427
|
+
get query(): DrizzleClient['query'];
|
|
428
|
+
select: DrizzleClient['select'];
|
|
429
|
+
insert: DrizzleClient['insert'];
|
|
430
|
+
update: DrizzleClient['update'];
|
|
431
|
+
delete: DrizzleClient['delete'];
|
|
432
|
+
execute: DrizzleClient['execute'];
|
|
433
|
+
transaction: DrizzleClient['transaction'];
|
|
434
|
+
/**
|
|
435
|
+
* Canonical accessor for the **shared-handles pattern**. Returns the
|
|
436
|
+
* process-wide Drizzle handle backing `client.database`. Cross-surface
|
|
437
|
+
* SDK consumers (RAG today, memory / queues tomorrow) call this — never
|
|
438
|
+
* the implementation-detail singleton in `./shared-handle.ts` — to
|
|
439
|
+
* ensure they share the one `postgres()` pool the agent process owns
|
|
440
|
+
* against `STACKBONE_POSTGRES_URL`.
|
|
441
|
+
*
|
|
442
|
+
* Contract: same input → same instance. Calling `shared()` twice in the
|
|
443
|
+
* same process returns the same reference; the underlying postgres-js
|
|
444
|
+
* `Sql` is reachable as `shared().$client` for callers (e.g. RAG) that
|
|
445
|
+
* need the template-tag function instead of the Drizzle facade.
|
|
446
|
+
*
|
|
447
|
+
* Like `raw()`, this is a synchronous accessor and intentionally does
|
|
448
|
+
* NOT consult the contract gate — surfaces that gate behaviour gate it
|
|
449
|
+
* at their own call sites (`pipeline.ingest`, etc.), not at handle
|
|
450
|
+
* acquisition. Configuration errors (`database_not_configured`) throw
|
|
451
|
+
* with the same tagged `Error` shape every other `client.database`
|
|
452
|
+
* surface uses.
|
|
453
|
+
*/
|
|
454
|
+
shared(): DrizzleClient;
|
|
455
|
+
/**
|
|
456
|
+
* Escape hatch for **creator** callers that want the raw Drizzle handle
|
|
457
|
+
* (e.g. to pass it to a library that expects `PostgresJsDatabase`).
|
|
458
|
+
* Functionally identical to `shared()` — both return the same singleton
|
|
459
|
+
* and trigger the same lazy initialisation — but kept under a distinct
|
|
460
|
+
* name because the audiences differ: `shared()` is consumed by other
|
|
461
|
+
* SDK surfaces and documents the cross-surface contract; `raw()` is
|
|
462
|
+
* documented as the creator's escape hatch out of the structured
|
|
463
|
+
* `select/insert/...` surface. Reading either intentionally does NOT
|
|
464
|
+
* consult the contract gate; the caller is opting out of the SDK's
|
|
465
|
+
* structured surface.
|
|
466
|
+
*/
|
|
467
|
+
raw(): DrizzleClient;
|
|
193
468
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Public surface behind `client.ai`. Wraps the official `openai` SDK with a
|
|
472
|
+
* `baseURL` override pointing to OpenRouter, so 300+ models are reachable
|
|
473
|
+
* with a single, OpenAI-compatible API.
|
|
474
|
+
*
|
|
475
|
+
* Lazy: the underlying `OpenAI` client is built on the first method call
|
|
476
|
+
* (or the first time a sub-namespace is touched) so env vars mutated after
|
|
477
|
+
* `createClient()` are still picked up — same pattern as `DatabaseModule`.
|
|
478
|
+
*
|
|
479
|
+
* Error model: every method returns `Result<T>`. Upstream `APIError`s are
|
|
480
|
+
* mapped to `SdkError` with stable `ai_*` codes. For streaming, the
|
|
481
|
+
* `Result` envelope only covers connection establishment; once the stream
|
|
482
|
+
* is open, mid-flight errors propagate through the iterator — callers must
|
|
483
|
+
* wrap their `for await` loop in `try/catch`. Aborts via `signal.abort()`
|
|
484
|
+
* surface as `ai_aborted` in non-streaming requests; in streaming, the
|
|
485
|
+
* iterator simply terminates (the upstream SDK swallows the abort).
|
|
486
|
+
*
|
|
487
|
+
* Test override: pass `clientOverride` to inject a pre-built `OpenAI`
|
|
488
|
+
* instance (or a stand-in) instead of letting the module build one. The
|
|
489
|
+
* override flows to all four namespaces — `models.list()` included —
|
|
490
|
+
* and to the cross-surface `shared()` accessor.
|
|
491
|
+
*
|
|
492
|
+
* Shared-handles pattern: `shared(): Result<OpenAI>` is the canonical
|
|
493
|
+
* accessor cross-surface SDK consumers (RAG today, memory tomorrow) use
|
|
494
|
+
* to reach the single OpenRouter client. Same instance every call; one
|
|
495
|
+
* credential, one base URL, one retry policy, one error shape. See
|
|
496
|
+
* README + CHANGELOG.
|
|
497
|
+
*/
|
|
498
|
+
declare class AiModule {
|
|
499
|
+
private readonly _resolved;
|
|
500
|
+
private _openai;
|
|
501
|
+
private _chat;
|
|
502
|
+
private _embeddings;
|
|
503
|
+
private _images;
|
|
504
|
+
private _models;
|
|
505
|
+
private readonly _gate;
|
|
506
|
+
constructor(_resolved: ResolvedConfig, clientOverride?: OpenAI,
|
|
507
|
+
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
508
|
+
gate?: ModuleGate);
|
|
509
|
+
get chat(): {
|
|
510
|
+
completions: ChatCompletionsNamespace;
|
|
200
511
|
};
|
|
512
|
+
get embeddings(): EmbeddingsNamespace;
|
|
513
|
+
get images(): ImagesNamespace;
|
|
514
|
+
get models(): ModelsNamespace;
|
|
515
|
+
/**
|
|
516
|
+
* Canonical accessor for the **shared-handles pattern** on the AI
|
|
517
|
+
* surface. Returns the lazily-constructed OpenRouter-flavoured `OpenAI`
|
|
518
|
+
* client backing `client.ai`. Cross-surface SDK consumers (RAG today,
|
|
519
|
+
* memory tomorrow) call this — never instantiate a parallel `OpenAI`
|
|
520
|
+
* client — to keep credential resolution, base-URL routing, retry /
|
|
521
|
+
* timeout policy, and token accounting on a single instance.
|
|
522
|
+
*
|
|
523
|
+
* Contract: same input → same instance. Calling `shared()` twice in
|
|
524
|
+
* the same process (with the same `ResolvedConfig`) returns the same
|
|
525
|
+
* reference. The Result envelope surfaces `openrouter_key_missing`
|
|
526
|
+
* when `OPENROUTER_API_KEY` is unset, matching the rest of the
|
|
527
|
+
* `client.ai.*` API; the gate is intentionally NOT consulted here
|
|
528
|
+
* (surfaces that gate behaviour gate it at their own call sites).
|
|
529
|
+
*
|
|
530
|
+
* The constructor's `clientOverride` flows through this accessor too,
|
|
531
|
+
* so tests that inject a fake `OpenAI` see consistent behaviour across
|
|
532
|
+
* `chat / embeddings / images / models` and any cross-surface
|
|
533
|
+
* consumer.
|
|
534
|
+
*/
|
|
535
|
+
shared(): Result<OpenAI>;
|
|
201
536
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
result: O;
|
|
209
|
-
};
|
|
210
|
-
interface ApprovalTool<I, O> {
|
|
211
|
-
name: string;
|
|
212
|
-
description: string;
|
|
213
|
-
parameters: Record<string, unknown>;
|
|
214
|
-
openaiSpec(): OpenAIToolSpec;
|
|
215
|
-
invoke(input: I): Promise<Result<ToolInvokeResult<O>>>;
|
|
537
|
+
declare class ChatCompletionsNamespace {
|
|
538
|
+
private readonly _getClient;
|
|
539
|
+
private readonly _gate;
|
|
540
|
+
constructor(_getClient: () => Result<OpenAI>, _gate: ModuleGate);
|
|
541
|
+
create(params: ChatCompletionCreateParamsStreaming, options?: RequestOptions$1): Promise<Result<Stream<ChatCompletionChunk>>>;
|
|
542
|
+
create(params: ChatCompletionCreateParamsNonStreaming, options?: RequestOptions$1): Promise<Result<ChatCompletion>>;
|
|
216
543
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
toleranceSeconds?: number;
|
|
223
|
-
/** Inject a clock for tests. Defaults to `Date.now`. */
|
|
224
|
-
now?: () => number;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
type ApprovalTopic = string;
|
|
228
|
-
declare const DECISION_STATUSES: readonly ["approved", "rejected", "timed_out", "cancelled"];
|
|
229
|
-
type DecisionStatus = (typeof DECISION_STATUSES)[number];
|
|
230
|
-
type ApprovalStatus = 'pending' | DecisionStatus;
|
|
231
|
-
type Decision<T = unknown> = {
|
|
232
|
-
status: 'approved' | 'rejected';
|
|
233
|
-
payload: T;
|
|
234
|
-
approver: ApproverInfo;
|
|
235
|
-
decidedAt: string;
|
|
236
|
-
reason?: string;
|
|
237
|
-
} | {
|
|
238
|
-
status: 'timed_out';
|
|
239
|
-
decidedAt: string;
|
|
240
|
-
} | {
|
|
241
|
-
status: 'cancelled';
|
|
242
|
-
decidedAt: string;
|
|
243
|
-
reason?: string;
|
|
244
|
-
};
|
|
245
|
-
interface ApproverInfo {
|
|
246
|
-
id: string;
|
|
247
|
-
email: string;
|
|
248
|
-
name?: string;
|
|
249
|
-
}
|
|
250
|
-
interface ApprovalRequestOptions<T = unknown> {
|
|
251
|
-
topic: ApprovalTopic;
|
|
252
|
-
payload: T;
|
|
253
|
-
title?: string;
|
|
254
|
-
description?: string;
|
|
255
|
-
/** Path on the agent the control plane will POST the decision to (resolved against the agent's public URL server-side). */
|
|
256
|
-
onDecide: string;
|
|
257
|
-
/** ISO 8601 duration (`'24h'`) or milliseconds. Default 24h. */
|
|
258
|
-
timeout?: string | number;
|
|
259
|
-
onTimeout?: 'reject' | 'approve' | 'ignore';
|
|
260
|
-
approver?: string;
|
|
261
|
-
/** Same `(topic, idempotencyKey)` returns the same `approvalId` instead of creating a new one. */
|
|
262
|
-
idempotencyKey?: string;
|
|
263
|
-
metadata?: Record<string, unknown>;
|
|
264
|
-
}
|
|
265
|
-
interface ApprovalRequest {
|
|
266
|
-
approvalId: string;
|
|
267
|
-
status: 'pending';
|
|
268
|
-
callbackUrl: string;
|
|
269
|
-
expiresAt: string;
|
|
270
|
-
}
|
|
271
|
-
interface ApprovalRecord<T = unknown> {
|
|
272
|
-
approvalId: string;
|
|
273
|
-
topic: ApprovalTopic;
|
|
274
|
-
status: ApprovalStatus;
|
|
275
|
-
payload: T;
|
|
276
|
-
decision?: Decision<T>;
|
|
277
|
-
createdAt: string;
|
|
278
|
-
expiresAt: string;
|
|
279
|
-
metadata?: Record<string, unknown>;
|
|
280
|
-
}
|
|
281
|
-
interface ApprovalListOptions {
|
|
282
|
-
status?: ApprovalStatus;
|
|
283
|
-
topic?: ApprovalTopic;
|
|
284
|
-
cursor?: string;
|
|
285
|
-
/** 1..100. Default 50. */
|
|
286
|
-
limit?: number;
|
|
287
|
-
}
|
|
288
|
-
interface ApprovalListResult<T = unknown> {
|
|
289
|
-
items: ApprovalRecord<T>[];
|
|
290
|
-
nextCursor?: string;
|
|
291
|
-
}
|
|
292
|
-
declare class ApprovalFacade {
|
|
293
|
-
private readonly _resolved;
|
|
294
|
-
private readonly _http;
|
|
295
|
-
private readonly _gate;
|
|
296
|
-
constructor(_resolved: ResolvedConfig, _http: HttpClient,
|
|
297
|
-
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
298
|
-
gate?: ModuleGate);
|
|
299
|
-
request<T = unknown>(options: ApprovalRequestOptions<T>): Promise<Result<ApprovalRequest>>;
|
|
300
|
-
cancel(approvalId: string, reason?: string): Promise<Result<void>>;
|
|
301
|
-
get<T = unknown>(approvalId: string): Promise<Result<ApprovalRecord<T>>>;
|
|
302
|
-
list<T = unknown>(options?: ApprovalListOptions): Promise<Result<ApprovalListResult<T>>>;
|
|
303
|
-
/**
|
|
304
|
-
* Local crypto verification — does not touch the datapath, so it is NOT
|
|
305
|
-
* gated by the contract handshake. Auditing rule: a method gates iff it
|
|
306
|
-
* issues an HTTP request to the configured baseUrl.
|
|
307
|
-
*/
|
|
308
|
-
verify<T = unknown>(request: Request, options?: VerifyOptions): Promise<Result<Decision<T>>>;
|
|
309
|
-
/**
|
|
310
|
-
* Pure factory — returns an `ApprovalTool` whose `invoke()` ultimately
|
|
311
|
-
* calls back into `ApprovalFacade.request`, so the gate fires there. Not
|
|
312
|
-
* gated here.
|
|
313
|
-
*/
|
|
314
|
-
tool<I, O>(spec: ApprovalToolSpec<I, O>): ApprovalTool<I, O>;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
declare class ConfigFacade {
|
|
318
|
-
private readonly _http;
|
|
319
|
-
private readonly _gate;
|
|
320
|
-
constructor(resolved: ResolvedConfig, _http: HttpClient,
|
|
321
|
-
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
322
|
-
gate?: ModuleGate);
|
|
323
|
-
get<T = unknown>(key: string): Promise<Result<T>>;
|
|
324
|
-
/**
|
|
325
|
-
* Keys absent from the agent's config come back as omissions in the
|
|
326
|
-
* response — the returned map only contains entries the control plane
|
|
327
|
-
* actually has, so callers must access fields with `?.` (the return type
|
|
328
|
-
* is `Partial<T>`).
|
|
329
|
-
*/
|
|
330
|
-
getMany<T extends Record<string, unknown> = Record<string, unknown>>(keys: string[]): Promise<Result<Partial<T>>>;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** OAuth connections (Notion, GDrive, Slack…). */
|
|
334
|
-
declare class ConnectionsFacade {
|
|
335
|
-
private readonly _resolved;
|
|
336
|
-
private readonly _http;
|
|
337
|
-
constructor(_resolved: ResolvedConfig, _http: HttpClient);
|
|
338
|
-
list(): Promise<Result<readonly never[]>>;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/** Emit events to the organization event bus. */
|
|
342
|
-
declare class EventsFacade {
|
|
343
|
-
private readonly _http;
|
|
344
|
-
private readonly _gate;
|
|
345
|
-
constructor(resolved: ResolvedConfig, _http: HttpClient,
|
|
346
|
-
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
347
|
-
gate?: ModuleGate);
|
|
348
|
-
emit(_name: string, _payload: unknown): Promise<Result<void>>;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* A prompt managed by the Stackbone control plane. Templates are plain strings
|
|
353
|
-
* with Mustache-style `{{var}}` placeholders — no conditionals or loops.
|
|
354
|
-
*/
|
|
355
|
-
interface Prompt {
|
|
356
|
-
name: string;
|
|
357
|
-
template: string;
|
|
358
|
-
/** Monotonically increasing per `name`. The first `create` produces version `1`. */
|
|
359
|
-
version: number;
|
|
360
|
-
/** Variable names parsed from the template. */
|
|
361
|
-
variables?: readonly string[];
|
|
362
|
-
metadata?: Record<string, unknown>;
|
|
363
|
-
/** ISO 8601 UTC timestamp. */
|
|
364
|
-
createdAt: string;
|
|
365
|
-
updatedAt: string;
|
|
366
|
-
}
|
|
367
|
-
interface GetPromptOptions {
|
|
368
|
-
/** Pin a specific version. Omitted -> latest. */
|
|
369
|
-
version?: number;
|
|
370
|
-
}
|
|
371
|
-
interface ListPromptsOptions {
|
|
372
|
-
/** 1..100. Default 50. */
|
|
373
|
-
limit?: number;
|
|
374
|
-
cursor?: string;
|
|
375
|
-
}
|
|
376
|
-
interface ListPromptsResult {
|
|
377
|
-
/** Latest version of each prompt. */
|
|
378
|
-
items: readonly Prompt[];
|
|
379
|
-
nextCursor?: string;
|
|
380
|
-
}
|
|
381
|
-
interface CreatePromptRequest {
|
|
382
|
-
name: string;
|
|
383
|
-
template: string;
|
|
384
|
-
metadata?: Record<string, unknown>;
|
|
385
|
-
}
|
|
386
|
-
interface UpdatePromptOptions {
|
|
387
|
-
/** New template. Bumps `version` by one. */
|
|
388
|
-
template?: string;
|
|
389
|
-
/** Shallow-merged onto the existing metadata. */
|
|
390
|
-
metadata?: Record<string, unknown>;
|
|
391
|
-
}
|
|
392
|
-
interface DeletePromptOptions {
|
|
393
|
-
/** Delete a specific version. Omitted -> delete every version under `name`. */
|
|
394
|
-
version?: number;
|
|
395
|
-
}
|
|
396
|
-
interface DeletePromptResult {
|
|
397
|
-
name: string;
|
|
398
|
-
/** Number of versions actually removed. */
|
|
399
|
-
deleted: number;
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* `client.prompts` — managed prompts for the agent. Prompts live outside the
|
|
403
|
-
* code so creators can edit, version and A/B test them without rebuilding the
|
|
404
|
-
* container. The first iteration is a scaffolding placeholder: every method
|
|
405
|
-
* returns `not_implemented`. The real backend will be the Stackbone control
|
|
406
|
-
* plane and the public surface defined here is the contract callers can
|
|
407
|
-
* already type against.
|
|
408
|
-
*/
|
|
409
|
-
declare class PromptsFacade {
|
|
410
|
-
private readonly _http;
|
|
411
|
-
constructor(_resolved: ResolvedConfig, _http: HttpClient);
|
|
412
|
-
get(_name: string, _options?: GetPromptOptions): Promise<Result<Prompt>>;
|
|
413
|
-
compile(_name: string, _variables: Record<string, unknown>, _options?: GetPromptOptions): Promise<Result<string>>;
|
|
414
|
-
list(_options?: ListPromptsOptions): Promise<Result<ListPromptsResult>>;
|
|
415
|
-
create(_request: CreatePromptRequest): Promise<Result<Prompt>>;
|
|
416
|
-
update(_name: string, _options: UpdatePromptOptions): Promise<Result<Prompt>>;
|
|
417
|
-
delete(_name: string, _options?: DeletePromptOptions): Promise<Result<DeletePromptResult>>;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
declare class SecretsFacade {
|
|
421
|
-
private readonly _http;
|
|
422
|
-
private readonly _gate;
|
|
423
|
-
constructor(resolved: ResolvedConfig, _http: HttpClient,
|
|
424
|
-
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
425
|
-
gate?: ModuleGate);
|
|
426
|
-
get(name: string): Promise<Result<string>>;
|
|
427
|
-
/**
|
|
428
|
-
* Names absent from the organization come back as omissions in the response —
|
|
429
|
-
* the returned map only contains entries the control plane actually has.
|
|
430
|
-
* Callers that need "all-or-nothing" semantics should diff the keys.
|
|
431
|
-
*/
|
|
432
|
-
getMany(names: string[]): Promise<Result<Record<string, string>>>;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Public surface behind `client.ai`. Wraps the official `openai` SDK with a
|
|
437
|
-
* `baseURL` override pointing to OpenRouter, so 300+ models are reachable
|
|
438
|
-
* with a single, OpenAI-compatible API.
|
|
439
|
-
*
|
|
440
|
-
* Lazy: the underlying `OpenAI` client is built on the first method call
|
|
441
|
-
* (or the first time a sub-namespace is touched) so env vars mutated after
|
|
442
|
-
* `createClient()` are still picked up — same pattern as `DatabaseModule`.
|
|
443
|
-
*
|
|
444
|
-
* Error model: every method returns `Result<T>`. Upstream `APIError`s are
|
|
445
|
-
* mapped to `SdkError` with stable `ai_*` codes. For streaming, the
|
|
446
|
-
* `Result` envelope only covers connection establishment; once the stream
|
|
447
|
-
* is open, mid-flight errors propagate through the iterator — callers must
|
|
448
|
-
* wrap their `for await` loop in `try/catch`. Aborts via `signal.abort()`
|
|
449
|
-
* surface as `ai_aborted` in non-streaming requests; in streaming, the
|
|
450
|
-
* iterator simply terminates (the upstream SDK swallows the abort).
|
|
451
|
-
*
|
|
452
|
-
* Test override: pass `clientOverride` to inject a pre-built `OpenAI`
|
|
453
|
-
* instance (or a stand-in) instead of letting the module build one. The
|
|
454
|
-
* override flows to all four namespaces — `models.list()` included.
|
|
455
|
-
*/
|
|
456
|
-
declare class AiModule {
|
|
457
|
-
private readonly _resolved;
|
|
458
|
-
private _openai;
|
|
459
|
-
private _chat;
|
|
460
|
-
private _embeddings;
|
|
461
|
-
private _images;
|
|
462
|
-
private _models;
|
|
463
|
-
private readonly _gate;
|
|
464
|
-
constructor(_resolved: ResolvedConfig, clientOverride?: OpenAI,
|
|
465
|
-
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
466
|
-
gate?: ModuleGate);
|
|
467
|
-
get chat(): {
|
|
468
|
-
completions: ChatCompletionsNamespace;
|
|
469
|
-
};
|
|
470
|
-
get embeddings(): EmbeddingsNamespace;
|
|
471
|
-
get images(): ImagesNamespace;
|
|
472
|
-
get models(): ModelsNamespace;
|
|
473
|
-
private client;
|
|
474
|
-
}
|
|
475
|
-
declare class ChatCompletionsNamespace {
|
|
476
|
-
private readonly _getClient;
|
|
477
|
-
private readonly _gate;
|
|
478
|
-
constructor(_getClient: () => Result<OpenAI>, _gate: ModuleGate);
|
|
479
|
-
create(params: ChatCompletionCreateParamsStreaming, options?: RequestOptions): Promise<Result<Stream<ChatCompletionChunk>>>;
|
|
480
|
-
create(params: ChatCompletionCreateParamsNonStreaming, options?: RequestOptions): Promise<Result<ChatCompletion>>;
|
|
481
|
-
}
|
|
482
|
-
declare class EmbeddingsNamespace {
|
|
483
|
-
private readonly _getClient;
|
|
484
|
-
private readonly _gate;
|
|
485
|
-
constructor(_getClient: () => Result<OpenAI>, _gate: ModuleGate);
|
|
486
|
-
create(params: EmbeddingCreateParams, options?: RequestOptions): Promise<Result<CreateEmbeddingResponse>>;
|
|
544
|
+
declare class EmbeddingsNamespace {
|
|
545
|
+
private readonly _getClient;
|
|
546
|
+
private readonly _gate;
|
|
547
|
+
constructor(_getClient: () => Result<OpenAI>, _gate: ModuleGate);
|
|
548
|
+
create(params: EmbeddingCreateParams, options?: RequestOptions$1): Promise<Result<CreateEmbeddingResponse>>;
|
|
487
549
|
}
|
|
488
550
|
interface ImageGenerateParams {
|
|
489
551
|
model: string;
|
|
@@ -520,7 +582,7 @@ declare class ImagesNamespace {
|
|
|
520
582
|
* empty success — surfacing the failure to the caller instead of
|
|
521
583
|
* silently producing zero images.
|
|
522
584
|
*/
|
|
523
|
-
generate(params: ImageGenerateParams, options?: RequestOptions): Promise<Result<ImagesResponse>>;
|
|
585
|
+
generate(params: ImageGenerateParams, options?: RequestOptions$1): Promise<Result<ImagesResponse>>;
|
|
524
586
|
}
|
|
525
587
|
interface OpenRouterModel {
|
|
526
588
|
id: string;
|
|
@@ -550,432 +612,12 @@ declare class ModelsNamespace {
|
|
|
550
612
|
* the namespaces — and any `clientOverride` injected for tests applies
|
|
551
613
|
* here too.
|
|
552
614
|
*/
|
|
553
|
-
list(options?: RequestOptions): Promise<Result<ModelsListResponse>>;
|
|
615
|
+
list(options?: RequestOptions$1): Promise<Result<ModelsListResponse>>;
|
|
554
616
|
}
|
|
555
|
-
interface RequestOptions {
|
|
617
|
+
interface RequestOptions$1 {
|
|
556
618
|
signal?: AbortSignal;
|
|
557
619
|
}
|
|
558
620
|
|
|
559
|
-
/**
|
|
560
|
-
* Public surface a creator can use through `client.database`. We pin it to the
|
|
561
|
-
* `postgres-js` Drizzle wrapper because that is the only driver `@stackbone/sdk`
|
|
562
|
-
* ships and the only one `STACKBONE_POSTGRES_URL` is contracted to point at.
|
|
563
|
-
*
|
|
564
|
-
* Re-exporting Drizzle's own type instead of inventing a structural alias keeps
|
|
565
|
-
* the surface 1:1 with what `drizzle-orm` documents and what `@stackbone/sdk/db`
|
|
566
|
-
* re-exports — so types flow end to end without translation.
|
|
567
|
-
*/
|
|
568
|
-
type DrizzleClient = PostgresJsDatabase<Record<string, never>> & {
|
|
569
|
-
$client: Sql;
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* `client.database` is a lazy wrapper over a `drizzle()` instance bound to
|
|
574
|
-
* `STACKBONE_POSTGRES_URL`. The underlying postgres-js connection and the
|
|
575
|
-
* Drizzle facade are constructed on the first method call so env vars rotated
|
|
576
|
-
* post-`createClient()` are still honoured.
|
|
577
|
-
*
|
|
578
|
-
* Native Drizzle methods (`select`, `insert`, `update`, `delete`, `transaction`,
|
|
579
|
-
* `execute`) are exposed verbatim — we explicitly avoid wrapping them in a
|
|
580
|
-
* Result envelope here. Drizzle already throws on misuse and returns typed rows
|
|
581
|
-
* on success; mirroring its API keeps the contract identical to what the
|
|
582
|
-
* `@stackbone/sdk/db` re-exports document, so creators write idiomatic Drizzle.
|
|
583
|
-
*
|
|
584
|
-
* Missing `STACKBONE_POSTGRES_URL` raises `SdkError('database_not_configured')`
|
|
585
|
-
* with an actionable hint that points to `stackbone dev`. This is a creator
|
|
586
|
-
* setup bug, not something the agent's code can recover from at runtime.
|
|
587
|
-
*
|
|
588
|
-
* Methods are declared with their full Drizzle generic signatures (rather than
|
|
589
|
-
* `(...args) => Drizzle[k](...args)` arrow properties) so the inferred row
|
|
590
|
-
* types from `pgTable(...)` flow end-to-end into the caller, which is the
|
|
591
|
-
* promise the `@stackbone/sdk/db` re-export makes.
|
|
592
|
-
*
|
|
593
|
-
* The actual `postgres()` pool and the Drizzle handle are owned by the
|
|
594
|
-
* module-internal singleton in `./internal.ts`. `client.database` and any
|
|
595
|
-
* other internal SDK module that imports `getDatabaseHandle` therefore share
|
|
596
|
-
* one pool per agent process — see `internal.spec.ts` for the invariant.
|
|
597
|
-
*/
|
|
598
|
-
declare class DatabaseModule {
|
|
599
|
-
private readonly _resolved;
|
|
600
|
-
private readonly _gate;
|
|
601
|
-
constructor(_resolved: ResolvedConfig,
|
|
602
|
-
/**
|
|
603
|
-
* Test seam — see `ModuleGate`. Defaults to the lazy contract gate.
|
|
604
|
-
* The gate is awaited at the terminal `await` of every Drizzle chain
|
|
605
|
-
* (`select/insert/update/delete/execute/transaction`); when it returns
|
|
606
|
-
* an error, a tagged `GateBlockedError` is thrown so callers see the
|
|
607
|
-
* same `Error & { code, message, meta }` surface as the existing
|
|
608
|
-
* `database_not_configured` setup-bug.
|
|
609
|
-
*/
|
|
610
|
-
gate?: ModuleGate);
|
|
611
|
-
/**
|
|
612
|
-
* Escape hatch — returns Drizzle's relational query builder verbatim. The
|
|
613
|
-
* contract gate does NOT fire here because the surface is a synchronous
|
|
614
|
-
* accessor; the user can still bypass the gate via `client.database.raw()`
|
|
615
|
-
* for the same reason. Audit: `query` and `raw()` are intentionally
|
|
616
|
-
* ungated escape hatches; the standard `select/insert/update/delete/
|
|
617
|
-
* execute/transaction` path is fully gated.
|
|
618
|
-
*/
|
|
619
|
-
get query(): DrizzleClient['query'];
|
|
620
|
-
select: DrizzleClient['select'];
|
|
621
|
-
insert: DrizzleClient['insert'];
|
|
622
|
-
update: DrizzleClient['update'];
|
|
623
|
-
delete: DrizzleClient['delete'];
|
|
624
|
-
execute: DrizzleClient['execute'];
|
|
625
|
-
transaction: DrizzleClient['transaction'];
|
|
626
|
-
/**
|
|
627
|
-
* Escape hatch for callers that want the raw Drizzle handle (e.g. to pass
|
|
628
|
-
* it to a library that expects `PostgresJsDatabase`). Reading this property
|
|
629
|
-
* triggers the same lazy initialisation as any other method but
|
|
630
|
-
* intentionally does NOT consult the contract gate — the surface is sync
|
|
631
|
-
* and the caller is opting out of the SDK's structured surface.
|
|
632
|
-
*/
|
|
633
|
-
raw(): DrizzleClient;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* Where a memory lives. Default `'user'`. `'session'` is short-lived and
|
|
638
|
-
* collapsed (or dropped) by `endSession()`; `'agent'` is shared across every
|
|
639
|
-
* user of the agent.
|
|
640
|
-
*/
|
|
641
|
-
type MemoryScope = 'user' | 'session' | 'agent';
|
|
642
|
-
/**
|
|
643
|
-
* Either raw text or an OpenAI-shaped conversation. The future mem0 backend
|
|
644
|
-
* accepts both: strings ingest verbatim, message arrays are summarised into
|
|
645
|
-
* one or more facts.
|
|
646
|
-
*/
|
|
647
|
-
type MemoryContent = string | ChatCompletionMessageParam[];
|
|
648
|
-
interface AddMemoryRequest {
|
|
649
|
-
userId: string;
|
|
650
|
-
/** Conversation/session bucket. Required when `scope: 'session'`. */
|
|
651
|
-
sessionId?: string;
|
|
652
|
-
/** Default `'user'`. */
|
|
653
|
-
scope?: MemoryScope;
|
|
654
|
-
metadata?: Record<string, unknown>;
|
|
655
|
-
}
|
|
656
|
-
interface MemoryItem {
|
|
657
|
-
id: string;
|
|
658
|
-
content: string;
|
|
659
|
-
userId: string;
|
|
660
|
-
sessionId?: string;
|
|
661
|
-
scope: MemoryScope;
|
|
662
|
-
metadata?: Record<string, unknown>;
|
|
663
|
-
/** ISO 8601 UTC timestamp. */
|
|
664
|
-
createdAt: string;
|
|
665
|
-
updatedAt: string;
|
|
666
|
-
}
|
|
667
|
-
interface SearchMemoryOptions {
|
|
668
|
-
userId?: string;
|
|
669
|
-
sessionId?: string;
|
|
670
|
-
/** 1..100. Default 10. */
|
|
671
|
-
limit?: number;
|
|
672
|
-
/** Minimum cosine similarity in [0, 1]. Hits below this are dropped. */
|
|
673
|
-
threshold?: number;
|
|
674
|
-
/** Metadata predicates AND-ed to the semantic match. */
|
|
675
|
-
filters?: Record<string, unknown>;
|
|
676
|
-
/** Restrict the search to a subset of scopes. Default: every scope. */
|
|
677
|
-
includeScopes?: readonly MemoryScope[];
|
|
678
|
-
}
|
|
679
|
-
interface MemoryHit extends MemoryItem {
|
|
680
|
-
/** Cosine similarity in [0, 1]. */
|
|
681
|
-
score: number;
|
|
682
|
-
}
|
|
683
|
-
interface ListMemoryRequest {
|
|
684
|
-
userId: string;
|
|
685
|
-
/** 1..100. Default 50. */
|
|
686
|
-
limit?: number;
|
|
687
|
-
cursor?: string;
|
|
688
|
-
}
|
|
689
|
-
interface ListMemoryResult {
|
|
690
|
-
items: readonly MemoryItem[];
|
|
691
|
-
nextCursor?: string;
|
|
692
|
-
}
|
|
693
|
-
interface UpdateMemoryOptions {
|
|
694
|
-
content?: string;
|
|
695
|
-
/** Shallow-merged onto the existing metadata (mem0 semantics). */
|
|
696
|
-
metadata?: Record<string, unknown>;
|
|
697
|
-
}
|
|
698
|
-
interface DeleteAllMemoryRequest {
|
|
699
|
-
userId: string;
|
|
700
|
-
}
|
|
701
|
-
type MemoryHistoryEvent = 'created' | 'updated' | 'accessed' | 'deleted';
|
|
702
|
-
interface MemoryHistoryEntry {
|
|
703
|
-
id: string;
|
|
704
|
-
memoryId: string;
|
|
705
|
-
event: MemoryHistoryEvent;
|
|
706
|
-
/** ISO 8601 UTC timestamp. */
|
|
707
|
-
at: string;
|
|
708
|
-
/** Identifier of who triggered the event (user id, agent id, etc.). */
|
|
709
|
-
actor?: string;
|
|
710
|
-
/** Content snapshot before the event. Only set for `updated`/`deleted`. */
|
|
711
|
-
before?: string;
|
|
712
|
-
/** Content snapshot after the event. Only set for `created`/`updated`. */
|
|
713
|
-
after?: string;
|
|
714
|
-
metadata?: Record<string, unknown>;
|
|
715
|
-
}
|
|
716
|
-
interface EndSessionOptions {
|
|
717
|
-
/** Persist session-scoped facts to long-term (`'user'`) memory before closing. Default `true`. */
|
|
718
|
-
persist?: boolean;
|
|
719
|
-
}
|
|
720
|
-
interface EndSessionResult {
|
|
721
|
-
sessionId: string;
|
|
722
|
-
/** Number of session memories promoted to long-term storage. `0` when `persist: false`. */
|
|
723
|
-
persisted: number;
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* `client.memory` — long-term memory for the agent. The first iteration is a
|
|
727
|
-
* scaffolding placeholder: every method returns `not_implemented`. The real
|
|
728
|
-
* backend will be mem0 (`mem0ApiKey` / `MEM0_API_KEY`) and the public surface
|
|
729
|
-
* defined here is the contract callers can already type against.
|
|
730
|
-
*/
|
|
731
|
-
declare class MemoryModule {
|
|
732
|
-
private readonly _resolved;
|
|
733
|
-
constructor(_resolved: ResolvedConfig);
|
|
734
|
-
add(_content: MemoryContent, _request: AddMemoryRequest): Promise<Result<MemoryItem>>;
|
|
735
|
-
search(_query: string, _options?: SearchMemoryOptions): Promise<Result<readonly MemoryHit[]>>;
|
|
736
|
-
delete(_memoryId: string): Promise<Result<{
|
|
737
|
-
id: string;
|
|
738
|
-
}>>;
|
|
739
|
-
deleteAll(_request: DeleteAllMemoryRequest): Promise<Result<{
|
|
740
|
-
deleted: number;
|
|
741
|
-
}>>;
|
|
742
|
-
get(_memoryId: string): Promise<Result<MemoryItem>>;
|
|
743
|
-
list(_request: ListMemoryRequest): Promise<Result<ListMemoryResult>>;
|
|
744
|
-
update(_memoryId: string, _options: UpdateMemoryOptions): Promise<Result<MemoryItem>>;
|
|
745
|
-
history(_memoryId: string): Promise<Result<readonly MemoryHistoryEntry[]>>;
|
|
746
|
-
endSession(_sessionId: string, _options?: EndSessionOptions): Promise<Result<EndSessionResult>>;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
declare const RUN_STEP_TYPES: readonly ["agent", "llm_call", "db_query", "http_fetch", "queue_publish", "hitl_pause", "tool_call", "rag_query", "storage_op"];
|
|
750
|
-
type RunStepType = (typeof RUN_STEP_TYPES)[number];
|
|
751
|
-
declare const STEP_TYPE_ATTRIBUTE = "stackbone.step.type";
|
|
752
|
-
declare const RUN_ID_ATTRIBUTE = "stackbone.run.id";
|
|
753
|
-
/**
|
|
754
|
-
* Minimal duck-typed view of an OpenTelemetry `ReadableSpan`. We mirror the
|
|
755
|
-
* shape the OTel SDK exposes on `@opentelemetry/sdk-trace-base` rather than
|
|
756
|
-
* importing the package so `@stackbone/sdk` doesn't pull the OTel runtime into
|
|
757
|
-
* every agent that doesn't enable Studio. Agents wire the processor with
|
|
758
|
-
* `provider.addSpanProcessor(new RunStepsSpanProcessor(...))` and the
|
|
759
|
-
* structural match satisfies TypeScript.
|
|
760
|
-
*/
|
|
761
|
-
interface SpanContextLike {
|
|
762
|
-
spanId: string;
|
|
763
|
-
traceId: string;
|
|
764
|
-
}
|
|
765
|
-
interface ReadableSpanLike {
|
|
766
|
-
spanContext(): SpanContextLike;
|
|
767
|
-
parentSpanContext?: SpanContextLike | undefined;
|
|
768
|
-
parentSpanId?: string | undefined;
|
|
769
|
-
name: string;
|
|
770
|
-
attributes: Record<string, unknown>;
|
|
771
|
-
status?: {
|
|
772
|
-
code: number;
|
|
773
|
-
message?: string;
|
|
774
|
-
} | undefined;
|
|
775
|
-
startTime?: [number, number] | undefined;
|
|
776
|
-
endTime?: [number, number] | undefined;
|
|
777
|
-
duration?: [number, number] | undefined;
|
|
778
|
-
}
|
|
779
|
-
interface PostgresLike {
|
|
780
|
-
unsafe(query: string, params?: unknown[]): Promise<unknown>;
|
|
781
|
-
end?(): Promise<void>;
|
|
782
|
-
}
|
|
783
|
-
interface RunStepsSpanProcessorOptions {
|
|
784
|
-
/**
|
|
785
|
-
* Postgres connection string of the install (local emulator at :5433 in
|
|
786
|
-
* dev, Neon TCP/HTTP URL in cloud). Falls back to `DATABASE_URL`.
|
|
787
|
-
*/
|
|
788
|
-
connectionString?: string | undefined;
|
|
789
|
-
/** Flush trigger by buffer size. Defaults to 50. */
|
|
790
|
-
flushBatchSize?: number | undefined;
|
|
791
|
-
/** Flush trigger by elapsed time (ms). Defaults to 500. */
|
|
792
|
-
flushIntervalMs?: number | undefined;
|
|
793
|
-
/** Override how a span maps to a step type. */
|
|
794
|
-
resolveStepType?: ((span: ReadableSpanLike) => RunStepType) | undefined;
|
|
795
|
-
/** Test seam — inject a postgres-js-compatible client. */
|
|
796
|
-
sql?: PostgresLike | undefined;
|
|
797
|
-
/** Test seam — deterministic UUID generator. */
|
|
798
|
-
newId?: (() => string) | undefined;
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Materialises every OTel span produced by the agent into a row of
|
|
802
|
-
* `stackbone_platform.run_steps`. Designed to coexist with the OTLP exporter:
|
|
803
|
-
* cloud builds register both processors so Postgres feeds Studio (paridad
|
|
804
|
-
* con local) and Tempo feeds Platform Ops with retention/aggregation. If a
|
|
805
|
-
* write fails the processor logs to stderr and keeps accepting spans —
|
|
806
|
-
* observability is never load-bearing for agent execution.
|
|
807
|
-
*
|
|
808
|
-
* Spec: docs/arquitectura/specs/stackbone-agent-protocol-v1.md §6.3
|
|
809
|
-
* ADR: docs/arquitectura/decisiones/2026-05-03-stackbone-studio-datapath-y-scope-mvp.md §D9
|
|
810
|
-
*/
|
|
811
|
-
declare class RunStepsSpanProcessor {
|
|
812
|
-
private readonly buffer;
|
|
813
|
-
private readonly stepIdBySpanId;
|
|
814
|
-
private readonly batchSize;
|
|
815
|
-
private readonly intervalMs;
|
|
816
|
-
private readonly resolveStepType;
|
|
817
|
-
private readonly newId;
|
|
818
|
-
private readonly connectionString;
|
|
819
|
-
private readonly ownsSql;
|
|
820
|
-
private sql;
|
|
821
|
-
private timer;
|
|
822
|
-
private inFlight;
|
|
823
|
-
constructor(options?: RunStepsSpanProcessorOptions);
|
|
824
|
-
onStart(span: ReadableSpanLike): void;
|
|
825
|
-
onEnd(span: ReadableSpanLike): void;
|
|
826
|
-
forceFlush(): Promise<void>;
|
|
827
|
-
shutdown(): Promise<void>;
|
|
828
|
-
private buildRow;
|
|
829
|
-
private armTimer;
|
|
830
|
-
private disarmTimer;
|
|
831
|
-
private flush;
|
|
832
|
-
private ensureSql;
|
|
833
|
-
private writeBatch;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
interface AggregateRunCostOptions {
|
|
837
|
-
sql: PostgresLike;
|
|
838
|
-
}
|
|
839
|
-
interface AggregateRunCostResult {
|
|
840
|
-
costEstimatedUsd: number;
|
|
841
|
-
}
|
|
842
|
-
declare function aggregateRunCost(runId: string, options: AggregateRunCostOptions): Promise<AggregateRunCostResult>;
|
|
843
|
-
|
|
844
|
-
declare const LOG_LEVELS: {
|
|
845
|
-
readonly trace: 10;
|
|
846
|
-
readonly debug: 20;
|
|
847
|
-
readonly info: 30;
|
|
848
|
-
readonly warn: 40;
|
|
849
|
-
readonly error: 50;
|
|
850
|
-
readonly fatal: 60;
|
|
851
|
-
};
|
|
852
|
-
type LogLevelName = keyof typeof LOG_LEVELS;
|
|
853
|
-
type LogLevelNumber = (typeof LOG_LEVELS)[LogLevelName];
|
|
854
|
-
interface LogRecord {
|
|
855
|
-
/** Pino-style numeric level. */
|
|
856
|
-
level: LogLevelNumber;
|
|
857
|
-
/** Epoch milliseconds. Pino uses `time` as a number. */
|
|
858
|
-
time: number;
|
|
859
|
-
msg: string;
|
|
860
|
-
run_id: string;
|
|
861
|
-
trace_id?: string;
|
|
862
|
-
installation_id?: string;
|
|
863
|
-
agent_id?: string;
|
|
864
|
-
[key: string]: unknown;
|
|
865
|
-
}
|
|
866
|
-
interface PlatformLoggerOptions {
|
|
867
|
-
runId: string;
|
|
868
|
-
installationId?: string | undefined;
|
|
869
|
-
agentId?: string | undefined;
|
|
870
|
-
/**
|
|
871
|
-
* When set (cloud), logs are batched and POSTed to `<endpoint>/v1/logs` as
|
|
872
|
-
* OTLP/HTTP/JSON. When unset (local), logs are appended to
|
|
873
|
-
* `<runsDir>/<runId>.jsonl`. Falls back to `OTEL_EXPORTER_OTLP_ENDPOINT`.
|
|
874
|
-
*/
|
|
875
|
-
otelEndpoint?: string | undefined;
|
|
876
|
-
/** Local-only: directory where JSONL files live. Defaults to `~/.stackbone/dev/runs`. */
|
|
877
|
-
runsDir?: string | undefined;
|
|
878
|
-
/** Cloud-only: extra OTel resource attributes to send with every batch. */
|
|
879
|
-
resourceAttributes?: Record<string, string> | undefined;
|
|
880
|
-
/** Cloud-only: HTTP timeout per OTLP POST in ms. Defaults to 5_000. */
|
|
881
|
-
httpTimeoutMs?: number | undefined;
|
|
882
|
-
/** Flush trigger by buffer size. Defaults to 50. */
|
|
883
|
-
flushBatchSize?: number | undefined;
|
|
884
|
-
/** Flush trigger by elapsed time (ms). Defaults to 1_000. */
|
|
885
|
-
flushIntervalMs?: number | undefined;
|
|
886
|
-
/** Test seam — fetch implementation. Defaults to global `fetch`. */
|
|
887
|
-
fetchImpl?: typeof fetch | undefined;
|
|
888
|
-
/** Test seam — `fs.appendFile`-shaped sink the local destination uses. */
|
|
889
|
-
appendFileImpl?: ((path: string, data: string) => Promise<void>) | undefined;
|
|
890
|
-
/** Test seam — `fs.mkdir`-shaped helper the local destination uses. */
|
|
891
|
-
mkdirImpl?: ((path: string) => Promise<void>) | undefined;
|
|
892
|
-
/** Test seam — clock for `time` field. Defaults to `Date.now`. */
|
|
893
|
-
now?: (() => number) | undefined;
|
|
894
|
-
/** Override mode resolution (skips the env detection). */
|
|
895
|
-
mode?: PlatformLoggerMode | undefined;
|
|
896
|
-
}
|
|
897
|
-
type PlatformLoggerMode = 'local' | 'cloud';
|
|
898
|
-
interface PinoLike {
|
|
899
|
-
trace(msgOrObj: unknown, msg?: string): void;
|
|
900
|
-
debug(msgOrObj: unknown, msg?: string): void;
|
|
901
|
-
info(msgOrObj: unknown, msg?: string): void;
|
|
902
|
-
warn(msgOrObj: unknown, msg?: string): void;
|
|
903
|
-
error(msgOrObj: unknown, msg?: string): void;
|
|
904
|
-
fatal(msgOrObj: unknown, msg?: string): void;
|
|
905
|
-
}
|
|
906
|
-
interface PlatformLogger extends PinoLike {
|
|
907
|
-
readonly mode: PlatformLoggerMode;
|
|
908
|
-
/** Bind extra fields and return a child logger sharing the same destination. */
|
|
909
|
-
child(bindings: Record<string, unknown>): PlatformLogger;
|
|
910
|
-
/** Force-flush pending writes (close file handle or POST OTLP batch). */
|
|
911
|
-
flush(): Promise<void>;
|
|
912
|
-
/** Stop accepting writes and release resources. */
|
|
913
|
-
close(): Promise<void>;
|
|
914
|
-
}
|
|
915
|
-
/**
|
|
916
|
-
* Creates a Pino-aligned logger that writes JSONL to disk in local mode and
|
|
917
|
-
* batches OTLP/HTTP/JSON log records to an OTel collector in cloud mode. Mode
|
|
918
|
-
* is resolved from `OTEL_EXPORTER_OTLP_ENDPOINT` or the explicit override.
|
|
919
|
-
*/
|
|
920
|
-
declare function createPlatformLogger(options: PlatformLoggerOptions): PlatformLogger;
|
|
921
|
-
declare function defaultRunsDir(): string;
|
|
922
|
-
|
|
923
|
-
declare class ObservabilityModule {
|
|
924
|
-
private readonly _resolved;
|
|
925
|
-
private _processor?;
|
|
926
|
-
private readonly _loggers;
|
|
927
|
-
private _sql;
|
|
928
|
-
private _sqlResolved;
|
|
929
|
-
constructor(_resolved: ResolvedConfig);
|
|
930
|
-
/**
|
|
931
|
-
* Singleton SpanProcessor that the agent registers in its OTel
|
|
932
|
-
* TracerProvider. Coexists with the OTLP exporter — cloud builds wire
|
|
933
|
-
* both so Postgres feeds Studio (paridad con local) and Tempo feeds
|
|
934
|
-
* Platform Ops.
|
|
935
|
-
*/
|
|
936
|
-
spanProcessor(options?: RunStepsSpanProcessorOptions): RunStepsSpanProcessor;
|
|
937
|
-
flush(): Promise<Result<void>>;
|
|
938
|
-
/**
|
|
939
|
-
* Per-run structured logger. In local mode (no `OTEL_EXPORTER_OTLP_ENDPOINT`)
|
|
940
|
-
* appends JSONL to `~/.stackbone/dev/runs/<runId>.jsonl`; in cloud mode batches
|
|
941
|
-
* OTLP/HTTP/JSON log records to the collector. Cached by `runId` so the
|
|
942
|
-
* file handle / buffered timer stays stable across repeated calls inside
|
|
943
|
-
* the same run.
|
|
944
|
-
*/
|
|
945
|
-
logger(options: {
|
|
946
|
-
runId: string;
|
|
947
|
-
} & Partial<PlatformLoggerOptions>): PlatformLogger;
|
|
948
|
-
/** Closes the per-run logger (flushes the JSONL file or the OTLP buffer). */
|
|
949
|
-
closeLogger(runId: string): Promise<void>;
|
|
950
|
-
/**
|
|
951
|
-
* Hook the agent calls when a run finishes. Sums `cost_usd` from every
|
|
952
|
-
* `llm_call` span of the run and writes the total to
|
|
953
|
-
* `stackbone_platform.runs.cost_estimated_usd`. Errors are folded into the
|
|
954
|
-
* Result envelope so the agent's run cleanup never aborts on a flaky
|
|
955
|
-
* Postgres call — the cost is decorative metadata, not load-bearing.
|
|
956
|
-
*/
|
|
957
|
-
closeRun(runId: string): Promise<Result<AggregateRunCostResult>>;
|
|
958
|
-
private resolveSql;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
interface PublishRequest {
|
|
962
|
-
url: string;
|
|
963
|
-
body: unknown;
|
|
964
|
-
retries?: number;
|
|
965
|
-
delay?: number;
|
|
966
|
-
deduplicationId?: string;
|
|
967
|
-
headers?: Record<string, string>;
|
|
968
|
-
}
|
|
969
|
-
declare class QueuesModule {
|
|
970
|
-
private readonly _gate;
|
|
971
|
-
constructor(resolved: ResolvedConfig,
|
|
972
|
-
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
973
|
-
gate?: ModuleGate);
|
|
974
|
-
publish(_request: PublishRequest): Promise<Result<{
|
|
975
|
-
messageId: string;
|
|
976
|
-
}>>;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
621
|
type ChunkStrategy = 'recursive' | 'sentence';
|
|
980
622
|
interface ChunkOptions {
|
|
981
623
|
/** Splitter algorithm. Default `recursive`. */
|
|
@@ -1158,10 +800,12 @@ interface IngestAsyncHandle {
|
|
|
1158
800
|
* configuration errors. See ADR `2026-05-10-rag-consolidation-on-client-database`.
|
|
1159
801
|
*
|
|
1160
802
|
* Connection ownership: this module never opens its own postgres pool. It
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
803
|
+
* reaches the underlying `postgres-js` `Sql` through the **shared-handles
|
|
804
|
+
* pattern** — `client.database.shared().$client` — so an agent that
|
|
1163
805
|
* touches both surfaces still opens exactly one connection pool against
|
|
1164
|
-
* `STACKBONE_POSTGRES_URL`.
|
|
806
|
+
* `STACKBONE_POSTGRES_URL`. RAG is the canonical first cross-surface
|
|
807
|
+
* consumer; memory and queues will adopt the same accessor when they
|
|
808
|
+
* land.
|
|
1165
809
|
*
|
|
1166
810
|
* Schema readiness: starting with feature 30, the canonical schema is
|
|
1167
811
|
* installed by the CLI (`stackbone db migrate add-rag` + `migrate up`). When
|
|
@@ -1174,18 +818,49 @@ interface IngestAsyncHandle {
|
|
|
1174
818
|
* shared-pool lookup and the SQL-backed job writer.
|
|
1175
819
|
*/
|
|
1176
820
|
interface RagModuleTestDeps {
|
|
1177
|
-
/**
|
|
821
|
+
/**
|
|
822
|
+
* Replaces the `client.database.shared().$client` lookup used by
|
|
823
|
+
* `ingestAsync`. Tests can plug a fake `Sql` here without standing up
|
|
824
|
+
* the database surface.
|
|
825
|
+
*/
|
|
1178
826
|
sqlProvider?: () => Result<Sql>;
|
|
1179
827
|
/** Builds an `IngestJobWriter` from the resolved SQL. Defaults to `createSqlJobWriter`. */
|
|
1180
828
|
jobWriterFactory?: (sql: Sql) => IngestJobWriter;
|
|
1181
829
|
}
|
|
1182
830
|
declare class RagModule {
|
|
1183
831
|
private readonly _resolved;
|
|
832
|
+
/**
|
|
833
|
+
* Lazy accessor for `client.database`. RAG calls `getDatabase().shared()`
|
|
834
|
+
* to reach the process-wide Drizzle handle (and its `$client` Sql) via
|
|
835
|
+
* the shared-handles pattern — never reaches into a sibling module's
|
|
836
|
+
* implementation file.
|
|
837
|
+
*/
|
|
838
|
+
private readonly _getDatabase;
|
|
839
|
+
/**
|
|
840
|
+
* Lazy accessor for `client.ai`. RAG calls `getAi()` for the embeddings
|
|
841
|
+
* namespace; the `Embedder` adapter reaches the shared OpenAI client
|
|
842
|
+
* through the same `client.ai.embeddings.create` public surface, so
|
|
843
|
+
* RAG never instantiates a parallel `OpenAI` client.
|
|
844
|
+
*/
|
|
1184
845
|
private readonly _getAi;
|
|
1185
846
|
private readonly _gate;
|
|
1186
847
|
private readonly _testSqlOverride?;
|
|
1187
848
|
private readonly _testJobWriterFactory?;
|
|
1188
|
-
constructor(_resolved: ResolvedConfig,
|
|
849
|
+
constructor(_resolved: ResolvedConfig,
|
|
850
|
+
/**
|
|
851
|
+
* Lazy accessor for `client.database`. RAG calls `getDatabase().shared()`
|
|
852
|
+
* to reach the process-wide Drizzle handle (and its `$client` Sql) via
|
|
853
|
+
* the shared-handles pattern — never reaches into a sibling module's
|
|
854
|
+
* implementation file.
|
|
855
|
+
*/
|
|
856
|
+
_getDatabase: () => DatabaseModule,
|
|
857
|
+
/**
|
|
858
|
+
* Lazy accessor for `client.ai`. RAG calls `getAi()` for the embeddings
|
|
859
|
+
* namespace; the `Embedder` adapter reaches the shared OpenAI client
|
|
860
|
+
* through the same `client.ai.embeddings.create` public surface, so
|
|
861
|
+
* RAG never instantiates a parallel `OpenAI` client.
|
|
862
|
+
*/
|
|
863
|
+
_getAi: () => AiModule,
|
|
1189
864
|
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
1190
865
|
gate?: ModuleGate, testDeps?: RagModuleTestDeps);
|
|
1191
866
|
ingest(request: IngestRequest): Promise<Result<IngestResponse>>;
|
|
@@ -1199,9 +874,10 @@ declare class RagModule {
|
|
|
1199
874
|
* polls between chunk batches and bails out with `rag_ingest_cancelled`.
|
|
1200
875
|
*
|
|
1201
876
|
* Connection ownership: the `IngestJobWriter` is bound to the same SQL
|
|
1202
|
-
* pulled off the shared `client.database` handle, so a single
|
|
1203
|
-
* issues `ingestAsync` and `client.database.select` in
|
|
1204
|
-
* exactly one pool against
|
|
877
|
+
* pulled off the shared `client.database.shared()` handle, so a single
|
|
878
|
+
* agent that issues `ingestAsync` and `client.database.select` in
|
|
879
|
+
* flight still opens exactly one pool against
|
|
880
|
+
* `STACKBONE_POSTGRES_URL`.
|
|
1205
881
|
*/
|
|
1206
882
|
ingestAsync(request: IngestRequest): Promise<Result<IngestAsyncHandle>>;
|
|
1207
883
|
delete(ids: string | string[], options?: DeleteOptions): Promise<Result<DeleteResponse>>;
|
|
@@ -1225,159 +901,1071 @@ declare class RagModule {
|
|
|
1225
901
|
*/
|
|
1226
902
|
parse(input: ParseInput, options?: ParseOptions): Promise<string>;
|
|
1227
903
|
/**
|
|
1228
|
-
* Resolves the postgres-js `Sql` to use for this operation.
|
|
1229
|
-
*
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
904
|
+
* Resolves the postgres-js `Sql` to use for this operation. Goes through
|
|
905
|
+
* the **shared-handles pattern** — `client.database.shared().$client` —
|
|
906
|
+
* to pull the postgres-js template-tag function off the same Drizzle
|
|
907
|
+
* handle the rest of the SDK uses. Single pool per agent process.
|
|
908
|
+
* Future cross-module transaction propagation plugs a tx-bound `Sql`
|
|
909
|
+
* into the pipeline at the same call site, replacing the pool-bound
|
|
910
|
+
* one without touching the rest of RAG.
|
|
1233
911
|
*
|
|
1234
912
|
* Configuration errors (`STACKBONE_POSTGRES_URL` unset) flow up as a
|
|
1235
|
-
* `Result.err` with the same `database_not_configured` shape
|
|
1236
|
-
* raises, instead of throwing through the public
|
|
913
|
+
* `Result.err` with the same `database_not_configured` shape
|
|
914
|
+
* `client.database` raises, instead of throwing through the public
|
|
915
|
+
* surface.
|
|
1237
916
|
*/
|
|
1238
917
|
private sql;
|
|
1239
918
|
private withPipeline;
|
|
1240
919
|
}
|
|
1241
920
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
921
|
+
/** Subset of `RequestInit['body']` we serialize without modification. */
|
|
922
|
+
type SerializedBody = NonNullable<RequestInit['body']>;
|
|
923
|
+
/**
|
|
924
|
+
* Per-status overrides facades attach when an HTTP status carries a
|
|
925
|
+
* non-canonical meaning for the endpoint — e.g. a `POST` whose `409` means
|
|
926
|
+
* "already exists" instead of the default "conflict". Keys are HTTP statuses,
|
|
927
|
+
* values are domain codes the transport should surface verbatim (no prefix
|
|
928
|
+
* concatenation — the override is the final code).
|
|
929
|
+
*
|
|
930
|
+
* The value is typed against the catalog (`SdkErrorCode`) so an endpoint
|
|
931
|
+
* cannot smuggle a wire code that isn't in the documented inventory.
|
|
932
|
+
*/
|
|
933
|
+
type ErrorOverrides = Record<number, SdkErrorCode>;
|
|
934
|
+
/**
|
|
935
|
+
* Tells the transport how to remap HTTP status codes onto the surface's
|
|
936
|
+
* domain code prefix. `404 → '<prefix>_not_found'`, `401 → '<prefix>_unauthorized'`,
|
|
937
|
+
* etc. Endpoint-specific overrides win over the canonical mapping.
|
|
938
|
+
*
|
|
939
|
+
* `prefix` is constrained to `SdkErrorPrefix` (the catalog's prefix tuple) so
|
|
940
|
+
* an unknown surface name doesn't compile — keeps the wire codes the
|
|
941
|
+
* transport emits inside the typed inventory.
|
|
942
|
+
*/
|
|
943
|
+
interface ErrorMapping {
|
|
944
|
+
/** Prefix concatenated with `_not_found`, `_unauthorized`, …. Required for any domain remap. */
|
|
945
|
+
prefix: SdkErrorPrefix;
|
|
946
|
+
/** Per-status final code that overrides the canonical mapping. */
|
|
947
|
+
overrides?: ErrorOverrides;
|
|
948
|
+
}
|
|
949
|
+
interface RequestOptions extends Omit<RequestInit, 'body' | 'signal'> {
|
|
950
|
+
/** Scalar query params. `undefined` values are dropped, others get coerced via `String()`. */
|
|
951
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
952
|
+
/**
|
|
953
|
+
* Repeated string-array params validated and serialised by the transport.
|
|
954
|
+
* Empty arrays / blank elements reject upstream with `<prefix>_invalid_request`
|
|
955
|
+
* (falls back to `http_invalid_request` when no `errorMapping` is set). Each
|
|
956
|
+
* array is joined with `,` to match the canonical control-plane shape.
|
|
957
|
+
*/
|
|
958
|
+
arrayParams?: Record<string, readonly string[]>;
|
|
959
|
+
body?: SerializedBody | Record<string, unknown> | unknown[] | null;
|
|
960
|
+
signal?: AbortSignal;
|
|
961
|
+
/** Allow retrying non-idempotent requests (POST, PATCH). Off by default to prevent duplicate writes. */
|
|
962
|
+
idempotent?: boolean;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Shape every facade method ideally collapses into: "POST to /path with this
|
|
966
|
+
* body, validate against this schema, surface errors as `<prefix>_*`". The
|
|
967
|
+
* transport handles validation, HTTP→domain remapping, querystring assembly
|
|
968
|
+
* and the `Result` envelope so facades stay tiny orchestrators.
|
|
969
|
+
*
|
|
970
|
+
* `arrayParams` keys default to `errorMapping.prefix` for the validation
|
|
971
|
+
* error code (e.g. an empty `names: []` becomes `secrets_invalid_request`).
|
|
972
|
+
*/
|
|
973
|
+
interface TransportRequest<T> extends RequestOptions {
|
|
974
|
+
method: string;
|
|
975
|
+
path: string;
|
|
976
|
+
/**
|
|
977
|
+
* Zod schema (or any object with a `.safeParse()` method) the parsed body
|
|
978
|
+
* must validate against. On failure the transport surfaces
|
|
979
|
+
* `<prefix>_invalid_response` (or `http_invalid_response` if no prefix is
|
|
980
|
+
* configured) with the schema issues attached to `meta.issues`.
|
|
981
|
+
*/
|
|
982
|
+
responseSchema?: ResponseSchema<T>;
|
|
983
|
+
/** HTTP→domain remapping. Omit to let `http_*` codes flow through unchanged. */
|
|
984
|
+
errorMapping?: ErrorMapping;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Minimal contract the transport needs from a schema — `safeParse`. Compatible
|
|
988
|
+
* with Zod (`z.object(…).safeParse(value)`) and any hand-rolled validator that
|
|
989
|
+
* follows the same shape. Avoids leaking Zod's full surface into the transport
|
|
990
|
+
* signature.
|
|
991
|
+
*/
|
|
992
|
+
interface ResponseSchema<T> {
|
|
993
|
+
safeParse(input: unknown): {
|
|
994
|
+
success: true;
|
|
995
|
+
data: T;
|
|
996
|
+
} | {
|
|
997
|
+
success: false;
|
|
998
|
+
error: {
|
|
999
|
+
issues: unknown;
|
|
1000
|
+
};
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
interface HttpClientOptions {
|
|
1004
|
+
/** Default 30_000. Set to 0 to disable. */
|
|
1005
|
+
timeout?: number;
|
|
1006
|
+
/** Default 3. Set to 0 to disable. */
|
|
1007
|
+
retryCount?: number;
|
|
1008
|
+
/** Initial backoff in ms; doubles each attempt with ±15% jitter. Default 500. */
|
|
1009
|
+
retryDelay?: number;
|
|
1010
|
+
/** Override the global fetch (useful for tests). */
|
|
1011
|
+
fetch?: typeof fetch;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Shared HTTP client used by every facade that talks to the Stackbone
|
|
1015
|
+
* control plane. Wraps `fetch` with timeout, exponential backoff with jitter,
|
|
1016
|
+
* idempotent-only retries, response validation (Zod-compatible schemas) and
|
|
1017
|
+
* a uniform `Result` envelope so facades never throw at the SDK boundary.
|
|
1018
|
+
*
|
|
1019
|
+
* Each facade tells the transport its error prefix (`'secrets'`, `'config'`,
|
|
1020
|
+
* `'approval'`) so 404/401/403/429/5xx remap to surface-specific codes —
|
|
1021
|
+
* `secrets_not_found`, `approval_unauthorized`, etc. Endpoint-specific
|
|
1022
|
+
* remaps (e.g. `409 → 'secrets_already_exists'`) flow through
|
|
1023
|
+
* `errorMapping.overrides`. Transport-level failures (network, timeout,
|
|
1024
|
+
* parse) keep the `http_*` prefix the README contract documents.
|
|
1025
|
+
*
|
|
1026
|
+
* Reads `STACKBONE_API_URL` and `STACKBONE_AGENT_JWT` lazily on every request so
|
|
1027
|
+
* env-var rotation is picked up without restarting the client.
|
|
1028
|
+
*/
|
|
1029
|
+
declare class HttpClient {
|
|
1030
|
+
private readonly resolved;
|
|
1031
|
+
private readonly timeout;
|
|
1032
|
+
private readonly retryCount;
|
|
1033
|
+
private readonly retryDelay;
|
|
1034
|
+
private readonly fetchImpl;
|
|
1035
|
+
constructor(resolved: ResolvedConfig, options?: HttpClientOptions);
|
|
1036
|
+
/**
|
|
1037
|
+
* Convenience for `request({ method: 'GET', path, ... })`. Kept so legacy
|
|
1038
|
+
* callers (tests, pending facades) keep compiling — the deep work happens
|
|
1039
|
+
* in `request()`.
|
|
1040
|
+
*/
|
|
1041
|
+
get<T>(path: string, options?: RequestOptions): Promise<Result<T>>;
|
|
1042
|
+
post<T>(path: string, body?: RequestOptions['body'], options?: RequestOptions): Promise<Result<T>>;
|
|
1043
|
+
put<T>(path: string, body?: RequestOptions['body'], options?: RequestOptions): Promise<Result<T>>;
|
|
1044
|
+
patch<T>(path: string, body?: RequestOptions['body'], options?: RequestOptions): Promise<Result<T>>;
|
|
1045
|
+
delete<T>(path: string, options?: RequestOptions): Promise<Result<T>>;
|
|
1046
|
+
/**
|
|
1047
|
+
* The transport entry point every facade should target. Handles base-URL
|
|
1048
|
+
* resolution, header injection, body/querystring serialisation, retry +
|
|
1049
|
+
* timeout, response validation and HTTP→domain error remapping.
|
|
1050
|
+
*/
|
|
1051
|
+
request<T>(req: TransportRequest<T>): Promise<Result<T>>;
|
|
1052
|
+
private computeBackoff;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
interface ApprovalToolSpec<I, O> {
|
|
1056
|
+
name: string;
|
|
1057
|
+
description: string;
|
|
1058
|
+
parameters: Record<string, unknown>;
|
|
1059
|
+
needsApproval?: boolean | ((input: I) => boolean | Promise<boolean>);
|
|
1060
|
+
/**
|
|
1061
|
+
* Maps the LLM input to the approval request fields. `topic` and `payload`
|
|
1062
|
+
* default to the tool name and the raw input respectively when omitted;
|
|
1063
|
+
* `onDecide` must always be supplied so the control plane knows where to
|
|
1064
|
+
* deliver the eventual decision.
|
|
1065
|
+
*/
|
|
1066
|
+
toRequest: (input: I) => Omit<ApprovalRequestOptions<I>, 'payload' | 'topic'> & Partial<Pick<ApprovalRequestOptions<I>, 'payload' | 'topic'>>;
|
|
1067
|
+
execute: (input: I) => Promise<O> | O;
|
|
1068
|
+
}
|
|
1069
|
+
interface OpenAIToolSpec {
|
|
1070
|
+
type: 'function';
|
|
1071
|
+
function: {
|
|
1072
|
+
name: string;
|
|
1073
|
+
description: string;
|
|
1074
|
+
parameters: Record<string, unknown>;
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
type ToolInvokeResult<O> = {
|
|
1078
|
+
status: 'pending';
|
|
1079
|
+
approvalId: string;
|
|
1080
|
+
expiresAt: string;
|
|
1081
|
+
} | {
|
|
1082
|
+
status: 'ok';
|
|
1083
|
+
result: O;
|
|
1084
|
+
};
|
|
1085
|
+
interface ApprovalTool<I, O> {
|
|
1086
|
+
name: string;
|
|
1087
|
+
description: string;
|
|
1088
|
+
parameters: Record<string, unknown>;
|
|
1089
|
+
openaiSpec(): OpenAIToolSpec;
|
|
1090
|
+
invoke(input: I): Promise<Result<ToolInvokeResult<O>>>;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
interface VerifyOptions {
|
|
1094
|
+
/** Override the signing key resolved from `STACKBONE_APPROVAL_SIGNING_KEY` / `approvalSigningKey`. */
|
|
1095
|
+
signingKey?: string;
|
|
1096
|
+
/** Reject signatures whose timestamp is older than this (seconds). Default 300. */
|
|
1097
|
+
toleranceSeconds?: number;
|
|
1098
|
+
/** Inject a clock for tests. Defaults to `Date.now`. */
|
|
1099
|
+
now?: () => number;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
type ApprovalTopic = string;
|
|
1103
|
+
declare const DECISION_STATUSES: readonly ["approved", "rejected", "timed_out", "cancelled"];
|
|
1104
|
+
type DecisionStatus = (typeof DECISION_STATUSES)[number];
|
|
1105
|
+
type ApprovalStatus = 'pending' | DecisionStatus;
|
|
1106
|
+
type Decision<T = unknown> = {
|
|
1107
|
+
status: 'approved' | 'rejected';
|
|
1108
|
+
payload: T;
|
|
1109
|
+
approver: ApproverInfo;
|
|
1110
|
+
decidedAt: string;
|
|
1111
|
+
reason?: string;
|
|
1112
|
+
} | {
|
|
1113
|
+
status: 'timed_out';
|
|
1114
|
+
decidedAt: string;
|
|
1115
|
+
} | {
|
|
1116
|
+
status: 'cancelled';
|
|
1117
|
+
decidedAt: string;
|
|
1118
|
+
reason?: string;
|
|
1119
|
+
};
|
|
1120
|
+
interface ApproverInfo {
|
|
1121
|
+
id: string;
|
|
1122
|
+
email: string;
|
|
1123
|
+
name?: string;
|
|
1124
|
+
}
|
|
1125
|
+
interface ApprovalRequestOptions<T = unknown> {
|
|
1126
|
+
topic: ApprovalTopic;
|
|
1127
|
+
payload: T;
|
|
1128
|
+
title?: string;
|
|
1129
|
+
description?: string;
|
|
1130
|
+
/** Path on the agent the control plane will POST the decision to (resolved against the agent's public URL server-side). */
|
|
1131
|
+
onDecide: string;
|
|
1132
|
+
/** ISO 8601 duration (`'24h'`) or milliseconds. Default 24h. */
|
|
1133
|
+
timeout?: string | number;
|
|
1134
|
+
onTimeout?: 'reject' | 'approve' | 'ignore';
|
|
1135
|
+
approver?: string;
|
|
1136
|
+
/** Same `(topic, idempotencyKey)` returns the same `approvalId` instead of creating a new one. */
|
|
1137
|
+
idempotencyKey?: string;
|
|
1138
|
+
metadata?: Record<string, unknown>;
|
|
1139
|
+
}
|
|
1140
|
+
interface ApprovalRequest {
|
|
1141
|
+
approvalId: string;
|
|
1142
|
+
status: 'pending';
|
|
1143
|
+
callbackUrl: string;
|
|
1144
|
+
expiresAt: string;
|
|
1145
|
+
}
|
|
1146
|
+
interface ApprovalRecord<T = unknown> {
|
|
1147
|
+
approvalId: string;
|
|
1148
|
+
topic: ApprovalTopic;
|
|
1149
|
+
status: ApprovalStatus;
|
|
1150
|
+
payload: T;
|
|
1151
|
+
decision?: Decision<T>;
|
|
1152
|
+
createdAt: string;
|
|
1153
|
+
expiresAt: string;
|
|
1154
|
+
metadata?: Record<string, unknown>;
|
|
1155
|
+
}
|
|
1156
|
+
interface ApprovalListOptions {
|
|
1157
|
+
status?: ApprovalStatus;
|
|
1158
|
+
topic?: ApprovalTopic;
|
|
1159
|
+
cursor?: string;
|
|
1160
|
+
/** 1..100. Default 50. */
|
|
1161
|
+
limit?: number;
|
|
1162
|
+
}
|
|
1163
|
+
interface ApprovalListResult<T = unknown> {
|
|
1164
|
+
items: ApprovalRecord<T>[];
|
|
1165
|
+
nextCursor?: string;
|
|
1166
|
+
}
|
|
1167
|
+
declare class ApprovalFacade {
|
|
1168
|
+
private readonly _resolved;
|
|
1169
|
+
private readonly _http;
|
|
1170
|
+
private readonly _gate;
|
|
1171
|
+
constructor(_resolved: ResolvedConfig, _http: HttpClient,
|
|
1172
|
+
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
1173
|
+
gate?: ModuleGate);
|
|
1174
|
+
request<T = unknown>(options: ApprovalRequestOptions<T>): Promise<Result<ApprovalRequest>>;
|
|
1175
|
+
cancel(approvalId: string, reason?: string): Promise<Result<void>>;
|
|
1176
|
+
get<T = unknown>(approvalId: string): Promise<Result<ApprovalRecord<T>>>;
|
|
1177
|
+
list<T = unknown>(options?: ApprovalListOptions): Promise<Result<ApprovalListResult<T>>>;
|
|
1178
|
+
/**
|
|
1179
|
+
* Local crypto verification — does not touch the datapath, so it is NOT
|
|
1180
|
+
* gated by the contract handshake. Auditing rule: a method gates iff it
|
|
1181
|
+
* issues an HTTP request to the configured baseUrl.
|
|
1182
|
+
*/
|
|
1183
|
+
verify<T = unknown>(request: Request, options?: VerifyOptions): Promise<Result<Decision<T>>>;
|
|
1184
|
+
/**
|
|
1185
|
+
* Pure factory — returns an `ApprovalTool` whose `invoke()` ultimately
|
|
1186
|
+
* calls back into `ApprovalFacade.request`, so the gate fires there. Not
|
|
1187
|
+
* gated here.
|
|
1188
|
+
*/
|
|
1189
|
+
tool<I, O>(spec: ApprovalToolSpec<I, O>): ApprovalTool<I, O>;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
declare class ConfigFacade {
|
|
1193
|
+
private readonly _http;
|
|
1194
|
+
private readonly _gate;
|
|
1195
|
+
constructor(resolved: ResolvedConfig, _http: HttpClient,
|
|
1196
|
+
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
1197
|
+
gate?: ModuleGate);
|
|
1198
|
+
get<T = unknown>(key: string): Promise<Result<T>>;
|
|
1199
|
+
/**
|
|
1200
|
+
* Keys absent from the agent's config come back as omissions in the
|
|
1201
|
+
* response — the returned map only contains entries the control plane
|
|
1202
|
+
* actually has, so callers must access fields with `?.` (the return type
|
|
1203
|
+
* is `Partial<T>`).
|
|
1204
|
+
*/
|
|
1205
|
+
getMany<T extends Record<string, unknown> = Record<string, unknown>>(keys: string[]): Promise<Result<Partial<T>>>;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
declare class SecretsFacade {
|
|
1209
|
+
private readonly _http;
|
|
1210
|
+
private readonly _gate;
|
|
1211
|
+
constructor(resolved: ResolvedConfig, _http: HttpClient,
|
|
1212
|
+
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
1213
|
+
gate?: ModuleGate);
|
|
1214
|
+
get(name: string): Promise<Result<string>>;
|
|
1215
|
+
/**
|
|
1216
|
+
* Names absent from the organization come back as omissions in the response —
|
|
1217
|
+
* the returned map only contains entries the control plane actually has.
|
|
1218
|
+
* Callers that need "all-or-nothing" semantics should diff the keys.
|
|
1219
|
+
*/
|
|
1220
|
+
getMany(names: string[]): Promise<Result<Record<string, string>>>;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* `client.observability` — span processor + per-run JSONL/OTLP logger +
|
|
1225
|
+
* run-cost rollup. Local mode writes JSONL to `~/.stackbone/dev/runs/`; cloud
|
|
1226
|
+
* mode forwards OTLP/HTTP to the collector.
|
|
1227
|
+
*
|
|
1228
|
+
* Contract gating: not gated. Local writes touch the filesystem (no
|
|
1229
|
+
* Stackbone wire surface) and cloud writes go to OpenTelemetry, not the
|
|
1230
|
+
* Stackbone Agent Protocol — so there is no `Capability` to assert against.
|
|
1231
|
+
* No entry in `MODULE_CAPABILITIES` per the rule documented in
|
|
1232
|
+
* `contract/capability-registry.ts`.
|
|
1233
|
+
*/
|
|
1234
|
+
declare class ObservabilityModule {
|
|
1235
|
+
private readonly _resolved;
|
|
1236
|
+
private _processor?;
|
|
1237
|
+
private readonly _loggers;
|
|
1238
|
+
private _sql;
|
|
1239
|
+
private _sqlResolved;
|
|
1240
|
+
constructor(_resolved: ResolvedConfig);
|
|
1241
|
+
/**
|
|
1242
|
+
* Singleton SpanProcessor that the agent registers in its OTel
|
|
1243
|
+
* TracerProvider. Coexists with the OTLP exporter — cloud builds wire
|
|
1244
|
+
* both so Postgres feeds Studio (paridad con local) and Tempo feeds
|
|
1245
|
+
* Platform Ops.
|
|
1246
|
+
*/
|
|
1247
|
+
spanProcessor(options?: RunStepsSpanProcessorOptions): RunStepsSpanProcessor;
|
|
1248
|
+
flush(): Promise<Result<void>>;
|
|
1249
|
+
/**
|
|
1250
|
+
* Per-run structured logger. In local mode (no `OTEL_EXPORTER_OTLP_ENDPOINT`)
|
|
1251
|
+
* appends JSONL to `~/.stackbone/dev/runs/<runId>.jsonl`; in cloud mode batches
|
|
1252
|
+
* OTLP/HTTP/JSON log records to the collector. Cached by `runId` so the
|
|
1253
|
+
* file handle / buffered timer stays stable across repeated calls inside
|
|
1254
|
+
* the same run.
|
|
1255
|
+
*/
|
|
1256
|
+
logger(options: {
|
|
1257
|
+
runId: string;
|
|
1258
|
+
} & Partial<PlatformLoggerOptions>): PlatformLogger;
|
|
1259
|
+
/** Closes the per-run logger (flushes the JSONL file or the OTLP buffer). */
|
|
1260
|
+
closeLogger(runId: string): Promise<void>;
|
|
1261
|
+
/**
|
|
1262
|
+
* Hook the agent calls when a run finishes. Sums `cost_usd` from every
|
|
1263
|
+
* `llm_call` span of the run and writes the total to
|
|
1264
|
+
* `stackbone_platform.runs.cost_estimated_usd`. Errors are folded into the
|
|
1265
|
+
* Result envelope so the agent's run cleanup never aborts on a flaky
|
|
1266
|
+
* Postgres call — the cost is decorative metadata, not load-bearing.
|
|
1267
|
+
*/
|
|
1268
|
+
closeRun(runId: string): Promise<Result<AggregateRunCostResult>>;
|
|
1269
|
+
private resolveSql;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
interface StorageObject {
|
|
1273
|
+
key: string;
|
|
1274
|
+
size: number;
|
|
1275
|
+
lastModified?: Date;
|
|
1276
|
+
etag?: string;
|
|
1277
|
+
}
|
|
1278
|
+
interface UploadOptions {
|
|
1249
1279
|
contentType?: string;
|
|
1250
1280
|
metadata?: Record<string, string>;
|
|
1251
1281
|
}
|
|
1252
|
-
interface ListOptions {
|
|
1253
|
-
prefix?: string;
|
|
1254
|
-
/** Maximum number of objects to return per page. Must be > 0. */
|
|
1282
|
+
interface ListOptions {
|
|
1283
|
+
prefix?: string;
|
|
1284
|
+
/** Maximum number of objects to return per page. Must be > 0. */
|
|
1285
|
+
limit?: number;
|
|
1286
|
+
cursor?: string;
|
|
1287
|
+
}
|
|
1288
|
+
interface SignedUrlOptions {
|
|
1289
|
+
/** Seconds until the URL expires. Defaults to 3600 (1h). */
|
|
1290
|
+
expiresIn?: number;
|
|
1291
|
+
/** Only honoured by `getSignedUploadUrl` — pinned into the signature so the uploader must send a matching `Content-Type`. */
|
|
1292
|
+
contentType?: string;
|
|
1293
|
+
}
|
|
1294
|
+
interface SignedUrl {
|
|
1295
|
+
url: string;
|
|
1296
|
+
expiresAt: Date;
|
|
1297
|
+
}
|
|
1298
|
+
type StorageBody = Blob | Uint8Array | string;
|
|
1299
|
+
interface S3Settings {
|
|
1300
|
+
client: S3Client;
|
|
1301
|
+
endpoint: string;
|
|
1302
|
+
bucket: string;
|
|
1303
|
+
agentId: string;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Public surface behind `client.storage`. Wraps `@aws-sdk/client-s3` against
|
|
1307
|
+
* R2 (prod) or MinIO (dev). Exposes a bucket-scoped API via `from(bucket)`.
|
|
1308
|
+
*
|
|
1309
|
+
* Multi-tenancy: every key is prefixed with `agentId/bucket/` before hitting
|
|
1310
|
+
* S3. Credentials are scoped to the agent's physical bucket (env `S3_BUCKET`),
|
|
1311
|
+
* and the `bucket` argument is a logical namespace the agent uses to organise
|
|
1312
|
+
* its own objects. The full prefixed key is an internal detail — public
|
|
1313
|
+
* methods accept and return the user-facing key (without prefix). Metadata
|
|
1314
|
+
* tracking in `_storage_objects` (Neon) is out-of-scope for this iteration.
|
|
1315
|
+
*
|
|
1316
|
+
* Memory note: `download()` materialises the whole object as a `Blob` in
|
|
1317
|
+
* memory. For agents that need to stream large objects, prefer
|
|
1318
|
+
* `getSignedDownloadUrl()` and `fetch()` the URL directly to consume the
|
|
1319
|
+
* stream incrementally.
|
|
1320
|
+
*/
|
|
1321
|
+
declare class StorageModule {
|
|
1322
|
+
private readonly _resolved;
|
|
1323
|
+
private _s3;
|
|
1324
|
+
private readonly _gate;
|
|
1325
|
+
constructor(_resolved: ResolvedConfig,
|
|
1326
|
+
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
1327
|
+
gate?: ModuleGate);
|
|
1328
|
+
from(bucket: string): StorageBucket;
|
|
1329
|
+
private settings;
|
|
1330
|
+
}
|
|
1331
|
+
declare class StorageBucket {
|
|
1332
|
+
private readonly bucketName;
|
|
1333
|
+
private readonly resolveSettings;
|
|
1334
|
+
private readonly gate;
|
|
1335
|
+
constructor(bucketName: string, resolveSettings: () => Result<S3Settings>, gate: ModuleGate);
|
|
1336
|
+
upload(key: string, body: StorageBody, options?: UploadOptions): Promise<Result<{
|
|
1337
|
+
key: string;
|
|
1338
|
+
etag?: string;
|
|
1339
|
+
}>>;
|
|
1340
|
+
download(key: string): Promise<Result<Blob>>;
|
|
1341
|
+
list(options?: ListOptions): Promise<Result<{
|
|
1342
|
+
objects: readonly StorageObject[];
|
|
1343
|
+
nextCursor?: string;
|
|
1344
|
+
}>>;
|
|
1345
|
+
remove(key: string): Promise<Result<{
|
|
1346
|
+
key: string;
|
|
1347
|
+
}>>;
|
|
1348
|
+
/**
|
|
1349
|
+
* Returns the canonical S3-style URL for an object. Pure URL-builder — no
|
|
1350
|
+
* S3 round-trip — so this method intentionally bypasses the contract gate.
|
|
1351
|
+
* Whether the URL is publicly fetchable depends on the bucket policy; for
|
|
1352
|
+
* private buckets, use `getSignedDownloadUrl` instead.
|
|
1353
|
+
*/
|
|
1354
|
+
getPublicUrl(key: string): Result<string>;
|
|
1355
|
+
getSignedUploadUrl(key: string, options?: SignedUrlOptions): Promise<Result<SignedUrl>>;
|
|
1356
|
+
getSignedDownloadUrl(key: string, options?: SignedUrlOptions): Promise<Result<SignedUrl>>;
|
|
1357
|
+
private signUrl;
|
|
1358
|
+
/**
|
|
1359
|
+
* Resolves S3 settings, validates the user key, and prefixes it with
|
|
1360
|
+
* `${agentId}/${bucketName}/`. Rejects path-traversal segments (`..`) so a
|
|
1361
|
+
* caller-controlled key cannot escape the agent's namespace.
|
|
1362
|
+
*/
|
|
1363
|
+
private resolve;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* OAuth connections (Notion, GDrive, Slack…).
|
|
1368
|
+
*
|
|
1369
|
+
* Contract gating: not gated. The OAuth surface targets third-party
|
|
1370
|
+
* providers, not the Stackbone Agent Protocol, so there is no entry in
|
|
1371
|
+
* `MODULE_CAPABILITIES` (see `contract/capability-registry.ts`). When the
|
|
1372
|
+
* runtime lands and a Stackbone-mediated capability shows up (e.g. control
|
|
1373
|
+
* plane OAuth broker), we add the row and wire the gate.
|
|
1374
|
+
*/
|
|
1375
|
+
declare class ConnectionsFacade {
|
|
1376
|
+
constructor(_resolved: ResolvedConfig, _http: HttpClient);
|
|
1377
|
+
list(): Promise<Result<readonly never[]>>;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/** Emit events to the organization event bus. */
|
|
1381
|
+
declare class EventsFacade {
|
|
1382
|
+
private readonly _http;
|
|
1383
|
+
private readonly _gate;
|
|
1384
|
+
constructor(resolved: ResolvedConfig, _http: HttpClient,
|
|
1385
|
+
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
1386
|
+
gate?: ModuleGate);
|
|
1387
|
+
emit(_name: string, _payload: unknown): Promise<Result<void>>;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/**
|
|
1391
|
+
* Where a memory lives. Default `'user'`. `'session'` is short-lived and
|
|
1392
|
+
* collapsed (or dropped) by `endSession()`; `'agent'` is shared across every
|
|
1393
|
+
* user of the agent.
|
|
1394
|
+
*/
|
|
1395
|
+
type MemoryScope = 'user' | 'session' | 'agent';
|
|
1396
|
+
/**
|
|
1397
|
+
* Either raw text or an OpenAI-shaped conversation. The future mem0 backend
|
|
1398
|
+
* accepts both: strings ingest verbatim, message arrays are summarised into
|
|
1399
|
+
* one or more facts.
|
|
1400
|
+
*/
|
|
1401
|
+
type MemoryContent = string | ChatCompletionMessageParam[];
|
|
1402
|
+
interface AddMemoryRequest {
|
|
1403
|
+
userId: string;
|
|
1404
|
+
/** Conversation/session bucket. Required when `scope: 'session'`. */
|
|
1405
|
+
sessionId?: string;
|
|
1406
|
+
/** Default `'user'`. */
|
|
1407
|
+
scope?: MemoryScope;
|
|
1408
|
+
metadata?: Record<string, unknown>;
|
|
1409
|
+
}
|
|
1410
|
+
interface MemoryItem {
|
|
1411
|
+
id: string;
|
|
1412
|
+
content: string;
|
|
1413
|
+
userId: string;
|
|
1414
|
+
sessionId?: string;
|
|
1415
|
+
scope: MemoryScope;
|
|
1416
|
+
metadata?: Record<string, unknown>;
|
|
1417
|
+
/** ISO 8601 UTC timestamp. */
|
|
1418
|
+
createdAt: string;
|
|
1419
|
+
updatedAt: string;
|
|
1420
|
+
}
|
|
1421
|
+
interface SearchMemoryOptions {
|
|
1422
|
+
userId?: string;
|
|
1423
|
+
sessionId?: string;
|
|
1424
|
+
/** 1..100. Default 10. */
|
|
1425
|
+
limit?: number;
|
|
1426
|
+
/** Minimum cosine similarity in [0, 1]. Hits below this are dropped. */
|
|
1427
|
+
threshold?: number;
|
|
1428
|
+
/** Metadata predicates AND-ed to the semantic match. */
|
|
1429
|
+
filters?: Record<string, unknown>;
|
|
1430
|
+
/** Restrict the search to a subset of scopes. Default: every scope. */
|
|
1431
|
+
includeScopes?: readonly MemoryScope[];
|
|
1432
|
+
}
|
|
1433
|
+
interface MemoryHit extends MemoryItem {
|
|
1434
|
+
/** Cosine similarity in [0, 1]. */
|
|
1435
|
+
score: number;
|
|
1436
|
+
}
|
|
1437
|
+
interface ListMemoryRequest {
|
|
1438
|
+
userId: string;
|
|
1439
|
+
/** 1..100. Default 50. */
|
|
1440
|
+
limit?: number;
|
|
1441
|
+
cursor?: string;
|
|
1442
|
+
}
|
|
1443
|
+
interface ListMemoryResult {
|
|
1444
|
+
items: readonly MemoryItem[];
|
|
1445
|
+
nextCursor?: string;
|
|
1446
|
+
}
|
|
1447
|
+
interface UpdateMemoryOptions {
|
|
1448
|
+
content?: string;
|
|
1449
|
+
/** Shallow-merged onto the existing metadata (mem0 semantics). */
|
|
1450
|
+
metadata?: Record<string, unknown>;
|
|
1451
|
+
}
|
|
1452
|
+
interface DeleteAllMemoryRequest {
|
|
1453
|
+
userId: string;
|
|
1454
|
+
}
|
|
1455
|
+
type MemoryHistoryEvent = 'created' | 'updated' | 'accessed' | 'deleted';
|
|
1456
|
+
interface MemoryHistoryEntry {
|
|
1457
|
+
id: string;
|
|
1458
|
+
memoryId: string;
|
|
1459
|
+
event: MemoryHistoryEvent;
|
|
1460
|
+
/** ISO 8601 UTC timestamp. */
|
|
1461
|
+
at: string;
|
|
1462
|
+
/** Identifier of who triggered the event (user id, agent id, etc.). */
|
|
1463
|
+
actor?: string;
|
|
1464
|
+
/** Content snapshot before the event. Only set for `updated`/`deleted`. */
|
|
1465
|
+
before?: string;
|
|
1466
|
+
/** Content snapshot after the event. Only set for `created`/`updated`. */
|
|
1467
|
+
after?: string;
|
|
1468
|
+
metadata?: Record<string, unknown>;
|
|
1469
|
+
}
|
|
1470
|
+
interface EndSessionOptions {
|
|
1471
|
+
/** Persist session-scoped facts to long-term (`'user'`) memory before closing. Default `true`. */
|
|
1472
|
+
persist?: boolean;
|
|
1473
|
+
}
|
|
1474
|
+
interface EndSessionResult {
|
|
1475
|
+
sessionId: string;
|
|
1476
|
+
/** Number of session memories promoted to long-term storage. `0` when `persist: false`. */
|
|
1477
|
+
persisted: number;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* `client.memory` — long-term memory for the agent. The first iteration is a
|
|
1481
|
+
* scaffolding placeholder: every method returns `not_implemented`. The real
|
|
1482
|
+
* backend will be mem0 (`mem0ApiKey` / `MEM0_API_KEY`) and the public surface
|
|
1483
|
+
* defined here is the contract callers can already type against.
|
|
1484
|
+
*
|
|
1485
|
+
* Contract gating: not gated. Memory targets mem0 (a non-Stackbone partner),
|
|
1486
|
+
* so there is no Stackbone Agent Protocol capability to assert against. No
|
|
1487
|
+
* entry in `MODULE_CAPABILITIES` per the rule documented in
|
|
1488
|
+
* `contract/capability-registry.ts`. When/if the runtime moves to a
|
|
1489
|
+
* Stackbone-served backend we add the capability and wire the gate.
|
|
1490
|
+
*/
|
|
1491
|
+
declare class MemoryModule {
|
|
1492
|
+
constructor(_resolved: ResolvedConfig);
|
|
1493
|
+
add(_content: MemoryContent, _request: AddMemoryRequest): Promise<Result<MemoryItem>>;
|
|
1494
|
+
search(_query: string, _options?: SearchMemoryOptions): Promise<Result<readonly MemoryHit[]>>;
|
|
1495
|
+
delete(_memoryId: string): Promise<Result<{
|
|
1496
|
+
id: string;
|
|
1497
|
+
}>>;
|
|
1498
|
+
deleteAll(_request: DeleteAllMemoryRequest): Promise<Result<{
|
|
1499
|
+
deleted: number;
|
|
1500
|
+
}>>;
|
|
1501
|
+
get(_memoryId: string): Promise<Result<MemoryItem>>;
|
|
1502
|
+
list(_request: ListMemoryRequest): Promise<Result<ListMemoryResult>>;
|
|
1503
|
+
update(_memoryId: string, _options: UpdateMemoryOptions): Promise<Result<MemoryItem>>;
|
|
1504
|
+
history(_memoryId: string): Promise<Result<readonly MemoryHistoryEntry[]>>;
|
|
1505
|
+
endSession(_sessionId: string, _options?: EndSessionOptions): Promise<Result<EndSessionResult>>;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* A prompt managed by the Stackbone control plane. Templates are plain strings
|
|
1510
|
+
* with Mustache-style `{{var}}` placeholders — no conditionals or loops.
|
|
1511
|
+
*/
|
|
1512
|
+
interface Prompt {
|
|
1513
|
+
name: string;
|
|
1514
|
+
template: string;
|
|
1515
|
+
/** Monotonically increasing per `name`. The first `create` produces version `1`. */
|
|
1516
|
+
version: number;
|
|
1517
|
+
/** Variable names parsed from the template. */
|
|
1518
|
+
variables?: readonly string[];
|
|
1519
|
+
metadata?: Record<string, unknown>;
|
|
1520
|
+
/** ISO 8601 UTC timestamp. */
|
|
1521
|
+
createdAt: string;
|
|
1522
|
+
updatedAt: string;
|
|
1523
|
+
}
|
|
1524
|
+
interface GetPromptOptions {
|
|
1525
|
+
/** Pin a specific version. Omitted -> latest. */
|
|
1526
|
+
version?: number;
|
|
1527
|
+
}
|
|
1528
|
+
interface ListPromptsOptions {
|
|
1529
|
+
/** 1..100. Default 50. */
|
|
1255
1530
|
limit?: number;
|
|
1256
1531
|
cursor?: string;
|
|
1257
1532
|
}
|
|
1258
|
-
interface
|
|
1259
|
-
/**
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
contentType?: string;
|
|
1533
|
+
interface ListPromptsResult {
|
|
1534
|
+
/** Latest version of each prompt. */
|
|
1535
|
+
items: readonly Prompt[];
|
|
1536
|
+
nextCursor?: string;
|
|
1263
1537
|
}
|
|
1264
|
-
interface
|
|
1265
|
-
|
|
1266
|
-
|
|
1538
|
+
interface CreatePromptRequest {
|
|
1539
|
+
name: string;
|
|
1540
|
+
template: string;
|
|
1541
|
+
metadata?: Record<string, unknown>;
|
|
1267
1542
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1543
|
+
interface UpdatePromptOptions {
|
|
1544
|
+
/** New template. Bumps `version` by one. */
|
|
1545
|
+
template?: string;
|
|
1546
|
+
/** Shallow-merged onto the existing metadata. */
|
|
1547
|
+
metadata?: Record<string, unknown>;
|
|
1548
|
+
}
|
|
1549
|
+
interface DeletePromptOptions {
|
|
1550
|
+
/** Delete a specific version. Omitted -> delete every version under `name`. */
|
|
1551
|
+
version?: number;
|
|
1552
|
+
}
|
|
1553
|
+
interface DeletePromptResult {
|
|
1554
|
+
name: string;
|
|
1555
|
+
/** Number of versions actually removed. */
|
|
1556
|
+
deleted: number;
|
|
1274
1557
|
}
|
|
1275
1558
|
/**
|
|
1276
|
-
*
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
* its own objects. The full prefixed key is an internal detail — public
|
|
1283
|
-
* methods accept and return the user-facing key (without prefix). Metadata
|
|
1284
|
-
* tracking in `_storage_objects` (Neon) is out-of-scope for this iteration.
|
|
1559
|
+
* `client.prompts` — managed prompts for the agent. Prompts live outside the
|
|
1560
|
+
* code so creators can edit, version and A/B test them without rebuilding the
|
|
1561
|
+
* container. The first iteration is a scaffolding placeholder: every method
|
|
1562
|
+
* returns `not_implemented`. The real backend will be the Stackbone control
|
|
1563
|
+
* plane and the public surface defined here is the contract callers can
|
|
1564
|
+
* already type against.
|
|
1285
1565
|
*
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1566
|
+
* Contract gating: not gated yet. Prompts will eventually talk to the
|
|
1567
|
+
* Stackbone control plane, but no `Capability` has been declared for it
|
|
1568
|
+
* (no entry in `MODULE_CAPABILITIES` — see `contract/capability-registry.ts`).
|
|
1569
|
+
* When the runtime lands we add the row (e.g. `prompts.read_write`) and
|
|
1570
|
+
* wire the gate the same way `secrets` / `config` do.
|
|
1290
1571
|
*/
|
|
1291
|
-
declare class
|
|
1292
|
-
|
|
1293
|
-
|
|
1572
|
+
declare class PromptsFacade {
|
|
1573
|
+
constructor(_resolved: ResolvedConfig, _http: HttpClient);
|
|
1574
|
+
get(_name: string, _options?: GetPromptOptions): Promise<Result<Prompt>>;
|
|
1575
|
+
compile(_name: string, _variables: Record<string, unknown>, _options?: GetPromptOptions): Promise<Result<string>>;
|
|
1576
|
+
list(_options?: ListPromptsOptions): Promise<Result<ListPromptsResult>>;
|
|
1577
|
+
create(_request: CreatePromptRequest): Promise<Result<Prompt>>;
|
|
1578
|
+
update(_name: string, _options: UpdatePromptOptions): Promise<Result<Prompt>>;
|
|
1579
|
+
delete(_name: string, _options?: DeletePromptOptions): Promise<Result<DeletePromptResult>>;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
interface PublishRequest {
|
|
1583
|
+
url: string;
|
|
1584
|
+
body: unknown;
|
|
1585
|
+
retries?: number;
|
|
1586
|
+
delay?: number;
|
|
1587
|
+
deduplicationId?: string;
|
|
1588
|
+
headers?: Record<string, string>;
|
|
1589
|
+
}
|
|
1590
|
+
declare class QueuesModule {
|
|
1294
1591
|
private readonly _gate;
|
|
1295
|
-
constructor(
|
|
1592
|
+
constructor(resolved: ResolvedConfig,
|
|
1296
1593
|
/** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
|
|
1297
1594
|
gate?: ModuleGate);
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
}
|
|
1301
|
-
declare class StorageBucket {
|
|
1302
|
-
private readonly bucketName;
|
|
1303
|
-
private readonly resolveSettings;
|
|
1304
|
-
private readonly gate;
|
|
1305
|
-
constructor(bucketName: string, resolveSettings: () => Result<S3Settings>, gate: ModuleGate);
|
|
1306
|
-
upload(key: string, body: StorageBody, options?: UploadOptions): Promise<Result<{
|
|
1307
|
-
key: string;
|
|
1308
|
-
etag?: string;
|
|
1309
|
-
}>>;
|
|
1310
|
-
download(key: string): Promise<Result<Blob>>;
|
|
1311
|
-
list(options?: ListOptions): Promise<Result<{
|
|
1312
|
-
objects: readonly StorageObject[];
|
|
1313
|
-
nextCursor?: string;
|
|
1314
|
-
}>>;
|
|
1315
|
-
remove(key: string): Promise<Result<{
|
|
1316
|
-
key: string;
|
|
1595
|
+
publish(_request: PublishRequest): Promise<Result<{
|
|
1596
|
+
messageId: string;
|
|
1317
1597
|
}>>;
|
|
1318
|
-
/**
|
|
1319
|
-
* Returns the canonical S3-style URL for an object. Pure URL-builder — no
|
|
1320
|
-
* S3 round-trip — so this method intentionally bypasses the contract gate.
|
|
1321
|
-
* Whether the URL is publicly fetchable depends on the bucket policy; for
|
|
1322
|
-
* private buckets, use `getSignedDownloadUrl` instead.
|
|
1323
|
-
*/
|
|
1324
|
-
getPublicUrl(key: string): Result<string>;
|
|
1325
|
-
getSignedUploadUrl(key: string, options?: SignedUrlOptions): Promise<Result<SignedUrl>>;
|
|
1326
|
-
getSignedDownloadUrl(key: string, options?: SignedUrlOptions): Promise<Result<SignedUrl>>;
|
|
1327
|
-
private signUrl;
|
|
1328
|
-
/**
|
|
1329
|
-
* Resolves S3 settings, validates the user key, and prefixes it with
|
|
1330
|
-
* `${agentId}/${bucketName}/`. Rejects path-traversal segments (`..`) so a
|
|
1331
|
-
* caller-controlled key cannot escape the agent's namespace.
|
|
1332
|
-
*/
|
|
1333
|
-
private resolve;
|
|
1334
1598
|
}
|
|
1335
1599
|
|
|
1336
1600
|
/**
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1601
|
+
* The live agent surfaces exposed to the creator's code through a single
|
|
1602
|
+
* `createClient()` entry point. The folder layout under `src/surfaces/`
|
|
1603
|
+
* groups each surface by where its state lives:
|
|
1604
|
+
*
|
|
1605
|
+
* - `surfaces/agent-local/` — Postgres branch owned by the agent
|
|
1606
|
+
* (`database`, `rag`).
|
|
1607
|
+
* - `surfaces/external/` — managed partner SDKs the SDK wraps directly
|
|
1608
|
+
* (`ai` / OpenRouter, `storage` / S3 + R2 / MinIO, `observability` /
|
|
1609
|
+
* OpenTelemetry).
|
|
1610
|
+
* - `surfaces/control-plane/` — thin HTTP calls to Stackbone's control
|
|
1611
|
+
* plane (`approval`, `secrets`, `config`).
|
|
1612
|
+
* - `surfaces/pending/` — public surface is stable but the runtime is
|
|
1613
|
+
* not built yet. They are wired as live `client.X` accessors so MVP
|
|
1614
|
+
* agent code can be authored against the eventual contract today; each
|
|
1615
|
+
* method returns `Result<{ error: { code: "not_implemented" } }>` until
|
|
1616
|
+
* the backing runtime ships. Today: queues, memory, prompts,
|
|
1617
|
+
* connections, events.
|
|
1618
|
+
*
|
|
1619
|
+
* Each accessor builds its surface on first access and caches it — env
|
|
1620
|
+
* vars and partner SDK init costs are paid only when the agent actually
|
|
1621
|
+
* touches that surface.
|
|
1622
|
+
*
|
|
1623
|
+
* Capability gating: every surface listed in `MODULE_CAPABILITIES` builds
|
|
1624
|
+
* its `ModuleGate` against the per-client `ContractStore` owned by this
|
|
1625
|
+
* instance — two clients constructed in the same process do NOT share
|
|
1626
|
+
* handshake cache or suppression-warning state. See
|
|
1627
|
+
* `contract/capability-registry.ts` for the membership rule.
|
|
1340
1628
|
*/
|
|
1341
1629
|
declare class StackboneClient {
|
|
1342
1630
|
private readonly resolved;
|
|
1343
1631
|
private _database?;
|
|
1344
1632
|
private _storage?;
|
|
1345
1633
|
private _ai?;
|
|
1346
|
-
private _queues?;
|
|
1347
1634
|
private _rag?;
|
|
1348
|
-
private _memory?;
|
|
1349
1635
|
private _observability?;
|
|
1350
1636
|
private _approval?;
|
|
1351
1637
|
private _secrets?;
|
|
1352
|
-
private _connections?;
|
|
1353
1638
|
private _config?;
|
|
1639
|
+
private _queues?;
|
|
1640
|
+
private _memory?;
|
|
1354
1641
|
private _events?;
|
|
1355
1642
|
private _prompts?;
|
|
1643
|
+
private _connections?;
|
|
1356
1644
|
private _httpClient?;
|
|
1645
|
+
private readonly _contractStore;
|
|
1357
1646
|
constructor(resolved: ResolvedConfig);
|
|
1358
1647
|
private http;
|
|
1359
1648
|
get database(): DatabaseModule;
|
|
1360
1649
|
get storage(): StorageModule;
|
|
1361
1650
|
get ai(): AiModule;
|
|
1362
|
-
get queues(): QueuesModule;
|
|
1363
1651
|
get rag(): RagModule;
|
|
1364
|
-
get memory(): MemoryModule;
|
|
1365
1652
|
get observability(): ObservabilityModule;
|
|
1366
1653
|
get approval(): ApprovalFacade;
|
|
1367
1654
|
get secrets(): SecretsFacade;
|
|
1368
|
-
get connections(): ConnectionsFacade;
|
|
1369
1655
|
get config(): ConfigFacade;
|
|
1656
|
+
/**
|
|
1657
|
+
* Pending surface — runtime not built. Every method returns
|
|
1658
|
+
* `not_implemented` so MVP agent code can wire against the eventual
|
|
1659
|
+
* contract today and switch over the day the backing runtime ships.
|
|
1660
|
+
* Gated against `queues.pgmq` so the handshake-blocked / capability-
|
|
1661
|
+
* missing paths are exercised the same way as the live surfaces.
|
|
1662
|
+
*/
|
|
1663
|
+
get queues(): QueuesModule;
|
|
1664
|
+
/**
|
|
1665
|
+
* Pending surface — runtime not built. Every method returns
|
|
1666
|
+
* `not_implemented`. Mem0 is a third-party integration, not a Stackbone
|
|
1667
|
+
* Agent Protocol capability, so this surface is intentionally not gated.
|
|
1668
|
+
*/
|
|
1669
|
+
get memory(): MemoryModule;
|
|
1670
|
+
/**
|
|
1671
|
+
* Pending surface — runtime not built. Every method returns
|
|
1672
|
+
* `not_implemented`. Gated against `events.bus` so handshake errors
|
|
1673
|
+
* surface symmetrically with the other control-plane facades.
|
|
1674
|
+
*/
|
|
1370
1675
|
get events(): EventsFacade;
|
|
1676
|
+
/**
|
|
1677
|
+
* Pending surface — runtime not built. Every method returns
|
|
1678
|
+
* `not_implemented`. Will gate once a capability row is declared in
|
|
1679
|
+
* `MODULE_CAPABILITIES`; until then it is intentionally ungated.
|
|
1680
|
+
*/
|
|
1371
1681
|
get prompts(): PromptsFacade;
|
|
1682
|
+
/**
|
|
1683
|
+
* Pending surface — runtime not built. Returns an empty list today.
|
|
1684
|
+
* Connections target third-party OAuth providers, not the Stackbone
|
|
1685
|
+
* Agent Protocol, so this surface is intentionally not gated.
|
|
1686
|
+
*/
|
|
1687
|
+
get connections(): ConnectionsFacade;
|
|
1372
1688
|
/**
|
|
1373
1689
|
* Read-only view of the most recently resolved Stackbone Agent Protocol
|
|
1374
1690
|
* contract for this client's `stackboneApiUrl`. Returns `null` until a
|
|
1375
1691
|
* handshake has resolved successfully (the handshake fires lazily on the
|
|
1376
1692
|
* first gated module call — slices #4/#5). Synchronous, never throws,
|
|
1377
1693
|
* performs no fetches.
|
|
1694
|
+
*
|
|
1695
|
+
* Backed by this client's own `ContractStore`, so two `StackboneClient`
|
|
1696
|
+
* instances see independent `client.contract` views even when they point
|
|
1697
|
+
* at the same baseUrl.
|
|
1378
1698
|
*/
|
|
1379
1699
|
get contract(): ContractResponse | null;
|
|
1380
1700
|
}
|
|
1381
1701
|
declare function createClient(config?: ClientConfig): StackboneClient;
|
|
1382
1702
|
|
|
1383
|
-
|
|
1703
|
+
/**
|
|
1704
|
+
* Structured fields the wrapper, an upstream consumer, or a handler can
|
|
1705
|
+
* attach to a single log line. Values are `unknown` because the wrapper
|
|
1706
|
+
* does not enforce a schema — the destination decides how to serialise
|
|
1707
|
+
* each value.
|
|
1708
|
+
*/
|
|
1709
|
+
type LoggerBindings = Record<string, unknown>;
|
|
1710
|
+
/**
|
|
1711
|
+
* The handler-facing logger. Every method takes a human-readable message
|
|
1712
|
+
* plus an optional bag of structured fields the destination merges with
|
|
1713
|
+
* the bindings already set by the wrapper (typically `invocationId` and
|
|
1714
|
+
* `runId`).
|
|
1715
|
+
*
|
|
1716
|
+
* F5 wired the binding contract only; F6 replaces the stub implementation
|
|
1717
|
+
* with the JSON/STACKBONE_MODE-aware variant. Keeping the surface here
|
|
1718
|
+
* (instead of in the CLI) lets the SDK ship a typed seam creators can
|
|
1719
|
+
* reference from their own tests.
|
|
1720
|
+
*/
|
|
1721
|
+
interface Logger {
|
|
1722
|
+
/** Fine-grained diagnostic information. */
|
|
1723
|
+
debug(msg: string, meta?: LoggerBindings): void;
|
|
1724
|
+
/** Routine progress information. */
|
|
1725
|
+
info(msg: string, meta?: LoggerBindings): void;
|
|
1726
|
+
/** Recoverable problem the handler still resolved. */
|
|
1727
|
+
warn(msg: string, meta?: LoggerBindings): void;
|
|
1728
|
+
/** Unrecoverable failure the handler is about to report. */
|
|
1729
|
+
error(msg: string, meta?: LoggerBindings): void;
|
|
1730
|
+
/**
|
|
1731
|
+
* Returns a new logger that merges `bindings` into every subsequent
|
|
1732
|
+
* log line. The original logger is left untouched.
|
|
1733
|
+
*/
|
|
1734
|
+
child(bindings: LoggerBindings): Logger;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Minimal writable surface the structured logger drains into. Both
|
|
1738
|
+
* `process.stdout` and arbitrary test doubles satisfy it — every consumer
|
|
1739
|
+
* only needs `write(string): unknown`.
|
|
1740
|
+
*/
|
|
1741
|
+
interface LogSink {
|
|
1742
|
+
write(chunk: string): unknown;
|
|
1743
|
+
}
|
|
1744
|
+
interface StructuredLoggerOptions {
|
|
1745
|
+
/** Output stream. Defaults to `process.stdout` for production use. */
|
|
1746
|
+
stream?: LogSink;
|
|
1747
|
+
/** Initial bindings every line inherits (e.g. process-wide metadata). */
|
|
1748
|
+
bindings?: LoggerBindings;
|
|
1749
|
+
/** Clock seam for the `ts` field. Defaults to `() => new Date()`. */
|
|
1750
|
+
now?: () => Date;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Builds a structured `Logger` that emits one NDJSON line per call. Output
|
|
1754
|
+
* shape is fixed by the wrapper PRD: `{ ts, level, msg, ...bindings,
|
|
1755
|
+
* ...meta }`. The wrapper layers extra fields on top via `child()`
|
|
1756
|
+
* (`invocationId`, `runId`, `latencyMs`, `status`).
|
|
1757
|
+
*/
|
|
1758
|
+
declare const createStructuredLogger: (opts?: StructuredLoggerOptions) => Logger;
|
|
1759
|
+
|
|
1760
|
+
/**
|
|
1761
|
+
* The data the wrapper hands to the creator's `run` function. F5 widens the
|
|
1762
|
+
* shape from F1's bare `{ input }` to the full per-invocation context:
|
|
1763
|
+
*
|
|
1764
|
+
* - `signal` is a fresh `AbortSignal` per invocation. The wrapper aborts
|
|
1765
|
+
* it when the deadline expires; the handler is expected to honour it
|
|
1766
|
+
* on cancellable work (fetches, DB queries, model calls).
|
|
1767
|
+
* - `invocationId` / `runId` come straight from the parsed envelope.
|
|
1768
|
+
* - `client` is the **same** `StackboneClient` instance for every
|
|
1769
|
+
* invocation in the process, so sub-modules can keep their internal
|
|
1770
|
+
* pools warm.
|
|
1771
|
+
* - `logger` is a child of the wrapper's structured logger already
|
|
1772
|
+
* bound to `invocationId` and `runId`, so the handler does not have to
|
|
1773
|
+
* pass them on every line. The actual JSON format lands in F6.
|
|
1774
|
+
*/
|
|
1775
|
+
interface InvokeContext<I extends z.ZodType> {
|
|
1776
|
+
input: z.infer<I>;
|
|
1777
|
+
signal: AbortSignal;
|
|
1778
|
+
invocationId: string;
|
|
1779
|
+
runId: string;
|
|
1780
|
+
client: StackboneClient;
|
|
1781
|
+
logger: Logger;
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* The single `invoke` capability the creator exposes. Mirrors the wrapper's
|
|
1785
|
+
* three HTTP verbs (`/invoke`, `/health`, `/schema`) — F1 lands only the
|
|
1786
|
+
* invoke half; `/health` and `/schema` land in later slices.
|
|
1787
|
+
*
|
|
1788
|
+
* The fields are deliberately enumerated (no `[k: string]: unknown` escape
|
|
1789
|
+
* hatch) so the parent `AgentSpec` can reject unknown keys at the type level
|
|
1790
|
+
* via `keyof` checks downstream.
|
|
1791
|
+
*/
|
|
1792
|
+
interface InvokeCapability<I extends z.ZodType, O extends z.ZodType> {
|
|
1793
|
+
/** Zod schema for the parsed `/invoke` request body. */
|
|
1794
|
+
input: I;
|
|
1795
|
+
/** Zod schema for the `/invoke` response body. */
|
|
1796
|
+
output: O;
|
|
1797
|
+
/** Creator-supplied handler. Returns either the parsed output or a promise of it. */
|
|
1798
|
+
run: (ctx: InvokeContext<I>) => z.infer<O> | Promise<z.infer<O>>;
|
|
1799
|
+
/** Optional per-invocation timeout in milliseconds. Wired by a later slice. */
|
|
1800
|
+
timeoutMs?: number;
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* The declarative shape every Stackbone agent returns from `src/index.ts`.
|
|
1804
|
+
*
|
|
1805
|
+
* `AgentSpec` is a closed object: the keys are listed explicitly so adding
|
|
1806
|
+
* `{ invoke, anyOtherKey }` at the call site fails `tsc`. The corresponding
|
|
1807
|
+
* runtime guard in `defineAgent` enforces the same invariant against callers
|
|
1808
|
+
* that bypass the type system through `as any`.
|
|
1809
|
+
*
|
|
1810
|
+
* `health` and other capabilities will join `invoke` as they land — every
|
|
1811
|
+
* addition is an explicit, reviewable edit to this type.
|
|
1812
|
+
*/
|
|
1813
|
+
interface AgentSpec<I extends z.ZodType, O extends z.ZodType> {
|
|
1814
|
+
invoke: InvokeCapability<I, O>;
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Hard ceiling on `invoke.timeoutMs`. The platform saga kills the Machine
|
|
1818
|
+
* before the wrapper would time out at higher values, so a creator declaring
|
|
1819
|
+
* a 10-minute timeout would publish a contract the runtime cannot keep.
|
|
1820
|
+
* Surface that conflict at boot instead of as a mysterious mid-invocation
|
|
1821
|
+
* SIGKILL in production.
|
|
1822
|
+
*/
|
|
1823
|
+
declare const INVOKE_TIMEOUT_HARD_CAP_MS = 300000;
|
|
1824
|
+
/**
|
|
1825
|
+
* Identity function with two jobs:
|
|
1826
|
+
*
|
|
1827
|
+
* 1. **Inference anchor** — pins the input/output Zod schemas so creators get
|
|
1828
|
+
* a fully typed `ctx.input` and a typed return value inside `run` without
|
|
1829
|
+
* repeating themselves.
|
|
1830
|
+
* 2. **Boot-time invariant** — throws on any unknown top-level or invoke-block
|
|
1831
|
+
* key. This is the safety net for creators that force a shape through
|
|
1832
|
+
* `as any`; the type system already rejects this, but the wrapper PRD's
|
|
1833
|
+
* "impossible to break the contract from creator code" success criterion
|
|
1834
|
+
* requires the runtime check too.
|
|
1835
|
+
*
|
|
1836
|
+
* Returns the spec verbatim so callers can `export default defineAgent({...})`
|
|
1837
|
+
* and the wrapper can read the same object back through dynamic import.
|
|
1838
|
+
*/
|
|
1839
|
+
declare const defineAgent: <I extends z.ZodType, O extends z.ZodType>(spec: AgentSpec<I, O>) => AgentSpec<I, O>;
|
|
1840
|
+
|
|
1841
|
+
/**
|
|
1842
|
+
* Reserved error codes. Order is intentional: keep them flat (string) so
|
|
1843
|
+
* the envelope JSON stays trivial to inspect.
|
|
1844
|
+
*
|
|
1845
|
+
* F2 owns `VALIDATION_ERROR` and `HANDLER_ERROR`. `TIMEOUT_ERROR` is
|
|
1846
|
+
* declared here so the collision check is stable across slices — the F5
|
|
1847
|
+
* timeout slice will start emitting it without needing to amend the
|
|
1848
|
+
* envelope contract.
|
|
1849
|
+
*/
|
|
1850
|
+
declare const RESERVED_ERROR_CODES: readonly ["VALIDATION_ERROR", "HANDLER_ERROR", "TIMEOUT_ERROR"];
|
|
1851
|
+
type ReservedErrorCode = (typeof RESERVED_ERROR_CODES)[number];
|
|
1852
|
+
/**
|
|
1853
|
+
* HTTP header carrying `invocationId` so the dispatcher can correlate
|
|
1854
|
+
* requests without parsing the JSON body.
|
|
1855
|
+
*/
|
|
1856
|
+
declare const INVOCATION_ID_HEADER = "X-Stackbone-Invocation-Id";
|
|
1857
|
+
/**
|
|
1858
|
+
* HTTP header carrying `runId` so the dispatcher can correlate requests
|
|
1859
|
+
* across approval callbacks, log lookups, and trace spans without parsing
|
|
1860
|
+
* the JSON body.
|
|
1861
|
+
*/
|
|
1862
|
+
declare const RUN_ID_HEADER = "X-Stackbone-Run-Id";
|
|
1863
|
+
/**
|
|
1864
|
+
* Envelope a client (the platform dispatcher, the local emulator, or a
|
|
1865
|
+
* curl smoke test) sends to `POST /invoke`. `payload` is unknown at this
|
|
1866
|
+
* layer — the wrapper revalidates it against `spec.invoke.input` before
|
|
1867
|
+
* handing it to the creator's `run`.
|
|
1868
|
+
*/
|
|
1869
|
+
declare const invokeRequestSchema: z.ZodObject<{
|
|
1870
|
+
invocationId: z.ZodString;
|
|
1871
|
+
runId: z.ZodString;
|
|
1872
|
+
payload: z.ZodUnknown;
|
|
1873
|
+
}, z.core.$strip>;
|
|
1874
|
+
type InvokeRequest = z.infer<typeof invokeRequestSchema>;
|
|
1875
|
+
/**
|
|
1876
|
+
* Error portion of a failure envelope. `issues` mirrors Zod's `ZodIssue[]`
|
|
1877
|
+
* shape verbatim so the dispatcher can render the same field-level paths
|
|
1878
|
+
* an SDK consumer would see locally.
|
|
1879
|
+
*/
|
|
1880
|
+
interface InvokeEnvelopeError {
|
|
1881
|
+
code: string;
|
|
1882
|
+
message: string;
|
|
1883
|
+
issues?: z.ZodIssue[];
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Success envelope returned by `/invoke`. `TResult` is left generic so the
|
|
1887
|
+
* wrapper can wrap the parsed output without paying for an extra type
|
|
1888
|
+
* assertion at the call site.
|
|
1889
|
+
*/
|
|
1890
|
+
interface InvokeSuccessEnvelope<TResult = unknown> {
|
|
1891
|
+
invocationId: string;
|
|
1892
|
+
runId: string;
|
|
1893
|
+
result: TResult;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Failure envelope returned by `/invoke`. Keeps `invocationId` and `runId`
|
|
1897
|
+
* at the top level so a client can correlate a 4xx/5xx with the request
|
|
1898
|
+
* without inspecting `error.issues`.
|
|
1899
|
+
*/
|
|
1900
|
+
interface InvokeErrorEnvelope {
|
|
1901
|
+
invocationId: string;
|
|
1902
|
+
runId: string;
|
|
1903
|
+
error: InvokeEnvelopeError;
|
|
1904
|
+
}
|
|
1905
|
+
type InvokeResponse<TResult = unknown> = InvokeSuccessEnvelope<TResult> | InvokeErrorEnvelope;
|
|
1906
|
+
/**
|
|
1907
|
+
* True when `code` is one of the reserved tokens the wrapper owns. Used by
|
|
1908
|
+
* the duck-typed error path: a creator that throws an error tagged with a
|
|
1909
|
+
* reserved code falls back to `HANDLER_ERROR` so reserved codes always
|
|
1910
|
+
* mean what the platform expects.
|
|
1911
|
+
*/
|
|
1912
|
+
declare const isReservedErrorCode: (code: string) => code is ReservedErrorCode;
|
|
1913
|
+
|
|
1914
|
+
/**
|
|
1915
|
+
* Which half of the contract the diagnostic belongs to. The wrapper's error
|
|
1916
|
+
* message names the half so the creator can find the offending field
|
|
1917
|
+
* immediately ("input.user.email", not just "user.email").
|
|
1918
|
+
*/
|
|
1919
|
+
type SchemaHalf = 'input' | 'output';
|
|
1920
|
+
/**
|
|
1921
|
+
* Constructs the wrapper either rejects outright or warns about. Listed
|
|
1922
|
+
* verbatim so a future ADR can extend the union without changing the type
|
|
1923
|
+
* shape downstream consumers rely on.
|
|
1924
|
+
*/
|
|
1925
|
+
type FatalConstruct = 'transform' | 'preprocess' | 'coerce';
|
|
1926
|
+
type WarnConstruct = 'refine' | 'lazy';
|
|
1927
|
+
interface SchemaDiagnostic<Construct extends string> {
|
|
1928
|
+
half: SchemaHalf;
|
|
1929
|
+
/** Dotted JSON pointer-ish path: `email`, `user.address.street`, `items[]`. */
|
|
1930
|
+
path: string;
|
|
1931
|
+
construct: Construct;
|
|
1932
|
+
}
|
|
1933
|
+
interface AgentSchemaPair {
|
|
1934
|
+
input: z.ZodType;
|
|
1935
|
+
output: z.ZodType;
|
|
1936
|
+
}
|
|
1937
|
+
interface SchemaIntrospectionResult {
|
|
1938
|
+
fatal: SchemaDiagnostic<FatalConstruct>[];
|
|
1939
|
+
warnings: SchemaDiagnostic<WarnConstruct>[];
|
|
1940
|
+
schemas: {
|
|
1941
|
+
input: JsonSchemaDocument;
|
|
1942
|
+
output: JsonSchemaDocument;
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* JSON Schema 2020-12 document. We intentionally keep this loose — Zod's
|
|
1947
|
+
* `toJSONSchema` returns an arbitrary structure and the wrapper only needs
|
|
1948
|
+
* to serialise it verbatim to the catalog. A future ADR can tighten this
|
|
1949
|
+
* to a generated type once the catalog has a real consumer.
|
|
1950
|
+
*/
|
|
1951
|
+
interface JsonSchemaDocument {
|
|
1952
|
+
$schema?: string;
|
|
1953
|
+
type?: string | string[];
|
|
1954
|
+
properties?: Record<string, JsonSchemaDocument>;
|
|
1955
|
+
required?: string[];
|
|
1956
|
+
[key: string]: unknown;
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Walks the agent's input/output schemas, classifies offending constructs,
|
|
1960
|
+
* and emits the JSON Schema 2020-12 documents the wrapper serves from
|
|
1961
|
+
* `GET /schema`. The function never throws — the caller decides what to do
|
|
1962
|
+
* with the fatal list.
|
|
1963
|
+
*
|
|
1964
|
+
* Derivation runs through Zod's native `z.toJSONSchema`. We pass
|
|
1965
|
+
* `unrepresentable: 'any'` so a `.refine()`-bearing string still surfaces as
|
|
1966
|
+
* a string (the catalog drops the constraint, the wrapper still runs it at
|
|
1967
|
+
* invoke time).
|
|
1968
|
+
*/
|
|
1969
|
+
declare const analyzeAgentSchemas: (pair: AgentSchemaPair) => SchemaIntrospectionResult;
|
|
1970
|
+
|
|
1971
|
+
export { type AddMemoryRequest, type AgentSchemaPair, type AgentSpec, type ApprovalListOptions, type ApprovalListResult, type ApprovalRecord, type ApprovalRequest, type ApprovalRequestOptions, type ApprovalStatus, type ApprovalTool, type ApprovalToolSpec, type ApprovalTopic, type ApproverInfo, type ChunkOptions, type ChunkStrategy, type ClientConfig, type CreatePromptRequest, type Decision, type DecisionStatus, type DeleteAllMemoryRequest, type DeleteOptions, type DeletePromptOptions, type DeletePromptResult, type DeleteResponse, type EndSessionOptions, type EndSessionResult, type FatalConstruct, type GeneratedImage, type GetPromptOptions, INVOCATION_ID_HEADER, INVOKE_TIMEOUT_HARD_CAP_MS, type ImageGenerateParams, type ImagesResponse, type IngestAsyncHandle, type IngestChunk, type IngestRequest, type IngestRequestAutoEmbed, type IngestRequestPrecomputed, type IngestResponse, type InvokeCapability, type InvokeContext, type InvokeEnvelopeError, type InvokeErrorEnvelope, type InvokeRequest, type InvokeResponse, type InvokeSuccessEnvelope, type JsonSchemaDocument, type ListMemoryRequest, type ListMemoryResult, type ListOptions, type ListPromptsOptions, type ListPromptsResult, type LogSink, type Logger, type LoggerBindings, type MemoryContent, type MemoryHistoryEntry, type MemoryHistoryEvent, type MemoryHit, type MemoryItem, type MemoryScope, type ModelsListResponse, type OpenRouterModel, type ParseInput, type ParseOptions, type Prompt, type PublishRequest, RESERVED_ERROR_CODES, RUN_ID_HEADER, type RagIngestProgress, type ReservedErrorCode, type Result, type RetrieveHit, type RetrieveRequest, type RetrieveRequestAutoEmbed, type RetrieveRequestPrecomputed, type SchemaDiagnostic, type SchemaHalf, type SchemaIntrospectionResult, type SdkError, type SdkErrorCode, type SearchMemoryOptions, type SignedUrl, type SignedUrlOptions, StackboneClient, type StorageBody, type StorageObject, type StructuredLoggerOptions, type UpdateMemoryOptions, type UpdatePromptOptions, type UploadOptions, type VerifyOptions, type WarnConstruct, analyzeAgentSchemas, createClient, createStructuredLogger, defineAgent, invokeRequestSchema, isReservedErrorCode, isSdkErrorCode };
|