@myapihq/sdk 1.2.8 → 1.3.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/services.ts CHANGED
@@ -20,7 +20,8 @@ export type ServiceCategory =
20
20
  | 'capture' // inbound: webhook, pixel
21
21
  | 'data' // read/filter the data layer: people, company, audience, crm
22
22
  | 'store' // persist bytes/JSON: storage, database
23
- | 'compute' // run code or inference: llm, workflow
23
+ | 'compute' // run code or inference: llm, fn
24
+ | 'orchestrate' // control flow: workflow, queue, task
24
25
  | 'identity' // auth/billing/account: hq
25
26
  | 'infra'; // platform glue: domain
26
27
 
@@ -65,16 +66,16 @@ export const SERVICES: readonly ServiceMeta[] = [
65
66
 
66
67
  // ── send ────────────────────────────────────────────────────────────
67
68
  {
68
- // Customer-facing email surface (mailboxes, campaigns, templates,
69
- // warmup) is gated pre-launch — 503 SERVICE_NOT_LAUNCHED. Sibling
70
- // `my-email-verify-api` stays GA and remains the discoverable entry
71
- // point for the email primitive today.
69
+ // Customer-facing email surface mailboxes, campaigns, templates,
70
+ // warmup. Launched 2026-05-17: the backend lifted the pre-launch gate
71
+ // (the [disabled, pre-launch] / 503 SERVICE_NOT_LAUNCHED state is gone).
72
+ // Sibling `my-email-verify-api` is the sync single-address verifier.
72
73
  module: 'email',
73
74
  skill: 'my-email-api',
74
75
  domain: 'myemailapi.com',
75
76
  description: 'Send transactional and bulk email from your own domain. Mailboxes, AI templates, drip campaigns, and warmup.',
76
77
  category: 'send',
77
- status: 'preview',
78
+ status: 'ga',
78
79
  keywords: k('email', 'transactional', 'campaign', 'smtp'),
79
80
  },
80
81
  {
@@ -201,15 +202,6 @@ export const SERVICES: readonly ServiceMeta[] = [
201
202
  },
202
203
 
203
204
  // ── compute ─────────────────────────────────────────────────────────
204
- {
205
- module: 'workflow',
206
- skill: 'my-workflow-api',
207
- domain: 'myworkflowapi.com',
208
- description: 'React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.',
209
- category: 'compute',
210
- status: 'ga',
211
- keywords: k('workflow', 'automation', 'orchestration'),
212
- },
213
205
  {
214
206
  module: 'llm',
215
207
  skill: 'my-llm-api',
@@ -266,4 +258,54 @@ export const SERVICES: readonly ServiceMeta[] = [
266
258
  status: 'preview',
267
259
  keywords: k('container', 'cloud-run', 'service', 'worker', 'job'),
268
260
  },
261
+ {
262
+ // CLI top-level command + SDK namespace are both `git`; skill directory
263
+ // is `my-git-api`. A stateless git-over-HTTP surface (go-git engine;
264
+ // objects in GCS, refs in Postgres) — repos, commits, branches, history.
265
+ module: 'git',
266
+ skill: 'my-git-api',
267
+ domain: 'mygitapi.com',
268
+ description: 'Hosted git repositories over HTTP — create repos, commit files, manage branches and tags, read trees, blobs, history, and diffs. No local clone required.',
269
+ category: 'store',
270
+ status: 'preview',
271
+ keywords: k('git', 'repository', 'version-control', 'commit', 'scm'),
272
+ },
273
+
274
+ // ── orchestrate ─────────────────────────────────────────────────────
275
+ // Control flow, not actions. workflow = event→action, queue = durable
276
+ // async machine work, task = work that needs an agent/human decision.
277
+ // See docs/orchestration-decision-guide.md.
278
+ {
279
+ module: 'workflow',
280
+ skill: 'my-workflow-api',
281
+ domain: 'myworkflowapi.com',
282
+ description: 'React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.',
283
+ category: 'orchestrate',
284
+ status: 'ga',
285
+ keywords: k('workflow', 'automation', 'orchestration'),
286
+ },
287
+ {
288
+ // CLI top-level command + SDK namespace are both `queue`; skill
289
+ // directory is `my-queue-api`. A durable HTTP-consumer job queue —
290
+ // retry/backoff, concurrency caps, dependency DAG.
291
+ module: 'queue',
292
+ skill: 'my-queue-api',
293
+ domain: 'myqueueapi.com',
294
+ description: 'Durable job queue — enqueue work and have it retried against your HTTP consumer, with concurrency caps and a dependency DAG.',
295
+ category: 'orchestrate',
296
+ status: 'preview',
297
+ keywords: k('queue', 'job-queue', 'background-jobs', 'retry', 'async'),
298
+ },
299
+ {
300
+ // CLI top-level command + SDK namespace are both `task`; skill
301
+ // directory is `my-task-api`. The agent-task queue — the agent-loop
302
+ // hot path: file, claim under a lease, resolve.
303
+ module: 'task',
304
+ skill: 'my-task-api',
305
+ domain: 'mytaskapi.com',
306
+ description: 'Agent-task queue — file units of work, rank them, claim under a lease, then resolve, fail, or cancel. The agent-loop hot path.',
307
+ category: 'orchestrate',
308
+ status: 'preview',
309
+ keywords: k('task', 'task-queue', 'agent-loop', 'work-queue', 'lease'),
310
+ },
269
311
  ] as const;
package/src/task.ts ADDED
@@ -0,0 +1,198 @@
1
+ import { request } from './client';
2
+ import { TASK_BASE as BASE_URL } from './config';
3
+ import type { Exposes } from './exposes';
4
+
5
+ // Backend: my-task-api per myapi-hq/internal/routes/task/. An agent-task
6
+ // queue — the agent-loop hot path. Tasks are created, ranked by score,
7
+ // claimed under a lease, then resolved/failed/cancelled. A task with
8
+ // unresolved depends_on starts blocked; lease expiry auto-reverts a claimed
9
+ // task to open.
10
+ export const EXPOSES: Exposes = [
11
+ 'POST /task/orgs/{org_id}/tasks',
12
+ 'GET /task/orgs/{org_id}/tasks',
13
+ 'GET /task/orgs/{org_id}/tasks/{id}',
14
+ 'DELETE /task/orgs/{org_id}/tasks/{id}',
15
+ 'GET /task/orgs/{org_id}/tasks/{id}/body',
16
+ 'POST /task/orgs/{org_id}/tasks/{id}/claim',
17
+ 'POST /task/orgs/{org_id}/tasks/{id}/extend',
18
+ 'POST /task/orgs/{org_id}/tasks/{id}/fail',
19
+ 'POST /task/orgs/{org_id}/tasks/{id}/resolve',
20
+ ];
21
+
22
+ export type TaskStatus = 'open' | 'claimed' | 'blocked' | 'resolved' | 'failed' | 'cancelled';
23
+
24
+ export type TaskImportance = 'low' | 'normal' | 'high' | 'critical';
25
+
26
+ // Event matcher: a matching platform_events delivery auto-resolves the task.
27
+ export interface ResolveOn {
28
+ event_type: string;
29
+ field?: string;
30
+ value?: string;
31
+ }
32
+
33
+ // The projected list row — list deliberately returns only this triage-sized
34
+ // shape, not the full task. Read the body tier separately via getTaskBody.
35
+ export interface TaskRef {
36
+ id: string;
37
+ description: string;
38
+ score: number;
39
+ }
40
+
41
+ // Full task object. `claim` and `extend` return this — lease state is read
42
+ // off `lease_expires_at` / `claimed_by`, not a separate lease object.
43
+ export interface Task {
44
+ id: string;
45
+ org_id: string;
46
+ description: string;
47
+ status: TaskStatus;
48
+ importance: TaskImportance;
49
+ score: number;
50
+ tags: string[];
51
+ source: string;
52
+ depends_on: string[];
53
+ payload_url: string;
54
+ assignee?: string;
55
+ resolve_on?: ResolveOn;
56
+ dedup_key?: string;
57
+ claimed_by?: string;
58
+ lease_expires_at?: string;
59
+ due_at?: string;
60
+ fail_reason?: string;
61
+ resolved_at?: string;
62
+ created_at: string;
63
+ updated_at: string;
64
+ }
65
+
66
+ // Backlog health from the list envelope's meta — the agent's "am I keeping
67
+ // up" signal. `shown` is how many rows came back; `total_open` is the whole
68
+ // open queue depth.
69
+ export interface TaskListMeta {
70
+ shown: number;
71
+ total_open: number;
72
+ }
73
+
74
+ export interface TaskListResult {
75
+ tasks: TaskRef[];
76
+ meta: TaskListMeta;
77
+ }
78
+
79
+ export interface CreateTaskOptions {
80
+ description: string;
81
+ // Full Markdown context. Staged to the body tier; read once via getTaskBody.
82
+ body?: string;
83
+ importance?: TaskImportance;
84
+ dueAt?: string;
85
+ assignee?: string;
86
+ tags?: string[];
87
+ // Task ids this task waits on. Immutable — declared only at creation.
88
+ // Same shape as queue.enqueue's depends_on.
89
+ dependsOn?: string[];
90
+ dedupKey?: string;
91
+ resolveOn?: ResolveOn;
92
+ source?: string;
93
+ }
94
+
95
+ export interface ListTaskOptions {
96
+ status?: string;
97
+ tag?: string;
98
+ importance?: string;
99
+ assignee?: string;
100
+ source?: string;
101
+ limit?: number;
102
+ }
103
+
104
+ function tasksBase(orgId: string): string {
105
+ return `${BASE_URL}/task/orgs/${encodeURIComponent(orgId)}/tasks`;
106
+ }
107
+
108
+ function taskBase(orgId: string, id: string): string {
109
+ return `${tasksBase(orgId)}/${encodeURIComponent(id)}`;
110
+ }
111
+
112
+ // createTask is idempotent on (org_id, dedup_key). A task with unresolved
113
+ // depends_on starts blocked; one with an assignee emails them a magic link.
114
+ export async function createTask(apiKey: string, orgId: string, opts: CreateTaskOptions): Promise<Task> {
115
+ const body: Record<string, unknown> = { description: opts.description };
116
+ if (opts.body !== undefined) body.body = opts.body;
117
+ if (opts.importance !== undefined) body.importance = opts.importance;
118
+ if (opts.dueAt !== undefined) body.due_at = opts.dueAt;
119
+ if (opts.assignee !== undefined) body.assignee = opts.assignee;
120
+ if (opts.tags !== undefined) body.tags = opts.tags;
121
+ if (opts.dependsOn !== undefined) body.depends_on = opts.dependsOn;
122
+ if (opts.dedupKey !== undefined) body.dedup_key = opts.dedupKey;
123
+ if (opts.resolveOn !== undefined) body.resolve_on = opts.resolveOn;
124
+ if (opts.source !== undefined) body.source = opts.source;
125
+ return request('POST', tasksBase(orgId), apiKey, body);
126
+ }
127
+
128
+ // listTasks returns the ranked open queue (projected to TaskRef) plus the
129
+ // backlog counts. Default: top 20 open tasks by score. The backend nests
130
+ // {shown, total_open} inside the response `data.meta` (not the envelope meta).
131
+ export async function listTasks(apiKey: string, orgId: string, opts: ListTaskOptions = {}): Promise<TaskListResult> {
132
+ const q = new URLSearchParams();
133
+ if (opts.status) q.set('status', opts.status);
134
+ if (opts.tag) q.set('tag', opts.tag);
135
+ if (opts.importance) q.set('importance', opts.importance);
136
+ if (opts.assignee) q.set('assignee', opts.assignee);
137
+ if (opts.source) q.set('source', opts.source);
138
+ if (opts.limit !== undefined) q.set('limit', String(opts.limit));
139
+ const qs = q.toString();
140
+ const res = await request<{ tasks?: TaskRef[]; meta?: Partial<TaskListMeta> }>(
141
+ 'GET', `${tasksBase(orgId)}${qs ? `?${qs}` : ''}`, apiKey,
142
+ );
143
+ const tasks = res?.tasks ?? [];
144
+ const m = res?.meta ?? {};
145
+ return {
146
+ tasks,
147
+ meta: {
148
+ shown: typeof m.shown === 'number' ? m.shown : tasks.length,
149
+ total_open: typeof m.total_open === 'number' ? m.total_open : tasks.length,
150
+ },
151
+ };
152
+ }
153
+
154
+ // getTask returns the full task object. It deliberately does NOT fetch the
155
+ // Markdown body tier — call getTaskBody for that, once, on commit.
156
+ export async function getTask(apiKey: string, orgId: string, id: string): Promise<Task> {
157
+ return request('GET', taskBase(orgId, id), apiKey);
158
+ }
159
+
160
+ // getTaskBody fetches the body tier — the full Markdown context. Separate
161
+ // call by design: it keeps the list/get path token-cheap. The endpoint
162
+ // returns { task_id, body }.
163
+ export async function getTaskBody(apiKey: string, orgId: string, id: string): Promise<string> {
164
+ const res = await request<{ task_id: string; body: string }>('GET', `${taskBase(orgId, id)}/body`, apiKey);
165
+ return res?.body ?? '';
166
+ }
167
+
168
+ // claimTask takes an atomic lease (default 10 min) and returns the full
169
+ // updated task — lease state is on `lease_expires_at` / `claimed_by`.
170
+ export async function claimTask(apiKey: string, orgId: string, id: string, opts: { leaseSeconds?: number; worker?: string } = {}): Promise<Task> {
171
+ const body: Record<string, unknown> = {};
172
+ if (opts.leaseSeconds !== undefined) body.lease_seconds = opts.leaseSeconds;
173
+ if (opts.worker !== undefined) body.worker = opts.worker;
174
+ return request('POST', `${taskBase(orgId, id)}/claim`, apiKey, body);
175
+ }
176
+
177
+ // extendTask is a heartbeat for long work — extends a live claim.
178
+ export async function extendTask(apiKey: string, orgId: string, id: string, opts: { leaseSeconds?: number } = {}): Promise<Task> {
179
+ const body: Record<string, unknown> = {};
180
+ if (opts.leaseSeconds !== undefined) body.lease_seconds = opts.leaseSeconds;
181
+ return request('POST', `${taskBase(orgId, id)}/extend`, apiKey, body);
182
+ }
183
+
184
+ // resolveTask resolves a task (terminal) — unblocks any dependents.
185
+ export async function resolveTask(apiKey: string, orgId: string, id: string): Promise<Task> {
186
+ return request('POST', `${taskBase(orgId, id)}/resolve`, apiKey, {});
187
+ }
188
+
189
+ // failTask fails a task (terminal, with a reason). Does not auto-retry;
190
+ // dependents that can never proceed are auto-failed.
191
+ export async function failTask(apiKey: string, orgId: string, id: string, reason: string): Promise<Task> {
192
+ return request('POST', `${taskBase(orgId, id)}/fail`, apiKey, { reason });
193
+ }
194
+
195
+ // cancelTask cancels a task (terminal, distinct from fail).
196
+ export async function cancelTask(apiKey: string, orgId: string, id: string): Promise<void> {
197
+ return request('DELETE', taskBase(orgId, id), apiKey);
198
+ }
package/src/workflow.ts CHANGED
@@ -33,7 +33,13 @@ export type WorkflowStep =
33
33
  // Backend (2026-05-15): `http_request` step is now accepted (alias: `http`).
34
34
  // POSTs (or other method) to `url` with the inbound payload; supports
35
35
  // template substitution in url/body the same way email steps do.
36
- | { type: 'http_request' | 'http'; url: string; method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; body?: string; headers?: Record<string, string> };
36
+ | { type: 'http_request' | 'http'; url: string; method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; body?: string; headers?: Record<string, string> }
37
+ // `enqueue_job` (alias: `enqueue`) hands durable work to my-queue-api —
38
+ // the step enqueues a job on `queue` carrying `payload` (template
39
+ // substitution supported, same as email/http steps). The workflow stays
40
+ // the trigger layer; the queue owns retry/durability. Backend: pending —
41
+ // see docs/cross-repo-prompts/backend-orchestration-composition.md.
42
+ | { type: 'enqueue_job' | 'enqueue'; queue: string; payload?: string; dedup_key?: string; delay_seconds?: number };
37
43
 
38
44
  export interface Workflow {
39
45
  id: string;