@myapihq/sdk 1.2.9 → 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/dist/client.js +4 -4
- package/dist/config.d.ts +2 -0
- package/dist/config.js +3 -1
- package/dist/email.d.ts +5 -0
- package/dist/email.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/queue.d.ts +49 -0
- package/dist/queue.js +71 -0
- package/dist/services.d.ts +1 -1
- package/dist/services.js +37 -9
- package/dist/task.d.ts +78 -0
- package/dist/task.js +131 -0
- package/dist/workflow.d.ts +6 -0
- package/package.json +1 -1
- package/src/client.ts +4 -4
- package/src/config.ts +2 -0
- package/src/email.ts +14 -0
- package/src/index.ts +2 -0
- package/src/queue.ts +110 -0
- package/src/services.ts +40 -10
- package/src/task.ts +198 -0
- package/src/workflow.ts +7 -1
package/dist/client.js
CHANGED
|
@@ -64,16 +64,16 @@ async function request(method, url, apiKey, body, extraHeaders) {
|
|
|
64
64
|
const err = result?.error;
|
|
65
65
|
const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
|
|
66
66
|
const detail = typeof err === 'object' ? (err?.message || undefined) : undefined;
|
|
67
|
-
const
|
|
68
|
-
throw new MyApiError(code, response.status, detail,
|
|
67
|
+
const errBody = typeof err === 'object' ? err : undefined;
|
|
68
|
+
throw new MyApiError(code, response.status, detail, errBody);
|
|
69
69
|
}
|
|
70
70
|
const apiResponse = result;
|
|
71
71
|
if (!apiResponse.success) {
|
|
72
72
|
const err = apiResponse.error;
|
|
73
73
|
const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
|
|
74
74
|
const detail = typeof err === 'object' ? (err?.message || undefined) : undefined;
|
|
75
|
-
const
|
|
76
|
-
throw new MyApiError(code, response.status, detail,
|
|
75
|
+
const errBody = typeof err === 'object' ? err : undefined;
|
|
76
|
+
throw new MyApiError(code, response.status, detail, errBody);
|
|
77
77
|
}
|
|
78
78
|
return apiResponse.data;
|
|
79
79
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export declare const FUNCTION_BASE: string;
|
|
|
5
5
|
export declare const PAYMENTS_BASE: string;
|
|
6
6
|
export declare const CONTAINER_BASE: string;
|
|
7
7
|
export declare const GIT_BASE: string;
|
|
8
|
+
export declare const QUEUE_BASE: string;
|
|
9
|
+
export declare const TASK_BASE: string;
|
|
8
10
|
export declare const IMAGE_BASE: string;
|
|
9
11
|
export declare const WEBHOOK_BASE: string;
|
|
10
12
|
export declare const WORKFLOW_BASE: string;
|
package/dist/config.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// local dev — `source scripts/local-env.sh` still points everything at
|
|
9
9
|
// localhost:8080 regardless of the prod default.
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.CRM_BASE = exports.DATABASE_BASE = exports.LLM_BASE = exports.AUDIENCE_BASE = exports.COMPANY_BASE = exports.PEOPLE_BASE = exports.PIXEL_BASE = exports.URL_BASE = exports.STORAGE_BASE = exports.EMAIL_BASE = exports.WORKFLOW_BASE = exports.WEBHOOK_BASE = exports.IMAGE_BASE = exports.GIT_BASE = exports.CONTAINER_BASE = exports.PAYMENTS_BASE = exports.FUNCTION_BASE = exports.FUNNEL_BASE = exports.DOMAIN_BASE = exports.HQ_BASE = void 0;
|
|
11
|
+
exports.CRM_BASE = exports.DATABASE_BASE = exports.LLM_BASE = exports.AUDIENCE_BASE = exports.COMPANY_BASE = exports.PEOPLE_BASE = exports.PIXEL_BASE = exports.URL_BASE = exports.STORAGE_BASE = exports.EMAIL_BASE = exports.WORKFLOW_BASE = exports.WEBHOOK_BASE = exports.IMAGE_BASE = exports.TASK_BASE = exports.QUEUE_BASE = exports.GIT_BASE = exports.CONTAINER_BASE = exports.PAYMENTS_BASE = exports.FUNCTION_BASE = exports.FUNNEL_BASE = exports.DOMAIN_BASE = exports.HQ_BASE = void 0;
|
|
12
12
|
const GATEWAY = 'https://api.myapihq.com';
|
|
13
13
|
exports.HQ_BASE = process.env.MYAPI_HQ_URL ?? process.env.MYAPI_API_BASE ?? GATEWAY;
|
|
14
14
|
exports.DOMAIN_BASE = process.env.MYAPI_DOMAIN_URL ?? 'https://api.mydomainapi.com';
|
|
@@ -19,6 +19,8 @@ exports.FUNCTION_BASE = process.env.MYAPI_FUNCTION_URL ?? GATEWAY;
|
|
|
19
19
|
exports.PAYMENTS_BASE = process.env.MYAPI_PAYMENTS_URL ?? GATEWAY;
|
|
20
20
|
exports.CONTAINER_BASE = process.env.MYAPI_CONTAINER_URL ?? GATEWAY;
|
|
21
21
|
exports.GIT_BASE = process.env.MYAPI_GIT_URL ?? GATEWAY;
|
|
22
|
+
exports.QUEUE_BASE = process.env.MYAPI_QUEUE_URL ?? GATEWAY;
|
|
23
|
+
exports.TASK_BASE = process.env.MYAPI_TASK_URL ?? GATEWAY;
|
|
22
24
|
exports.IMAGE_BASE = process.env.MYAPI_IMAGE_URL ?? 'https://api.myimageapi.com';
|
|
23
25
|
exports.WEBHOOK_BASE = process.env.MYAPI_WEBHOOK_URL ?? 'https://api.mywebhookapi.com';
|
|
24
26
|
exports.WORKFLOW_BASE = process.env.MYAPI_WORKFLOW_URL ?? 'https://api.myworkflowapi.com';
|
package/dist/email.d.ts
CHANGED
|
@@ -118,6 +118,11 @@ export declare function activateSending(apiKey: string, address: string): Promis
|
|
|
118
118
|
sending_enabled: boolean;
|
|
119
119
|
emails_quota_remaining: number;
|
|
120
120
|
}>;
|
|
121
|
+
export declare function setForwarding(apiKey: string, address: string, forwardTo: string): Promise<{
|
|
122
|
+
address: string;
|
|
123
|
+
forward_to: string;
|
|
124
|
+
}>;
|
|
125
|
+
export declare function deleteForwarding(apiKey: string, address: string): Promise<void>;
|
|
121
126
|
export declare function sendEmail(apiKey: string, payload: {
|
|
122
127
|
from: string;
|
|
123
128
|
to: string[];
|
package/dist/email.js
CHANGED
|
@@ -7,6 +7,8 @@ exports.getVerifyJob = getVerifyJob;
|
|
|
7
7
|
exports.createMailbox = createMailbox;
|
|
8
8
|
exports.listMailboxes = listMailboxes;
|
|
9
9
|
exports.activateSending = activateSending;
|
|
10
|
+
exports.setForwarding = setForwarding;
|
|
11
|
+
exports.deleteForwarding = deleteForwarding;
|
|
10
12
|
exports.sendEmail = sendEmail;
|
|
11
13
|
exports.getEmailStatus = getEmailStatus;
|
|
12
14
|
exports.getSentEmails = getSentEmails;
|
|
@@ -43,6 +45,8 @@ exports.EXPOSES = [
|
|
|
43
45
|
// Mailbox / sending activation
|
|
44
46
|
'POST /email/mailboxes/create',
|
|
45
47
|
'GET /email/mailboxes',
|
|
48
|
+
'PUT /email/mailboxes/{address}/forwarding',
|
|
49
|
+
'DELETE /email/mailboxes/{address}/forwarding',
|
|
46
50
|
'POST /email/sending/activate',
|
|
47
51
|
// Send + read
|
|
48
52
|
'POST /email/send',
|
|
@@ -115,6 +119,16 @@ async function listMailboxes(apiKey, options) {
|
|
|
115
119
|
async function activateSending(apiKey, address) {
|
|
116
120
|
return (0, client_1.request)('POST', `${config_1.EMAIL_BASE}/email/sending/activate`, apiKey, { address });
|
|
117
121
|
}
|
|
122
|
+
// setForwarding redirects a copy of every incoming message to an external
|
|
123
|
+
// address (server-side; the original is kept in the mailbox). `forwardTo`
|
|
124
|
+
// must be a valid address and cannot equal the mailbox itself.
|
|
125
|
+
async function setForwarding(apiKey, address, forwardTo) {
|
|
126
|
+
return (0, client_1.request)('PUT', `${config_1.EMAIL_BASE}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey, { forward_to: forwardTo });
|
|
127
|
+
}
|
|
128
|
+
// deleteForwarding stops forwarding for a mailbox.
|
|
129
|
+
async function deleteForwarding(apiKey, address) {
|
|
130
|
+
return (0, client_1.request)('DELETE', `${config_1.EMAIL_BASE}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey);
|
|
131
|
+
}
|
|
118
132
|
// ── Sending and reading (account-scoped) ─────────────────────────────────────
|
|
119
133
|
async function sendEmail(apiKey, payload) {
|
|
120
134
|
return (0, client_1.request)('POST', `${config_1.EMAIL_BASE}/email/send`, apiKey, payload);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.git = exports.container = exports.payments = exports.fn = exports.crm = exports.database = exports.llm = exports.audience = exports.company = exports.people = exports.url = exports.workflow = exports.webhook = exports.storage = exports.pixel = exports.image = exports.funnel = exports.email = exports.domain = exports.hq = void 0;
|
|
39
|
+
exports.task = exports.queue = exports.git = exports.container = exports.payments = exports.fn = exports.crm = exports.database = exports.llm = exports.audience = exports.company = exports.people = exports.url = exports.workflow = exports.webhook = exports.storage = exports.pixel = exports.image = exports.funnel = exports.email = exports.domain = exports.hq = void 0;
|
|
40
40
|
__exportStar(require("./types"), exports);
|
|
41
41
|
__exportStar(require("./client"), exports);
|
|
42
42
|
// Note: config constants (STORAGE_BASE etc.) are NOT re-exported from the
|
|
@@ -65,3 +65,5 @@ exports.fn = __importStar(require("./function"));
|
|
|
65
65
|
exports.payments = __importStar(require("./payments"));
|
|
66
66
|
exports.container = __importStar(require("./container"));
|
|
67
67
|
exports.git = __importStar(require("./git"));
|
|
68
|
+
exports.queue = __importStar(require("./queue"));
|
|
69
|
+
exports.task = __importStar(require("./task"));
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Exposes } from './exposes';
|
|
2
|
+
export declare const EXPOSES: Exposes;
|
|
3
|
+
export interface Queue {
|
|
4
|
+
id: string;
|
|
5
|
+
org_id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
consumer_url: string;
|
|
8
|
+
max_attempts: number;
|
|
9
|
+
max_concurrency: number;
|
|
10
|
+
created_at: string;
|
|
11
|
+
}
|
|
12
|
+
export type JobStatus = 'pending' | 'blocked' | 'running' | 'succeeded' | 'dead';
|
|
13
|
+
export interface Job {
|
|
14
|
+
id: string;
|
|
15
|
+
queue_id: string;
|
|
16
|
+
org_id: string;
|
|
17
|
+
payload: unknown;
|
|
18
|
+
status: JobStatus;
|
|
19
|
+
attempt: number;
|
|
20
|
+
max_attempts: number;
|
|
21
|
+
not_before: string;
|
|
22
|
+
depends_on: string[];
|
|
23
|
+
dedup_key?: string;
|
|
24
|
+
last_error?: string;
|
|
25
|
+
started_at?: string;
|
|
26
|
+
created_at: string;
|
|
27
|
+
updated_at: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CreateQueueOptions {
|
|
30
|
+
name: string;
|
|
31
|
+
consumerUrl: string;
|
|
32
|
+
maxAttempts?: number;
|
|
33
|
+
maxConcurrency?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface EnqueueOptions {
|
|
36
|
+
payload?: unknown;
|
|
37
|
+
dedupKey?: string;
|
|
38
|
+
delaySeconds?: number;
|
|
39
|
+
dependsOn?: string[];
|
|
40
|
+
}
|
|
41
|
+
export declare function createQueue(apiKey: string, orgId: string, opts: CreateQueueOptions): Promise<Queue>;
|
|
42
|
+
export declare function listQueues(apiKey: string, orgId: string): Promise<Queue[]>;
|
|
43
|
+
export declare function getQueue(apiKey: string, orgId: string, name: string): Promise<Queue>;
|
|
44
|
+
export declare function enqueueJob(apiKey: string, orgId: string, name: string, opts?: EnqueueOptions): Promise<Job>;
|
|
45
|
+
export declare function listJobs(apiKey: string, orgId: string, name: string, opts?: {
|
|
46
|
+
status?: string;
|
|
47
|
+
limit?: number;
|
|
48
|
+
}): Promise<Job[]>;
|
|
49
|
+
export declare function getJob(apiKey: string, orgId: string, jobId: string): Promise<Job>;
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EXPOSES = void 0;
|
|
4
|
+
exports.createQueue = createQueue;
|
|
5
|
+
exports.listQueues = listQueues;
|
|
6
|
+
exports.getQueue = getQueue;
|
|
7
|
+
exports.enqueueJob = enqueueJob;
|
|
8
|
+
exports.listJobs = listJobs;
|
|
9
|
+
exports.getJob = getJob;
|
|
10
|
+
const client_1 = require("./client");
|
|
11
|
+
const config_1 = require("./config");
|
|
12
|
+
// Backend: my-queue-api per myapi-hq/internal/routes/queue/. A durable
|
|
13
|
+
// HTTP-consumer job queue — each named queue POSTs its jobs to a configured
|
|
14
|
+
// consumer_url, with retry/backoff to max_attempts, a concurrency cap, and a
|
|
15
|
+
// dependency DAG (a job with unsucceeded depends_on starts blocked).
|
|
16
|
+
exports.EXPOSES = [
|
|
17
|
+
'POST /queue/orgs/{org_id}/queues',
|
|
18
|
+
'GET /queue/orgs/{org_id}/queues',
|
|
19
|
+
'GET /queue/orgs/{org_id}/queues/{name}',
|
|
20
|
+
'POST /queue/orgs/{org_id}/queues/{name}/jobs',
|
|
21
|
+
'GET /queue/orgs/{org_id}/queues/{name}/jobs',
|
|
22
|
+
'GET /queue/orgs/{org_id}/jobs/{id}',
|
|
23
|
+
];
|
|
24
|
+
function queuesBase(orgId) {
|
|
25
|
+
return `${config_1.QUEUE_BASE}/queue/orgs/${encodeURIComponent(orgId)}/queues`;
|
|
26
|
+
}
|
|
27
|
+
// createQueue registers a named queue with its retry + concurrency policy
|
|
28
|
+
// and the HTTP consumer that runs its jobs.
|
|
29
|
+
async function createQueue(apiKey, orgId, opts) {
|
|
30
|
+
const body = { name: opts.name, consumer_url: opts.consumerUrl };
|
|
31
|
+
if (opts.maxAttempts !== undefined)
|
|
32
|
+
body.max_attempts = opts.maxAttempts;
|
|
33
|
+
if (opts.maxConcurrency !== undefined)
|
|
34
|
+
body.max_concurrency = opts.maxConcurrency;
|
|
35
|
+
return (0, client_1.request)('POST', queuesBase(orgId), apiKey, body);
|
|
36
|
+
}
|
|
37
|
+
async function listQueues(apiKey, orgId) {
|
|
38
|
+
const res = await (0, client_1.request)('GET', queuesBase(orgId), apiKey);
|
|
39
|
+
return res?.queues ?? [];
|
|
40
|
+
}
|
|
41
|
+
async function getQueue(apiKey, orgId, name) {
|
|
42
|
+
return (0, client_1.request)('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}`, apiKey);
|
|
43
|
+
}
|
|
44
|
+
// enqueueJob is idempotent on (queue, dedup_key). A job with unsucceeded
|
|
45
|
+
// depends_on starts blocked.
|
|
46
|
+
async function enqueueJob(apiKey, orgId, name, opts = {}) {
|
|
47
|
+
const body = {};
|
|
48
|
+
if (opts.payload !== undefined)
|
|
49
|
+
body.payload = opts.payload;
|
|
50
|
+
if (opts.dedupKey !== undefined)
|
|
51
|
+
body.dedup_key = opts.dedupKey;
|
|
52
|
+
if (opts.delaySeconds !== undefined)
|
|
53
|
+
body.delay_seconds = opts.delaySeconds;
|
|
54
|
+
if (opts.dependsOn !== undefined)
|
|
55
|
+
body.depends_on = opts.dependsOn;
|
|
56
|
+
return (0, client_1.request)('POST', `${queuesBase(orgId)}/${encodeURIComponent(name)}/jobs`, apiKey, body);
|
|
57
|
+
}
|
|
58
|
+
// listJobs lists a queue's jobs, newest first. `limit` is 1-200.
|
|
59
|
+
async function listJobs(apiKey, orgId, name, opts = {}) {
|
|
60
|
+
const q = new URLSearchParams();
|
|
61
|
+
if (opts.status)
|
|
62
|
+
q.set('status', opts.status);
|
|
63
|
+
if (opts.limit !== undefined)
|
|
64
|
+
q.set('limit', String(opts.limit));
|
|
65
|
+
const qs = q.toString();
|
|
66
|
+
const res = await (0, client_1.request)('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}/jobs${qs ? `?${qs}` : ''}`, apiKey);
|
|
67
|
+
return res?.jobs ?? [];
|
|
68
|
+
}
|
|
69
|
+
async function getJob(apiKey, orgId, jobId) {
|
|
70
|
+
return (0, client_1.request)('GET', `${config_1.QUEUE_BASE}/queue/orgs/${encodeURIComponent(orgId)}/jobs/${encodeURIComponent(jobId)}`, apiKey);
|
|
71
|
+
}
|
package/dist/services.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type ServiceStatus = 'ga' | 'preview' | 'planned';
|
|
2
|
-
export type ServiceCategory = 'send' | 'capture' | 'data' | 'store' | 'compute' | 'identity' | 'infra';
|
|
2
|
+
export type ServiceCategory = 'send' | 'capture' | 'data' | 'store' | 'compute' | 'orchestrate' | 'identity' | 'infra';
|
|
3
3
|
export interface ServiceMeta {
|
|
4
4
|
/** SDK namespace (also OpenAPI tag and CLI top-level command). */
|
|
5
5
|
module: string;
|
package/dist/services.js
CHANGED
|
@@ -171,15 +171,6 @@ exports.SERVICES = [
|
|
|
171
171
|
keywords: k('kv-store', 'database', 'state'),
|
|
172
172
|
},
|
|
173
173
|
// ── compute ─────────────────────────────────────────────────────────
|
|
174
|
-
{
|
|
175
|
-
module: 'workflow',
|
|
176
|
-
skill: 'my-workflow-api',
|
|
177
|
-
domain: 'myworkflowapi.com',
|
|
178
|
-
description: 'React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.',
|
|
179
|
-
category: 'compute',
|
|
180
|
-
status: 'ga',
|
|
181
|
-
keywords: k('workflow', 'automation', 'orchestration'),
|
|
182
|
-
},
|
|
183
174
|
{
|
|
184
175
|
module: 'llm',
|
|
185
176
|
skill: 'my-llm-api',
|
|
@@ -248,4 +239,41 @@ exports.SERVICES = [
|
|
|
248
239
|
status: 'preview',
|
|
249
240
|
keywords: k('git', 'repository', 'version-control', 'commit', 'scm'),
|
|
250
241
|
},
|
|
242
|
+
// ── orchestrate ─────────────────────────────────────────────────────
|
|
243
|
+
// Control flow, not actions. workflow = event→action, queue = durable
|
|
244
|
+
// async machine work, task = work that needs an agent/human decision.
|
|
245
|
+
// See docs/orchestration-decision-guide.md.
|
|
246
|
+
{
|
|
247
|
+
module: 'workflow',
|
|
248
|
+
skill: 'my-workflow-api',
|
|
249
|
+
domain: 'myworkflowapi.com',
|
|
250
|
+
description: 'React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.',
|
|
251
|
+
category: 'orchestrate',
|
|
252
|
+
status: 'ga',
|
|
253
|
+
keywords: k('workflow', 'automation', 'orchestration'),
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
// CLI top-level command + SDK namespace are both `queue`; skill
|
|
257
|
+
// directory is `my-queue-api`. A durable HTTP-consumer job queue —
|
|
258
|
+
// retry/backoff, concurrency caps, dependency DAG.
|
|
259
|
+
module: 'queue',
|
|
260
|
+
skill: 'my-queue-api',
|
|
261
|
+
domain: 'myqueueapi.com',
|
|
262
|
+
description: 'Durable job queue — enqueue work and have it retried against your HTTP consumer, with concurrency caps and a dependency DAG.',
|
|
263
|
+
category: 'orchestrate',
|
|
264
|
+
status: 'preview',
|
|
265
|
+
keywords: k('queue', 'job-queue', 'background-jobs', 'retry', 'async'),
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
// CLI top-level command + SDK namespace are both `task`; skill
|
|
269
|
+
// directory is `my-task-api`. The agent-task queue — the agent-loop
|
|
270
|
+
// hot path: file, claim under a lease, resolve.
|
|
271
|
+
module: 'task',
|
|
272
|
+
skill: 'my-task-api',
|
|
273
|
+
domain: 'mytaskapi.com',
|
|
274
|
+
description: 'Agent-task queue — file units of work, rank them, claim under a lease, then resolve, fail, or cancel. The agent-loop hot path.',
|
|
275
|
+
category: 'orchestrate',
|
|
276
|
+
status: 'preview',
|
|
277
|
+
keywords: k('task', 'task-queue', 'agent-loop', 'work-queue', 'lease'),
|
|
278
|
+
},
|
|
251
279
|
];
|
package/dist/task.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Exposes } from './exposes';
|
|
2
|
+
export declare const EXPOSES: Exposes;
|
|
3
|
+
export type TaskStatus = 'open' | 'claimed' | 'blocked' | 'resolved' | 'failed' | 'cancelled';
|
|
4
|
+
export type TaskImportance = 'low' | 'normal' | 'high' | 'critical';
|
|
5
|
+
export interface ResolveOn {
|
|
6
|
+
event_type: string;
|
|
7
|
+
field?: string;
|
|
8
|
+
value?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TaskRef {
|
|
11
|
+
id: string;
|
|
12
|
+
description: string;
|
|
13
|
+
score: number;
|
|
14
|
+
}
|
|
15
|
+
export interface Task {
|
|
16
|
+
id: string;
|
|
17
|
+
org_id: string;
|
|
18
|
+
description: string;
|
|
19
|
+
status: TaskStatus;
|
|
20
|
+
importance: TaskImportance;
|
|
21
|
+
score: number;
|
|
22
|
+
tags: string[];
|
|
23
|
+
source: string;
|
|
24
|
+
depends_on: string[];
|
|
25
|
+
payload_url: string;
|
|
26
|
+
assignee?: string;
|
|
27
|
+
resolve_on?: ResolveOn;
|
|
28
|
+
dedup_key?: string;
|
|
29
|
+
claimed_by?: string;
|
|
30
|
+
lease_expires_at?: string;
|
|
31
|
+
due_at?: string;
|
|
32
|
+
fail_reason?: string;
|
|
33
|
+
resolved_at?: string;
|
|
34
|
+
created_at: string;
|
|
35
|
+
updated_at: string;
|
|
36
|
+
}
|
|
37
|
+
export interface TaskListMeta {
|
|
38
|
+
shown: number;
|
|
39
|
+
total_open: number;
|
|
40
|
+
}
|
|
41
|
+
export interface TaskListResult {
|
|
42
|
+
tasks: TaskRef[];
|
|
43
|
+
meta: TaskListMeta;
|
|
44
|
+
}
|
|
45
|
+
export interface CreateTaskOptions {
|
|
46
|
+
description: string;
|
|
47
|
+
body?: string;
|
|
48
|
+
importance?: TaskImportance;
|
|
49
|
+
dueAt?: string;
|
|
50
|
+
assignee?: string;
|
|
51
|
+
tags?: string[];
|
|
52
|
+
dependsOn?: string[];
|
|
53
|
+
dedupKey?: string;
|
|
54
|
+
resolveOn?: ResolveOn;
|
|
55
|
+
source?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ListTaskOptions {
|
|
58
|
+
status?: string;
|
|
59
|
+
tag?: string;
|
|
60
|
+
importance?: string;
|
|
61
|
+
assignee?: string;
|
|
62
|
+
source?: string;
|
|
63
|
+
limit?: number;
|
|
64
|
+
}
|
|
65
|
+
export declare function createTask(apiKey: string, orgId: string, opts: CreateTaskOptions): Promise<Task>;
|
|
66
|
+
export declare function listTasks(apiKey: string, orgId: string, opts?: ListTaskOptions): Promise<TaskListResult>;
|
|
67
|
+
export declare function getTask(apiKey: string, orgId: string, id: string): Promise<Task>;
|
|
68
|
+
export declare function getTaskBody(apiKey: string, orgId: string, id: string): Promise<string>;
|
|
69
|
+
export declare function claimTask(apiKey: string, orgId: string, id: string, opts?: {
|
|
70
|
+
leaseSeconds?: number;
|
|
71
|
+
worker?: string;
|
|
72
|
+
}): Promise<Task>;
|
|
73
|
+
export declare function extendTask(apiKey: string, orgId: string, id: string, opts?: {
|
|
74
|
+
leaseSeconds?: number;
|
|
75
|
+
}): Promise<Task>;
|
|
76
|
+
export declare function resolveTask(apiKey: string, orgId: string, id: string): Promise<Task>;
|
|
77
|
+
export declare function failTask(apiKey: string, orgId: string, id: string, reason: string): Promise<Task>;
|
|
78
|
+
export declare function cancelTask(apiKey: string, orgId: string, id: string): Promise<void>;
|
package/dist/task.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EXPOSES = void 0;
|
|
4
|
+
exports.createTask = createTask;
|
|
5
|
+
exports.listTasks = listTasks;
|
|
6
|
+
exports.getTask = getTask;
|
|
7
|
+
exports.getTaskBody = getTaskBody;
|
|
8
|
+
exports.claimTask = claimTask;
|
|
9
|
+
exports.extendTask = extendTask;
|
|
10
|
+
exports.resolveTask = resolveTask;
|
|
11
|
+
exports.failTask = failTask;
|
|
12
|
+
exports.cancelTask = cancelTask;
|
|
13
|
+
const client_1 = require("./client");
|
|
14
|
+
const config_1 = require("./config");
|
|
15
|
+
// Backend: my-task-api per myapi-hq/internal/routes/task/. An agent-task
|
|
16
|
+
// queue — the agent-loop hot path. Tasks are created, ranked by score,
|
|
17
|
+
// claimed under a lease, then resolved/failed/cancelled. A task with
|
|
18
|
+
// unresolved depends_on starts blocked; lease expiry auto-reverts a claimed
|
|
19
|
+
// task to open.
|
|
20
|
+
exports.EXPOSES = [
|
|
21
|
+
'POST /task/orgs/{org_id}/tasks',
|
|
22
|
+
'GET /task/orgs/{org_id}/tasks',
|
|
23
|
+
'GET /task/orgs/{org_id}/tasks/{id}',
|
|
24
|
+
'DELETE /task/orgs/{org_id}/tasks/{id}',
|
|
25
|
+
'GET /task/orgs/{org_id}/tasks/{id}/body',
|
|
26
|
+
'POST /task/orgs/{org_id}/tasks/{id}/claim',
|
|
27
|
+
'POST /task/orgs/{org_id}/tasks/{id}/extend',
|
|
28
|
+
'POST /task/orgs/{org_id}/tasks/{id}/fail',
|
|
29
|
+
'POST /task/orgs/{org_id}/tasks/{id}/resolve',
|
|
30
|
+
];
|
|
31
|
+
function tasksBase(orgId) {
|
|
32
|
+
return `${config_1.TASK_BASE}/task/orgs/${encodeURIComponent(orgId)}/tasks`;
|
|
33
|
+
}
|
|
34
|
+
function taskBase(orgId, id) {
|
|
35
|
+
return `${tasksBase(orgId)}/${encodeURIComponent(id)}`;
|
|
36
|
+
}
|
|
37
|
+
// createTask is idempotent on (org_id, dedup_key). A task with unresolved
|
|
38
|
+
// depends_on starts blocked; one with an assignee emails them a magic link.
|
|
39
|
+
async function createTask(apiKey, orgId, opts) {
|
|
40
|
+
const body = { description: opts.description };
|
|
41
|
+
if (opts.body !== undefined)
|
|
42
|
+
body.body = opts.body;
|
|
43
|
+
if (opts.importance !== undefined)
|
|
44
|
+
body.importance = opts.importance;
|
|
45
|
+
if (opts.dueAt !== undefined)
|
|
46
|
+
body.due_at = opts.dueAt;
|
|
47
|
+
if (opts.assignee !== undefined)
|
|
48
|
+
body.assignee = opts.assignee;
|
|
49
|
+
if (opts.tags !== undefined)
|
|
50
|
+
body.tags = opts.tags;
|
|
51
|
+
if (opts.dependsOn !== undefined)
|
|
52
|
+
body.depends_on = opts.dependsOn;
|
|
53
|
+
if (opts.dedupKey !== undefined)
|
|
54
|
+
body.dedup_key = opts.dedupKey;
|
|
55
|
+
if (opts.resolveOn !== undefined)
|
|
56
|
+
body.resolve_on = opts.resolveOn;
|
|
57
|
+
if (opts.source !== undefined)
|
|
58
|
+
body.source = opts.source;
|
|
59
|
+
return (0, client_1.request)('POST', tasksBase(orgId), apiKey, body);
|
|
60
|
+
}
|
|
61
|
+
// listTasks returns the ranked open queue (projected to TaskRef) plus the
|
|
62
|
+
// backlog counts. Default: top 20 open tasks by score. The backend nests
|
|
63
|
+
// {shown, total_open} inside the response `data.meta` (not the envelope meta).
|
|
64
|
+
async function listTasks(apiKey, orgId, opts = {}) {
|
|
65
|
+
const q = new URLSearchParams();
|
|
66
|
+
if (opts.status)
|
|
67
|
+
q.set('status', opts.status);
|
|
68
|
+
if (opts.tag)
|
|
69
|
+
q.set('tag', opts.tag);
|
|
70
|
+
if (opts.importance)
|
|
71
|
+
q.set('importance', opts.importance);
|
|
72
|
+
if (opts.assignee)
|
|
73
|
+
q.set('assignee', opts.assignee);
|
|
74
|
+
if (opts.source)
|
|
75
|
+
q.set('source', opts.source);
|
|
76
|
+
if (opts.limit !== undefined)
|
|
77
|
+
q.set('limit', String(opts.limit));
|
|
78
|
+
const qs = q.toString();
|
|
79
|
+
const res = await (0, client_1.request)('GET', `${tasksBase(orgId)}${qs ? `?${qs}` : ''}`, apiKey);
|
|
80
|
+
const tasks = res?.tasks ?? [];
|
|
81
|
+
const m = res?.meta ?? {};
|
|
82
|
+
return {
|
|
83
|
+
tasks,
|
|
84
|
+
meta: {
|
|
85
|
+
shown: typeof m.shown === 'number' ? m.shown : tasks.length,
|
|
86
|
+
total_open: typeof m.total_open === 'number' ? m.total_open : tasks.length,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// getTask returns the full task object. It deliberately does NOT fetch the
|
|
91
|
+
// Markdown body tier — call getTaskBody for that, once, on commit.
|
|
92
|
+
async function getTask(apiKey, orgId, id) {
|
|
93
|
+
return (0, client_1.request)('GET', taskBase(orgId, id), apiKey);
|
|
94
|
+
}
|
|
95
|
+
// getTaskBody fetches the body tier — the full Markdown context. Separate
|
|
96
|
+
// call by design: it keeps the list/get path token-cheap. The endpoint
|
|
97
|
+
// returns { task_id, body }.
|
|
98
|
+
async function getTaskBody(apiKey, orgId, id) {
|
|
99
|
+
const res = await (0, client_1.request)('GET', `${taskBase(orgId, id)}/body`, apiKey);
|
|
100
|
+
return res?.body ?? '';
|
|
101
|
+
}
|
|
102
|
+
// claimTask takes an atomic lease (default 10 min) and returns the full
|
|
103
|
+
// updated task — lease state is on `lease_expires_at` / `claimed_by`.
|
|
104
|
+
async function claimTask(apiKey, orgId, id, opts = {}) {
|
|
105
|
+
const body = {};
|
|
106
|
+
if (opts.leaseSeconds !== undefined)
|
|
107
|
+
body.lease_seconds = opts.leaseSeconds;
|
|
108
|
+
if (opts.worker !== undefined)
|
|
109
|
+
body.worker = opts.worker;
|
|
110
|
+
return (0, client_1.request)('POST', `${taskBase(orgId, id)}/claim`, apiKey, body);
|
|
111
|
+
}
|
|
112
|
+
// extendTask is a heartbeat for long work — extends a live claim.
|
|
113
|
+
async function extendTask(apiKey, orgId, id, opts = {}) {
|
|
114
|
+
const body = {};
|
|
115
|
+
if (opts.leaseSeconds !== undefined)
|
|
116
|
+
body.lease_seconds = opts.leaseSeconds;
|
|
117
|
+
return (0, client_1.request)('POST', `${taskBase(orgId, id)}/extend`, apiKey, body);
|
|
118
|
+
}
|
|
119
|
+
// resolveTask resolves a task (terminal) — unblocks any dependents.
|
|
120
|
+
async function resolveTask(apiKey, orgId, id) {
|
|
121
|
+
return (0, client_1.request)('POST', `${taskBase(orgId, id)}/resolve`, apiKey, {});
|
|
122
|
+
}
|
|
123
|
+
// failTask fails a task (terminal, with a reason). Does not auto-retry;
|
|
124
|
+
// dependents that can never proceed are auto-failed.
|
|
125
|
+
async function failTask(apiKey, orgId, id, reason) {
|
|
126
|
+
return (0, client_1.request)('POST', `${taskBase(orgId, id)}/fail`, apiKey, { reason });
|
|
127
|
+
}
|
|
128
|
+
// cancelTask cancels a task (terminal, distinct from fail).
|
|
129
|
+
async function cancelTask(apiKey, orgId, id) {
|
|
130
|
+
return (0, client_1.request)('DELETE', taskBase(orgId, id), apiKey);
|
|
131
|
+
}
|
package/dist/workflow.d.ts
CHANGED
|
@@ -28,6 +28,12 @@ export type WorkflowStep = {
|
|
|
28
28
|
method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
29
29
|
body?: string;
|
|
30
30
|
headers?: Record<string, string>;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'enqueue_job' | 'enqueue';
|
|
33
|
+
queue: string;
|
|
34
|
+
payload?: string;
|
|
35
|
+
dedup_key?: string;
|
|
36
|
+
delay_seconds?: number;
|
|
31
37
|
};
|
|
32
38
|
export interface Workflow {
|
|
33
39
|
id: string;
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -72,8 +72,8 @@ export async function request<T>(
|
|
|
72
72
|
const err = result?.error;
|
|
73
73
|
const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
|
|
74
74
|
const detail = typeof err === 'object' ? (err?.message || undefined) : undefined;
|
|
75
|
-
const
|
|
76
|
-
throw new MyApiError(code, response.status, detail,
|
|
75
|
+
const errBody = typeof err === 'object' ? err : undefined;
|
|
76
|
+
throw new MyApiError(code, response.status, detail, errBody);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const apiResponse = result as ApiResponse<T>;
|
|
@@ -81,8 +81,8 @@ export async function request<T>(
|
|
|
81
81
|
const err = apiResponse.error as any;
|
|
82
82
|
const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
|
|
83
83
|
const detail = typeof err === 'object' ? (err?.message || undefined) : undefined;
|
|
84
|
-
const
|
|
85
|
-
throw new MyApiError(code, response.status, detail,
|
|
84
|
+
const errBody = typeof err === 'object' ? err : undefined;
|
|
85
|
+
throw new MyApiError(code, response.status, detail, errBody);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
return apiResponse.data as T;
|
package/src/config.ts
CHANGED
|
@@ -18,6 +18,8 @@ export const FUNCTION_BASE= process.env.MYAPI_FUNCTION_URL?? GATEWAY;
|
|
|
18
18
|
export const PAYMENTS_BASE= process.env.MYAPI_PAYMENTS_URL?? GATEWAY;
|
|
19
19
|
export const CONTAINER_BASE=process.env.MYAPI_CONTAINER_URL?? GATEWAY;
|
|
20
20
|
export const GIT_BASE = process.env.MYAPI_GIT_URL ?? GATEWAY;
|
|
21
|
+
export const QUEUE_BASE = process.env.MYAPI_QUEUE_URL ?? GATEWAY;
|
|
22
|
+
export const TASK_BASE = process.env.MYAPI_TASK_URL ?? GATEWAY;
|
|
21
23
|
export const IMAGE_BASE = process.env.MYAPI_IMAGE_URL ?? 'https://api.myimageapi.com';
|
|
22
24
|
export const WEBHOOK_BASE = process.env.MYAPI_WEBHOOK_URL ?? 'https://api.mywebhookapi.com';
|
|
23
25
|
export const WORKFLOW_BASE= process.env.MYAPI_WORKFLOW_URL?? 'https://api.myworkflowapi.com';
|
package/src/email.ts
CHANGED
|
@@ -6,6 +6,8 @@ export const EXPOSES: Exposes = [
|
|
|
6
6
|
// Mailbox / sending activation
|
|
7
7
|
'POST /email/mailboxes/create',
|
|
8
8
|
'GET /email/mailboxes',
|
|
9
|
+
'PUT /email/mailboxes/{address}/forwarding',
|
|
10
|
+
'DELETE /email/mailboxes/{address}/forwarding',
|
|
9
11
|
'POST /email/sending/activate',
|
|
10
12
|
// Send + read
|
|
11
13
|
'POST /email/send',
|
|
@@ -151,6 +153,18 @@ export async function activateSending(apiKey: string, address: string): Promise<
|
|
|
151
153
|
return request('POST', `${BASE_URL}/email/sending/activate`, apiKey, { address });
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
// setForwarding redirects a copy of every incoming message to an external
|
|
157
|
+
// address (server-side; the original is kept in the mailbox). `forwardTo`
|
|
158
|
+
// must be a valid address and cannot equal the mailbox itself.
|
|
159
|
+
export async function setForwarding(apiKey: string, address: string, forwardTo: string): Promise<{ address: string; forward_to: string }> {
|
|
160
|
+
return request('PUT', `${BASE_URL}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey, { forward_to: forwardTo });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// deleteForwarding stops forwarding for a mailbox.
|
|
164
|
+
export async function deleteForwarding(apiKey: string, address: string): Promise<void> {
|
|
165
|
+
return request('DELETE', `${BASE_URL}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey);
|
|
166
|
+
}
|
|
167
|
+
|
|
154
168
|
// ── Sending and reading (account-scoped) ─────────────────────────────────────
|
|
155
169
|
|
|
156
170
|
export async function sendEmail(apiKey: string, payload: { from: string; to: string[]; subject: string; html?: string; text?: string; template_id?: string; template_vars?: Record<string, string> }): Promise<{ message_id: string }> {
|
package/src/index.ts
CHANGED
package/src/queue.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { request } from './client';
|
|
2
|
+
import { QUEUE_BASE as BASE_URL } from './config';
|
|
3
|
+
import type { Exposes } from './exposes';
|
|
4
|
+
|
|
5
|
+
// Backend: my-queue-api per myapi-hq/internal/routes/queue/. A durable
|
|
6
|
+
// HTTP-consumer job queue — each named queue POSTs its jobs to a configured
|
|
7
|
+
// consumer_url, with retry/backoff to max_attempts, a concurrency cap, and a
|
|
8
|
+
// dependency DAG (a job with unsucceeded depends_on starts blocked).
|
|
9
|
+
export const EXPOSES: Exposes = [
|
|
10
|
+
'POST /queue/orgs/{org_id}/queues',
|
|
11
|
+
'GET /queue/orgs/{org_id}/queues',
|
|
12
|
+
'GET /queue/orgs/{org_id}/queues/{name}',
|
|
13
|
+
'POST /queue/orgs/{org_id}/queues/{name}/jobs',
|
|
14
|
+
'GET /queue/orgs/{org_id}/queues/{name}/jobs',
|
|
15
|
+
'GET /queue/orgs/{org_id}/jobs/{id}',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export interface Queue {
|
|
19
|
+
id: string;
|
|
20
|
+
org_id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
consumer_url: string;
|
|
23
|
+
max_attempts: number;
|
|
24
|
+
max_concurrency: number;
|
|
25
|
+
created_at: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// A job that exhausts max_attempts goes straight to `dead` — there is no
|
|
29
|
+
// separate `failed` state.
|
|
30
|
+
export type JobStatus = 'pending' | 'blocked' | 'running' | 'succeeded' | 'dead';
|
|
31
|
+
|
|
32
|
+
export interface Job {
|
|
33
|
+
id: string;
|
|
34
|
+
queue_id: string;
|
|
35
|
+
org_id: string;
|
|
36
|
+
payload: unknown;
|
|
37
|
+
status: JobStatus;
|
|
38
|
+
attempt: number;
|
|
39
|
+
max_attempts: number;
|
|
40
|
+
not_before: string;
|
|
41
|
+
depends_on: string[];
|
|
42
|
+
dedup_key?: string;
|
|
43
|
+
last_error?: string;
|
|
44
|
+
started_at?: string;
|
|
45
|
+
created_at: string;
|
|
46
|
+
updated_at: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CreateQueueOptions {
|
|
50
|
+
name: string;
|
|
51
|
+
consumerUrl: string;
|
|
52
|
+
maxAttempts?: number;
|
|
53
|
+
maxConcurrency?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface EnqueueOptions {
|
|
57
|
+
payload?: unknown;
|
|
58
|
+
dedupKey?: string;
|
|
59
|
+
delaySeconds?: number;
|
|
60
|
+
// Job ids this job waits on. Immutable — the DAG is declared at enqueue.
|
|
61
|
+
// Same shape as task.create's depends_on.
|
|
62
|
+
dependsOn?: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function queuesBase(orgId: string): string {
|
|
66
|
+
return `${BASE_URL}/queue/orgs/${encodeURIComponent(orgId)}/queues`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// createQueue registers a named queue with its retry + concurrency policy
|
|
70
|
+
// and the HTTP consumer that runs its jobs.
|
|
71
|
+
export async function createQueue(apiKey: string, orgId: string, opts: CreateQueueOptions): Promise<Queue> {
|
|
72
|
+
const body: Record<string, unknown> = { name: opts.name, consumer_url: opts.consumerUrl };
|
|
73
|
+
if (opts.maxAttempts !== undefined) body.max_attempts = opts.maxAttempts;
|
|
74
|
+
if (opts.maxConcurrency !== undefined) body.max_concurrency = opts.maxConcurrency;
|
|
75
|
+
return request('POST', queuesBase(orgId), apiKey, body);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function listQueues(apiKey: string, orgId: string): Promise<Queue[]> {
|
|
79
|
+
const res = await request<{ queues?: Queue[] }>('GET', queuesBase(orgId), apiKey);
|
|
80
|
+
return res?.queues ?? [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function getQueue(apiKey: string, orgId: string, name: string): Promise<Queue> {
|
|
84
|
+
return request('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}`, apiKey);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// enqueueJob is idempotent on (queue, dedup_key). A job with unsucceeded
|
|
88
|
+
// depends_on starts blocked.
|
|
89
|
+
export async function enqueueJob(apiKey: string, orgId: string, name: string, opts: EnqueueOptions = {}): Promise<Job> {
|
|
90
|
+
const body: Record<string, unknown> = {};
|
|
91
|
+
if (opts.payload !== undefined) body.payload = opts.payload;
|
|
92
|
+
if (opts.dedupKey !== undefined) body.dedup_key = opts.dedupKey;
|
|
93
|
+
if (opts.delaySeconds !== undefined) body.delay_seconds = opts.delaySeconds;
|
|
94
|
+
if (opts.dependsOn !== undefined) body.depends_on = opts.dependsOn;
|
|
95
|
+
return request('POST', `${queuesBase(orgId)}/${encodeURIComponent(name)}/jobs`, apiKey, body);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// listJobs lists a queue's jobs, newest first. `limit` is 1-200.
|
|
99
|
+
export async function listJobs(apiKey: string, orgId: string, name: string, opts: { status?: string; limit?: number } = {}): Promise<Job[]> {
|
|
100
|
+
const q = new URLSearchParams();
|
|
101
|
+
if (opts.status) q.set('status', opts.status);
|
|
102
|
+
if (opts.limit !== undefined) q.set('limit', String(opts.limit));
|
|
103
|
+
const qs = q.toString();
|
|
104
|
+
const res = await request<{ jobs?: Job[] }>('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}/jobs${qs ? `?${qs}` : ''}`, apiKey);
|
|
105
|
+
return res?.jobs ?? [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function getJob(apiKey: string, orgId: string, jobId: string): Promise<Job> {
|
|
109
|
+
return request('GET', `${BASE_URL}/queue/orgs/${encodeURIComponent(orgId)}/jobs/${encodeURIComponent(jobId)}`, apiKey);
|
|
110
|
+
}
|
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,
|
|
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
|
|
|
@@ -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',
|
|
@@ -278,4 +270,42 @@ export const SERVICES: readonly ServiceMeta[] = [
|
|
|
278
270
|
status: 'preview',
|
|
279
271
|
keywords: k('git', 'repository', 'version-control', 'commit', 'scm'),
|
|
280
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
|
+
},
|
|
281
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;
|