@argentic-ai/client 0.0.0-stage → 0.1.13

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/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Argentic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Temporary Holding Version
1
+ # Argentic for TypeScript
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Argentic runs the agent loop. Your Vercel AI SDK or Mastra tools and system prompt carry over; the framework's own loop (`generateText`, `Agent`, Mastra `Agent.generate`) is not used.
4
+
5
+ Each turn, Argentic rebuilds the prompt from a journal of the run instead of replaying an ever-growing transcript: state blocks plus a recent tail of about 40K tokens. The journal keeps the full record, so you can see every step the agent took and check it against what the model saw.
6
+
7
+ Each tool call the agent makes comes back to your code to carry out. If the process dies mid-run, run it again: it resumes without repeating a finished action.
8
+
9
+ ## Try it
10
+
11
+ ```
12
+ npx @argentic-ai/client demo
13
+ ```
14
+
15
+ Set `ARGENTIC_API_KEY` and `MODEL_KEY` first. Press Ctrl-C while it works, run it again, and it skips what already finished. Details in the [repo README](../../README.md#run-the-demo-2-minutes).
16
+
17
+ ## Install
18
+
19
+ ```
20
+ npm i @argentic-ai/client
21
+ ```
22
+
23
+ - `@argentic-ai/client`: the client and `runDurable`, no dependencies, Node 22+.
24
+ - `@argentic-ai/client/ai-sdk`: `fromAiSdkTools`, needs `ai` v5, v6 or v7.
25
+ - `@argentic-ai/client/tools`: `toolPack`, retry-safe tools for Stripe, Resend, SMTP, Slack, GitHub and a generic HTTP POST. See [TOOLS.md](TOOLS.md).
26
+ - `@argentic-ai/client/mastra`: `fromMastraTools`, takes the `createTool()` tools you would pass to an Agent (the key is the tool name). Needs `@mastra/core` 1.x for the tools; the adapter itself imports nothing.
27
+
28
+ To vendor it instead: clone this repo and copy `argentic.ts`, `ai-sdk.ts` and `mastra.ts` into your project (Node 22+, no package needed beyond what you already use).
29
+
30
+ ## Use
31
+
32
+ ```ts
33
+ import { tool } from 'ai'
34
+ import { z } from 'zod'
35
+ import { Argentic, runDurable } from '@argentic-ai/client'
36
+ import { fromAiSdkTools } from '@argentic-ai/client/ai-sdk'
37
+
38
+ const tools = { refund_order: tool({
39
+ description: 'Refund an order.',
40
+ inputSchema: z.object({ order_id: z.string(), amount_usd: z.number() }),
41
+ execute: async (args, { toolCallId }) => refund(args, toolCallId),
42
+ }) }
43
+
44
+ // apiKey defaults to ARGENTIC_API_KEY and modelKey to MODEL_KEY
45
+ const client = new Argentic()
46
+ const out = await runDurable({
47
+ client, agent: 'my-agent', runId: 'order-1182', ledgerPath: '.argentic-ledger.json',
48
+ task: 'Refund order 1182.', system: 'Call one tool per turn.',
49
+ tools: await fromAiSdkTools(tools),
50
+ })
51
+ console.log(out.reply)
52
+ ```
53
+
54
+ Keep `agent` and `runId` the same across restarts. A request that takes longer than `timeoutMs` (default 300000, 0 for no limit) counts as a network failure and is retried with the same key. If the ledger file is not valid JSON, `runDurable` warns on stderr, sets the file aside as `<path>.corrupt` and starts empty, so finished actions may run again. `client.replay(agent, wakeId)` re-renders a wake's prompts from the journal and compares them to what the model saw; it calls no model and spends nothing.
55
+
56
+ ## Once per operation (`once_per`, 0.1.7)
57
+
58
+ A tool can declare `once_per`, a key such as `refund:{order_id}` filled in from the call's arguments. Argentic then refuses a second call with the same key, in this wake or any later one. Set it on a `DurableTool` directly, or pass `{ oncePer: { refund_order: 'refund:{order_id}' } }` as the second argument to `fromAiSdkTools` or `fromMastraTools`. The template is checked before the request is sent. See [TOOLS.md](TOOLS.md#declaring-a-tool-once_per). 0.1.6 and earlier dropped the field.
59
+
60
+ ## Tools, context and deferred loading (0.1.10)
61
+
62
+ Declare an agent's tools once with `client.putTools(agent, tools)` and leave `tools` out of later wakes; `client.tools(agent)` reads them back. A wake can also carry `context` (facts for that call only, such as the signed-in user's plan; it is not written to the journal) and `tool_choice` (`'auto'`, `'required'` or `{ type: 'tool', name }`). On a tool, `defer_loading: true` keeps its schema out of the prompt until the agent looks it up, which pays off from about ten tools.
63
+
64
+ ```ts
65
+ await client.putTools('support-bot', [{ name: 'book_slot', description: 'Book a slot.', parameters: { type: 'object', properties: { day: { type: 'string' } }, required: ['day'] } }])
66
+ const out = await client.wake('support-bot', { message: 'Book Tuesday.', model: freeModel, context: 'Plan: pro' }, 'book-1')
67
+ ```
68
+
69
+ ## Structured tool results (0.1.11)
70
+
71
+ `runDurable` answers the tools an agent asked for with a text message the model reads, and now also sends the same results as the `results` field of the wake: `[{ seq, result }]`, where `seq` is the action's `seq` and `result` is exactly what the tool returned (or the `{ error }` object when it threw). The API journals each as the real result of that call. The first wake of a run has none, and a result the ledger already held is sent again like the others. Pass `structuredResults: false` to send the text only. If the API you talk to does not know the field yet, it answers 400 and names `results`; the client sends that wake again once without it, under the same key, and leaves it out from then on. Calling `client.wake()` yourself, add `results` to the body next to the `message`, never with `fire_due`, at most 100 entries.
72
+
73
+ ## Waking on a schedule (handleDue)
74
+
75
+ Create a recurring schedule or a one-shot timer, and read what is due:
76
+
77
+ ```ts
78
+ const client = new Argentic()
79
+ await client.putSchedule('support-bot', 'morning-check', { cron: '0 9 * * *', message: 'Review overnight tickets.' }) // five UTC fields
80
+ await client.putSchedule('support-bot', 'follow-up', { at: '2026-11-20T09:00:00Z', message: 'Chase the open refund.' }) // one-shot
81
+ await client.schedules('support-bot') // name, cron or at, next_at, last_fired_at, fires
82
+ await client.due() // what is due now; wake each agent with fire_due, or let the notice below do it
83
+ await client.deleteSchedule('support-bot', 'follow-up')
84
+ ```
85
+
86
+
87
+ Beta, off by default on the hosted API, and open to single beta users only for now. When an agent falls due (a plan's `wake_at`, a schedule, a timer or a hook event), Argentic POSTs a signed notice to the `wake_url` you registered. The notice names the agent and why it is due and carries no agent content and no model key. `handleDue` checks the signature and the time, then wakes the agent with `fire_due` on your own model key.
88
+
89
+ Register the address and read the secret with the client: `await client.setWakeUrl('https://app.example.com/argentic/due')`, then `(await client.notices()).signing_secret` (`whsec_...`). Store it like a password, for example as `ARGENTIC_WEBHOOK_SECRET`. The model key stays on your server: set `MODEL_KEY` there, as for any other wake.
90
+
91
+ ```ts
92
+ import { Hono } from 'hono'
93
+ import { handleDueResponse } from '@argentic-ai/client'
94
+
95
+ const app = new Hono()
96
+ // Reads ARGENTIC_API_KEY, MODEL_KEY and ARGENTIC_WEBHOOK_SECRET from the environment.
97
+ app.post('/argentic/due', (c) => handleDueResponse(c.req.raw))
98
+ ```
99
+
100
+ `handleDueResponse` answers 200 when the agent was woken, 401 if the signature or time is wrong, 400 if the body is not an `agent.due` notice, 500 if the secret is missing or malformed, and 502 if the wake itself failed (the notice is then sent again). It takes a Fetch `Request` or `{ body, headers }` with the raw body text, and works the same in a Next.js route handler. `handleDue(request, { secret, client, model, toleranceSeconds })` returns `{ notice, wake }` and throws `WebhookVerificationError` (with a `code`) before any call is made. `verifyDue(request)` only verifies and parses, so you can queue the notice and wake later. The wake uses the notice's `webhook-id` as its idempotency key, so a notice delivered twice replays the first wake instead of waking twice. Timestamps more than 300 seconds off are refused.
101
+
102
+ ## What to know
103
+
104
+ Delivery is at-least-once. An action that was running when the process died, before its result reached the local ledger, runs again on resume. Make your handlers safe to repeat: the AI SDK `toolCallId` your `execute` receives is a stable key (`agent:seq`) to dedupe on.
105
+
106
+ Mastra tools get the same key as `context.agent.toolCallId`.
107
+
108
+ Full walkthroughs: [`examples/ai-sdk`](../../examples/ai-sdk), [`examples/mastra`](../../examples/mastra).
@@ -0,0 +1,9 @@
1
+ import { type ToolSet } from 'ai';
2
+ import { type DurableTool } from './argentic.ts';
3
+ /**
4
+ * The tool name is the ToolSet key. Async because a JSON schema may be a promise.
5
+ * `oncePer` maps a tool name to its `once_per` template, such as `{ refund_order: 'refund:{order_id}' }`.
6
+ */
7
+ export declare function fromAiSdkTools(tools: ToolSet, opts?: {
8
+ oncePer?: Record<string, string>;
9
+ }): Promise<DurableTool[]>;
package/dist/ai-sdk.js ADDED
@@ -0,0 +1,32 @@
1
+ // Vercel AI SDK (v5/v6) on-ramp: your tool() objects become Argentic tools.
2
+ // Only the tools carry over. Argentic runs the loop, not generateText or Agent.
3
+ import { asSchema } from 'ai';
4
+ import { withOncePer } from "./argentic.js";
5
+ const isAsyncIterable = (v) => typeof v === 'object' && v !== null && Symbol.asyncIterator in v;
6
+ /**
7
+ * The tool name is the ToolSet key. Async because a JSON schema may be a promise.
8
+ * `oncePer` maps a tool name to its `once_per` template, such as `{ refund_order: 'refund:{order_id}' }`.
9
+ */
10
+ export async function fromAiSdkTools(tools, opts = {}) {
11
+ return withOncePer(await Promise.all(Object.entries(tools).map(async ([name, t]) => {
12
+ const parameters = (await asSchema(t.inputSchema).jsonSchema);
13
+ return {
14
+ name,
15
+ description: t.description ?? '',
16
+ parameters,
17
+ execute: async (args, ctx) => {
18
+ if (!t.execute)
19
+ return { error: `tool ${name} has no execute function` };
20
+ let out = await t.execute(args, { toolCallId: ctx.idempotencyKey, messages: [] });
21
+ // A streaming tool yields many values; the last one is its result.
22
+ if (isAsyncIterable(out)) {
23
+ let last;
24
+ for await (const part of out)
25
+ last = part;
26
+ out = last;
27
+ }
28
+ return out;
29
+ },
30
+ };
31
+ })), opts.oncePer);
32
+ }
@@ -0,0 +1,376 @@
1
+ export declare const defaultBaseUrl = "https://api.getargentic.com";
2
+ /** A wake can run a long time, so a try waits five minutes, as Python's client does. */
3
+ export declare const defaultTimeoutMs = 300000;
4
+ export interface ModelSpec {
5
+ provider: string;
6
+ id: string;
7
+ prices: {
8
+ in: number;
9
+ out: number;
10
+ };
11
+ }
12
+ /** The free default model. */
13
+ export declare const freeModel: ModelSpec;
14
+ /** What Argentic is told about a tool. It is declared, never executed by Argentic. */
15
+ export interface ToolDecl {
16
+ name: string;
17
+ description: string;
18
+ parameters: Record<string, unknown>;
19
+ /**
20
+ * A business-operation key such as `refund:{order_id}`. Each `{name}` is filled in from the call's arguments, and Argentic
21
+ * refuses a second call that produces the same key, whatever else it writes. Every `{name}` must be a required string,
22
+ * number, integer or boolean parameter of this tool. Left out, nothing about the tool request changes.
23
+ */
24
+ once_per?: string;
25
+ /**
26
+ * Leave this tool out of the prompt. The agent finds it by describing what it needs (`find_tools`) and calls it through
27
+ * `use_tool`; the call still comes back in `actions` as this tool. Worth it from about ten tools or ten thousand tokens of
28
+ * schemas. Not allowed on a tool that waits for a person or is named in `tool_choice`.
29
+ */
30
+ defer_loading?: true;
31
+ }
32
+ /**
33
+ * Trim a JSON schema to the subset Argentic accepts: an object of string, number,
34
+ * integer, boolean or array-of-those properties, with description, enum and required.
35
+ * Framework extras ($schema, additionalProperties, title, defaults) are dropped.
36
+ */
37
+ export declare function toArgenticSchema(schema: Record<string, any>): Record<string, unknown>;
38
+ /**
39
+ * Check a `once_per` template the way the API does, so a mistake fails here with a clear message instead of as a refused wake.
40
+ * `parameters` is the tool's JSON schema (before or after `toArgenticSchema`). Throws; returns nothing.
41
+ */
42
+ export declare function checkOncePer(tool: string, template: unknown, parameters: Record<string, unknown>): void;
43
+ /** Set `once_per` on tools by name (for the AI SDK and Mastra adapters, whose tools have nowhere to carry it). Throws on a name that is not a tool or a template the API would refuse. */
44
+ export declare function withOncePer<T extends ToolDecl>(tools: T[], oncePer?: Record<string, string>): T[];
45
+ export interface WakeBody {
46
+ /** Required unless `fire_due` is set; the API refuses both together. */
47
+ message?: string;
48
+ /** Wake for the plans, schedules and timers that have fallen due instead of with a message. */
49
+ fire_due?: boolean;
50
+ /** Which memory of the agent to wake. Left out, the agent's default. */
51
+ memory?: string;
52
+ model: ModelSpec;
53
+ system?: string;
54
+ /** Facts for this call only (opening hours, the signed-in user's plan), up to 16,000 characters. Not written to the journal or memory; send it again on the next call if the agent still needs it. */
55
+ context?: string;
56
+ tools?: ToolDecl[];
57
+ /** Whether this call must end with a call to one of your tools. `auto` (default), `required`, or `{ type: 'tool', name }`. */
58
+ tool_choice?: 'auto' | 'required' | {
59
+ type: 'tool';
60
+ name: string;
61
+ };
62
+ limits?: {
63
+ max_usd?: number;
64
+ max_turns?: number;
65
+ };
66
+ /**
67
+ * What your app's tools returned for the `actions` of the previous wake, as `{ seq, result }`: `seq` is the action's
68
+ * `seq` and `result` is exactly what the tool returned. The API journals each as the real result of that call. Send it
69
+ * together with a `message` (the text the model reads), never with `fire_due`; at most 100 entries. A server that does
70
+ * not know the field yet refuses it, and `wake()` then sends the same wake again without it and stops sending it on
71
+ * this client.
72
+ */
73
+ results?: {
74
+ seq: number;
75
+ result: unknown;
76
+ }[];
77
+ }
78
+ export interface Action {
79
+ id: string;
80
+ tool: string;
81
+ args: Record<string, unknown>;
82
+ seq: number;
83
+ }
84
+ export type WakeStatus = 'done' | 'spend_cap' | 'budget_cap' | 'turn_cap' | 'time_cap' | 'error' | 'approval_required';
85
+ export interface WakeResult {
86
+ status: WakeStatus;
87
+ reply: string;
88
+ spent_usd: number;
89
+ turns: number;
90
+ wake_id: string;
91
+ actions?: Action[];
92
+ resumed?: boolean;
93
+ error?: string;
94
+ /** True when this key was already answered and the stored answer came back. */
95
+ idempotentReplay: boolean;
96
+ }
97
+ export interface ReplayTurn {
98
+ turn_seq: number;
99
+ wake_id: string | null;
100
+ match: boolean | null;
101
+ reason?: string;
102
+ }
103
+ export interface ReplayResult {
104
+ turns: ReplayTurn[];
105
+ matched: number;
106
+ mismatched: number;
107
+ unverifiable: number;
108
+ [key: string]: unknown;
109
+ }
110
+ export declare class ArgenticError extends Error {
111
+ readonly status?: number | undefined;
112
+ readonly body?: unknown | undefined;
113
+ constructor(message: string, status?: number | undefined, body?: unknown | undefined);
114
+ }
115
+ /** A next step for an error a first run tends to hit, or undefined. Examples print it instead of a stack trace. */
116
+ export declare function hintFor(e: unknown): string | undefined;
117
+ /** The body of a schedule write: exactly one of `cron` or `at`. */
118
+ export interface ScheduleBody {
119
+ /** Five numeric UTC fields (numbers, `*`, ranges, lists, steps). Occurrences closer than the host's grid (60 minutes in the beta) are refused. */
120
+ cron?: string;
121
+ /** A one-shot timer: ISO 8601 with an offset, in the future by at least the grid, at most 366 days ahead. */
122
+ at?: string;
123
+ /** What the agent is told when it fires, up to 4,000 characters. */
124
+ message: string;
125
+ enabled?: boolean;
126
+ }
127
+ export interface Schedule {
128
+ name: string;
129
+ /** Present on a recurring schedule. */
130
+ cron?: string;
131
+ /** Present on a one-shot timer, with `timer: true`. */
132
+ at?: string;
133
+ timer?: true;
134
+ message: string;
135
+ enabled: boolean;
136
+ next_at: string;
137
+ last_fired_at: string | null;
138
+ fires: number;
139
+ }
140
+ /** One entry of due(): a plan (`plan_id`, `wake_at`), a schedule or timer (`schedule`, `scheduled_at`) or an agent waiting on an event (`reasons`, `since`). */
141
+ export interface DueItem {
142
+ agent: string;
143
+ memory?: string;
144
+ plan_id?: number;
145
+ text?: string;
146
+ wake_at?: string;
147
+ schedule?: string;
148
+ scheduled_at?: string;
149
+ timer?: true;
150
+ reasons?: string[];
151
+ since?: string;
152
+ /** The notice to your wake_url ran out of attempts. */
153
+ notice_failing?: true;
154
+ }
155
+ export interface NoticeSettings {
156
+ wake_url: string | null;
157
+ /** Give this to handleDue / verifyDue as `secret`. */
158
+ signing_secret: string;
159
+ }
160
+ export interface ClientOptions {
161
+ /** Defaults to the ARGENTIC_API_KEY environment variable. */
162
+ apiKey?: string;
163
+ /** Sent as X-Model-Key on wake. Defaults to the MODEL_KEY environment variable. */
164
+ modelKey?: string;
165
+ baseUrl?: string;
166
+ /** How long one request may take before that try counts as a network failure, in milliseconds. Default 300000; 0 means no limit. */
167
+ timeoutMs?: number;
168
+ fetch?: typeof fetch;
169
+ /** Waits between retries of a 5xx or network failure. */
170
+ backoffMs?: number[];
171
+ /** Upper bound on 409 queued retries before giving up. */
172
+ maxQueuedWaits?: number;
173
+ sleep?: (ms: number) => Promise<void>;
174
+ }
175
+ export declare class Argentic {
176
+ private readonly apiKey;
177
+ private readonly modelKey?;
178
+ private readonly baseUrl;
179
+ private readonly timeoutMs;
180
+ private readonly doFetch;
181
+ private readonly backoffMs;
182
+ private readonly maxQueuedWaits;
183
+ private readonly sleep;
184
+ /** Set once a server has refused `results` as an unknown field; later wakes of this client leave it out. */
185
+ private resultsRefused;
186
+ constructor(opts?: ClientOptions);
187
+ /**
188
+ * Wake an agent with a message. Safe to call again with the same key:
189
+ * a queued (409) answer waits and resends, a 5xx or network error retries
190
+ * with backoff, and a reused key with a different body throws. If the server answers 400 and names `results`, it does
191
+ * not know that field yet: the same wake goes out once more under the same key without `results`, and this client leaves
192
+ * `results` out from then on.
193
+ */
194
+ wake(agent: string, body: WakeBody, idempotencyKey: string): Promise<WakeResult>;
195
+ /** Re-render a wake's prompts from the journal and compare them to what the model was shown. Spends nothing. */
196
+ replay(agent: string, wakeId: string): Promise<ReplayResult>;
197
+ /** What is due now: plans past their wake_at, schedules and timers whose time has come, and agents waiting on an answered approval or a hook. Read only; costs nothing. */
198
+ due(opts?: {
199
+ limit?: number;
200
+ }): Promise<DueItem[]>;
201
+ /** Create or replace a recurring schedule (`cron`, five UTC fields) or a one-shot timer (`at`, ISO 8601 with an offset) on an agent. Needs the hosted API to have schedules switched on. */
202
+ putSchedule(agent: string, name: string, body: ScheduleBody, opts?: {
203
+ memory?: string;
204
+ }): Promise<Schedule>;
205
+ /** The schedules and timers of one agent. */
206
+ schedules(agent: string, opts?: {
207
+ memory?: string;
208
+ }): Promise<Schedule[]>;
209
+ /** Delete a schedule or timer. Throws a 404 ArgenticError if it does not exist. */
210
+ deleteSchedule(agent: string, name: string, opts?: {
211
+ memory?: string;
212
+ }): Promise<void>;
213
+ /**
214
+ * Declare an agent's tools once (at most 32). A wake that sends no `tools` uses them; a wake that does send `tools` uses
215
+ * those instead. Replaces what was stored; an empty list clears it. Returns the stored tools.
216
+ */
217
+ putTools(agent: string, tools: ToolDecl[]): Promise<ToolDecl[]>;
218
+ /** The tools stored for an agent with putTools(), or an empty list. */
219
+ tools(agent: string): Promise<ToolDecl[]>;
220
+ /** Where Argentic posts a signed `agent.due` notice (https, port 443, a public host), or null to stop the notices and keep polling due(). Returns the stored address. */
221
+ setWakeUrl(url: string | null): Promise<string | null>;
222
+ /** The wake URL and the signing secret (`whsec_...`) to give handleDue as `secret`. */
223
+ notices(): Promise<NoticeSettings>;
224
+ private plainHeaders;
225
+ private schedulePath;
226
+ private send;
227
+ }
228
+ /** What the notice says. It names the agent and why it is due, never its content. */
229
+ export interface DueNotice {
230
+ type: 'agent.due';
231
+ agent: string;
232
+ memory: string | null;
233
+ reasons: string[];
234
+ /** ISO time the agent became due. */
235
+ since: string;
236
+ }
237
+ export type WebhookErrorCode = 'bad_secret' | 'missing_header' | 'bad_timestamp' | 'stale' | 'bad_signature' | 'bad_body';
238
+ /** The notice cannot be trusted or read. Nothing was woken. `code` says why. */
239
+ export declare class WebhookVerificationError extends Error {
240
+ readonly code: WebhookErrorCode;
241
+ constructor(message: string, code: WebhookErrorCode);
242
+ }
243
+ /** A Fetch API Request, or the raw body text with its headers. */
244
+ export type DueRequest = Request | {
245
+ body: string;
246
+ headers: Record<string, string> | Headers;
247
+ };
248
+ export interface VerifyOptions {
249
+ /** The `whsec_...` secret shown when the wake_url was registered. Defaults to the ARGENTIC_WEBHOOK_SECRET environment variable. */
250
+ secret?: string;
251
+ /** How far the notice's timestamp may be from now, either way, in seconds. Default 300. */
252
+ toleranceSeconds?: number;
253
+ /** The current time in milliseconds. For tests. */
254
+ now?: () => number;
255
+ }
256
+ export interface DueOptions extends VerifyOptions {
257
+ /** The client that wakes the agent. Defaults to a new Argentic(), which reads ARGENTIC_API_KEY and MODEL_KEY. */
258
+ client?: Argentic;
259
+ model?: ModelSpec;
260
+ }
261
+ /** `v1,<base64 HMAC-SHA256>` over "id.timestamp.body", keyed by the base64-decoded part of the secret after `whsec_`. Standard Webhooks. */
262
+ export declare function signNotice(secret: string, id: string, timestamp: number | string, body: string): string;
263
+ /**
264
+ * Check the signature and the time, then parse the notice. No network. The signature is checked against the body exactly as it arrived,
265
+ * so pass the raw text, not re-serialized JSON. Use it to queue the notice and wake later; the returned `id` is the key to wake with.
266
+ */
267
+ export declare function verifyDue(req: DueRequest, opts?: VerifyOptions): Promise<{
268
+ notice: DueNotice;
269
+ id: string;
270
+ }>;
271
+ /**
272
+ * Verify a due notice, then wake the agent for what fell due, on your own model key (the notice carries none).
273
+ * The wake's idempotency key comes from the notice's webhook-id, so a notice delivered again replays the first wake's answer.
274
+ * Throws WebhookVerificationError before any call is made if the notice is not valid.
275
+ */
276
+ export declare function handleDue(req: DueRequest, opts?: DueOptions): Promise<{
277
+ notice: DueNotice;
278
+ wake: WakeResult;
279
+ }>;
280
+ /** handleDue as a route handler's answer: 200 woken, 401 not verified, 400 unreadable body, 500 secret not set up, 502 the wake failed (so the notice is sent again). */
281
+ export declare function handleDueResponse(req: DueRequest, opts?: DueOptions): Promise<Response>;
282
+ export interface LedgerEntry {
283
+ tool: string;
284
+ args: Record<string, unknown>;
285
+ result: unknown;
286
+ at: string;
287
+ }
288
+ /** A wake that stopped at its turn or time limit with actions to run, kept so a restart sends their results instead of asking the wake again. */
289
+ export type StoppedWake = Omit<WakeResult, 'idempotentReplay'>;
290
+ export declare class Ledger {
291
+ readonly path: string;
292
+ private entries;
293
+ private wakes;
294
+ /**
295
+ * A missing file starts an empty ledger. A file that cannot be read or parsed is set aside as `<path>.corrupt` and `onCorrupt`
296
+ * is told, because an empty ledger means finished actions may run again. `onCorrupt` defaults to console.warn.
297
+ */
298
+ constructor(path: string, onCorrupt?: (message: string) => void);
299
+ get(key: string): LedgerEntry | undefined;
300
+ set(key: string, entry: LedgerEntry): void;
301
+ /** The stopped wake answered for this idempotency key, if one was kept. */
302
+ getWake(key: string): StoppedWake | undefined;
303
+ setWake(key: string, wake: StoppedWake): void;
304
+ /** Write temp file, then rename, so a crash never leaves half a ledger. */
305
+ private save;
306
+ }
307
+ /**
308
+ * Throw this from a tool when the outcome is not known yet and the same call is safe to try again:
309
+ * a network drop, a timeout, a provider that is down. runDurable does not write it to the ledger,
310
+ * so it stops the run. Run it again and the action is asked for again with the same key.
311
+ */
312
+ export declare class RetryLater extends Error {
313
+ readonly cause?: unknown | undefined;
314
+ constructor(message: string, cause?: unknown | undefined);
315
+ }
316
+ export interface ToolContext {
317
+ seq: number;
318
+ /** `${agent}:${seq}`. Use it to make the side effect safe to repeat. */
319
+ idempotencyKey: string;
320
+ }
321
+ export interface DurableTool extends ToolDecl {
322
+ execute: (args: any, ctx: ToolContext) => unknown | Promise<unknown>;
323
+ }
324
+ export interface ActionInfo {
325
+ tool: string;
326
+ seq: number;
327
+ /** Number of actions newly executed in this process so far. */
328
+ executed: number;
329
+ }
330
+ export interface RunOptions {
331
+ client: Argentic;
332
+ agent: string;
333
+ /** Chosen by you and kept the same across restarts. */
334
+ runId: string;
335
+ task: string;
336
+ system?: string;
337
+ tools: DurableTool[];
338
+ ledgerPath: string;
339
+ model?: ModelSpec;
340
+ maxSteps?: number;
341
+ limits?: WakeBody['limits'];
342
+ /** Called just before each wake is sent, so a caller can say it is waiting on the model (the first answer can take several seconds). */
343
+ onWakeStart?: (info: {
344
+ step: number;
345
+ }) => void;
346
+ /** Called after each newly executed action is written to the ledger. */
347
+ onAction?: (info: ActionInfo) => void;
348
+ /** Called for an action the ledger says already finished, so it is not run again. */
349
+ onSkip?: (info: {
350
+ tool: string;
351
+ seq: number;
352
+ }) => void;
353
+ /** Called after each wake answers. `replayed` means the API sent back its stored answer for this key, or the ledger kept a stopped one. */
354
+ onWake?: (info: {
355
+ step: number;
356
+ wakeId: string;
357
+ status: WakeStatus;
358
+ replayed: boolean;
359
+ }) => void;
360
+ /** Kill this process hard after the Nth newly executed action (to try a crash). */
361
+ crashAfterActions?: number;
362
+ /**
363
+ * Send each follow-up wake's tool results as the structured `results` field as well as in the message text, so the API
364
+ * journals the real result of every call. Default true. Set false to send the text only.
365
+ */
366
+ structuredResults?: boolean;
367
+ log?: (line: string) => void;
368
+ }
369
+ export interface RunResult {
370
+ reply: string;
371
+ wakes: string[];
372
+ steps: number;
373
+ status: WakeStatus | 'max_steps';
374
+ spentUsd: number;
375
+ }
376
+ export declare function runDurable(opts: RunOptions): Promise<RunResult>;