@dingdawg/sdk 2.0.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/src/durable.ts ADDED
@@ -0,0 +1,347 @@
1
+ /**
2
+ * @dingdawg/sdk/durable — DurableDingDawgClient
3
+ *
4
+ * Extends DingDawgClient with DDAG v1 crash-proof execution:
5
+ * - invokeWithCheckpoint() — run agent step, get back a CID resume token
6
+ * - resume() — continue from a checkpoint CID (any machine)
7
+ * - getSoul() — retrieve an agent's IPFS-pinned identity
8
+ *
9
+ * What competitors don't have (in one SDK):
10
+ * ✅ Log-first WAL execution — crash = replay from log, never lose progress
11
+ * ✅ IPFS content-addressed checkpoints — stateless resume on any machine
12
+ * ✅ Exactly-once semantics — idempotency keys prevent double-execution
13
+ * ✅ Agent Soul persistence — identity survives crashes and migrations
14
+ * ✅ Memory class retention — A/B/C/D with IPFS anchoring for Class A
15
+ */
16
+
17
+ import { DingDawgClient, DingDawgApiError } from "./client.js";
18
+ import type {
19
+ DingDawgClientOptions,
20
+ SendMessageOptions,
21
+ TriggerResponse,
22
+ ApiErrorBody,
23
+ } from "./types.js";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // FSM state enum (mirrors isg_agent/protocol/ddag_v1.py AgentFSMState)
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export enum AgentFSMState {
30
+ Idle = "idle",
31
+ Running = "running",
32
+ ToolPending = "tool_pending",
33
+ Verifying = "verifying",
34
+ Committing = "committing",
35
+ Remediating = "remediating",
36
+ Checkpointed = "checkpointed",
37
+ Resuming = "resuming",
38
+ Done = "done",
39
+ Failed = "failed",
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Durable types
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /**
47
+ * A content-addressed checkpoint snapshot.
48
+ * Save `state_cid` — pass it to `resume()` to continue from this exact point.
49
+ */
50
+ export interface CheckpointState {
51
+ /** Session this checkpoint belongs to. */
52
+ session_id: string;
53
+ /** Execution step index (0-based). */
54
+ step_index: number;
55
+ /**
56
+ * Content-addressed CID — the durable resume token.
57
+ * Format: "ipfs:<hash>" when IPFS is available, "sha256:<hex>" for local fallback.
58
+ */
59
+ state_cid: string;
60
+ /** FSM state at checkpoint time. */
61
+ fsm_state: AgentFSMState;
62
+ /** ISO-8601 UTC timestamp. */
63
+ created_at: string;
64
+ }
65
+
66
+ /** IPFS-pinned agent identity — survives crashes, restarts, and migrations. */
67
+ export interface AgentSoul {
68
+ soul_id: string;
69
+ agent_id: string;
70
+ /** CID of the current serialised soul state. */
71
+ soul_cid: string;
72
+ /** Plain-text mission statement — never changes. */
73
+ mission: string;
74
+ /** Mutable preferences updated each session. */
75
+ learned_prefs: Record<string, unknown>;
76
+ created_at: string;
77
+ updated_at: string;
78
+ }
79
+
80
+ /** Options for a durable agent invocation. */
81
+ export interface DurableSession extends SendMessageOptions {
82
+ /**
83
+ * Resume from this checkpoint CID.
84
+ * When provided, the agent skips already-completed steps (exactly-once guarantee).
85
+ */
86
+ resume_cid?: string;
87
+ /**
88
+ * Caller-supplied idempotency key.
89
+ * If omitted the server derives one from (session_id + step_index + tool_name).
90
+ */
91
+ idempotency_key?: string;
92
+ /** Checkpoint after every N steps (default: 1 — checkpoint every step). */
93
+ checkpoint_every?: number;
94
+ }
95
+
96
+ /** Response from a durable invocation — always includes a CID for future resume. */
97
+ export interface DurableResponse extends TriggerResponse {
98
+ /** Always returned — store this to resume after a crash. */
99
+ checkpoint_cid: string;
100
+ /** Current execution step index. */
101
+ step_index: number;
102
+ /** True when CHR PALP verification passed. */
103
+ verified: boolean;
104
+ /** IPFS CID of the CHR PALP proof (present only when verification ran). */
105
+ proof_cid?: string;
106
+ /** FSM state at response time. */
107
+ fsm_state: AgentFSMState;
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // SDK version
112
+ // ---------------------------------------------------------------------------
113
+
114
+ const SDK_USER_AGENT_DURABLE = "@dingdawg/sdk-durable/2.0.0";
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // DurableDingDawgClient
118
+ // ---------------------------------------------------------------------------
119
+
120
+ /**
121
+ * DurableDingDawgClient — crash-proof agent execution SDK.
122
+ *
123
+ * @example Basic durable invocation
124
+ * ```ts
125
+ * import { DurableDingDawgClient } from "@dingdawg/sdk";
126
+ *
127
+ * const client = new DurableDingDawgClient({ apiKey: "dd_live_..." });
128
+ *
129
+ * const result = await client.invokeWithCheckpoint("acme-support", {
130
+ * message: "Run the quarterly compliance report",
131
+ * userId: "user_123",
132
+ * });
133
+ *
134
+ * // Save checkpoint_cid — if the process crashes, pass it to resume()
135
+ * console.log(result.checkpoint_cid); // "ipfs:Qm..." or "sha256:..."
136
+ * ```
137
+ *
138
+ * @example Resume after crash
139
+ * ```ts
140
+ * const resumed = await client.resume("acme-support", savedCheckpointCid);
141
+ * // Agent continues from the exact step it left off — no re-execution
142
+ * ```
143
+ */
144
+ export class DurableDingDawgClient extends DingDawgClient {
145
+ private readonly _durableApiKey: string;
146
+ private readonly _durableBaseUrl: string;
147
+
148
+ constructor(opts: DingDawgClientOptions) {
149
+ super(opts);
150
+ this._durableApiKey = opts.apiKey.trim();
151
+ this._durableBaseUrl = (opts.baseUrl ?? "https://api.dingdawg.com").replace(/\/$/, "");
152
+ }
153
+
154
+ // -------------------------------------------------------------------------
155
+ // Internal HTTP helper (mirrors DingDawgClient._request — kept private)
156
+ // -------------------------------------------------------------------------
157
+
158
+ private async _durableRequest<T>(
159
+ method: "GET" | "POST",
160
+ path: string,
161
+ body?: unknown,
162
+ extraHeaders?: Record<string, string>
163
+ ): Promise<T> {
164
+ const url = `${this._durableBaseUrl}${path}`;
165
+
166
+ const headers: Record<string, string> = {
167
+ "Content-Type": "application/json",
168
+ "User-Agent": SDK_USER_AGENT_DURABLE,
169
+ Authorization: `Bearer ${this._durableApiKey}`,
170
+ "X-DingDawg-SDK": "2",
171
+ ...extraHeaders,
172
+ };
173
+
174
+ const init: RequestInit = {
175
+ method,
176
+ headers,
177
+ ...(body !== undefined && method !== "GET"
178
+ ? { body: JSON.stringify(body) }
179
+ : {}),
180
+ };
181
+
182
+ let resp: Response;
183
+ try {
184
+ resp = await fetch(url, init);
185
+ } catch (err: unknown) {
186
+ const msg = err instanceof Error ? err.message : String(err);
187
+ throw new DingDawgApiError(
188
+ { status: 0, body: { detail: `Network error: ${msg}` } },
189
+ `Network error reaching DingDawg API: ${msg}`
190
+ );
191
+ }
192
+
193
+ if (!resp.ok) {
194
+ let errBody: ApiErrorBody | null = null;
195
+ try {
196
+ errBody = (await resp.json()) as ApiErrorBody;
197
+ } catch { /* non-JSON error */ }
198
+ throw new DingDawgApiError({ status: resp.status, body: errBody });
199
+ }
200
+
201
+ return (await resp.json()) as T;
202
+ }
203
+
204
+ // -------------------------------------------------------------------------
205
+ // Public durable API
206
+ // -------------------------------------------------------------------------
207
+
208
+ /**
209
+ * Invoke an agent with checkpoint support.
210
+ *
211
+ * The server journalises intent before execution and checkpoints state after.
212
+ * The returned `checkpoint_cid` is the durable resume token — store it.
213
+ * On crash, call `resume(handle, checkpoint_cid)` to continue without re-executing
214
+ * completed steps.
215
+ *
216
+ * @param agentHandle - Agent handle (e.g. "acme-support").
217
+ * @param opts - Message options + optional resume_cid, idempotency_key, checkpoint_every.
218
+ * @returns DurableResponse including checkpoint_cid, step_index, verified, fsm_state.
219
+ */
220
+ async invokeWithCheckpoint(
221
+ agentHandle: string,
222
+ opts: DurableSession
223
+ ): Promise<DurableResponse> {
224
+ const payload: Record<string, unknown> = {
225
+ message: opts.message,
226
+ user_id: opts.userId,
227
+ session_id: opts.sessionId,
228
+ metadata: opts.metadata,
229
+ checkpoint_every: opts.checkpoint_every ?? 1,
230
+ };
231
+
232
+ if (opts.resume_cid !== undefined) payload["resume_cid"] = opts.resume_cid;
233
+ if (opts.idempotency_key !== undefined) payload["idempotency_key"] = opts.idempotency_key;
234
+
235
+ const extraHeaders: Record<string, string> = {};
236
+ if (opts.idempotency_key !== undefined) {
237
+ extraHeaders["Idempotency-Key"] = opts.idempotency_key;
238
+ }
239
+
240
+ const raw = await this._durableRequest<Record<string, unknown>>(
241
+ "POST",
242
+ `/api/v2/agents/${encodeURIComponent(agentHandle)}/durable/invoke`,
243
+ payload,
244
+ extraHeaders
245
+ );
246
+
247
+ return _normalizeDurableResponse(raw);
248
+ }
249
+
250
+ /**
251
+ * Resume execution from a checkpoint CID.
252
+ *
253
+ * Stateless — can be called from any process, any machine.
254
+ * The server restores state from the CID, skips already-completed steps,
255
+ * and continues forward.
256
+ *
257
+ * @param agentHandle - Agent handle.
258
+ * @param checkpointCid - The CID returned by a previous invokeWithCheckpoint call.
259
+ * @returns DurableResponse with the next checkpoint_cid.
260
+ */
261
+ async resume(agentHandle: string, checkpointCid: string): Promise<DurableResponse> {
262
+ const raw = await this._durableRequest<Record<string, unknown>>(
263
+ "POST",
264
+ `/api/v2/agents/${encodeURIComponent(agentHandle)}/durable/resume`,
265
+ { checkpoint_cid: checkpointCid }
266
+ );
267
+
268
+ return _normalizeDurableResponse(raw);
269
+ }
270
+
271
+ /**
272
+ * Retrieve an agent's soul — IPFS-pinned identity that survives all restarts.
273
+ *
274
+ * @param agentHandle - Agent handle.
275
+ * @returns AgentSoul with soul_cid, mission, and learned_prefs.
276
+ */
277
+ async getSoul(agentHandle: string): Promise<AgentSoul> {
278
+ const raw = await this._durableRequest<Record<string, unknown>>(
279
+ "GET",
280
+ `/api/v2/agents/${encodeURIComponent(agentHandle)}/soul`
281
+ );
282
+
283
+ return {
284
+ soul_id: String(raw["soul_id"] ?? ""),
285
+ agent_id: String(raw["agent_id"] ?? ""),
286
+ soul_cid: String(raw["soul_cid"] ?? ""),
287
+ mission: String(raw["mission"] ?? ""),
288
+ learned_prefs: (raw["learned_prefs"] as Record<string, unknown>) ?? {},
289
+ created_at: String(raw["created_at"] ?? ""),
290
+ updated_at: String(raw["updated_at"] ?? ""),
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Get the latest checkpoint for a session.
296
+ *
297
+ * Useful for polling long-running agent tasks.
298
+ *
299
+ * @param agentHandle - Agent handle.
300
+ * @param sessionId - Session ID.
301
+ * @returns CheckpointState or null if no checkpoint exists yet.
302
+ */
303
+ async getCheckpoint(
304
+ agentHandle: string,
305
+ sessionId: string
306
+ ): Promise<CheckpointState | null> {
307
+ try {
308
+ const raw = await this._durableRequest<Record<string, unknown>>(
309
+ "GET",
310
+ `/api/v2/agents/${encodeURIComponent(agentHandle)}/durable/checkpoint/${encodeURIComponent(sessionId)}`
311
+ );
312
+ return _normalizeCheckpointState(raw);
313
+ } catch (err: unknown) {
314
+ if (err instanceof DingDawgApiError && err.status === 404) return null;
315
+ throw err;
316
+ }
317
+ }
318
+ }
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // Normalizers
322
+ // ---------------------------------------------------------------------------
323
+
324
+ function _normalizeDurableResponse(raw: Record<string, unknown>): DurableResponse {
325
+ return {
326
+ reply: String(raw["reply"] ?? raw["response"] ?? ""),
327
+ sessionId: String(raw["session_id"] ?? raw["sessionId"] ?? ""),
328
+ timestamp: String(raw["timestamp"] ?? new Date().toISOString()),
329
+ queued: raw["queued"] === true,
330
+ ...(raw["model"] !== undefined ? { model: String(raw["model"]) } : {}),
331
+ checkpoint_cid: String(raw["checkpoint_cid"] ?? ""),
332
+ step_index: (raw["step_index"] as number) ?? 0,
333
+ verified: raw["verified"] === true,
334
+ fsm_state: (raw["fsm_state"] as AgentFSMState) ?? AgentFSMState.Done,
335
+ ...(raw["proof_cid"] !== undefined ? { proof_cid: String(raw["proof_cid"]) } : {}),
336
+ };
337
+ }
338
+
339
+ function _normalizeCheckpointState(raw: Record<string, unknown>): CheckpointState {
340
+ return {
341
+ session_id: String(raw["session_id"] ?? ""),
342
+ step_index: (raw["step_index"] as number) ?? 0,
343
+ state_cid: String(raw["state_cid"] ?? ""),
344
+ fsm_state: (raw["fsm_state"] as AgentFSMState) ?? AgentFSMState.Idle,
345
+ created_at: String(raw["created_at"] ?? ""),
346
+ };
347
+ }
package/src/index.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @dingdawg/sdk — Public API
3
+ *
4
+ * Re-exports everything a partner integration needs:
5
+ * - DingDawgClient (main class)
6
+ * - DurableDingDawgClient (crash-proof execution — DDAG v1)
7
+ * - DingDawgApiError (typed error)
8
+ * - All TypeScript interfaces and types
9
+ */
10
+
11
+ export { DingDawgClient, DingDawgApiError } from "./client.js";
12
+
13
+ export type {
14
+ // Client config
15
+ DingDawgClientOptions,
16
+ // Agent types
17
+ AgentType,
18
+ AgentStatus,
19
+ AgentBranding,
20
+ CreateAgentOptions,
21
+ AgentRecord,
22
+ // Message / trigger types
23
+ SendMessageOptions,
24
+ TriggerResponse,
25
+ // Billing types
26
+ BillingLineItem,
27
+ MonthlyBillingSummary,
28
+ BillingSummary,
29
+ // Utilities
30
+ PaginatedList,
31
+ ApiErrorBody,
32
+ DingDawgApiErrorDetails,
33
+ } from "./types.js";
34
+
35
+ // DDAG v1 — Durable execution SDK
36
+ export { DurableDingDawgClient, AgentFSMState } from "./durable.js";
37
+
38
+ export type {
39
+ CheckpointState,
40
+ AgentSoul,
41
+ DurableSession,
42
+ DurableResponse,
43
+ } from "./durable.js";
package/src/types.ts ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * @dingdawg/sdk — TypeScript types for all request and response shapes.
3
+ *
4
+ * All interfaces are exported so partner integrations can type their own code.
5
+ * Zero runtime imports — types are erased at compile time.
6
+ */
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Client configuration
10
+ // ---------------------------------------------------------------------------
11
+
12
+ /** Options passed to the DingDawgClient constructor. */
13
+ export interface DingDawgClientOptions {
14
+ /** API key issued via the DingDawg dashboard or partner program. */
15
+ apiKey: string;
16
+ /** Base URL of the DingDawg API. Defaults to https://api.dingdawg.com */
17
+ baseUrl?: string;
18
+ }
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Error types
22
+ // ---------------------------------------------------------------------------
23
+
24
+ /** Structured API error returned by the DingDawg backend. */
25
+ export interface ApiErrorBody {
26
+ detail?: string;
27
+ message?: string;
28
+ code?: string;
29
+ [key: string]: unknown;
30
+ }
31
+
32
+ /** Error thrown when an API call fails with a non-2xx response. */
33
+ export interface DingDawgApiErrorDetails {
34
+ /** HTTP status code (e.g. 401, 422, 500). */
35
+ status: number;
36
+ /** Parsed error body from the API, if available. */
37
+ body: ApiErrorBody | null;
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Agent types
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /** Agent type identifier. */
45
+ export type AgentType =
46
+ | "personal"
47
+ | "business"
48
+ | "b2b"
49
+ | "a2a"
50
+ | "compliance"
51
+ | "enterprise"
52
+ | "health"
53
+ | "gaming";
54
+
55
+ /** Agent status values. */
56
+ export type AgentStatus = "active" | "inactive" | "suspended";
57
+
58
+ /** Branding configuration for an agent. */
59
+ export interface AgentBranding {
60
+ /** Hex color string (e.g. "#7C3AED"). */
61
+ primaryColor?: string;
62
+ /** Public URL to an avatar image. */
63
+ avatarUrl?: string;
64
+ }
65
+
66
+ /** Options for creating a new agent. */
67
+ export interface CreateAgentOptions {
68
+ /** Display name of the agent. */
69
+ name: string;
70
+ /** Unique handle (without @). Must be 3-32 alphanumeric + underscores. */
71
+ handle: string;
72
+ /** Agent type selector. Defaults to "business". */
73
+ agentType?: AgentType;
74
+ /** Sector / industry label (e.g. "restaurant", "gaming", "legal"). */
75
+ industry?: string;
76
+ /** System prompt / constitution text. */
77
+ systemPrompt?: string;
78
+ /** Template ID to initialise the agent from. */
79
+ templateId?: string;
80
+ /** Branding overrides. */
81
+ branding?: AgentBranding;
82
+ }
83
+
84
+ /** A deployed agent record returned by the API. */
85
+ export interface AgentRecord {
86
+ id: string;
87
+ handle: string;
88
+ name: string;
89
+ agentType: AgentType;
90
+ industry: string | null;
91
+ status: AgentStatus;
92
+ createdAt: string;
93
+ updatedAt: string;
94
+ /** Parsed branding config. Present on GET /agents/:id, may be absent in list. */
95
+ branding?: AgentBranding;
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Message / trigger types
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /** Options for sending a message to an agent via the trigger endpoint. */
103
+ export interface SendMessageOptions {
104
+ /** The message text to deliver. */
105
+ message: string;
106
+ /** User / customer identifier (for conversation continuity). */
107
+ userId?: string;
108
+ /** Session identifier to continue an existing conversation. */
109
+ sessionId?: string;
110
+ /** Arbitrary metadata forwarded to the agent runtime. */
111
+ metadata?: Record<string, unknown>;
112
+ }
113
+
114
+ /** Response returned after triggering an agent. */
115
+ export interface TriggerResponse {
116
+ /** The agent's reply text. */
117
+ reply: string;
118
+ /** Session ID (reuse to continue the conversation). */
119
+ sessionId: string;
120
+ /** ISO-8601 timestamp of the response. */
121
+ timestamp: string;
122
+ /** Model / provider used to generate the reply. */
123
+ model?: string;
124
+ /** True when the agent queued the message for async processing. */
125
+ queued?: boolean;
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Billing types
130
+ // ---------------------------------------------------------------------------
131
+
132
+ /** Line item in a billing period. */
133
+ export interface BillingLineItem {
134
+ /** Action / skill name (e.g. "crm_lookup", "email_send"). */
135
+ action: string;
136
+ /** Number of times this action was executed. */
137
+ count: number;
138
+ /** Cost in USD cents. */
139
+ costCents: number;
140
+ }
141
+
142
+ /** Billing summary for a single month. */
143
+ export interface MonthlyBillingSummary {
144
+ /** ISO-8601 month string (e.g. "2026-03"). */
145
+ month: string;
146
+ /** Total actions executed. */
147
+ totalActions: number;
148
+ /** Total charges in USD cents. */
149
+ totalCents: number;
150
+ /** Number of free-tier actions remaining (50 free/month). */
151
+ freeActionsRemaining: number;
152
+ /** Breakdown by action type. */
153
+ lineItems: BillingLineItem[];
154
+ }
155
+
156
+ /** Aggregate billing summary across all time. */
157
+ export interface BillingSummary {
158
+ /** Total lifetime actions. */
159
+ totalActions: number;
160
+ /** Total lifetime spend in USD cents. */
161
+ totalCents: number;
162
+ /** Current month billing. */
163
+ currentMonth: MonthlyBillingSummary;
164
+ /** Stripe customer ID if billing is set up. */
165
+ stripeCustomerId?: string;
166
+ }
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Pagination
170
+ // ---------------------------------------------------------------------------
171
+
172
+ /** Generic paginated list wrapper. */
173
+ export interface PaginatedList<T> {
174
+ items: T[];
175
+ total: number;
176
+ limit: number;
177
+ offset: number;
178
+ }