@zonko-ai/harbor 0.1.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/README.md +15 -0
- package/dist/api.d.ts +53 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +842 -0
- package/dist/api.js.map +1 -0
- package/dist/capabilities.d.ts +5 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +308 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/constants.js.map +1 -0
- package/dist/dev.d.ts +14 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +203 -0
- package/dist/dev.js.map +1 -0
- package/dist/foreground.d.ts +13 -0
- package/dist/foreground.d.ts.map +1 -0
- package/dist/foreground.js +54 -0
- package/dist/foreground.js.map +1 -0
- package/dist/import-openapi.d.ts +12 -0
- package/dist/import-openapi.d.ts.map +1 -0
- package/dist/import-openapi.js +488 -0
- package/dist/import-openapi.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +941 -0
- package/dist/index.js.map +1 -0
- package/dist/local-config.d.ts +12 -0
- package/dist/local-config.d.ts.map +1 -0
- package/dist/local-config.js +93 -0
- package/dist/local-config.js.map +1 -0
- package/dist/local-daemon-entry.d.ts +2 -0
- package/dist/local-daemon-entry.d.ts.map +1 -0
- package/dist/local-daemon-entry.js +545 -0
- package/dist/local-daemon-entry.js.map +1 -0
- package/dist/local-daemon.d.ts +58 -0
- package/dist/local-daemon.d.ts.map +1 -0
- package/dist/local-daemon.js +385 -0
- package/dist/local-daemon.js.map +1 -0
- package/dist/mcp/api-client.d.ts +72 -0
- package/dist/mcp/api-client.d.ts.map +1 -0
- package/dist/mcp/api-client.js +210 -0
- package/dist/mcp/api-client.js.map +1 -0
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +18 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/schema.d.ts +3 -0
- package/dist/mcp/schema.d.ts.map +1 -0
- package/dist/mcp/schema.js +95 -0
- package/dist/mcp/schema.js.map +1 -0
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +57 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +12 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +54 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/output-download.d.ts +8 -0
- package/dist/output-download.d.ts.map +1 -0
- package/dist/output-download.js +210 -0
- package/dist/output-download.js.map +1 -0
- package/dist/output.d.ts +124 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +752 -0
- package/dist/output.js.map +1 -0
- package/dist/passthrough.d.ts +15 -0
- package/dist/passthrough.d.ts.map +1 -0
- package/dist/passthrough.js +68 -0
- package/dist/passthrough.js.map +1 -0
- package/dist/runtime-attribution.d.ts +5 -0
- package/dist/runtime-attribution.d.ts.map +1 -0
- package/dist/runtime-attribution.js +86 -0
- package/dist/runtime-attribution.js.map +1 -0
- package/dist/state.d.ts +56 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +374 -0
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +284 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
import { userInfo } from 'node:os';
|
|
2
|
+
import { detectLocalHostCliAuthReferences, detectLocalSecretBindings } from './local-config.js';
|
|
3
|
+
import { ensureLocalRuntime, persistLocalProfileSessionState, requestLocalApiViaDaemon } from './local-daemon.js';
|
|
4
|
+
import { detectClaimPlatform, serializeRuntimeAttributionHeader } from './runtime-attribution.js';
|
|
5
|
+
import { buildFingerprint, findWorkspaceRoot, getCatalog, getProfile, isDaemonManagedProfile, isLocalProfile, saveCatalog, saveProfile } from './state.js';
|
|
6
|
+
export class ApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
code;
|
|
9
|
+
payload;
|
|
10
|
+
constructor(status, code, message, payload) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.payload = payload;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const withTimeout = (ms) => AbortSignal.timeout(ms);
|
|
18
|
+
const parseBody = async (response) => {
|
|
19
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
20
|
+
if (contentType.includes('application/json')) {
|
|
21
|
+
return response.json();
|
|
22
|
+
}
|
|
23
|
+
const text = await response.text();
|
|
24
|
+
if (!text) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(text);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return text;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
let claimAutoRestoreInFlight = null;
|
|
35
|
+
const isExpired = (value, skewMs = 30_000) => {
|
|
36
|
+
if (!value) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const expiresAt = Date.parse(value);
|
|
40
|
+
if (Number.isNaN(expiresAt)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return expiresAt <= Date.now() + skewMs;
|
|
44
|
+
};
|
|
45
|
+
const buildDevOwnerIdentity = () => {
|
|
46
|
+
const profile = getProfile();
|
|
47
|
+
const username = userInfo().username.trim().replace(/[^a-zA-Z0-9._+-]+/g, '-').replace(/^-+|-+$/g, '') || 'local';
|
|
48
|
+
return {
|
|
49
|
+
email: profile.owner ?? process.env.HARBOR_DEV_OWNER_EMAIL ?? `${username}@harbor.local`,
|
|
50
|
+
display_name: profile.owner_display_name ?? process.env.HARBOR_DEV_OWNER_NAME ?? userInfo().username,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const tokenForAuthMode = (auth) => {
|
|
54
|
+
const profile = getProfile();
|
|
55
|
+
if (auth === 'agent') {
|
|
56
|
+
return !isExpired(profile.token_expires_at) ? profile.agent_token : null;
|
|
57
|
+
}
|
|
58
|
+
if (auth === 'owner') {
|
|
59
|
+
return !isExpired(profile.owner_token_expires_at) ? profile.owner_token : null;
|
|
60
|
+
}
|
|
61
|
+
if (auth === 'session') {
|
|
62
|
+
if (profile.agent_token && !isExpired(profile.token_expires_at)) {
|
|
63
|
+
return profile.agent_token;
|
|
64
|
+
}
|
|
65
|
+
if (profile.owner_token && !isExpired(profile.owner_token_expires_at)) {
|
|
66
|
+
return profile.owner_token;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
const buildHeaders = (auth, extraHeaders) => {
|
|
72
|
+
const headers = {
|
|
73
|
+
Accept: 'application/json',
|
|
74
|
+
...extraHeaders,
|
|
75
|
+
};
|
|
76
|
+
const token = tokenForAuthMode(auth);
|
|
77
|
+
if (token) {
|
|
78
|
+
headers.Authorization = `Bearer ${token}`;
|
|
79
|
+
}
|
|
80
|
+
return headers;
|
|
81
|
+
};
|
|
82
|
+
const resolveApiUrl = async () => {
|
|
83
|
+
const profile = getProfile();
|
|
84
|
+
if (isDaemonManagedProfile(profile)) {
|
|
85
|
+
await ensureLocalRuntime();
|
|
86
|
+
}
|
|
87
|
+
const resolvedProfile = getProfile(profile.profile_slug);
|
|
88
|
+
if (!resolvedProfile.api_url) {
|
|
89
|
+
throw new ApiError(0, 'profile_missing', isDaemonManagedProfile(resolvedProfile)
|
|
90
|
+
? 'Local Harbor is not ready. Start it with `harbor dev up`.'
|
|
91
|
+
: 'No Harbor API is configured for this profile.');
|
|
92
|
+
}
|
|
93
|
+
return resolvedProfile.api_url;
|
|
94
|
+
};
|
|
95
|
+
const tryAutoRestoreApprovedClaim = async () => {
|
|
96
|
+
if (claimAutoRestoreInFlight) {
|
|
97
|
+
return await claimAutoRestoreInFlight;
|
|
98
|
+
}
|
|
99
|
+
claimAutoRestoreInFlight = (async () => {
|
|
100
|
+
const profile = getProfile();
|
|
101
|
+
const displayName = profile.pending_claim_display_name;
|
|
102
|
+
if (!displayName || profile.agent_token) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const claim = await claimAgent(displayName);
|
|
107
|
+
return claim.result === 'claimed' || claim.result === 'restored';
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
claimAutoRestoreInFlight = null;
|
|
114
|
+
}
|
|
115
|
+
})();
|
|
116
|
+
return await claimAutoRestoreInFlight;
|
|
117
|
+
};
|
|
118
|
+
export const request = async (method, path, body, timeoutMs = 10_000, extraHeaders, auth = 'session') => {
|
|
119
|
+
if ((auth === 'agent' || auth === 'session') && !tokenForAuthMode(auth)) {
|
|
120
|
+
await tryAutoRestoreApprovedClaim();
|
|
121
|
+
}
|
|
122
|
+
const profile = getProfile();
|
|
123
|
+
const headers = {
|
|
124
|
+
...buildHeaders(auth, extraHeaders),
|
|
125
|
+
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
126
|
+
};
|
|
127
|
+
if (method.toUpperCase() === 'POST' && !headers['Idempotency-Key'] && !headers['idempotency-key']) {
|
|
128
|
+
headers['Idempotency-Key'] = crypto.randomUUID();
|
|
129
|
+
}
|
|
130
|
+
let status = 0;
|
|
131
|
+
let payload;
|
|
132
|
+
let statusText = 'Request failed';
|
|
133
|
+
if (isDaemonManagedProfile(profile)) {
|
|
134
|
+
await ensureLocalRuntime();
|
|
135
|
+
const proxied = await requestLocalApiViaDaemon(method, path, body, headers, auth, timeoutMs);
|
|
136
|
+
status = proxied.status;
|
|
137
|
+
payload = proxied.body;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const apiUrl = await resolveApiUrl();
|
|
141
|
+
const response = await fetch(`${apiUrl}${path}`, {
|
|
142
|
+
method,
|
|
143
|
+
headers,
|
|
144
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
145
|
+
signal: withTimeout(timeoutMs),
|
|
146
|
+
});
|
|
147
|
+
status = response.status;
|
|
148
|
+
statusText = response.statusText;
|
|
149
|
+
payload = await parseBody(response);
|
|
150
|
+
}
|
|
151
|
+
if (status < 200 || status >= 300) {
|
|
152
|
+
const record = pickRecord(payload);
|
|
153
|
+
const errorRecord = pickRecord(record.error);
|
|
154
|
+
const message = pickString(record.message, errorRecord.message, typeof record.error === 'string' ? record.error : null, statusText) ?? 'Request failed';
|
|
155
|
+
const code = pickString(record.code, errorRecord.code) ?? `http_${status}`;
|
|
156
|
+
throw new ApiError(status, code, message, payload);
|
|
157
|
+
}
|
|
158
|
+
return payload;
|
|
159
|
+
};
|
|
160
|
+
const pickRecord = (value) => {
|
|
161
|
+
if (typeof value === 'object' && value !== null) {
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
return {};
|
|
165
|
+
};
|
|
166
|
+
const pickString = (...values) => {
|
|
167
|
+
for (const value of values) {
|
|
168
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
};
|
|
174
|
+
const pickNumber = (...values) => {
|
|
175
|
+
for (const value of values) {
|
|
176
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
};
|
|
182
|
+
const pickBoolean = (...values) => {
|
|
183
|
+
for (const value of values) {
|
|
184
|
+
if (typeof value === 'boolean') {
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
};
|
|
190
|
+
const normalizeJsonSchema = (value) => {
|
|
191
|
+
const record = pickRecord(value);
|
|
192
|
+
return Object.keys(record).length > 0 ? record : undefined;
|
|
193
|
+
};
|
|
194
|
+
const RUNTIME_INJECTION_KINDS = new Set([
|
|
195
|
+
'owner_identity',
|
|
196
|
+
'agent_identity',
|
|
197
|
+
'execution_context',
|
|
198
|
+
'service_connection',
|
|
199
|
+
'service_lookup',
|
|
200
|
+
'service_route',
|
|
201
|
+
]);
|
|
202
|
+
const RUNTIME_INJECTION_ROUTE_FALLBACKS = new Set([
|
|
203
|
+
'agent',
|
|
204
|
+
'owner',
|
|
205
|
+
'single_active_account',
|
|
206
|
+
]);
|
|
207
|
+
const normalizeRuntimeInjectionRequirement = (value) => {
|
|
208
|
+
const record = pickRecord(value);
|
|
209
|
+
const name = pickString(record.name);
|
|
210
|
+
const kind = pickString(record.kind);
|
|
211
|
+
if (!name || !kind || !RUNTIME_INJECTION_KINDS.has(kind)) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
name,
|
|
216
|
+
kind: kind,
|
|
217
|
+
required: pickBoolean(record.required) ?? undefined,
|
|
218
|
+
service: pickString(record.service) ?? undefined,
|
|
219
|
+
lookup: pickString(record.lookup) ?? undefined,
|
|
220
|
+
input_path: pickString(record.input_path) ?? undefined,
|
|
221
|
+
selector_template: Object.keys(pickRecord(record.selector_template)).length > 0 ? pickRecord(record.selector_template) : undefined,
|
|
222
|
+
compatibility_template: Object.keys(pickRecord(record.compatibility_template)).length > 0 ? pickRecord(record.compatibility_template) : undefined,
|
|
223
|
+
fallback_order: Array.isArray(record.fallback_order)
|
|
224
|
+
? record.fallback_order.filter((entry) => typeof entry === 'string' && RUNTIME_INJECTION_ROUTE_FALLBACKS.has(entry))
|
|
225
|
+
: undefined,
|
|
226
|
+
secret_refs: Array.isArray(record.secret_refs)
|
|
227
|
+
? record.secret_refs.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
228
|
+
: undefined,
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
const normalizeRuntimeInjection = (value) => {
|
|
232
|
+
const record = pickRecord(value);
|
|
233
|
+
const requirements = Array.isArray(record.requirements)
|
|
234
|
+
? record.requirements.flatMap((requirement) => {
|
|
235
|
+
const normalized = normalizeRuntimeInjectionRequirement(requirement);
|
|
236
|
+
return normalized ? [normalized] : [];
|
|
237
|
+
})
|
|
238
|
+
: [];
|
|
239
|
+
const exports = typeof record.exports === 'object' && record.exports !== null
|
|
240
|
+
? Object.fromEntries(Object.entries(record.exports).filter((entry) => typeof entry[1] === 'string' && entry[1].length > 0))
|
|
241
|
+
: undefined;
|
|
242
|
+
const auditContextKeys = Array.isArray(pickRecord(record.audit).context_keys)
|
|
243
|
+
? pickRecord(record.audit).context_keys.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
244
|
+
: undefined;
|
|
245
|
+
if (requirements.length === 0 && !exports && !auditContextKeys?.length) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
requirements,
|
|
250
|
+
exports,
|
|
251
|
+
audit: auditContextKeys?.length ? { context_keys: auditContextKeys } : undefined,
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
const normalizeSnapshot = (value) => {
|
|
255
|
+
const record = pickRecord(value);
|
|
256
|
+
const capabilityRecord = pickRecord(record.capability);
|
|
257
|
+
const slug = pickString(record.slug, record.capability_slug, capabilityRecord.slug);
|
|
258
|
+
if (!slug) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const cliSurface = pickRecord(pickRecord(record.surface).cli);
|
|
262
|
+
const policy = pickRecord(record.policy);
|
|
263
|
+
const binding = pickRecord(record.binding);
|
|
264
|
+
const contract = pickRecord(record.contract);
|
|
265
|
+
const runtimeInjection = normalizeRuntimeInjection(record.runtime_injection);
|
|
266
|
+
return {
|
|
267
|
+
snapshot_id: pickString(record.snapshot_id) ?? undefined,
|
|
268
|
+
version_id: pickString(record.version_id) ?? undefined,
|
|
269
|
+
capability: {
|
|
270
|
+
slug,
|
|
271
|
+
title: pickString(capabilityRecord.title, record.title) ?? undefined,
|
|
272
|
+
description: pickString(capabilityRecord.description, record.description, cliSurface.summary) ?? undefined,
|
|
273
|
+
category: pickString(capabilityRecord.category, record.category, slug.split('.')[0]) ?? undefined,
|
|
274
|
+
},
|
|
275
|
+
contract: {
|
|
276
|
+
input_schema: normalizeJsonSchema(contract.input_schema ?? record.input_schema),
|
|
277
|
+
output_schema: normalizeJsonSchema(contract.output_schema ?? record.output_schema),
|
|
278
|
+
execution_mode: pickString(contract.execution_mode, record.execution_mode) ?? undefined,
|
|
279
|
+
},
|
|
280
|
+
binding: {
|
|
281
|
+
adapter_type: pickString(binding.adapter_type, record.adapter_type) ?? undefined,
|
|
282
|
+
adapter_operation: pickString(binding.adapter_operation) ?? undefined,
|
|
283
|
+
provider_ref: pickString(binding.provider_ref) ?? undefined,
|
|
284
|
+
config: pickRecord(binding.config),
|
|
285
|
+
},
|
|
286
|
+
policy: {
|
|
287
|
+
grant_required: pickBoolean(policy.grant_required) ?? undefined,
|
|
288
|
+
approval_mode: pickString(policy.approval_mode) ?? undefined,
|
|
289
|
+
authorization: {
|
|
290
|
+
mode: pickString(pickRecord(policy.authorization).mode) ?? undefined,
|
|
291
|
+
subject: pickString(pickRecord(policy.authorization).subject) ?? undefined,
|
|
292
|
+
owner_scope_field: pickString(pickRecord(policy.authorization).owner_scope_field) ?? undefined,
|
|
293
|
+
agent_field: pickString(pickRecord(policy.authorization).agent_field) ?? undefined,
|
|
294
|
+
},
|
|
295
|
+
constraint_shape: pickRecord(policy.constraint_shape),
|
|
296
|
+
},
|
|
297
|
+
secrets: {
|
|
298
|
+
required_refs: Array.isArray(pickRecord(record.secrets).required_refs)
|
|
299
|
+
? pickRecord(record.secrets).required_refs.flatMap((secret) => {
|
|
300
|
+
const name = pickString(secret.name);
|
|
301
|
+
const kind = pickString(secret.kind);
|
|
302
|
+
const purpose = pickString(secret.purpose);
|
|
303
|
+
return name && kind && purpose ? [{ name, kind, purpose }] : [];
|
|
304
|
+
})
|
|
305
|
+
: [],
|
|
306
|
+
},
|
|
307
|
+
runtime_injection: runtimeInjection,
|
|
308
|
+
surface: {
|
|
309
|
+
cli: {
|
|
310
|
+
display_name: pickString(cliSurface.display_name) ?? undefined,
|
|
311
|
+
summary: pickString(cliSurface.summary, capabilityRecord.description, record.description) ?? undefined,
|
|
312
|
+
args: Array.isArray(cliSurface.args)
|
|
313
|
+
? cliSurface.args.map((arg) => {
|
|
314
|
+
const argRecord = pickRecord(arg);
|
|
315
|
+
return {
|
|
316
|
+
name: pickString(argRecord.name) ?? '',
|
|
317
|
+
type: pickString(argRecord.type) ?? undefined,
|
|
318
|
+
required: pickBoolean(argRecord.required) ?? undefined,
|
|
319
|
+
positional: pickBoolean(argRecord.positional) ?? undefined,
|
|
320
|
+
description: pickString(argRecord.description) ?? undefined,
|
|
321
|
+
};
|
|
322
|
+
}).filter((arg) => arg.name)
|
|
323
|
+
: [],
|
|
324
|
+
examples: Array.isArray(cliSurface.examples)
|
|
325
|
+
? cliSurface.examples.filter((example) => typeof example === 'string')
|
|
326
|
+
: [],
|
|
327
|
+
plain_output_keys: Array.isArray(cliSurface.plain_output_keys)
|
|
328
|
+
? cliSurface.plain_output_keys.filter((key) => typeof key === 'string')
|
|
329
|
+
: [],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
published_at: pickString(record.published_at) ?? undefined,
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
const normalizeCapabilitySummary = (value) => {
|
|
336
|
+
const record = pickRecord(value);
|
|
337
|
+
const snapshot = normalizeSnapshot(record.snapshot ?? value);
|
|
338
|
+
const slug = pickString(record.slug, record.name, record.capability_slug, snapshot?.capability.slug);
|
|
339
|
+
if (!slug) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
const granted = pickBoolean(record.granted);
|
|
343
|
+
const defaultState = snapshot
|
|
344
|
+
? granted === true
|
|
345
|
+
? 'ready'
|
|
346
|
+
: snapshot.policy?.grant_required
|
|
347
|
+
? 'approval_required'
|
|
348
|
+
: 'published'
|
|
349
|
+
: 'unknown';
|
|
350
|
+
return {
|
|
351
|
+
slug,
|
|
352
|
+
state: pickString(record.state, record.status) ?? defaultState,
|
|
353
|
+
summary: pickString(record.summary, snapshot?.surface?.cli?.summary, snapshot?.capability.description) ?? '',
|
|
354
|
+
granted,
|
|
355
|
+
missing_secret_refs: Array.isArray(record.missing_secret_refs)
|
|
356
|
+
? record.missing_secret_refs.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
357
|
+
: undefined,
|
|
358
|
+
service_state: pickString(record.service_state) === 'linked' || pickString(record.service_state) === 'unlinked'
|
|
359
|
+
? pickString(record.service_state)
|
|
360
|
+
: undefined,
|
|
361
|
+
snapshot,
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
export const normalizeCatalog = (payload) => {
|
|
365
|
+
const record = pickRecord(payload);
|
|
366
|
+
const items = Array.isArray(payload)
|
|
367
|
+
? payload
|
|
368
|
+
: Array.isArray(record.capabilities)
|
|
369
|
+
? record.capabilities
|
|
370
|
+
: Array.isArray(record.items)
|
|
371
|
+
? record.items
|
|
372
|
+
: [];
|
|
373
|
+
return items.flatMap((item) => {
|
|
374
|
+
const normalized = normalizeCapabilitySummary(item);
|
|
375
|
+
return normalized ? [normalized] : [];
|
|
376
|
+
});
|
|
377
|
+
};
|
|
378
|
+
export const fetchCatalog = async (timeoutMs = 2_500) => {
|
|
379
|
+
const profile = getProfile();
|
|
380
|
+
const cachedCatalog = getCatalog();
|
|
381
|
+
const requestHeaders = buildHeaders('session', cachedCatalog?.etag ? { 'If-None-Match': cachedCatalog.etag } : undefined);
|
|
382
|
+
let status = 0;
|
|
383
|
+
let payload;
|
|
384
|
+
let etag = null;
|
|
385
|
+
let statusText = 'Request failed';
|
|
386
|
+
if (isDaemonManagedProfile(profile)) {
|
|
387
|
+
await ensureLocalRuntime();
|
|
388
|
+
const proxied = await requestLocalApiViaDaemon('GET', '/v1/capabilities', undefined, requestHeaders, 'session', timeoutMs);
|
|
389
|
+
status = proxied.status;
|
|
390
|
+
payload = proxied.body;
|
|
391
|
+
etag = pickString(proxied.headers.etag, proxied.headers.ETag);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
const apiUrl = await resolveApiUrl();
|
|
395
|
+
const response = await fetch(`${apiUrl}/v1/capabilities`, {
|
|
396
|
+
method: 'GET',
|
|
397
|
+
headers: requestHeaders,
|
|
398
|
+
signal: withTimeout(timeoutMs),
|
|
399
|
+
});
|
|
400
|
+
status = response.status;
|
|
401
|
+
statusText = response.statusText;
|
|
402
|
+
etag = response.headers.get('etag');
|
|
403
|
+
payload = await parseBody(response);
|
|
404
|
+
}
|
|
405
|
+
if (status === 304 && cachedCatalog) {
|
|
406
|
+
return cachedCatalog.capabilities;
|
|
407
|
+
}
|
|
408
|
+
if (status < 200 || status >= 300) {
|
|
409
|
+
const record = pickRecord(payload);
|
|
410
|
+
const errorRecord = pickRecord(record.error);
|
|
411
|
+
const message = pickString(record.message, errorRecord.message, typeof record.error === 'string' ? record.error : null, statusText) ?? 'Request failed';
|
|
412
|
+
const code = pickString(record.code, errorRecord.code) ?? `http_${status}`;
|
|
413
|
+
throw new ApiError(status, code, message, payload);
|
|
414
|
+
}
|
|
415
|
+
const capabilities = normalizeCatalog(payload);
|
|
416
|
+
saveCatalog(capabilities, etag);
|
|
417
|
+
return capabilities;
|
|
418
|
+
};
|
|
419
|
+
export const fetchCapability = async (slug) => {
|
|
420
|
+
const payload = await request('GET', `/v1/capabilities/${encodeURIComponent(slug)}`);
|
|
421
|
+
const record = pickRecord(payload);
|
|
422
|
+
const snapshot = normalizeSnapshot(record.snapshot ?? payload) ?? normalizeSnapshot(record.capability);
|
|
423
|
+
if (!snapshot) {
|
|
424
|
+
throw new ApiError(500, 'invalid_capability', `Capability payload for ${slug} is invalid`, payload);
|
|
425
|
+
}
|
|
426
|
+
return snapshot;
|
|
427
|
+
};
|
|
428
|
+
export const createDevOwnerSession = async () => {
|
|
429
|
+
const profile = getProfile();
|
|
430
|
+
const identity = buildDevOwnerIdentity();
|
|
431
|
+
const payload = await request('POST', '/v1/auth/dev-owner-sessions', {
|
|
432
|
+
email: identity.email,
|
|
433
|
+
display_name: identity.display_name,
|
|
434
|
+
}, 10_000, undefined, 'none');
|
|
435
|
+
const record = pickRecord(payload);
|
|
436
|
+
const ownerRecord = pickRecord(record.owner);
|
|
437
|
+
const normalized = {
|
|
438
|
+
owner_scope_id: pickString(ownerRecord.owner_id),
|
|
439
|
+
owner: pickString(ownerRecord.email, identity.email),
|
|
440
|
+
owner_display_name: pickString(ownerRecord.display_name, identity.display_name),
|
|
441
|
+
owner_token: pickString(record.owner_token),
|
|
442
|
+
expires_at: pickString(record.expires_at),
|
|
443
|
+
};
|
|
444
|
+
if (!normalized.owner_scope_id || !normalized.owner_token) {
|
|
445
|
+
throw new ApiError(500, 'invalid_owner_session', 'Dev owner session response is invalid', payload);
|
|
446
|
+
}
|
|
447
|
+
if (!(await persistLocalProfileSessionState({
|
|
448
|
+
profile_slug: profile.profile_slug,
|
|
449
|
+
owner_scope_id: normalized.owner_scope_id,
|
|
450
|
+
owner: normalized.owner,
|
|
451
|
+
owner_display_name: normalized.owner_display_name,
|
|
452
|
+
owner_token: normalized.owner_token,
|
|
453
|
+
owner_token_expires_at: normalized.expires_at,
|
|
454
|
+
}))) {
|
|
455
|
+
saveProfile({
|
|
456
|
+
profile_slug: profile.profile_slug,
|
|
457
|
+
owner_scope_id: normalized.owner_scope_id,
|
|
458
|
+
owner: normalized.owner,
|
|
459
|
+
owner_display_name: normalized.owner_display_name,
|
|
460
|
+
owner_token: normalized.owner_token,
|
|
461
|
+
owner_token_expires_at: normalized.expires_at,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return normalized;
|
|
465
|
+
};
|
|
466
|
+
export const ensureLocalOwnerSession = async () => {
|
|
467
|
+
const profile = getProfile();
|
|
468
|
+
if (!isLocalProfile(profile)) {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
if (profile.owner_token && !isExpired(profile.owner_token_expires_at)) {
|
|
472
|
+
try {
|
|
473
|
+
await request('GET', '/v1/auth/whoami', undefined, 5_000, undefined, 'owner');
|
|
474
|
+
return {
|
|
475
|
+
owner_scope_id: profile.owner_scope_id,
|
|
476
|
+
owner: profile.owner,
|
|
477
|
+
owner_display_name: profile.owner_display_name,
|
|
478
|
+
owner_token: profile.owner_token,
|
|
479
|
+
expires_at: profile.owner_token_expires_at,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
if (!(error instanceof ApiError) || (error.status !== 401 && error.status !== 403)) {
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return createDevOwnerSession();
|
|
489
|
+
};
|
|
490
|
+
export const syncLocalSecretReferences = async () => {
|
|
491
|
+
const profile = getProfile();
|
|
492
|
+
if (!isLocalProfile(profile)) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
await request('POST', '/v1/secret-references/host-cli-sync', {
|
|
496
|
+
refs: [
|
|
497
|
+
...detectLocalSecretBindings(findWorkspaceRoot()),
|
|
498
|
+
...detectLocalHostCliAuthReferences(findWorkspaceRoot()),
|
|
499
|
+
],
|
|
500
|
+
}, 10_000, undefined, 'session');
|
|
501
|
+
};
|
|
502
|
+
export const claimAgent = async (displayName = 'Harbor CLI') => {
|
|
503
|
+
const profile = getProfile();
|
|
504
|
+
const payload = await request('POST', '/v1/auth/agent/claim-intents', {
|
|
505
|
+
profile_slug: profile.profile_slug,
|
|
506
|
+
display_name: displayName,
|
|
507
|
+
platform: detectClaimPlatform(),
|
|
508
|
+
fingerprint: buildFingerprint(),
|
|
509
|
+
workspace_root: findWorkspaceRoot(),
|
|
510
|
+
restore_token: null,
|
|
511
|
+
}, 10_000, undefined, tokenForAuthMode('owner') ? 'owner' : 'none');
|
|
512
|
+
const record = pickRecord(payload);
|
|
513
|
+
const result = pickString(record.result);
|
|
514
|
+
if (result !== 'claimed' && result !== 'restored' && result !== 'approval_required') {
|
|
515
|
+
throw new ApiError(500, 'invalid_claim_result', 'Claim response is missing a valid result', payload);
|
|
516
|
+
}
|
|
517
|
+
const normalized = {
|
|
518
|
+
result,
|
|
519
|
+
owner_scope_id: pickString(record.owner_scope_id),
|
|
520
|
+
agent_id: pickString(record.agent_id),
|
|
521
|
+
agent_display_name: pickString(record.agent_display_name),
|
|
522
|
+
approval_id: pickString(record.approval_id),
|
|
523
|
+
approval_url: pickString(record.approval_url),
|
|
524
|
+
owner: pickString(record.owner, record.owner_email, profile.owner),
|
|
525
|
+
profile_slug: pickString(record.profile_slug),
|
|
526
|
+
agent_token: pickString(record.agent_token),
|
|
527
|
+
expires_at: pickString(record.expires_at),
|
|
528
|
+
};
|
|
529
|
+
if (normalized.agent_token) {
|
|
530
|
+
if (!(await persistLocalProfileSessionState({
|
|
531
|
+
profile_slug: profile.profile_slug,
|
|
532
|
+
agent_id: normalized.agent_id,
|
|
533
|
+
owner_scope_id: normalized.owner_scope_id,
|
|
534
|
+
owner: normalized.owner,
|
|
535
|
+
agent_token: normalized.agent_token,
|
|
536
|
+
agent_token_expires_at: normalized.expires_at,
|
|
537
|
+
pending_claim_display_name: null,
|
|
538
|
+
pending_claim_approval_id: null,
|
|
539
|
+
}))) {
|
|
540
|
+
saveProfile({
|
|
541
|
+
profile_slug: profile.profile_slug,
|
|
542
|
+
agent_id: normalized.agent_id,
|
|
543
|
+
owner_scope_id: normalized.owner_scope_id,
|
|
544
|
+
owner: normalized.owner,
|
|
545
|
+
agent_token: normalized.agent_token,
|
|
546
|
+
token_expires_at: normalized.expires_at,
|
|
547
|
+
pending_claim_display_name: null,
|
|
548
|
+
pending_claim_approval_id: null,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
void syncLocalSecretReferences().catch(() => undefined);
|
|
552
|
+
}
|
|
553
|
+
else if (normalized.result === 'approval_required') {
|
|
554
|
+
if (!(await persistLocalProfileSessionState({
|
|
555
|
+
profile_slug: profile.profile_slug,
|
|
556
|
+
pending_claim_display_name: displayName,
|
|
557
|
+
pending_claim_approval_id: normalized.approval_id,
|
|
558
|
+
}))) {
|
|
559
|
+
saveProfile({
|
|
560
|
+
profile_slug: profile.profile_slug,
|
|
561
|
+
pending_claim_display_name: displayName,
|
|
562
|
+
pending_claim_approval_id: normalized.approval_id,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return normalized;
|
|
567
|
+
};
|
|
568
|
+
export const fetchWhoAmI = async () => {
|
|
569
|
+
const payload = await request('GET', '/v1/auth/whoami');
|
|
570
|
+
const record = pickRecord(payload);
|
|
571
|
+
const ownerRecord = pickRecord(record.owner);
|
|
572
|
+
const agentRecord = pickRecord(record.agent);
|
|
573
|
+
return {
|
|
574
|
+
principal: pickString(record.principal) === 'owner' || pickString(record.principal) === 'agent'
|
|
575
|
+
? pickString(record.principal)
|
|
576
|
+
: null,
|
|
577
|
+
profile_slug: pickString(record.profile_slug, record.profile, agentRecord.profile_slug),
|
|
578
|
+
owner_scope_id: pickString(record.owner_scope_id, ownerRecord.owner_id, agentRecord.owner_scope_id),
|
|
579
|
+
owner: pickString(record.owner, record.owner_email, ownerRecord.email, pickRecord(record.owner_identity).email),
|
|
580
|
+
owner_display_name: pickString(ownerRecord.display_name),
|
|
581
|
+
agent_id: pickString(record.agent_id, agentRecord.agent_id),
|
|
582
|
+
agent_display_name: pickString(record.agent_display_name, agentRecord.display_name),
|
|
583
|
+
};
|
|
584
|
+
};
|
|
585
|
+
export const fetchHealth = async () => {
|
|
586
|
+
const payload = await request('GET', '/v1/health', undefined, 10_000, undefined, 'none');
|
|
587
|
+
const record = pickRecord(payload);
|
|
588
|
+
return {
|
|
589
|
+
reachable: true,
|
|
590
|
+
status: pickString(record.status, record.health) ?? 'ok',
|
|
591
|
+
api_url: pickString(record.api_url, record.url),
|
|
592
|
+
auth_url: pickString(record.auth_url, record.app_url),
|
|
593
|
+
published_capabilities: pickNumber(record.published_capabilities, pickRecord(record.summary).published_capabilities),
|
|
594
|
+
queued_executions: pickNumber(record.queued_executions, pickRecord(record.summary).queued_executions),
|
|
595
|
+
pending_approvals: pickNumber(record.pending_approvals, pickRecord(record.summary).pending_approvals),
|
|
596
|
+
recent_executions: pickNumber(record.recent_executions, pickRecord(record.summary).recent_executions),
|
|
597
|
+
};
|
|
598
|
+
};
|
|
599
|
+
export const approveRequest = async (approvalId) => {
|
|
600
|
+
await ensureLocalOwnerSession();
|
|
601
|
+
const payload = await request('POST', `/v1/approvals/${encodeURIComponent(approvalId)}/approve`, undefined, 10_000, undefined, 'owner');
|
|
602
|
+
return pickRecord(payload);
|
|
603
|
+
};
|
|
604
|
+
export const listSecretReferences = async () => {
|
|
605
|
+
await ensureLocalOwnerSession();
|
|
606
|
+
const payload = await request('GET', '/v1/secret-references', undefined, 10_000, undefined, 'owner');
|
|
607
|
+
const record = pickRecord(payload);
|
|
608
|
+
const items = Array.isArray(record.secret_references) ? record.secret_references : [];
|
|
609
|
+
return items.map((item) => normalizeSecretReference(item));
|
|
610
|
+
};
|
|
611
|
+
export const getSecretReference = async (name) => {
|
|
612
|
+
await ensureLocalOwnerSession();
|
|
613
|
+
const payload = await request('GET', `/v1/secret-references/${encodeURIComponent(name)}`, undefined, 10_000, undefined, 'owner');
|
|
614
|
+
return normalizeSecretReference(payload);
|
|
615
|
+
};
|
|
616
|
+
export const setSecretReference = async (name, value, options) => {
|
|
617
|
+
await ensureLocalOwnerSession();
|
|
618
|
+
const payload = await request('PUT', `/v1/secret-references/${encodeURIComponent(name)}`, {
|
|
619
|
+
value,
|
|
620
|
+
...(options?.display_name ? { display_name: options.display_name } : {}),
|
|
621
|
+
...(options?.kind ? { kind: options.kind } : {}),
|
|
622
|
+
}, 10_000, undefined, 'owner');
|
|
623
|
+
return normalizeSecretReference(payload);
|
|
624
|
+
};
|
|
625
|
+
export const deleteSecretReference = async (name) => {
|
|
626
|
+
await ensureLocalOwnerSession();
|
|
627
|
+
const payload = await request('DELETE', `/v1/secret-references/${encodeURIComponent(name)}`, undefined, 10_000, undefined, 'owner');
|
|
628
|
+
const record = pickRecord(payload);
|
|
629
|
+
return {
|
|
630
|
+
ok: pickBoolean(record.ok) ?? false,
|
|
631
|
+
};
|
|
632
|
+
};
|
|
633
|
+
const normalizeSecretReference = (value) => {
|
|
634
|
+
const record = pickRecord(value);
|
|
635
|
+
const name = pickString(record.name);
|
|
636
|
+
const displayName = pickString(record.display_name);
|
|
637
|
+
const kind = pickString(record.kind);
|
|
638
|
+
const resolverKind = pickString(record.resolver_kind);
|
|
639
|
+
const status = pickString(record.status);
|
|
640
|
+
const createdAt = pickString(record.created_at);
|
|
641
|
+
const updatedAt = pickString(record.updated_at);
|
|
642
|
+
if (!name || !displayName || !kind || !resolverKind || !status || !createdAt || !updatedAt) {
|
|
643
|
+
throw new ApiError(500, 'invalid_secret_reference', 'Secret reference payload is invalid', value);
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
secret_ref_id: pickString(record.secret_ref_id) ?? undefined,
|
|
647
|
+
name,
|
|
648
|
+
display_name: displayName,
|
|
649
|
+
kind,
|
|
650
|
+
resolver_kind: resolverKind,
|
|
651
|
+
status,
|
|
652
|
+
backend_locator: pickRecord(record.backend_locator),
|
|
653
|
+
managed_by: pickString(record.managed_by) ?? 'owner',
|
|
654
|
+
rotated_at: pickString(record.rotated_at),
|
|
655
|
+
created_at: createdAt,
|
|
656
|
+
updated_at: updatedAt,
|
|
657
|
+
stored_material_present: pickBoolean(record.stored_material_present) ?? undefined,
|
|
658
|
+
};
|
|
659
|
+
};
|
|
660
|
+
const normalizeComposioLinkSession = (value) => {
|
|
661
|
+
const record = pickRecord(value);
|
|
662
|
+
const linkSessionId = pickString(record.link_session_id);
|
|
663
|
+
const status = pickString(record.status);
|
|
664
|
+
if (!linkSessionId || !status) {
|
|
665
|
+
throw new ApiError(500, 'invalid_composio_link_session', 'Composio link session payload is invalid', value);
|
|
666
|
+
}
|
|
667
|
+
const subjectKind = pickString(record.subject_kind);
|
|
668
|
+
return {
|
|
669
|
+
link_session_id: linkSessionId,
|
|
670
|
+
owner_scope_id: pickString(record.owner_scope_id),
|
|
671
|
+
subject_kind: subjectKind === 'owner' || subjectKind === 'agent' ? subjectKind : undefined,
|
|
672
|
+
subject_id: pickString(record.subject_id),
|
|
673
|
+
toolkit_slug: pickString(record.toolkit_slug),
|
|
674
|
+
auth_config_id: pickString(record.auth_config_id),
|
|
675
|
+
connected_account_id: pickString(record.connected_account_id),
|
|
676
|
+
redirect_url: pickString(record.redirect_url),
|
|
677
|
+
status,
|
|
678
|
+
expires_at: pickString(record.expires_at),
|
|
679
|
+
completed_at: pickString(record.completed_at),
|
|
680
|
+
};
|
|
681
|
+
};
|
|
682
|
+
const normalizeComposioRoute = (value) => {
|
|
683
|
+
const record = pickRecord(value);
|
|
684
|
+
const routeId = pickString(record.route_id);
|
|
685
|
+
const subjectKind = pickString(record.subject_kind);
|
|
686
|
+
const subjectId = pickString(record.subject_id);
|
|
687
|
+
const toolkitSlug = pickString(record.toolkit_slug);
|
|
688
|
+
const authConfigId = pickString(record.auth_config_id);
|
|
689
|
+
const connectedAccountId = pickString(record.connected_account_id);
|
|
690
|
+
const status = pickString(record.status);
|
|
691
|
+
if (!routeId || (subjectKind !== 'owner' && subjectKind !== 'agent') || !subjectId || !toolkitSlug || !authConfigId || !connectedAccountId || !status) {
|
|
692
|
+
throw new ApiError(500, 'invalid_composio_route', 'Composio route payload is invalid', value);
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
route_id: routeId,
|
|
696
|
+
owner_scope_id: pickString(record.owner_scope_id),
|
|
697
|
+
subject_kind: subjectKind,
|
|
698
|
+
subject_id: subjectId,
|
|
699
|
+
toolkit_slug: toolkitSlug,
|
|
700
|
+
auth_config_id: authConfigId,
|
|
701
|
+
connected_account_id: connectedAccountId,
|
|
702
|
+
status,
|
|
703
|
+
created_at: pickString(record.created_at),
|
|
704
|
+
updated_at: pickString(record.updated_at),
|
|
705
|
+
};
|
|
706
|
+
};
|
|
707
|
+
export const createComposioLinkSession = async (params) => {
|
|
708
|
+
await ensureLocalOwnerSession();
|
|
709
|
+
await syncLocalSecretReferences();
|
|
710
|
+
const payload = await request('POST', '/v1/services/composio/link-sessions', params, 20_000, undefined, 'owner');
|
|
711
|
+
return normalizeComposioLinkSession(payload);
|
|
712
|
+
};
|
|
713
|
+
export const fetchComposioService = async () => {
|
|
714
|
+
await ensureLocalOwnerSession();
|
|
715
|
+
const payload = await request('GET', '/v1/services/composio', undefined, 10_000, undefined, 'owner');
|
|
716
|
+
const record = pickRecord(payload);
|
|
717
|
+
const principalRecord = pickRecord(record.principal);
|
|
718
|
+
const principalId = pickString(principalRecord.principal_id);
|
|
719
|
+
return {
|
|
720
|
+
principal: principalId
|
|
721
|
+
? {
|
|
722
|
+
principal_id: principalId,
|
|
723
|
+
owner_scope_id: pickString(principalRecord.owner_scope_id) ?? '',
|
|
724
|
+
composio_user_id: pickString(principalRecord.composio_user_id) ?? '',
|
|
725
|
+
status: pickString(principalRecord.status) ?? 'unknown',
|
|
726
|
+
created_at: pickString(principalRecord.created_at),
|
|
727
|
+
updated_at: pickString(principalRecord.updated_at),
|
|
728
|
+
}
|
|
729
|
+
: null,
|
|
730
|
+
routes: Array.isArray(record.routes) ? record.routes.map((route) => normalizeComposioRoute(route)) : [],
|
|
731
|
+
link_sessions: Array.isArray(record.link_sessions) ? record.link_sessions.map((session) => normalizeComposioLinkSession(session)) : [],
|
|
732
|
+
};
|
|
733
|
+
};
|
|
734
|
+
export const fetchComposioLinkSession = async (linkSessionId) => {
|
|
735
|
+
await ensureLocalOwnerSession();
|
|
736
|
+
const payload = await request('GET', `/v1/services/composio/link-sessions/${encodeURIComponent(linkSessionId)}`, undefined, 15_000, undefined, 'owner');
|
|
737
|
+
return normalizeComposioLinkSession(payload);
|
|
738
|
+
};
|
|
739
|
+
export const setComposioServiceRoute = async (params) => {
|
|
740
|
+
await ensureLocalOwnerSession();
|
|
741
|
+
await syncLocalSecretReferences();
|
|
742
|
+
const payload = await request('POST', '/v1/services/composio/routes', params, 15_000, undefined, 'owner');
|
|
743
|
+
return normalizeComposioRoute(payload);
|
|
744
|
+
};
|
|
745
|
+
export const deleteComposioServiceRoute = async (params) => {
|
|
746
|
+
await ensureLocalOwnerSession();
|
|
747
|
+
const search = new URLSearchParams();
|
|
748
|
+
if (params.route_id)
|
|
749
|
+
search.set('route_id', params.route_id);
|
|
750
|
+
if (params.subject_kind)
|
|
751
|
+
search.set('subject_kind', params.subject_kind);
|
|
752
|
+
if (params.subject_id)
|
|
753
|
+
search.set('subject_id', params.subject_id);
|
|
754
|
+
if (params.toolkit_slug)
|
|
755
|
+
search.set('toolkit_slug', params.toolkit_slug);
|
|
756
|
+
if (params.auth_config_id)
|
|
757
|
+
search.set('auth_config_id', params.auth_config_id);
|
|
758
|
+
const suffix = search.size > 0 ? `?${search.toString()}` : '';
|
|
759
|
+
const payload = await request('DELETE', `/v1/services/composio/routes${suffix}`, undefined, 15_000, undefined, 'owner');
|
|
760
|
+
return normalizeComposioRoute(payload);
|
|
761
|
+
};
|
|
762
|
+
export const startExecution = async (capabilitySlug, input, approvalId, timeoutMs = 30_000) => {
|
|
763
|
+
try {
|
|
764
|
+
await syncLocalSecretReferences();
|
|
765
|
+
const body = {
|
|
766
|
+
capability_slug: capabilitySlug,
|
|
767
|
+
input,
|
|
768
|
+
};
|
|
769
|
+
if (approvalId) {
|
|
770
|
+
body.approval_id = approvalId;
|
|
771
|
+
}
|
|
772
|
+
const runtimeAttributionHeader = serializeRuntimeAttributionHeader();
|
|
773
|
+
const payload = await request('POST', '/v1/executions', body, timeoutMs, {
|
|
774
|
+
'X-Harbor-Current-Dir': process.cwd(),
|
|
775
|
+
...(runtimeAttributionHeader ? { 'X-Harbor-Runtime-Attribution': runtimeAttributionHeader } : {}),
|
|
776
|
+
}, 'agent');
|
|
777
|
+
const record = pickRecord(payload);
|
|
778
|
+
return {
|
|
779
|
+
status: pickString(record.status, record.result) ?? 'accepted',
|
|
780
|
+
execution_id: pickString(record.execution_id),
|
|
781
|
+
capability: pickString(record.capability, record.capability_slug, capabilitySlug),
|
|
782
|
+
state: pickString(record.state) ?? null,
|
|
783
|
+
approval_id: pickString(record.approval_id),
|
|
784
|
+
approval_url: pickString(record.approval_url),
|
|
785
|
+
reason: pickString(record.reason, record.message),
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
if (error instanceof ApiError && error.code === 'approval_required') {
|
|
790
|
+
const payload = pickRecord(error.payload);
|
|
791
|
+
const errorRecord = pickRecord(payload.error);
|
|
792
|
+
const details = pickRecord(errorRecord.details);
|
|
793
|
+
return {
|
|
794
|
+
status: 'approval_required',
|
|
795
|
+
execution_id: null,
|
|
796
|
+
capability: capabilitySlug,
|
|
797
|
+
state: null,
|
|
798
|
+
approval_id: pickString(payload.approval_id, errorRecord.approval_id, details.approval_id),
|
|
799
|
+
approval_url: pickString(payload.approval_url, errorRecord.approval_url, details.approval_url),
|
|
800
|
+
reason: pickString(payload.reason, errorRecord.message, details.reason, error.message),
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
export const fetchExecutionLogs = async (executionId, follow = false, afterSequence, waitMs) => {
|
|
807
|
+
const search = new URLSearchParams();
|
|
808
|
+
if (follow) {
|
|
809
|
+
search.set('follow', 'true');
|
|
810
|
+
}
|
|
811
|
+
if (typeof afterSequence === 'number' && Number.isFinite(afterSequence) && afterSequence >= 0) {
|
|
812
|
+
search.set('after', String(afterSequence));
|
|
813
|
+
}
|
|
814
|
+
if (typeof waitMs === 'number' && Number.isFinite(waitMs) && waitMs > 0) {
|
|
815
|
+
search.set('wait_ms', String(Math.floor(waitMs)));
|
|
816
|
+
}
|
|
817
|
+
const suffix = search.size > 0 ? `?${search.toString()}` : '';
|
|
818
|
+
const timeoutMs = typeof waitMs === 'number' && waitMs > 0 ? Math.max(10_000, Math.floor(waitMs) + 5_000) : 10_000;
|
|
819
|
+
const payload = await request('GET', `/v1/executions/${encodeURIComponent(executionId)}/logs${suffix}`, undefined, timeoutMs, undefined, 'agent');
|
|
820
|
+
const record = pickRecord(payload);
|
|
821
|
+
const events = Array.isArray(record.events) ? record.events : [];
|
|
822
|
+
return {
|
|
823
|
+
execution_id: pickString(record.execution_id, executionId) ?? executionId,
|
|
824
|
+
state: pickString(record.state, record.status),
|
|
825
|
+
exit_code: pickNumber(record.exit_code),
|
|
826
|
+
started_at: pickString(record.started_at),
|
|
827
|
+
completed_at: pickString(record.completed_at),
|
|
828
|
+
error_code: pickString(record.error_code),
|
|
829
|
+
error_message: pickString(record.error_message),
|
|
830
|
+
events: events.map((event) => {
|
|
831
|
+
const eventRecord = pickRecord(event);
|
|
832
|
+
return {
|
|
833
|
+
sequence: pickNumber(eventRecord.sequence) ?? undefined,
|
|
834
|
+
kind: pickString(eventRecord.kind) ?? undefined,
|
|
835
|
+
message: pickString(eventRecord.message) ?? '',
|
|
836
|
+
payload_json: eventRecord.payload_json,
|
|
837
|
+
occurred_at: pickString(eventRecord.occurred_at) ?? undefined,
|
|
838
|
+
};
|
|
839
|
+
}),
|
|
840
|
+
};
|
|
841
|
+
};
|
|
842
|
+
//# sourceMappingURL=api.js.map
|