@stackbone/sdk 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts ADDED
@@ -0,0 +1,1325 @@
1
+ import { ContractResponse } from '@stackbone/validators';
2
+ import OpenAI from 'openai';
3
+ import { ChatCompletionCreateParamsStreaming, ChatCompletionChunk, ChatCompletionCreateParamsNonStreaming, ChatCompletion, EmbeddingCreateParams, CreateEmbeddingResponse, ChatCompletionMessageParam } from 'openai/resources';
4
+ import { Stream } from 'openai/streaming';
5
+ import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
6
+ import { Sql } from 'postgres';
7
+ import { S3Client } from '@aws-sdk/client-s3';
8
+
9
+ /**
10
+ * Optional overrides accepted by `createClient`. All fields are optional;
11
+ * modules fall back to the corresponding upstream env var on first access.
12
+ */
13
+ interface ClientConfig {
14
+ /** Ed25519 JWT signed by the control plane. Falls back to `STACKBONE_AGENT_JWT`. */
15
+ agentJwt?: string;
16
+ /** Stackbone control plane base URL used by the facade HttpClient. Falls back to `STACKBONE_API_URL`. */
17
+ stackboneApiUrl?: string;
18
+ /** Stable agent identifier injected at provisioning. Falls back to `STACKBONE_AGENT_ID`. Used as the multi-tenant key prefix in `client.storage`. */
19
+ agentId?: string;
20
+ /**
21
+ * Postgres connection string consumed by `client.rag` and the platform
22
+ * observability exporter. Falls back to `DATABASE_URL`. Note: `client.database`
23
+ * uses `STACKBONE_POSTGRES_URL` instead — the two env vars are intentionally
24
+ * different so that the SDK's own DB pool stays decoupled from the legacy
25
+ * `DATABASE_URL` consumers until `feature 30` consolidates them.
26
+ */
27
+ databaseUrl?: string;
28
+ openrouterKey?: string;
29
+ openrouterBaseUrl?: string;
30
+ qstashToken?: string;
31
+ qstashCurrentSigningKey?: string;
32
+ qstashNextSigningKey?: string;
33
+ llamaParseApiKey?: string;
34
+ /** mem0 API key for the long-term memory backend (`client.memory`). Falls back to `MEM0_API_KEY`. */
35
+ mem0ApiKey?: string;
36
+ /** mem0 API base URL. Falls back to `MEM0_BASE_URL` (defaults to mem0 cloud when unset). */
37
+ mem0BaseUrl?: string;
38
+ /** HMAC key the control plane uses to sign approval-decision callbacks. Falls back to `STACKBONE_APPROVAL_SIGNING_KEY`. */
39
+ approvalSigningKey?: string;
40
+ /**
41
+ * Identifies the install the agent runs inside. Sent as the
42
+ * `X-Stackbone-Installation-Id` header when the SDK talks to the control
43
+ * plane. Falls back to `STACKBONE_INSTALLATION_ID`.
44
+ * TODO(studio-v1): remove once `STACKBONE_AGENT_JWT` carries the install id
45
+ * and `apps/api` derives it from the bearer instead of an explicit header.
46
+ */
47
+ installationId?: string;
48
+ s3?: {
49
+ /** Falls back to `AWS_ACCESS_KEY_ID`. */
50
+ accessKeyId?: string;
51
+ /** Falls back to `AWS_SECRET_ACCESS_KEY`. */
52
+ secretAccessKey?: string;
53
+ /** Falls back to `S3_ENDPOINT`. */
54
+ endpoint?: string;
55
+ /** Physical R2/S3 bucket scoped to the agent. Falls back to `S3_BUCKET`. */
56
+ bucket?: string;
57
+ };
58
+ otel?: {
59
+ exporterOtlpEndpoint?: string;
60
+ resourceAttributes?: string;
61
+ };
62
+ /**
63
+ * RAG-related defaults parsed from `agent.yaml.rag` by the CLI/orchestrator
64
+ * and forwarded here. The SDK never reads `agent.yaml` itself — keeping
65
+ * filesystem ownership in the CLI. Defaults follow ADR
66
+ * `2026-05-10-rag-consolidation-on-client-database` (D2/D6).
67
+ */
68
+ rag?: {
69
+ /**
70
+ * Embedding model used by `client.rag.ingest`/`retrieve` when the caller
71
+ * omits `model`. Defaults to `openai/text-embedding-3-small`.
72
+ */
73
+ embeddingModel?: string;
74
+ };
75
+ /**
76
+ * Creator-side floor on the negotiated Stackbone Agent Protocol contract
77
+ * version. Sourced from `agent.yaml.protocol.required` by the
78
+ * CLI/orchestrator (the SDK never reads `agent.yaml` itself). When set and
79
+ * the datapath's negotiated `contract.version` is below this value, every
80
+ * gated module call returns `SdkError('contract_version_unsupported',
81
+ * { detected, required })` — independent of per-module capability gating.
82
+ * When omitted, the SDK uses `MIN_SUPPORTED_CONTRACT_VERSION` as the
83
+ * implicit floor (i.e. behaviour matches slice F5).
84
+ */
85
+ protocolRequired?: number;
86
+ }
87
+ /**
88
+ * `env` is a live reference to `process.env` (not a snapshot) so tests that
89
+ * mutate the environment between `createClient()` and the first module access
90
+ * still observe the new values via the lazy initialisers.
91
+ */
92
+ interface ResolvedConfig {
93
+ config: Readonly<ClientConfig>;
94
+ env: NodeJS.ProcessEnv;
95
+ }
96
+
97
+ /**
98
+ * Uniform error envelope returned by every SDK method. `cause` and `meta` are
99
+ * optional escape hatches so wrappers can attach upstream errors / context
100
+ * without breaking the public shape.
101
+ */
102
+ interface SdkError {
103
+ code: string;
104
+ message: string;
105
+ cause?: unknown;
106
+ meta?: Record<string, unknown>;
107
+ }
108
+ /**
109
+ * Discriminated result the entire SDK returns. Narrowing on `result.error`
110
+ * automatically refines `result.data` to `T`.
111
+ */
112
+ type Result<T> = {
113
+ data: T;
114
+ error: null;
115
+ } | {
116
+ data: null;
117
+ error: SdkError;
118
+ };
119
+
120
+ /**
121
+ * Async predicate every gated SDK module awaits before forwarding to its
122
+ * underlying implementation. Returns ok when the negotiated contract clears
123
+ * the module's gate (or the escape hatch suppresses a gating error); returns
124
+ * an error envelope otherwise — the caller propagates it back as the public
125
+ * `Result<T>` of the method.
126
+ *
127
+ * Test seam — every gated module exposes an optional `gate` constructor
128
+ * argument that defaults to `createModuleGate(<id>, resolved)`. Specs inject
129
+ * a stub returning a synthetic `Result<undefined>` to exercise the gating
130
+ * paths (ok / `contract_version_unsupported` / `capability_unavailable` /
131
+ * `contract_unreachable`) without driving a real handshake.
132
+ */
133
+ type ModuleGate = () => Promise<Result<undefined>>;
134
+
135
+ /** Subset of `RequestInit['body']` we serialize without modification. */
136
+ type SerializedBody = NonNullable<RequestInit['body']>;
137
+ interface RequestOptions$1 extends Omit<RequestInit, 'body' | 'signal'> {
138
+ params?: Record<string, string>;
139
+ body?: SerializedBody | Record<string, unknown> | unknown[] | null;
140
+ signal?: AbortSignal;
141
+ /** Allow retrying non-idempotent requests (POST, PATCH). Off by default to prevent duplicate writes. */
142
+ idempotent?: boolean;
143
+ }
144
+ interface HttpClientOptions {
145
+ /** Default 30_000. Set to 0 to disable. */
146
+ timeout?: number;
147
+ /** Default 3. Set to 0 to disable. */
148
+ retryCount?: number;
149
+ /** Initial backoff in ms; doubles each attempt with ±15% jitter. Default 500. */
150
+ retryDelay?: number;
151
+ /** Override the global fetch (useful for tests). */
152
+ fetch?: typeof fetch;
153
+ }
154
+ /**
155
+ * Shared HTTP client used by the control-plane facades (approval, secrets,
156
+ * connections, config, events). Wraps `fetch` with timeout, exponential
157
+ * backoff with jitter, idempotent-only retries, and a uniform Result
158
+ * envelope so facades never throw at the SDK boundary.
159
+ *
160
+ * Reads `STACKBONE_API_URL` and `STACKBONE_AGENT_JWT` lazily on every request so
161
+ * env-var rotation is picked up without restarting the client.
162
+ */
163
+ declare class HttpClient {
164
+ private readonly resolved;
165
+ private readonly timeout;
166
+ private readonly retryCount;
167
+ private readonly retryDelay;
168
+ private readonly fetchImpl;
169
+ constructor(resolved: ResolvedConfig, options?: HttpClientOptions);
170
+ get<T>(path: string, options?: RequestOptions$1): Promise<Result<T>>;
171
+ post<T>(path: string, body?: RequestOptions$1['body'], options?: RequestOptions$1): Promise<Result<T>>;
172
+ put<T>(path: string, body?: RequestOptions$1['body'], options?: RequestOptions$1): Promise<Result<T>>;
173
+ patch<T>(path: string, body?: RequestOptions$1['body'], options?: RequestOptions$1): Promise<Result<T>>;
174
+ delete<T>(path: string, options?: RequestOptions$1): Promise<Result<T>>;
175
+ request<T>(method: string, path: string, options?: RequestOptions$1): Promise<Result<T>>;
176
+ private computeBackoff;
177
+ }
178
+
179
+ interface ApprovalToolSpec<I, O> {
180
+ name: string;
181
+ description: string;
182
+ parameters: Record<string, unknown>;
183
+ needsApproval?: boolean | ((input: I) => boolean | Promise<boolean>);
184
+ /**
185
+ * Maps the LLM input to the approval request fields. `topic` and `payload`
186
+ * default to the tool name and the raw input respectively when omitted;
187
+ * `onDecide` must always be supplied so the control plane knows where to
188
+ * deliver the eventual decision.
189
+ */
190
+ toRequest: (input: I) => Omit<ApprovalRequestOptions<I>, 'payload' | 'topic'> & Partial<Pick<ApprovalRequestOptions<I>, 'payload' | 'topic'>>;
191
+ execute: (input: I) => Promise<O> | O;
192
+ }
193
+ interface OpenAIToolSpec {
194
+ type: 'function';
195
+ function: {
196
+ name: string;
197
+ description: string;
198
+ parameters: Record<string, unknown>;
199
+ };
200
+ }
201
+ type ToolInvokeResult<O> = {
202
+ status: 'pending';
203
+ approvalId: string;
204
+ expiresAt: string;
205
+ } | {
206
+ status: 'ok';
207
+ result: O;
208
+ };
209
+ interface ApprovalTool<I, O> {
210
+ name: string;
211
+ description: string;
212
+ parameters: Record<string, unknown>;
213
+ openaiSpec(): OpenAIToolSpec;
214
+ invoke(input: I): Promise<Result<ToolInvokeResult<O>>>;
215
+ }
216
+
217
+ interface VerifyOptions {
218
+ /** Override the signing key resolved from `STACKBONE_APPROVAL_SIGNING_KEY` / `approvalSigningKey`. */
219
+ signingKey?: string;
220
+ /** Reject signatures whose timestamp is older than this (seconds). Default 300. */
221
+ toleranceSeconds?: number;
222
+ /** Inject a clock for tests. Defaults to `Date.now`. */
223
+ now?: () => number;
224
+ }
225
+
226
+ type ApprovalTopic = string;
227
+ declare const DECISION_STATUSES: readonly ["approved", "rejected", "timed_out", "cancelled"];
228
+ type DecisionStatus = (typeof DECISION_STATUSES)[number];
229
+ type ApprovalStatus = 'pending' | DecisionStatus;
230
+ type Decision<T = unknown> = {
231
+ status: 'approved' | 'rejected';
232
+ payload: T;
233
+ approver: ApproverInfo;
234
+ decidedAt: string;
235
+ reason?: string;
236
+ } | {
237
+ status: 'timed_out';
238
+ decidedAt: string;
239
+ } | {
240
+ status: 'cancelled';
241
+ decidedAt: string;
242
+ reason?: string;
243
+ };
244
+ interface ApproverInfo {
245
+ id: string;
246
+ email: string;
247
+ name?: string;
248
+ }
249
+ interface ApprovalRequestOptions<T = unknown> {
250
+ topic: ApprovalTopic;
251
+ payload: T;
252
+ title?: string;
253
+ description?: string;
254
+ /** Path on the agent the control plane will POST the decision to (resolved against the agent's public URL server-side). */
255
+ onDecide: string;
256
+ /** ISO 8601 duration (`'24h'`) or milliseconds. Default 24h. */
257
+ timeout?: string | number;
258
+ onTimeout?: 'reject' | 'approve' | 'ignore';
259
+ approver?: string;
260
+ /** Same `(topic, idempotencyKey)` returns the same `approvalId` instead of creating a new one. */
261
+ idempotencyKey?: string;
262
+ metadata?: Record<string, unknown>;
263
+ }
264
+ interface ApprovalRequest {
265
+ approvalId: string;
266
+ status: 'pending';
267
+ callbackUrl: string;
268
+ expiresAt: string;
269
+ }
270
+ interface ApprovalRecord<T = unknown> {
271
+ approvalId: string;
272
+ topic: ApprovalTopic;
273
+ status: ApprovalStatus;
274
+ payload: T;
275
+ decision?: Decision<T>;
276
+ createdAt: string;
277
+ expiresAt: string;
278
+ metadata?: Record<string, unknown>;
279
+ }
280
+ interface ApprovalListOptions {
281
+ status?: ApprovalStatus;
282
+ topic?: ApprovalTopic;
283
+ cursor?: string;
284
+ /** 1..100. Default 50. */
285
+ limit?: number;
286
+ }
287
+ interface ApprovalListResult<T = unknown> {
288
+ items: ApprovalRecord<T>[];
289
+ nextCursor?: string;
290
+ }
291
+ declare class ApprovalFacade {
292
+ private readonly _resolved;
293
+ private readonly _http;
294
+ private readonly _gate;
295
+ constructor(_resolved: ResolvedConfig, _http: HttpClient,
296
+ /** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
297
+ gate?: ModuleGate);
298
+ request<T = unknown>(options: ApprovalRequestOptions<T>): Promise<Result<ApprovalRequest>>;
299
+ cancel(approvalId: string, reason?: string): Promise<Result<void>>;
300
+ get<T = unknown>(approvalId: string): Promise<Result<ApprovalRecord<T>>>;
301
+ list<T = unknown>(options?: ApprovalListOptions): Promise<Result<ApprovalListResult<T>>>;
302
+ /**
303
+ * Local crypto verification — does not touch the datapath, so it is NOT
304
+ * gated by the contract handshake. Auditing rule: a method gates iff it
305
+ * issues an HTTP request to the configured baseUrl.
306
+ */
307
+ verify<T = unknown>(request: Request, options?: VerifyOptions): Promise<Result<Decision<T>>>;
308
+ /**
309
+ * Pure factory — returns an `ApprovalTool` whose `invoke()` ultimately
310
+ * calls back into `ApprovalFacade.request`, so the gate fires there. Not
311
+ * gated here.
312
+ */
313
+ tool<I, O>(spec: ApprovalToolSpec<I, O>): ApprovalTool<I, O>;
314
+ }
315
+
316
+ declare class ConfigFacade {
317
+ private readonly _http;
318
+ private readonly _gate;
319
+ constructor(resolved: ResolvedConfig, _http: HttpClient,
320
+ /** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
321
+ gate?: ModuleGate);
322
+ get<T = unknown>(key: string): Promise<Result<T>>;
323
+ /**
324
+ * Keys absent from the agent's config come back as omissions in the
325
+ * response — the returned map only contains entries the control plane
326
+ * actually has, so callers must access fields with `?.` (the return type
327
+ * is `Partial<T>`).
328
+ */
329
+ getMany<T extends Record<string, unknown> = Record<string, unknown>>(keys: string[]): Promise<Result<Partial<T>>>;
330
+ }
331
+
332
+ /** OAuth connections (Notion, GDrive, Slack…). */
333
+ declare class ConnectionsFacade {
334
+ private readonly _resolved;
335
+ private readonly _http;
336
+ constructor(_resolved: ResolvedConfig, _http: HttpClient);
337
+ list(): Promise<Result<readonly never[]>>;
338
+ }
339
+
340
+ /** Emit events to the workspace event bus. */
341
+ declare class EventsFacade {
342
+ private readonly _http;
343
+ private readonly _gate;
344
+ constructor(resolved: ResolvedConfig, _http: HttpClient,
345
+ /** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
346
+ gate?: ModuleGate);
347
+ emit(_name: string, _payload: unknown): Promise<Result<void>>;
348
+ }
349
+
350
+ /**
351
+ * A prompt managed by the Stackbone control plane. Templates are plain strings
352
+ * with Mustache-style `{{var}}` placeholders — no conditionals or loops.
353
+ */
354
+ interface Prompt {
355
+ name: string;
356
+ template: string;
357
+ /** Monotonically increasing per `name`. The first `create` produces version `1`. */
358
+ version: number;
359
+ /** Variable names parsed from the template. */
360
+ variables?: readonly string[];
361
+ metadata?: Record<string, unknown>;
362
+ /** ISO 8601 UTC timestamp. */
363
+ createdAt: string;
364
+ updatedAt: string;
365
+ }
366
+ interface GetPromptOptions {
367
+ /** Pin a specific version. Omitted -> latest. */
368
+ version?: number;
369
+ }
370
+ interface ListPromptsOptions {
371
+ /** 1..100. Default 50. */
372
+ limit?: number;
373
+ cursor?: string;
374
+ }
375
+ interface ListPromptsResult {
376
+ /** Latest version of each prompt. */
377
+ items: readonly Prompt[];
378
+ nextCursor?: string;
379
+ }
380
+ interface CreatePromptRequest {
381
+ name: string;
382
+ template: string;
383
+ metadata?: Record<string, unknown>;
384
+ }
385
+ interface UpdatePromptOptions {
386
+ /** New template. Bumps `version` by one. */
387
+ template?: string;
388
+ /** Shallow-merged onto the existing metadata. */
389
+ metadata?: Record<string, unknown>;
390
+ }
391
+ interface DeletePromptOptions {
392
+ /** Delete a specific version. Omitted -> delete every version under `name`. */
393
+ version?: number;
394
+ }
395
+ interface DeletePromptResult {
396
+ name: string;
397
+ /** Number of versions actually removed. */
398
+ deleted: number;
399
+ }
400
+ /**
401
+ * `client.prompts` — managed prompts for the agent. Prompts live outside the
402
+ * code so creators can edit, version and A/B test them without rebuilding the
403
+ * container. The first iteration is a scaffolding placeholder: every method
404
+ * returns `not_implemented`. The real backend will be the Stackbone control
405
+ * plane and the public surface defined here is the contract callers can
406
+ * already type against.
407
+ */
408
+ declare class PromptsFacade {
409
+ private readonly _http;
410
+ constructor(_resolved: ResolvedConfig, _http: HttpClient);
411
+ get(_name: string, _options?: GetPromptOptions): Promise<Result<Prompt>>;
412
+ compile(_name: string, _variables: Record<string, unknown>, _options?: GetPromptOptions): Promise<Result<string>>;
413
+ list(_options?: ListPromptsOptions): Promise<Result<ListPromptsResult>>;
414
+ create(_request: CreatePromptRequest): Promise<Result<Prompt>>;
415
+ update(_name: string, _options: UpdatePromptOptions): Promise<Result<Prompt>>;
416
+ delete(_name: string, _options?: DeletePromptOptions): Promise<Result<DeletePromptResult>>;
417
+ }
418
+
419
+ declare class SecretsFacade {
420
+ private readonly _http;
421
+ private readonly _gate;
422
+ constructor(resolved: ResolvedConfig, _http: HttpClient,
423
+ /** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
424
+ gate?: ModuleGate);
425
+ get(name: string): Promise<Result<string>>;
426
+ /**
427
+ * Names absent from the workspace come back as omissions in the response —
428
+ * the returned map only contains entries the control plane actually has.
429
+ * Callers that need "all-or-nothing" semantics should diff the keys.
430
+ */
431
+ getMany(names: string[]): Promise<Result<Record<string, string>>>;
432
+ }
433
+
434
+ /**
435
+ * Public surface behind `client.ai`. Wraps the official `openai` SDK with a
436
+ * `baseURL` override pointing to OpenRouter, so 300+ models are reachable
437
+ * with a single, OpenAI-compatible API.
438
+ *
439
+ * Lazy: the underlying `OpenAI` client is built on the first method call
440
+ * (or the first time a sub-namespace is touched) so env vars mutated after
441
+ * `createClient()` are still picked up — same pattern as `DatabaseModule`.
442
+ *
443
+ * Error model: every method returns `Result<T>`. Upstream `APIError`s are
444
+ * mapped to `SdkError` with stable `ai_*` codes. For streaming, the
445
+ * `Result` envelope only covers connection establishment; once the stream
446
+ * is open, mid-flight errors propagate through the iterator — callers must
447
+ * wrap their `for await` loop in `try/catch`. Aborts via `signal.abort()`
448
+ * surface as `ai_aborted` in non-streaming requests; in streaming, the
449
+ * iterator simply terminates (the upstream SDK swallows the abort).
450
+ *
451
+ * Test override: pass `clientOverride` to inject a pre-built `OpenAI`
452
+ * instance (or a stand-in) instead of letting the module build one. The
453
+ * override flows to all four namespaces — `models.list()` included.
454
+ */
455
+ declare class AiModule {
456
+ private readonly _resolved;
457
+ private _openai;
458
+ private _chat;
459
+ private _embeddings;
460
+ private _images;
461
+ private _models;
462
+ private readonly _gate;
463
+ constructor(_resolved: ResolvedConfig, clientOverride?: OpenAI,
464
+ /** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
465
+ gate?: ModuleGate);
466
+ get chat(): {
467
+ completions: ChatCompletionsNamespace;
468
+ };
469
+ get embeddings(): EmbeddingsNamespace;
470
+ get images(): ImagesNamespace;
471
+ get models(): ModelsNamespace;
472
+ private client;
473
+ }
474
+ declare class ChatCompletionsNamespace {
475
+ private readonly _getClient;
476
+ private readonly _gate;
477
+ constructor(_getClient: () => Result<OpenAI>, _gate: ModuleGate);
478
+ create(params: ChatCompletionCreateParamsStreaming, options?: RequestOptions): Promise<Result<Stream<ChatCompletionChunk>>>;
479
+ create(params: ChatCompletionCreateParamsNonStreaming, options?: RequestOptions): Promise<Result<ChatCompletion>>;
480
+ }
481
+ declare class EmbeddingsNamespace {
482
+ private readonly _getClient;
483
+ private readonly _gate;
484
+ constructor(_getClient: () => Result<OpenAI>, _gate: ModuleGate);
485
+ create(params: EmbeddingCreateParams, options?: RequestOptions): Promise<Result<CreateEmbeddingResponse>>;
486
+ }
487
+ interface ImageGenerateParams {
488
+ model: string;
489
+ prompt: string;
490
+ /** Forwarded to OpenRouter as `image_config`. Use for aspect ratio, size, etc. */
491
+ imageConfig?: Record<string, unknown>;
492
+ }
493
+ interface ImagesResponse {
494
+ created: number;
495
+ data: GeneratedImage[];
496
+ }
497
+ interface GeneratedImage {
498
+ /** Original `data:image/...;base64,...` URL as returned by OpenRouter. */
499
+ url: string;
500
+ /** Base64 payload extracted from the data URL. */
501
+ b64Json?: string;
502
+ /** MIME type parsed from the data URL prefix. */
503
+ mimeType?: string;
504
+ }
505
+ declare class ImagesNamespace {
506
+ private readonly _getClient;
507
+ private readonly _gate;
508
+ constructor(_getClient: () => Result<OpenAI>, _gate: ModuleGate);
509
+ /**
510
+ * OpenRouter does not implement the OpenAI `/v1/images/generations`
511
+ * endpoint. Image models are reached via `/v1/chat/completions` with
512
+ * `modalities: ['image']`, and the resulting image is returned as a
513
+ * non-standard `message.images[]` array of base64 data URLs. This method
514
+ * encapsulates that quirk and surfaces an OpenAI-shaped response so
515
+ * agent code does not need to know.
516
+ *
517
+ * If the model does not return any images (wrong model, content policy,
518
+ * etc.) this returns an `ai_no_image_generated` error rather than an
519
+ * empty success — surfacing the failure to the caller instead of
520
+ * silently producing zero images.
521
+ */
522
+ generate(params: ImageGenerateParams, options?: RequestOptions): Promise<Result<ImagesResponse>>;
523
+ }
524
+ interface OpenRouterModel {
525
+ id: string;
526
+ name?: string;
527
+ description?: string;
528
+ context_length?: number;
529
+ pricing?: Record<string, unknown>;
530
+ architecture?: Record<string, unknown>;
531
+ supported_parameters?: string[];
532
+ }
533
+ interface ModelsListResponse {
534
+ data: OpenRouterModel[];
535
+ }
536
+ declare class ModelsNamespace {
537
+ private readonly _getClient;
538
+ private readonly _gate;
539
+ constructor(_getClient: () => Result<OpenAI>, _gate: ModuleGate);
540
+ /**
541
+ * Calls `GET ${baseURL}/models` directly with `fetch` — the upstream
542
+ * `openai.models.list()` parses the response into OpenAI's `Model` type
543
+ * which discards OpenRouter-specific fields (`pricing`, `context_length`,
544
+ * `supported_parameters`, `architecture`). Going through `fetch` keeps
545
+ * the full payload.
546
+ *
547
+ * Reuses the cached `OpenAI` instance (built lazily by `AiModule`) so
548
+ * the resolved `apiKey` and `baseURL` are consistent with the rest of
549
+ * the namespaces — and any `clientOverride` injected for tests applies
550
+ * here too.
551
+ */
552
+ list(options?: RequestOptions): Promise<Result<ModelsListResponse>>;
553
+ }
554
+ interface RequestOptions {
555
+ signal?: AbortSignal;
556
+ }
557
+
558
+ /**
559
+ * Public surface a creator can use through `client.database`. We pin it to the
560
+ * `postgres-js` Drizzle wrapper because that is the only driver `@stackbone/sdk`
561
+ * ships and the only one `STACKBONE_POSTGRES_URL` is contracted to point at.
562
+ *
563
+ * Re-exporting Drizzle's own type instead of inventing a structural alias keeps
564
+ * the surface 1:1 with what `drizzle-orm` documents and what `@stackbone/sdk/db`
565
+ * re-exports — so types flow end to end without translation.
566
+ */
567
+ type DrizzleClient = PostgresJsDatabase<Record<string, never>> & {
568
+ $client: Sql;
569
+ };
570
+
571
+ /**
572
+ * `client.database` is a lazy wrapper over a `drizzle()` instance bound to
573
+ * `STACKBONE_POSTGRES_URL`. The underlying postgres-js connection and the
574
+ * Drizzle facade are constructed on the first method call so env vars rotated
575
+ * post-`createClient()` are still honoured.
576
+ *
577
+ * Native Drizzle methods (`select`, `insert`, `update`, `delete`, `transaction`,
578
+ * `execute`) are exposed verbatim — we explicitly avoid wrapping them in a
579
+ * Result envelope here. Drizzle already throws on misuse and returns typed rows
580
+ * on success; mirroring its API keeps the contract identical to what the
581
+ * `@stackbone/sdk/db` re-exports document, so creators write idiomatic Drizzle.
582
+ *
583
+ * Missing `STACKBONE_POSTGRES_URL` raises `SdkError('database_not_configured')`
584
+ * with an actionable hint that points to `stackbone dev`. This is a creator
585
+ * setup bug, not something the agent's code can recover from at runtime.
586
+ *
587
+ * Methods are declared with their full Drizzle generic signatures (rather than
588
+ * `(...args) => Drizzle[k](...args)` arrow properties) so the inferred row
589
+ * types from `pgTable(...)` flow end-to-end into the caller, which is the
590
+ * promise the `@stackbone/sdk/db` re-export makes.
591
+ *
592
+ * The actual `postgres()` pool and the Drizzle handle are owned by the
593
+ * module-internal singleton in `./internal.ts`. `client.database` and any
594
+ * other internal SDK module that imports `getDatabaseHandle` therefore share
595
+ * one pool per agent process — see `internal.spec.ts` for the invariant.
596
+ */
597
+ declare class DatabaseModule {
598
+ private readonly _resolved;
599
+ private readonly _gate;
600
+ constructor(_resolved: ResolvedConfig,
601
+ /**
602
+ * Test seam — see `ModuleGate`. Defaults to the lazy contract gate.
603
+ * The gate is awaited at the terminal `await` of every Drizzle chain
604
+ * (`select/insert/update/delete/execute/transaction`); when it returns
605
+ * an error, a tagged `GateBlockedError` is thrown so callers see the
606
+ * same `Error & { code, message, meta }` surface as the existing
607
+ * `database_not_configured` setup-bug.
608
+ */
609
+ gate?: ModuleGate);
610
+ /**
611
+ * Escape hatch — returns Drizzle's relational query builder verbatim. The
612
+ * contract gate does NOT fire here because the surface is a synchronous
613
+ * accessor; the user can still bypass the gate via `client.database.raw()`
614
+ * for the same reason. Audit: `query` and `raw()` are intentionally
615
+ * ungated escape hatches; the standard `select/insert/update/delete/
616
+ * execute/transaction` path is fully gated.
617
+ */
618
+ get query(): DrizzleClient['query'];
619
+ select: DrizzleClient['select'];
620
+ insert: DrizzleClient['insert'];
621
+ update: DrizzleClient['update'];
622
+ delete: DrizzleClient['delete'];
623
+ execute: DrizzleClient['execute'];
624
+ transaction: DrizzleClient['transaction'];
625
+ /**
626
+ * Escape hatch for callers that want the raw Drizzle handle (e.g. to pass
627
+ * it to a library that expects `PostgresJsDatabase`). Reading this property
628
+ * triggers the same lazy initialisation as any other method but
629
+ * intentionally does NOT consult the contract gate — the surface is sync
630
+ * and the caller is opting out of the SDK's structured surface.
631
+ */
632
+ raw(): DrizzleClient;
633
+ }
634
+
635
+ /**
636
+ * Where a memory lives. Default `'user'`. `'session'` is short-lived and
637
+ * collapsed (or dropped) by `endSession()`; `'agent'` is shared across every
638
+ * user of the agent.
639
+ */
640
+ type MemoryScope = 'user' | 'session' | 'agent';
641
+ /**
642
+ * Either raw text or an OpenAI-shaped conversation. The future mem0 backend
643
+ * accepts both: strings ingest verbatim, message arrays are summarised into
644
+ * one or more facts.
645
+ */
646
+ type MemoryContent = string | ChatCompletionMessageParam[];
647
+ interface AddMemoryRequest {
648
+ userId: string;
649
+ /** Conversation/session bucket. Required when `scope: 'session'`. */
650
+ sessionId?: string;
651
+ /** Default `'user'`. */
652
+ scope?: MemoryScope;
653
+ metadata?: Record<string, unknown>;
654
+ }
655
+ interface MemoryItem {
656
+ id: string;
657
+ content: string;
658
+ userId: string;
659
+ sessionId?: string;
660
+ scope: MemoryScope;
661
+ metadata?: Record<string, unknown>;
662
+ /** ISO 8601 UTC timestamp. */
663
+ createdAt: string;
664
+ updatedAt: string;
665
+ }
666
+ interface SearchMemoryOptions {
667
+ userId?: string;
668
+ sessionId?: string;
669
+ /** 1..100. Default 10. */
670
+ limit?: number;
671
+ /** Minimum cosine similarity in [0, 1]. Hits below this are dropped. */
672
+ threshold?: number;
673
+ /** Metadata predicates AND-ed to the semantic match. */
674
+ filters?: Record<string, unknown>;
675
+ /** Restrict the search to a subset of scopes. Default: every scope. */
676
+ includeScopes?: readonly MemoryScope[];
677
+ }
678
+ interface MemoryHit extends MemoryItem {
679
+ /** Cosine similarity in [0, 1]. */
680
+ score: number;
681
+ }
682
+ interface ListMemoryRequest {
683
+ userId: string;
684
+ /** 1..100. Default 50. */
685
+ limit?: number;
686
+ cursor?: string;
687
+ }
688
+ interface ListMemoryResult {
689
+ items: readonly MemoryItem[];
690
+ nextCursor?: string;
691
+ }
692
+ interface UpdateMemoryOptions {
693
+ content?: string;
694
+ /** Shallow-merged onto the existing metadata (mem0 semantics). */
695
+ metadata?: Record<string, unknown>;
696
+ }
697
+ interface DeleteAllMemoryRequest {
698
+ userId: string;
699
+ }
700
+ type MemoryHistoryEvent = 'created' | 'updated' | 'accessed' | 'deleted';
701
+ interface MemoryHistoryEntry {
702
+ id: string;
703
+ memoryId: string;
704
+ event: MemoryHistoryEvent;
705
+ /** ISO 8601 UTC timestamp. */
706
+ at: string;
707
+ /** Identifier of who triggered the event (user id, agent id, etc.). */
708
+ actor?: string;
709
+ /** Content snapshot before the event. Only set for `updated`/`deleted`. */
710
+ before?: string;
711
+ /** Content snapshot after the event. Only set for `created`/`updated`. */
712
+ after?: string;
713
+ metadata?: Record<string, unknown>;
714
+ }
715
+ interface EndSessionOptions {
716
+ /** Persist session-scoped facts to long-term (`'user'`) memory before closing. Default `true`. */
717
+ persist?: boolean;
718
+ }
719
+ interface EndSessionResult {
720
+ sessionId: string;
721
+ /** Number of session memories promoted to long-term storage. `0` when `persist: false`. */
722
+ persisted: number;
723
+ }
724
+ /**
725
+ * `client.memory` — long-term memory for the agent. The first iteration is a
726
+ * scaffolding placeholder: every method returns `not_implemented`. The real
727
+ * backend will be mem0 (`mem0ApiKey` / `MEM0_API_KEY`) and the public surface
728
+ * defined here is the contract callers can already type against.
729
+ */
730
+ declare class MemoryModule {
731
+ private readonly _resolved;
732
+ constructor(_resolved: ResolvedConfig);
733
+ add(_content: MemoryContent, _request: AddMemoryRequest): Promise<Result<MemoryItem>>;
734
+ search(_query: string, _options?: SearchMemoryOptions): Promise<Result<readonly MemoryHit[]>>;
735
+ delete(_memoryId: string): Promise<Result<{
736
+ id: string;
737
+ }>>;
738
+ deleteAll(_request: DeleteAllMemoryRequest): Promise<Result<{
739
+ deleted: number;
740
+ }>>;
741
+ get(_memoryId: string): Promise<Result<MemoryItem>>;
742
+ list(_request: ListMemoryRequest): Promise<Result<ListMemoryResult>>;
743
+ update(_memoryId: string, _options: UpdateMemoryOptions): Promise<Result<MemoryItem>>;
744
+ history(_memoryId: string): Promise<Result<readonly MemoryHistoryEntry[]>>;
745
+ endSession(_sessionId: string, _options?: EndSessionOptions): Promise<Result<EndSessionResult>>;
746
+ }
747
+
748
+ declare const RUN_STEP_TYPES: readonly ["agent", "llm_call", "db_query", "http_fetch", "queue_publish", "hitl_pause", "tool_call", "rag_query", "storage_op"];
749
+ type RunStepType = (typeof RUN_STEP_TYPES)[number];
750
+ declare const STEP_TYPE_ATTRIBUTE = "stackbone.step.type";
751
+ declare const RUN_ID_ATTRIBUTE = "stackbone.run.id";
752
+ /**
753
+ * Minimal duck-typed view of an OpenTelemetry `ReadableSpan`. We mirror the
754
+ * shape the OTel SDK exposes on `@opentelemetry/sdk-trace-base` rather than
755
+ * importing the package so `@stackbone/sdk` doesn't pull the OTel runtime into
756
+ * every agent that doesn't enable Studio. Agents wire the processor with
757
+ * `provider.addSpanProcessor(new RunStepsSpanProcessor(...))` and the
758
+ * structural match satisfies TypeScript.
759
+ */
760
+ interface SpanContextLike {
761
+ spanId: string;
762
+ traceId: string;
763
+ }
764
+ interface ReadableSpanLike {
765
+ spanContext(): SpanContextLike;
766
+ parentSpanContext?: SpanContextLike | undefined;
767
+ parentSpanId?: string | undefined;
768
+ name: string;
769
+ attributes: Record<string, unknown>;
770
+ status?: {
771
+ code: number;
772
+ message?: string;
773
+ } | undefined;
774
+ startTime?: [number, number] | undefined;
775
+ endTime?: [number, number] | undefined;
776
+ duration?: [number, number] | undefined;
777
+ }
778
+ interface PostgresLike {
779
+ unsafe(query: string, params?: unknown[]): Promise<unknown>;
780
+ end?(): Promise<void>;
781
+ }
782
+ interface RunStepsSpanProcessorOptions {
783
+ /**
784
+ * Postgres connection string of the install (local emulator at :5433 in
785
+ * dev, Neon TCP/HTTP URL in cloud). Falls back to `DATABASE_URL`.
786
+ */
787
+ connectionString?: string | undefined;
788
+ /** Flush trigger by buffer size. Defaults to 50. */
789
+ flushBatchSize?: number | undefined;
790
+ /** Flush trigger by elapsed time (ms). Defaults to 500. */
791
+ flushIntervalMs?: number | undefined;
792
+ /** Override how a span maps to a step type. */
793
+ resolveStepType?: ((span: ReadableSpanLike) => RunStepType) | undefined;
794
+ /** Test seam — inject a postgres-js-compatible client. */
795
+ sql?: PostgresLike | undefined;
796
+ /** Test seam — deterministic UUID generator. */
797
+ newId?: (() => string) | undefined;
798
+ }
799
+ /**
800
+ * Materialises every OTel span produced by the agent into a row of
801
+ * `stackbone_platform.run_steps`. Designed to coexist with the OTLP exporter:
802
+ * cloud builds register both processors so Postgres feeds Studio (paridad
803
+ * con local) and Tempo feeds Platform Ops with retention/aggregation. If a
804
+ * write fails the processor logs to stderr and keeps accepting spans —
805
+ * observability is never load-bearing for agent execution.
806
+ *
807
+ * Spec: docs/arquitectura/specs/stackbone-agent-protocol-v1.md §6.3
808
+ * ADR: docs/arquitectura/decisiones/2026-05-03-stackbone-studio-datapath-y-scope-mvp.md §D9
809
+ */
810
+ declare class RunStepsSpanProcessor {
811
+ private readonly buffer;
812
+ private readonly stepIdBySpanId;
813
+ private readonly batchSize;
814
+ private readonly intervalMs;
815
+ private readonly resolveStepType;
816
+ private readonly newId;
817
+ private readonly connectionString;
818
+ private readonly ownsSql;
819
+ private sql;
820
+ private timer;
821
+ private inFlight;
822
+ constructor(options?: RunStepsSpanProcessorOptions);
823
+ onStart(span: ReadableSpanLike): void;
824
+ onEnd(span: ReadableSpanLike): void;
825
+ forceFlush(): Promise<void>;
826
+ shutdown(): Promise<void>;
827
+ private buildRow;
828
+ private armTimer;
829
+ private disarmTimer;
830
+ private flush;
831
+ private ensureSql;
832
+ private writeBatch;
833
+ }
834
+
835
+ interface AggregateRunCostOptions {
836
+ sql: PostgresLike;
837
+ }
838
+ interface AggregateRunCostResult {
839
+ costEstimatedUsd: number;
840
+ }
841
+ declare function aggregateRunCost(runId: string, options: AggregateRunCostOptions): Promise<AggregateRunCostResult>;
842
+
843
+ declare const LOG_LEVELS: {
844
+ readonly trace: 10;
845
+ readonly debug: 20;
846
+ readonly info: 30;
847
+ readonly warn: 40;
848
+ readonly error: 50;
849
+ readonly fatal: 60;
850
+ };
851
+ type LogLevelName = keyof typeof LOG_LEVELS;
852
+ type LogLevelNumber = (typeof LOG_LEVELS)[LogLevelName];
853
+ interface LogRecord {
854
+ /** Pino-style numeric level. */
855
+ level: LogLevelNumber;
856
+ /** Epoch milliseconds. Pino uses `time` as a number. */
857
+ time: number;
858
+ msg: string;
859
+ run_id: string;
860
+ trace_id?: string;
861
+ installation_id?: string;
862
+ agent_id?: string;
863
+ [key: string]: unknown;
864
+ }
865
+ interface PlatformLoggerOptions {
866
+ runId: string;
867
+ installationId?: string | undefined;
868
+ agentId?: string | undefined;
869
+ /**
870
+ * When set (cloud), logs are batched and POSTed to `<endpoint>/v1/logs` as
871
+ * OTLP/HTTP/JSON. When unset (local), logs are appended to
872
+ * `<runsDir>/<runId>.jsonl`. Falls back to `OTEL_EXPORTER_OTLP_ENDPOINT`.
873
+ */
874
+ otelEndpoint?: string | undefined;
875
+ /** Local-only: directory where JSONL files live. Defaults to `~/.stackbone/dev/runs`. */
876
+ runsDir?: string | undefined;
877
+ /** Cloud-only: extra OTel resource attributes to send with every batch. */
878
+ resourceAttributes?: Record<string, string> | undefined;
879
+ /** Cloud-only: HTTP timeout per OTLP POST in ms. Defaults to 5_000. */
880
+ httpTimeoutMs?: number | undefined;
881
+ /** Flush trigger by buffer size. Defaults to 50. */
882
+ flushBatchSize?: number | undefined;
883
+ /** Flush trigger by elapsed time (ms). Defaults to 1_000. */
884
+ flushIntervalMs?: number | undefined;
885
+ /** Test seam — fetch implementation. Defaults to global `fetch`. */
886
+ fetchImpl?: typeof fetch | undefined;
887
+ /** Test seam — `fs.appendFile`-shaped sink the local destination uses. */
888
+ appendFileImpl?: ((path: string, data: string) => Promise<void>) | undefined;
889
+ /** Test seam — `fs.mkdir`-shaped helper the local destination uses. */
890
+ mkdirImpl?: ((path: string) => Promise<void>) | undefined;
891
+ /** Test seam — clock for `time` field. Defaults to `Date.now`. */
892
+ now?: (() => number) | undefined;
893
+ /** Override mode resolution (skips the env detection). */
894
+ mode?: PlatformLoggerMode | undefined;
895
+ }
896
+ type PlatformLoggerMode = 'local' | 'cloud';
897
+ interface PinoLike {
898
+ trace(msgOrObj: unknown, msg?: string): void;
899
+ debug(msgOrObj: unknown, msg?: string): void;
900
+ info(msgOrObj: unknown, msg?: string): void;
901
+ warn(msgOrObj: unknown, msg?: string): void;
902
+ error(msgOrObj: unknown, msg?: string): void;
903
+ fatal(msgOrObj: unknown, msg?: string): void;
904
+ }
905
+ interface PlatformLogger extends PinoLike {
906
+ readonly mode: PlatformLoggerMode;
907
+ /** Bind extra fields and return a child logger sharing the same destination. */
908
+ child(bindings: Record<string, unknown>): PlatformLogger;
909
+ /** Force-flush pending writes (close file handle or POST OTLP batch). */
910
+ flush(): Promise<void>;
911
+ /** Stop accepting writes and release resources. */
912
+ close(): Promise<void>;
913
+ }
914
+ /**
915
+ * Creates a Pino-aligned logger that writes JSONL to disk in local mode and
916
+ * batches OTLP/HTTP/JSON log records to an OTel collector in cloud mode. Mode
917
+ * is resolved from `OTEL_EXPORTER_OTLP_ENDPOINT` or the explicit override.
918
+ */
919
+ declare function createPlatformLogger(options: PlatformLoggerOptions): PlatformLogger;
920
+ declare function defaultRunsDir(): string;
921
+
922
+ declare class ObservabilityModule {
923
+ private readonly _resolved;
924
+ private _processor?;
925
+ private readonly _loggers;
926
+ private _sql;
927
+ private _sqlResolved;
928
+ constructor(_resolved: ResolvedConfig);
929
+ /**
930
+ * Singleton SpanProcessor that the agent registers in its OTel
931
+ * TracerProvider. Coexists with the OTLP exporter — cloud builds wire
932
+ * both so Postgres feeds Studio (paridad con local) and Tempo feeds
933
+ * Platform Ops.
934
+ */
935
+ spanProcessor(options?: RunStepsSpanProcessorOptions): RunStepsSpanProcessor;
936
+ flush(): Promise<Result<void>>;
937
+ /**
938
+ * Per-run structured logger. In local mode (no `OTEL_EXPORTER_OTLP_ENDPOINT`)
939
+ * appends JSONL to `~/.stackbone/dev/runs/<runId>.jsonl`; in cloud mode batches
940
+ * OTLP/HTTP/JSON log records to the collector. Cached by `runId` so the
941
+ * file handle / buffered timer stays stable across repeated calls inside
942
+ * the same run.
943
+ */
944
+ logger(options: {
945
+ runId: string;
946
+ } & Partial<PlatformLoggerOptions>): PlatformLogger;
947
+ /** Closes the per-run logger (flushes the JSONL file or the OTLP buffer). */
948
+ closeLogger(runId: string): Promise<void>;
949
+ /**
950
+ * Hook the agent calls when a run finishes. Sums `cost_usd` from every
951
+ * `llm_call` span of the run and writes the total to
952
+ * `stackbone_platform.runs.cost_estimated_usd`. Errors are folded into the
953
+ * Result envelope so the agent's run cleanup never aborts on a flaky
954
+ * Postgres call — the cost is decorative metadata, not load-bearing.
955
+ */
956
+ closeRun(runId: string): Promise<Result<AggregateRunCostResult>>;
957
+ private resolveSql;
958
+ }
959
+
960
+ interface PublishRequest {
961
+ url: string;
962
+ body: unknown;
963
+ retries?: number;
964
+ delay?: number;
965
+ deduplicationId?: string;
966
+ headers?: Record<string, string>;
967
+ }
968
+ declare class QueuesModule {
969
+ private readonly _gate;
970
+ constructor(resolved: ResolvedConfig,
971
+ /** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
972
+ gate?: ModuleGate);
973
+ publish(_request: PublishRequest): Promise<Result<{
974
+ messageId: string;
975
+ }>>;
976
+ }
977
+
978
+ type ChunkStrategy = 'recursive' | 'sentence';
979
+ interface ChunkOptions {
980
+ /** Splitter algorithm. Default `recursive`. */
981
+ strategy?: ChunkStrategy;
982
+ /** Target characters per chunk. Default 512. */
983
+ size?: number;
984
+ /** Characters of overlap between consecutive chunks. Default 64. */
985
+ overlap?: number;
986
+ }
987
+
988
+ type RagIngestProgress = {
989
+ type: 'started';
990
+ jobId: string;
991
+ totalChunks: number;
992
+ } | {
993
+ type: 'progress';
994
+ jobId: string;
995
+ processedChunks: number;
996
+ totalChunks: number;
997
+ currentDocument?: {
998
+ source: string;
999
+ ordinal: number;
1000
+ };
1001
+ } | {
1002
+ type: 'completed';
1003
+ jobId: string;
1004
+ totalChunks: number;
1005
+ } | {
1006
+ type: 'failed';
1007
+ jobId: string;
1008
+ error: string;
1009
+ };
1010
+
1011
+ interface ParseOptions {
1012
+ /** MIME type override. If omitted, the parser sniffs the input (Blob.type or PDF magic bytes). */
1013
+ mime?: string;
1014
+ }
1015
+ type ParseInput = Blob | string | Uint8Array | ArrayBuffer;
1016
+
1017
+ interface IngestChunk {
1018
+ content: string;
1019
+ embedding: number[];
1020
+ }
1021
+ interface IngestRequestBase {
1022
+ /** Document identifier — re-ingesting with the same `id` replaces all its chunks atomically. */
1023
+ id: string;
1024
+ metadata?: Record<string, unknown>;
1025
+ /** Logical namespace for separation. Default `'default'`. */
1026
+ namespace?: string;
1027
+ /**
1028
+ * Canonical collection name used by the `stackbone_rag_jobs` writer (F07)
1029
+ * to bind the row to a `stackbone_rag_collections` parent. Optional for
1030
+ * back-compat with pre-F07 callers — when absent, no job row is written
1031
+ * and progress is delivered only via `onProgress` (if any).
1032
+ */
1033
+ collection?: string;
1034
+ /**
1035
+ * Optional sink for streaming progress events. Fired in addition to (and
1036
+ * before) the matching `IngestJobWriter` call so synchronous `ingest`
1037
+ * consumers can observe progress without paying the SQL writer cost.
1038
+ */
1039
+ onProgress?: (event: RagIngestProgress) => void | Promise<void>;
1040
+ }
1041
+ interface IngestRequestPrecomputed extends IngestRequestBase {
1042
+ chunks: IngestChunk[];
1043
+ }
1044
+ interface IngestRequestAutoEmbed extends IngestRequestBase {
1045
+ chunks: string[];
1046
+ /** Embedding model id. When set, the pipeline calls the configured Embedder. */
1047
+ model: string;
1048
+ /**
1049
+ * Items per embeddings request. Consumed by the facade when it builds the
1050
+ * `Embedder`; ignored by the pipeline itself (the embedder is fully
1051
+ * configured at construction time).
1052
+ */
1053
+ batchSize?: number;
1054
+ }
1055
+ type IngestRequest = IngestRequestPrecomputed | IngestRequestAutoEmbed;
1056
+ interface IngestResponse {
1057
+ id: string;
1058
+ chunks: number;
1059
+ }
1060
+ interface DeleteOptions {
1061
+ namespace?: string;
1062
+ }
1063
+ interface DeleteResponse {
1064
+ deleted: number;
1065
+ }
1066
+ interface RetrieveRequestBase {
1067
+ topK?: number;
1068
+ filter?: Record<string, unknown>;
1069
+ namespace?: string;
1070
+ includeContent?: boolean;
1071
+ includeMetadata?: boolean;
1072
+ }
1073
+ interface RetrieveRequestPrecomputed extends RetrieveRequestBase {
1074
+ embedding: number[];
1075
+ }
1076
+ interface RetrieveRequestAutoEmbed extends RetrieveRequestBase {
1077
+ text: string;
1078
+ /** Must match the model used at ingest time. */
1079
+ model: string;
1080
+ }
1081
+ type RetrieveRequest = RetrieveRequestPrecomputed | RetrieveRequestAutoEmbed;
1082
+ interface RetrieveHit {
1083
+ id: string;
1084
+ chunkIdx: number;
1085
+ content?: string;
1086
+ metadata?: Record<string, unknown>;
1087
+ score: number;
1088
+ }
1089
+
1090
+ /**
1091
+ * Handle returned by `client.rag.ingestAsync`. The job id is allocated
1092
+ * synchronously against `stackbone_rag_jobs` so the caller can track / cancel
1093
+ * the work via the `/api/rag/jobs/:jobId/*` surface; `events` is a streaming
1094
+ * channel of progress events (ADR §D9 shape); `result` settles when the
1095
+ * pipeline finishes (or fails / is cancelled).
1096
+ */
1097
+ interface IngestAsyncHandle {
1098
+ jobId: string;
1099
+ events: AsyncIterable<RagIngestProgress>;
1100
+ result: Promise<Result<IngestResponse>>;
1101
+ }
1102
+ /**
1103
+ * `client.rag` — `pgvector`-backed retrieval. Two shapes per write/read:
1104
+ * pass `model` to let the SDK embed for you; pass embeddings precomputed for
1105
+ * provider/dimension/batching control.
1106
+ *
1107
+ * Implementation note: `RagModule` is a thin facade over `RagPipeline`. The
1108
+ * pipeline owns parse → chunk → embed → persist; the facade resolves the
1109
+ * shared `client.database` pool, builds an `Embedder` on demand, and maps
1110
+ * configuration errors. See ADR `2026-05-10-rag-consolidation-on-client-database`.
1111
+ *
1112
+ * Connection ownership: this module never opens its own postgres pool. It
1113
+ * pulls the underlying `postgres-js` `Sql` from the shared
1114
+ * `getDatabaseHandle()` exposed by `client.database`, so an agent that
1115
+ * touches both surfaces still opens exactly one connection pool against
1116
+ * `STACKBONE_POSTGRES_URL`.
1117
+ *
1118
+ * Schema readiness: starting with feature 30, the canonical schema is
1119
+ * installed by the CLI (`stackbone db migrate add-rag` + `migrate up`). When
1120
+ * an operation hits `42P01 relation does not exist`, the pipeline returns
1121
+ * `SdkError('rag_schema_missing')` with an actionable hint.
1122
+ */
1123
+ declare class RagModule {
1124
+ private readonly _resolved;
1125
+ private readonly _getAi;
1126
+ private readonly _gate;
1127
+ constructor(_resolved: ResolvedConfig, _getAi: () => AiModule,
1128
+ /** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
1129
+ gate?: ModuleGate);
1130
+ ingest(request: IngestRequest): Promise<Result<IngestResponse>>;
1131
+ /**
1132
+ * Asynchronous ingest. Allocates a `stackbone_rag_jobs` row, returns the
1133
+ * job id immediately, and exposes both an `AsyncIterable` of streaming
1134
+ * progress events and the final `Result` so callers can `await` either
1135
+ * surface — typical webhook handlers consume `events` and ignore `result`,
1136
+ * tests `await result` directly. Cancellation is observed by flipping the
1137
+ * row's status (e.g. via `POST /api/rag/jobs/:jobId/cancel`); the worker
1138
+ * polls between chunk batches and bails out with `rag_ingest_cancelled`.
1139
+ *
1140
+ * Connection ownership: the `IngestJobWriter` is bound to the same SQL
1141
+ * pulled off the shared `client.database` handle, so a single agent that
1142
+ * issues `ingestAsync` and `client.database.select` in flight still opens
1143
+ * exactly one pool against `STACKBONE_POSTGRES_URL`.
1144
+ */
1145
+ ingestAsync(request: IngestRequest): Promise<Result<IngestAsyncHandle>>;
1146
+ /** Test-only seam — assigned by `ingest-async.spec.ts`. */
1147
+ private _testJobWriter?;
1148
+ /** Test-only seam — assigned by `ingest-async.spec.ts`. */
1149
+ private _testSqlOverride?;
1150
+ delete(ids: string | string[], options?: DeleteOptions): Promise<Result<DeleteResponse>>;
1151
+ deleteWhere(filter: Record<string, unknown>, options?: DeleteOptions): Promise<Result<DeleteResponse>>;
1152
+ retrieve(request: RetrieveRequest): Promise<Result<RetrieveHit[]>>;
1153
+ /**
1154
+ * Drops the legacy ad-hoc RAG schema. Kept for backwards compatibility with
1155
+ * pre-feature-30 agents whose tables were provisioned by `ensureSchema` on
1156
+ * first call. The canonical RAG schema (feature 30) is owned by the CLI and
1157
+ * removed via `stackbone db migrate` flows, not by this method.
1158
+ */
1159
+ reset(): Promise<Result<void>>;
1160
+ /**
1161
+ * Pure helper — exposed verbatim from the chunker. No DB, no AI, no
1162
+ * datapath traffic, so it is intentionally NOT gated.
1163
+ */
1164
+ chunk(text: string, options?: ChunkOptions): string[];
1165
+ /**
1166
+ * Pure helper — exposed verbatim from the parser. No DB, no AI, no
1167
+ * datapath traffic, so it is intentionally NOT gated.
1168
+ */
1169
+ parse(input: ParseInput, options?: ParseOptions): Promise<string>;
1170
+ /**
1171
+ * Resolves the postgres-js `Sql` to use for this operation. Pulls `$client`
1172
+ * off the shared Drizzle handle (single pool per agent process). Slice F03's
1173
+ * NOTE about cross-module transaction propagation lives here: this is the
1174
+ * seam where a future `database.transaction` integration plugs a tx-bound
1175
+ * `Sql` into the pipeline instead of the pool-bound one.
1176
+ *
1177
+ * Configuration errors (`STACKBONE_POSTGRES_URL` unset) flow up as a
1178
+ * `Result.err` with the same `database_not_configured` shape `client.database`
1179
+ * raises, instead of throwing through the public surface.
1180
+ */
1181
+ private sql;
1182
+ private withPipeline;
1183
+ }
1184
+
1185
+ interface StorageObject {
1186
+ key: string;
1187
+ size: number;
1188
+ lastModified?: Date;
1189
+ etag?: string;
1190
+ }
1191
+ interface UploadOptions {
1192
+ contentType?: string;
1193
+ metadata?: Record<string, string>;
1194
+ }
1195
+ interface ListOptions {
1196
+ prefix?: string;
1197
+ /** Maximum number of objects to return per page. Must be > 0. */
1198
+ limit?: number;
1199
+ cursor?: string;
1200
+ }
1201
+ interface SignedUrlOptions {
1202
+ /** Seconds until the URL expires. Defaults to 3600 (1h). */
1203
+ expiresIn?: number;
1204
+ /** Only honoured by `getSignedUploadUrl` — pinned into the signature so the uploader must send a matching `Content-Type`. */
1205
+ contentType?: string;
1206
+ }
1207
+ interface SignedUrl {
1208
+ url: string;
1209
+ expiresAt: Date;
1210
+ }
1211
+ type StorageBody = Blob | Uint8Array | string;
1212
+ interface S3Settings {
1213
+ client: S3Client;
1214
+ endpoint: string;
1215
+ bucket: string;
1216
+ agentId: string;
1217
+ }
1218
+ /**
1219
+ * Public surface behind `client.storage`. Wraps `@aws-sdk/client-s3` against
1220
+ * R2 (prod) or MinIO (dev). Exposes a bucket-scoped API via `from(bucket)`.
1221
+ *
1222
+ * Multi-tenancy: every key is prefixed with `agentId/bucket/` before hitting
1223
+ * S3. Credentials are scoped to the agent's physical bucket (env `S3_BUCKET`),
1224
+ * and the `bucket` argument is a logical namespace the agent uses to organise
1225
+ * its own objects. The full prefixed key is an internal detail — public
1226
+ * methods accept and return the user-facing key (without prefix). Metadata
1227
+ * tracking in `_storage_objects` (Neon) is out-of-scope for this iteration.
1228
+ *
1229
+ * Memory note: `download()` materialises the whole object as a `Blob` in
1230
+ * memory. For agents that need to stream large objects, prefer
1231
+ * `getSignedDownloadUrl()` and `fetch()` the URL directly to consume the
1232
+ * stream incrementally.
1233
+ */
1234
+ declare class StorageModule {
1235
+ private readonly _resolved;
1236
+ private _s3;
1237
+ private readonly _gate;
1238
+ constructor(_resolved: ResolvedConfig,
1239
+ /** Test seam — see `ModuleGate`. Defaults to the lazy contract gate. */
1240
+ gate?: ModuleGate);
1241
+ from(bucket: string): StorageBucket;
1242
+ private settings;
1243
+ }
1244
+ declare class StorageBucket {
1245
+ private readonly bucketName;
1246
+ private readonly resolveSettings;
1247
+ private readonly gate;
1248
+ constructor(bucketName: string, resolveSettings: () => Result<S3Settings>, gate: ModuleGate);
1249
+ upload(key: string, body: StorageBody, options?: UploadOptions): Promise<Result<{
1250
+ key: string;
1251
+ etag?: string;
1252
+ }>>;
1253
+ download(key: string): Promise<Result<Blob>>;
1254
+ list(options?: ListOptions): Promise<Result<{
1255
+ objects: readonly StorageObject[];
1256
+ nextCursor?: string;
1257
+ }>>;
1258
+ remove(key: string): Promise<Result<{
1259
+ key: string;
1260
+ }>>;
1261
+ /**
1262
+ * Returns the canonical S3-style URL for an object. Pure URL-builder — no
1263
+ * S3 round-trip — so this method intentionally bypasses the contract gate.
1264
+ * Whether the URL is publicly fetchable depends on the bucket policy; for
1265
+ * private buckets, use `getSignedDownloadUrl` instead.
1266
+ */
1267
+ getPublicUrl(key: string): Result<string>;
1268
+ getSignedUploadUrl(key: string, options?: SignedUrlOptions): Promise<Result<SignedUrl>>;
1269
+ getSignedDownloadUrl(key: string, options?: SignedUrlOptions): Promise<Result<SignedUrl>>;
1270
+ /**
1271
+ * Resolves S3 settings, validates the user key, and prefixes it with
1272
+ * `${agentId}/${bucketName}/`. Rejects path-traversal segments (`..`) so a
1273
+ * caller-controlled key cannot escape the agent's namespace.
1274
+ */
1275
+ private resolve;
1276
+ }
1277
+
1278
+ /**
1279
+ * Each accessor builds its module on first access and caches it — env vars
1280
+ * and partner SDK init costs are paid only when the agent actually touches
1281
+ * that surface.
1282
+ */
1283
+ declare class StackboneClient {
1284
+ private readonly resolved;
1285
+ private _database?;
1286
+ private _storage?;
1287
+ private _ai?;
1288
+ private _queues?;
1289
+ private _rag?;
1290
+ private _memory?;
1291
+ private _observability?;
1292
+ private _approval?;
1293
+ private _secrets?;
1294
+ private _connections?;
1295
+ private _config?;
1296
+ private _events?;
1297
+ private _prompts?;
1298
+ private _httpClient?;
1299
+ constructor(resolved: ResolvedConfig);
1300
+ private http;
1301
+ get database(): DatabaseModule;
1302
+ get storage(): StorageModule;
1303
+ get ai(): AiModule;
1304
+ get queues(): QueuesModule;
1305
+ get rag(): RagModule;
1306
+ get memory(): MemoryModule;
1307
+ get observability(): ObservabilityModule;
1308
+ get approval(): ApprovalFacade;
1309
+ get secrets(): SecretsFacade;
1310
+ get connections(): ConnectionsFacade;
1311
+ get config(): ConfigFacade;
1312
+ get events(): EventsFacade;
1313
+ get prompts(): PromptsFacade;
1314
+ /**
1315
+ * Read-only view of the most recently resolved Stackbone Agent Protocol
1316
+ * contract for this client's `stackboneApiUrl`. Returns `null` until a
1317
+ * handshake has resolved successfully (the handshake fires lazily on the
1318
+ * first gated module call — slices #4/#5). Synchronous, never throws,
1319
+ * performs no fetches.
1320
+ */
1321
+ get contract(): ContractResponse | null;
1322
+ }
1323
+ declare function createClient(config?: ClientConfig): StackboneClient;
1324
+
1325
+ export { type AddMemoryRequest, type AggregateRunCostOptions, type AggregateRunCostResult, 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 GeneratedImage, type GetPromptOptions, type ImageGenerateParams, type ImagesResponse, type IngestAsyncHandle, type IngestChunk, type IngestRequest, type IngestRequestAutoEmbed, type IngestRequestPrecomputed, type IngestResponse, LOG_LEVELS, type ListMemoryRequest, type ListMemoryResult, type ListOptions, type ListPromptsOptions, type ListPromptsResult, type LogLevelName, type LogLevelNumber, type LogRecord, type MemoryContent, type MemoryHistoryEntry, type MemoryHistoryEvent, type MemoryHit, type MemoryItem, type MemoryScope, type ModelsListResponse, type OpenRouterModel, type ParseInput, type ParseOptions, type PinoLike, type PlatformLogger, type PlatformLoggerMode, type PlatformLoggerOptions, type PostgresLike, type Prompt, type PublishRequest, RUN_ID_ATTRIBUTE, RUN_STEP_TYPES, type RagIngestProgress, type ReadableSpanLike, type Result, type RetrieveHit, type RetrieveRequest, type RetrieveRequestAutoEmbed, type RetrieveRequestPrecomputed, type RunStepType, RunStepsSpanProcessor, type RunStepsSpanProcessorOptions, STEP_TYPE_ATTRIBUTE, type SdkError, type SearchMemoryOptions, type SignedUrl, type SignedUrlOptions, type SpanContextLike, StackboneClient, type StorageBody, type StorageObject, type UpdateMemoryOptions, type UpdatePromptOptions, type UploadOptions, type VerifyOptions, aggregateRunCost, createClient, createPlatformLogger, defaultRunsDir };