@hirey/hi-agent-delivery 0.1.3

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/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # hi-agent-delivery
2
+
3
+ `hi-agent-delivery` 承载 Hi external agent control plane 的持久化层。
4
+
5
+ ## 职责
6
+
7
+ - `agent_event_outbox`
8
+ - 事务、查询、事件 envelope 映射
9
+ - outbox claim / ack / replay / idempotency
10
+ - 供 `hi-platform` / `hi-agent-gateway` 复用的最薄数据库原语
11
+
12
+ ## 非职责
13
+
14
+ - 不再承载旧的 `registered_agents` / `agent_delivery_endpoints` 兼容服务
15
+ - 不暴露公网 HTTP / registry / delivery profile 编排逻辑
16
+
17
+ 这个仓不暴露公网 HTTP;它是 `hi-agent-gateway` 与 `hi-platform` 共同依赖的持久化实现层。
@@ -0,0 +1,6 @@
1
+ export declare const CONFIG: {
2
+ DATABASE_URL: string;
3
+ DB_POOL_MAX: number;
4
+ };
5
+ export declare function validateRuntimeConfig(): void;
6
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF,wBAAgB,qBAAqB,SAIpC"}
package/dist/config.js ADDED
@@ -0,0 +1,14 @@
1
+ // hi-agent-delivery 只关心持久化层需要的数据库参数。
2
+ function readIntEnv(name, fallback) {
3
+ const value = Number(process.env[name] || fallback);
4
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
5
+ }
6
+ export const CONFIG = {
7
+ DATABASE_URL: String(process.env.DATABASE_URL || '').trim(),
8
+ DB_POOL_MAX: readIntEnv('DB_POOL_MAX', 10),
9
+ };
10
+ export function validateRuntimeConfig() {
11
+ if (!CONFIG.DATABASE_URL) {
12
+ throw new Error('[hi-agent-delivery] DATABASE_URL is required');
13
+ }
14
+ }
@@ -0,0 +1,5 @@
1
+ import { Pool, type PoolClient, type QueryResult, type QueryResultRow } from 'pg';
2
+ export declare const pgPool: Pool;
3
+ export declare function withTransaction<T>(fn: (client: PoolClient) => Promise<T>): Promise<T>;
4
+ export declare function query<T extends QueryResultRow = QueryResultRow>(text: string, params?: unknown[], client?: PoolClient): Promise<QueryResult<T>>;
5
+ //# sourceMappingURL=pg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../src/db/pg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,IAAI,CAAC;AAWlF,eAAO,MAAM,MAAM,MAKjB,CAAC;AAGH,wBAAsB,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAe3F;AAED,wBAAsB,KAAK,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EACnE,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,EAClB,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAGzB"}
package/dist/db/pg.js ADDED
@@ -0,0 +1,37 @@
1
+ import { Pool } from 'pg';
2
+ import { CONFIG, validateRuntimeConfig } from '../config.js';
3
+ validateRuntimeConfig();
4
+ const sslMode = String(process.env.DB_SSL_MODE || '').toLowerCase();
5
+ const sslEnabled = /^(1|true|yes|on|require|verify-ca)$/i.test(sslMode);
6
+ const rejectUnauthorized = !/^(0|false|no|off|insecure)$/i.test(String(process.env.DB_SSL_REJECT_UNAUTHORIZED || ''));
7
+ const ca = String(process.env.DB_SSL_CA || '').trim();
8
+ export const pgPool = new Pool({
9
+ connectionString: CONFIG.DATABASE_URL,
10
+ max: CONFIG.DB_POOL_MAX,
11
+ idleTimeoutMillis: 30_000,
12
+ ssl: sslEnabled ? { rejectUnauthorized, ...(ca ? { ca } : {}) } : false,
13
+ });
14
+ // 统一事务入口,避免 outbox/ack/registry 逻辑各自拼 BEGIN/ROLLBACK。
15
+ export async function withTransaction(fn) {
16
+ const client = await pgPool.connect();
17
+ try {
18
+ await client.query('BEGIN');
19
+ const result = await fn(client);
20
+ await client.query('COMMIT');
21
+ return result;
22
+ }
23
+ catch (error) {
24
+ try {
25
+ await client.query('ROLLBACK');
26
+ }
27
+ catch { }
28
+ throw error;
29
+ }
30
+ finally {
31
+ client.release();
32
+ }
33
+ }
34
+ export async function query(text, params, client) {
35
+ const runner = client || pgPool;
36
+ return runner.query(text, params);
37
+ }
@@ -0,0 +1,4 @@
1
+ export * from './services/agentGatewayEvents.js';
2
+ export * from './db/pg.js';
3
+ export * from './payloads.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './services/agentGatewayEvents.js';
2
+ export * from './db/pg.js';
3
+ export * from './payloads.js';
@@ -0,0 +1,44 @@
1
+ import type { OpenClawHooksAgentEndpointConfig, OpenResponsesEndpointConfig } from '@hirey/hi-agent-contracts';
2
+ export type SharedAgentDeliveryEventEnvelope = {
3
+ event_id: string;
4
+ target_agent_id: string;
5
+ topic: string;
6
+ preview?: Record<string, unknown> | null;
7
+ payload?: Record<string, unknown> | null;
8
+ resource_ref?: Record<string, unknown> | null;
9
+ };
10
+ export declare function buildGenericEventWebhookPayload(event: SharedAgentDeliveryEventEnvelope): {
11
+ profile: string;
12
+ event: SharedAgentDeliveryEventEnvelope;
13
+ delivery: {
14
+ mode: string;
15
+ delivered_at: string;
16
+ };
17
+ };
18
+ export declare function buildOpenClawHookPayload(args: {
19
+ event: SharedAgentDeliveryEventEnvelope;
20
+ config?: OpenClawHooksAgentEndpointConfig | Record<string, unknown> | null;
21
+ }): {
22
+ message: string;
23
+ name: string;
24
+ agentId: string | undefined;
25
+ sessionKey: string;
26
+ wakeMode: string;
27
+ deliver: boolean;
28
+ channel: string | undefined;
29
+ to: string | undefined;
30
+ model: string | undefined;
31
+ thinking: string | undefined;
32
+ timeoutSeconds: number | undefined;
33
+ };
34
+ export declare function buildOpenResponsesPayload(args: {
35
+ event: SharedAgentDeliveryEventEnvelope;
36
+ config: OpenResponsesEndpointConfig | Record<string, unknown>;
37
+ }): {
38
+ model: string;
39
+ input: string;
40
+ instructions: string | undefined;
41
+ user: string;
42
+ stream: boolean;
43
+ };
44
+ //# sourceMappingURL=payloads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payloads.d.ts","sourceRoot":"","sources":["../src/payloads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gCAAgC,EAChC,2BAA2B,EAC5B,MAAM,2BAA2B,CAAC;AAGnC,MAAM,MAAM,gCAAgC,GAAG;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC/C,CAAC;AAWF,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,gCAAgC;;;;;;;EAUtF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,KAAK,EAAE,gCAAgC,CAAC;IACxC,MAAM,CAAC,EAAE,gCAAgC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5E;;;;;;;;;;;;EAmBA;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,KAAK,EAAE,gCAAgC,CAAC;IACxC,MAAM,EAAE,2BAA2B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/D;;;;;;EAiBA"}
@@ -0,0 +1,60 @@
1
+ import { normalizeText } from '@hirey/hi-agent-contracts';
2
+ function stringifyEventSummary(event) {
3
+ if (event.preview?.text)
4
+ return String(event.preview.text);
5
+ return JSON.stringify({
6
+ topic: event.topic,
7
+ payload: event.payload || {},
8
+ resource_ref: event.resource_ref || {},
9
+ });
10
+ }
11
+ export function buildGenericEventWebhookPayload(event) {
12
+ // generic webhook 始终直接转发 canonical event envelope,不额外塞宿主私货。
13
+ return {
14
+ profile: 'generic.event-webhook.v1',
15
+ event,
16
+ delivery: {
17
+ mode: 'accepted_is_terminal',
18
+ delivered_at: new Date().toISOString(),
19
+ },
20
+ };
21
+ }
22
+ export function buildOpenClawHookPayload(args) {
23
+ const config = (args.config || {});
24
+ const sessionKeyPrefix = normalizeText(config.session_key_prefix) || 'hi';
25
+ const messagePrefix = normalizeText(config.message_prefix);
26
+ const eventSummary = stringifyEventSummary(args.event);
27
+ const message = [messagePrefix, eventSummary].filter(Boolean).join('\n\n') || eventSummary;
28
+ return {
29
+ message,
30
+ name: normalizeText(config.name) || 'Hi',
31
+ agentId: normalizeText(config.agent_id) || undefined,
32
+ sessionKey: `${sessionKeyPrefix}:${args.event.event_id}`,
33
+ wakeMode: normalizeText(config.wake_mode) || 'now',
34
+ deliver: config.deliver !== false,
35
+ channel: normalizeText(config.channel) || undefined,
36
+ to: normalizeText(config.to) || undefined,
37
+ model: normalizeText(config.model) || undefined,
38
+ thinking: normalizeText(config.thinking) || undefined,
39
+ timeoutSeconds: Number(config.timeout_seconds || 0) || undefined,
40
+ };
41
+ }
42
+ export function buildOpenResponsesPayload(args) {
43
+ const config = args.config;
44
+ const model = normalizeText(config.model);
45
+ if (!model)
46
+ throw new Error('missing_openresponses_model');
47
+ return {
48
+ model,
49
+ input: JSON.stringify({
50
+ topic: args.event.topic,
51
+ event_id: args.event.event_id,
52
+ payload: args.event.payload || null,
53
+ resource_ref: args.event.resource_ref || null,
54
+ preview: args.event.preview || null,
55
+ }),
56
+ instructions: normalizeText(config.instructions) || undefined,
57
+ user: normalizeText(config.user) || `hi:${args.event.target_agent_id}`,
58
+ stream: false,
59
+ };
60
+ }
@@ -0,0 +1,105 @@
1
+ import type { PoolClient } from 'pg';
2
+ import { type AgentGatewayEventPreview, type AgentGatewayResourceRef, type AgentGatewayTopic } from '@hirey/hi-agent-contracts';
3
+ export type AgentGatewayEventEnvelope = {
4
+ event_id: string;
5
+ target_agent_id: string;
6
+ stream_seq: number;
7
+ topic: AgentGatewayTopic;
8
+ event_type: AgentGatewayTopic;
9
+ occurred_at: string | null;
10
+ resource_ref: AgentGatewayResourceRef;
11
+ preview: AgentGatewayEventPreview | null;
12
+ fetch_uri: string;
13
+ idempotency_key: string | null;
14
+ };
15
+ export type AgentGatewayEventSnapshot = AgentGatewayEventEnvelope & {
16
+ status: 'queued' | 'processing' | 'consumed' | 'failed';
17
+ attempts: number;
18
+ available_at: string | null;
19
+ processing_started_at: string | null;
20
+ claimed_by_agent_id: string | null;
21
+ claimed_by_lease_id: string | null;
22
+ claimed_at: string | null;
23
+ consumed_at: string | null;
24
+ last_error: string | null;
25
+ result: Record<string, unknown> | null;
26
+ payload: Record<string, unknown> | null;
27
+ created_at: string | null;
28
+ updated_at: string | null;
29
+ };
30
+ export type CreateAgentGatewayEventInput = {
31
+ target_agent_id: string;
32
+ topic?: AgentGatewayTopic | string;
33
+ event_type?: AgentGatewayTopic | string;
34
+ occurred_at?: string | null;
35
+ resource_ref: AgentGatewayResourceRef | Record<string, unknown>;
36
+ preview?: AgentGatewayEventPreview | Record<string, unknown> | null;
37
+ payload: Record<string, unknown>;
38
+ idempotency_key?: string | null;
39
+ status?: string | null;
40
+ client?: PoolClient;
41
+ };
42
+ export declare function createAgentGatewayEvent(input: CreateAgentGatewayEventInput): Promise<{
43
+ ok: true;
44
+ duplicate: boolean;
45
+ event: AgentGatewayEventSnapshot;
46
+ }>;
47
+ export declare function listAgentGatewayEventEnvelopes(input: {
48
+ agent_id: string;
49
+ after_seq?: number | string | null;
50
+ limit?: number | string | null;
51
+ client?: PoolClient;
52
+ }): Promise<{
53
+ ok: true;
54
+ items: {
55
+ event_id: string;
56
+ target_agent_id: string;
57
+ stream_seq: number;
58
+ topic: AgentGatewayTopic;
59
+ event_type: AgentGatewayTopic;
60
+ occurred_at: string | null;
61
+ resource_ref: AgentGatewayResourceRef;
62
+ preview: AgentGatewayEventPreview | null;
63
+ fetch_uri: string;
64
+ idempotency_key: string | null;
65
+ }[];
66
+ }>;
67
+ export declare function getAgentGatewayEventById(input: {
68
+ agent_id: string;
69
+ event_id: string;
70
+ client?: PoolClient;
71
+ }): Promise<{
72
+ ok: false;
73
+ error: string;
74
+ event?: undefined;
75
+ } | {
76
+ ok: true;
77
+ event: AgentGatewayEventSnapshot;
78
+ error?: undefined;
79
+ }>;
80
+ export declare function claimAgentGatewayEvents(input: {
81
+ agent_id: string;
82
+ after_seq?: number | string | null;
83
+ limit?: number | string | null;
84
+ claim_lease_id?: string | null;
85
+ client?: PoolClient;
86
+ }): Promise<{
87
+ ok: true;
88
+ claim_lease_id: string;
89
+ items: AgentGatewayEventSnapshot[];
90
+ }>;
91
+ export declare function ackAgentGatewayEvents(input: {
92
+ agent_id: string;
93
+ acks: Array<{
94
+ event_id: string;
95
+ status: string;
96
+ last_error?: string | null;
97
+ result?: Record<string, unknown> | null;
98
+ retry_after_ms?: number | string | null;
99
+ }>;
100
+ client?: PoolClient;
101
+ }): Promise<{
102
+ ok: true;
103
+ items: AgentGatewayEventSnapshot[];
104
+ }>;
105
+ //# sourceMappingURL=agentGatewayEvents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentGatewayEvents.d.ts","sourceRoot":"","sources":["../../src/services/agentGatewayEvents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,258 @@
1
+ import { nanoid } from 'nanoid';
2
+ import { buildAgentEventFetchUri, normalizeAgentEventAckStatus, normalizeAgentEventStatus, normalizeAgentGatewayPreview, normalizeAgentGatewayResourceRef, normalizeAgentGatewayTopic, normalizeText, parseAgentEventAfterSeq, parseAgentEventBatchLimit, parseAgentEventRetryAfterMs, } from '@hirey/hi-agent-contracts';
3
+ import { query, withTransaction } from '../db/pg.js';
4
+ import { nowISO } from '../utils/time.js';
5
+ const AGENT_EVENT_STALE_PROCESSING_MS = 10 * 60_000;
6
+ const MAX_ERROR_LENGTH = 1000;
7
+ function parseJsonObject(input) {
8
+ if (!input)
9
+ return null;
10
+ if (typeof input === 'object' && !Array.isArray(input))
11
+ return input;
12
+ if (typeof input !== 'string')
13
+ return null;
14
+ try {
15
+ const parsed = JSON.parse(String(input));
16
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
17
+ ? parsed
18
+ : null;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function mapAgentGatewayEventRow(row) {
25
+ const topic = normalizeAgentGatewayTopic(row?.topic || row?.event_type);
26
+ return {
27
+ event_id: normalizeText(row?.event_id),
28
+ target_agent_id: normalizeText(row?.target_agent_id),
29
+ stream_seq: Number(row?.stream_seq || 0),
30
+ topic,
31
+ event_type: topic,
32
+ occurred_at: row?.occurred_at ? String(row.occurred_at) : null,
33
+ resource_ref: normalizeAgentGatewayResourceRef(parseJsonObject(row?.resource_ref_json)),
34
+ preview: normalizeAgentGatewayPreview(parseJsonObject(row?.preview_json)),
35
+ fetch_uri: buildAgentEventFetchUri(normalizeText(row?.event_id)),
36
+ idempotency_key: normalizeText(row?.idempotency_key) || null,
37
+ status: normalizeAgentEventStatus(row?.status),
38
+ attempts: Math.max(0, Number(row?.attempts || 0)),
39
+ available_at: row?.available_at ? String(row.available_at) : null,
40
+ processing_started_at: row?.processing_started_at ? String(row.processing_started_at) : null,
41
+ claimed_by_agent_id: normalizeText(row?.claimed_by_agent_id) || null,
42
+ claimed_by_lease_id: normalizeText(row?.claimed_by_lease_id) || null,
43
+ claimed_at: row?.claimed_at ? String(row.claimed_at) : null,
44
+ consumed_at: row?.consumed_at ? String(row.consumed_at) : null,
45
+ last_error: row?.last_error ? String(row.last_error) : null,
46
+ result: parseJsonObject(row?.result_json),
47
+ payload: parseJsonObject(row?.payload_json),
48
+ created_at: row?.created_at ? String(row.created_at) : null,
49
+ updated_at: row?.updated_at ? String(row.updated_at) : null,
50
+ };
51
+ }
52
+ async function withOptionalClient(client, fn) {
53
+ if (client)
54
+ return await fn(client);
55
+ return await withTransaction(fn);
56
+ }
57
+ export async function createAgentGatewayEvent(input) {
58
+ const targetAgentId = normalizeText(input.target_agent_id);
59
+ if (!targetAgentId)
60
+ throw new Error('missing_target_agent_id');
61
+ const topic = normalizeAgentGatewayTopic(input.topic || input.event_type);
62
+ const payload = input.payload && typeof input.payload === 'object' && !Array.isArray(input.payload)
63
+ ? input.payload
64
+ : null;
65
+ if (!payload)
66
+ throw new Error('invalid_agent_gateway_payload');
67
+ const resourceRef = normalizeAgentGatewayResourceRef(input.resource_ref);
68
+ const preview = normalizeAgentGatewayPreview(input.preview);
69
+ const idempotencyKey = normalizeText(input.idempotency_key) || null;
70
+ const eventStatus = normalizeAgentEventStatus(input.status);
71
+ return await withOptionalClient(input.client, async (client) => {
72
+ const eventId = `aevt_${nanoid(12)}`;
73
+ const now = nowISO();
74
+ const occurredAt = normalizeText(input.occurred_at) || now;
75
+ const insertResult = (await query(`INSERT INTO agent_event_outbox (
76
+ event_id, target_agent_id, topic, event_type, occurred_at,
77
+ resource_ref_json, preview_json, payload_json, idempotency_key,
78
+ status, attempts, available_at, processing_started_at,
79
+ claimed_by_agent_id, claimed_by_lease_id, claimed_at, consumed_at,
80
+ last_error, result_json, created_at, updated_at
81
+ ) VALUES (
82
+ $1,$2,$3,$3,$4,
83
+ $5,$6,$7,$8,
84
+ $9,0,$10,NULL,
85
+ NULL,NULL,NULL,NULL,
86
+ NULL,NULL,$10,$10
87
+ )
88
+ ON CONFLICT (target_agent_id, idempotency_key) DO NOTHING
89
+ RETURNING *`, [
90
+ eventId,
91
+ targetAgentId,
92
+ topic,
93
+ occurredAt,
94
+ JSON.stringify(resourceRef),
95
+ preview ? JSON.stringify(preview) : null,
96
+ JSON.stringify(payload),
97
+ idempotencyKey,
98
+ eventStatus,
99
+ now,
100
+ ], client)).rows[0];
101
+ if (insertResult?.event_id) {
102
+ return {
103
+ ok: true,
104
+ duplicate: false,
105
+ event: mapAgentGatewayEventRow(insertResult),
106
+ };
107
+ }
108
+ if (!idempotencyKey) {
109
+ throw new Error('agent_gateway_event_insert_failed');
110
+ }
111
+ const existing = (await query(`SELECT *
112
+ FROM agent_event_outbox
113
+ WHERE target_agent_id=$1
114
+ AND idempotency_key=$2
115
+ LIMIT 1`, [targetAgentId, idempotencyKey], client)).rows[0];
116
+ if (!existing?.event_id)
117
+ throw new Error('agent_gateway_event_insert_failed');
118
+ return {
119
+ ok: true,
120
+ duplicate: true,
121
+ event: mapAgentGatewayEventRow(existing),
122
+ };
123
+ });
124
+ }
125
+ export async function listAgentGatewayEventEnvelopes(input) {
126
+ const agentId = normalizeText(input.agent_id);
127
+ if (!agentId)
128
+ throw new Error('missing_agent_id');
129
+ const afterSeq = parseAgentEventAfterSeq(input.after_seq);
130
+ const limit = parseAgentEventBatchLimit(input.limit, 100, 200);
131
+ const rows = (await query(`SELECT *
132
+ FROM agent_event_outbox
133
+ WHERE target_agent_id=$1
134
+ AND stream_seq > $2
135
+ ORDER BY stream_seq ASC
136
+ LIMIT $3`, [agentId, afterSeq, limit], input.client)).rows;
137
+ return {
138
+ ok: true,
139
+ items: rows.map((row) => {
140
+ const snapshot = mapAgentGatewayEventRow(row);
141
+ const { status: _status, attempts: _attempts, available_at: _availableAt, processing_started_at: _processingStartedAt, claimed_by_agent_id: _claimedByAgentId, claimed_by_lease_id: _claimedByLeaseId, claimed_at: _claimedAt, consumed_at: _consumedAt, last_error: _lastError, result: _result, payload: _payload, created_at: _createdAt, updated_at: _updatedAt, ...envelope } = snapshot;
142
+ return envelope;
143
+ }),
144
+ };
145
+ }
146
+ export async function getAgentGatewayEventById(input) {
147
+ const agentId = normalizeText(input.agent_id);
148
+ const eventId = normalizeText(input.event_id);
149
+ if (!agentId || !eventId)
150
+ throw new Error('missing_agent_event_lookup_keys');
151
+ const row = (await query(`SELECT *
152
+ FROM agent_event_outbox
153
+ WHERE event_id=$1
154
+ AND target_agent_id=$2
155
+ LIMIT 1`, [eventId, agentId], input.client)).rows[0];
156
+ if (!row?.event_id)
157
+ return { ok: false, error: 'agent_event_not_found' };
158
+ return { ok: true, event: mapAgentGatewayEventRow(row) };
159
+ }
160
+ export async function claimAgentGatewayEvents(input) {
161
+ const agentId = normalizeText(input.agent_id);
162
+ if (!agentId)
163
+ throw new Error('missing_agent_id');
164
+ const afterSeq = parseAgentEventAfterSeq(input.after_seq);
165
+ const limit = parseAgentEventBatchLimit(input.limit, 20, 100);
166
+ const claimLeaseId = normalizeText(input.claim_lease_id) || `lease_${nanoid(12)}`;
167
+ return await withOptionalClient(input.client, async (client) => {
168
+ const now = nowISO();
169
+ const staleThreshold = new Date(Date.now() - AGENT_EVENT_STALE_PROCESSING_MS).toISOString();
170
+ const rows = (await query(`SELECT event_id
171
+ FROM agent_event_outbox
172
+ WHERE target_agent_id=$1
173
+ AND stream_seq > $2
174
+ AND (
175
+ (status='queued' AND available_at <= $3)
176
+ OR (status='failed' AND available_at <= $3)
177
+ OR (
178
+ status='processing'
179
+ AND processing_started_at IS NOT NULL
180
+ AND processing_started_at <= $4
181
+ )
182
+ )
183
+ ORDER BY stream_seq ASC
184
+ LIMIT $5
185
+ FOR UPDATE SKIP LOCKED`, [agentId, afterSeq, now, staleThreshold, limit], client)).rows;
186
+ const items = [];
187
+ for (const row of rows) {
188
+ const updated = (await query(`UPDATE agent_event_outbox
189
+ SET status='processing',
190
+ attempts=attempts + 1,
191
+ processing_started_at=$2,
192
+ claimed_by_agent_id=$3,
193
+ claimed_by_lease_id=$4,
194
+ claimed_at=$2,
195
+ updated_at=$2
196
+ WHERE event_id=$1
197
+ RETURNING *`, [normalizeText(row.event_id), now, agentId, claimLeaseId], client)).rows[0];
198
+ if (updated?.event_id)
199
+ items.push(mapAgentGatewayEventRow(updated));
200
+ }
201
+ return {
202
+ ok: true,
203
+ claim_lease_id: claimLeaseId,
204
+ items,
205
+ };
206
+ });
207
+ }
208
+ export async function ackAgentGatewayEvents(input) {
209
+ const agentId = normalizeText(input.agent_id);
210
+ if (!agentId)
211
+ throw new Error('missing_agent_id');
212
+ if (!Array.isArray(input.acks) || input.acks.length === 0)
213
+ throw new Error('missing_agent_event_acks');
214
+ return await withOptionalClient(input.client, async (client) => {
215
+ const items = [];
216
+ const now = nowISO();
217
+ for (const ack of input.acks.slice(0, 100)) {
218
+ const eventId = normalizeText(ack?.event_id);
219
+ if (!eventId)
220
+ continue;
221
+ const status = normalizeAgentEventAckStatus(ack?.status);
222
+ const retryAfterMs = parseAgentEventRetryAfterMs(ack?.retry_after_ms);
223
+ const nextAvailableAt = (status === 'failed' && retryAfterMs != null
224
+ ? new Date(Date.now() + retryAfterMs).toISOString()
225
+ : now);
226
+ const row = (await query(`UPDATE agent_event_outbox
227
+ SET status=$3,
228
+ available_at=$4,
229
+ processing_started_at=NULL,
230
+ claimed_by_agent_id=$2,
231
+ claimed_by_lease_id=NULL,
232
+ claimed_at=CASE WHEN status='processing' THEN COALESCE(claimed_at, $5) ELSE claimed_at END,
233
+ consumed_at=CASE WHEN $3='consumed' THEN $5 ELSE NULL END,
234
+ last_error=$6,
235
+ result_json=$7,
236
+ updated_at=$5
237
+ WHERE event_id=$1
238
+ AND target_agent_id=$2
239
+ RETURNING *`, [
240
+ eventId,
241
+ agentId,
242
+ status,
243
+ nextAvailableAt,
244
+ now,
245
+ ack?.last_error ? String(ack.last_error).slice(0, MAX_ERROR_LENGTH) : null,
246
+ ack?.result && typeof ack.result === 'object' && !Array.isArray(ack.result)
247
+ ? JSON.stringify(ack.result)
248
+ : null,
249
+ ], client)).rows[0];
250
+ if (row?.event_id)
251
+ items.push(mapAgentGatewayEventRow(row));
252
+ }
253
+ return {
254
+ ok: true,
255
+ items,
256
+ };
257
+ });
258
+ }
@@ -0,0 +1,2 @@
1
+ export declare function normalizeAgentId(raw: unknown): string | null;
2
+ //# sourceMappingURL=agentId.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentId.d.ts","sourceRoot":"","sources":["../../src/utils/agentId.ts"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI5D"}
@@ -0,0 +1,10 @@
1
+ function normalizeText(raw) {
2
+ return typeof raw === 'string' ? raw.trim() : '';
3
+ }
4
+ // 平台 agent 的正式对外标识统一使用 ag_*。
5
+ export function normalizeAgentId(raw) {
6
+ const value = normalizeText(raw);
7
+ if (!value)
8
+ return null;
9
+ return value.startsWith('ag_') ? value : null;
10
+ }
@@ -0,0 +1,2 @@
1
+ export declare function nowISO(): string;
2
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AACA,wBAAgB,MAAM,IAAI,MAAM,CAE/B"}
@@ -0,0 +1,4 @@
1
+ // 统一 ISO 时间戳,避免不同 repo/服务层各自实现。
2
+ export function nowISO() {
3
+ return new Date().toISOString();
4
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@hirey/hi-agent-delivery",
3
+ "version": "0.1.3",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist/",
10
+ "README.md"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./payloads": {
18
+ "types": "./dist/payloads.d.ts",
19
+ "default": "./dist/payloads.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
24
+ "build": "npm run clean && tsc -p tsconfig.json",
25
+ "prepack": "npm run build"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {
31
+ "@hirey/hi-agent-contracts": "^0.1.8",
32
+ "nanoid": "^5.0.7",
33
+ "pg": "^8.11.3"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.11.30",
37
+ "@types/pg": "^8.6.6",
38
+ "typescript": "^5.4.5"
39
+ }
40
+ }