@fulcrum-governance/sdk 0.1.4 → 0.1.5
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/CHANGELOG.md +12 -1
- package/README.md +40 -51
- package/dist/__tests__/clients.test.js +127 -12
- package/dist/clients/agent.js +18 -12
- package/dist/clients/approval.js +15 -2
- package/dist/clients/budget.js +15 -2
- package/dist/clients/checkpoint.js +15 -2
- package/dist/clients/envelope.d.ts +5 -4
- package/dist/clients/envelope.js +39 -10
- package/dist/clients/eventstore.js +15 -2
- package/dist/clients/metrics.js +15 -2
- package/dist/clients/policy.d.ts +7 -2
- package/dist/clients/policy.js +55 -10
- package/dist/clients/tenant.js +15 -2
- package/dist/index.js +6 -2
- package/dist/instrumentation/__tests__/autoGovern.test.js +3 -2
- package/dist/instrumentation/__tests__/costReporter.test.d.ts +5 -0
- package/dist/instrumentation/__tests__/costReporter.test.js +137 -0
- package/dist/instrumentation/autoGovern.js +63 -1
- package/dist/instrumentation/costReporter.d.ts +27 -0
- package/dist/instrumentation/costReporter.js +69 -0
- package/dist/instrumentation/evaluator.d.ts +8 -0
- package/dist/instrumentation/evaluator.js +124 -11
- package/dist/instrumentation/index.d.ts +3 -1
- package/dist/instrumentation/index.js +3 -1
- package/dist/instrumentation/types.d.ts +13 -0
- package/package.json +11 -4
- package/proto/events.proto +1 -1
- package/proto/fulcrum/agent/v1/agent_service.proto +1 -1
- package/proto/fulcrum/bridge/v1/bridge.proto +1 -1
- package/proto/fulcrum/checkpoint/v1/checkpoint_service.proto +1 -1
- package/proto/fulcrum/cost/v1/cost_service.proto +1 -1
- package/proto/fulcrum/envelope/v1/envelope.proto +1 -1
- package/proto/fulcrum/envelope/v1/envelope_service.proto +1 -1
- package/proto/fulcrum/eventstore/v1/eventstore.proto +1 -1
- package/proto/fulcrum/policy/v1/policy_service.proto +1 -1
- package/proto/fulcrum/tenant/v1/tenant_service.proto +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.5] - 2026-06-03
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Update `PolicyClient` to use the hosted gateway's `/api/v1/policies`
|
|
14
|
+
path and unwrap `{ policies, total_count }` list responses.
|
|
15
|
+
- Update `EnvelopeClient` to use the `/api/v1/envelopes/*` gateway paths and
|
|
16
|
+
handle unwrapped REST gateway responses.
|
|
17
|
+
- Allow REST envelope creation without a tenant ID because the hosted gateway
|
|
18
|
+
derives tenant context from the API key.
|
|
19
|
+
|
|
10
20
|
## [0.1.4] - 2026-02-06
|
|
11
21
|
|
|
12
22
|
### Changed
|
|
@@ -86,7 +96,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
86
96
|
- Basic gRPC-web connectivity
|
|
87
97
|
- Policy evaluation stub
|
|
88
98
|
|
|
89
|
-
[Unreleased]: https://github.com/Fulcrum-Governance/fulcrum-io/compare/sdk/typescript/v0.1.
|
|
99
|
+
[Unreleased]: https://github.com/Fulcrum-Governance/fulcrum-io/compare/sdk/typescript/v0.1.5...HEAD
|
|
100
|
+
[0.1.5]: https://github.com/Fulcrum-Governance/fulcrum-io/compare/sdk/typescript/v0.1.4...sdk/typescript/v0.1.5
|
|
90
101
|
[0.1.4]: https://github.com/Fulcrum-Governance/fulcrum-io/compare/sdk/typescript/v0.1.3...sdk/typescript/v0.1.4
|
|
91
102
|
[0.1.3]: https://github.com/Fulcrum-Governance/fulcrum-io/compare/sdk/typescript/v0.1.2...sdk/typescript/v0.1.3
|
|
92
103
|
[0.1.2]: https://github.com/fulcrum-dev/fulcrum/compare/sdk/typescript/v0.1.1...sdk/typescript/v0.1.2
|
package/README.md
CHANGED
|
@@ -14,33 +14,45 @@ npm install @fulcrum-governance/sdk
|
|
|
14
14
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
17
|
+
Hosted Fulcrum uses the REST API and `X-API-Key` authentication:
|
|
18
|
+
|
|
17
19
|
```typescript
|
|
18
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
PolicyClient,
|
|
22
|
+
EnvelopeClient,
|
|
23
|
+
} from '@fulcrum-governance/sdk';
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
const apiBase = process.env.FULCRUM_API_BASE || 'https://api.fulcrumlayer.io';
|
|
26
|
+
const apiKey = process.env.FULCRUM_API_KEY!;
|
|
27
|
+
|
|
28
|
+
const policyClient = new PolicyClient({
|
|
29
|
+
baseUrl: apiBase,
|
|
30
|
+
apiKey,
|
|
25
31
|
});
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
const policies = await policyClient.list();
|
|
34
|
+
console.log(`Visible policies: ${policies.length}`);
|
|
35
|
+
|
|
36
|
+
const envelopes = new EnvelopeClient({
|
|
37
|
+
baseUrl: apiBase,
|
|
38
|
+
apiKey,
|
|
31
39
|
});
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
const envelope = await envelopes.create({
|
|
42
|
+
workflow_id: 'customer-support-bot',
|
|
43
|
+
metadata: { example: 'typescript-sdk' },
|
|
44
|
+
});
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
await envelopes.updateStatus({
|
|
47
|
+
envelope_id: envelope.envelope_id,
|
|
48
|
+
status: 'RUNNING',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Run your agent work here, then close the envelope.
|
|
52
|
+
await envelopes.updateStatus({
|
|
53
|
+
envelope_id: envelope.envelope_id,
|
|
54
|
+
status: 'COMPLETED',
|
|
55
|
+
});
|
|
44
56
|
```
|
|
45
57
|
|
|
46
58
|
## Features
|
|
@@ -72,16 +84,14 @@ const client = new FulcrumClient({
|
|
|
72
84
|
### Environment Variables
|
|
73
85
|
|
|
74
86
|
```bash
|
|
75
|
-
export FULCRUM_HOST="localhost:50051"
|
|
76
87
|
export FULCRUM_API_KEY="your-api-key"
|
|
77
|
-
export
|
|
88
|
+
export FULCRUM_API_BASE="https://api.fulcrumlayer.io"
|
|
78
89
|
export FULCRUM_TIMEOUT_MS="500"
|
|
79
90
|
```
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
```
|
|
92
|
+
The lower-level `FulcrumClient` gRPC API is still available for compatible
|
|
93
|
+
self-hosted or direct gRPC deployments. The hosted first-run path uses the REST
|
|
94
|
+
clients above.
|
|
85
95
|
|
|
86
96
|
## API Reference
|
|
87
97
|
|
|
@@ -363,13 +373,14 @@ The SDK includes REST-based clients for managing Fulcrum resources through the D
|
|
|
363
373
|
|
|
364
374
|
### PolicyClient
|
|
365
375
|
|
|
366
|
-
|
|
376
|
+
List governance policies visible to your API key. Create and edit hosted
|
|
377
|
+
policies in the Fulcrum dashboard before using the API-key gateway.
|
|
367
378
|
|
|
368
379
|
```typescript
|
|
369
380
|
import { PolicyClient } from '@fulcrum-governance/sdk';
|
|
370
381
|
|
|
371
382
|
const policyClient = new PolicyClient({
|
|
372
|
-
baseUrl: 'https://
|
|
383
|
+
baseUrl: 'https://api.fulcrumlayer.io',
|
|
373
384
|
apiKey: 'your-api-key',
|
|
374
385
|
timeoutMs: 5000 // optional, default: 5000
|
|
375
386
|
});
|
|
@@ -379,33 +390,11 @@ const policies = await policyClient.list();
|
|
|
379
390
|
|
|
380
391
|
// List with filters
|
|
381
392
|
const activePolicies = await policyClient.list({
|
|
382
|
-
status: '
|
|
383
|
-
priority: 'high'
|
|
393
|
+
status: 'ACTIVE'
|
|
384
394
|
});
|
|
385
395
|
|
|
386
396
|
// Get a specific policy
|
|
387
397
|
const policy = await policyClient.get('policy-id');
|
|
388
|
-
|
|
389
|
-
// Create a new policy
|
|
390
|
-
const newPolicy = await policyClient.create({
|
|
391
|
-
name: 'No PII in Responses',
|
|
392
|
-
description: 'Prevents sensitive data exposure',
|
|
393
|
-
rules: [{ type: 'semantic', pattern: 'pii_detection' }],
|
|
394
|
-
priority: 1,
|
|
395
|
-
enabled: true
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
// Update a policy
|
|
399
|
-
const updated = await policyClient.update('policy-id', {
|
|
400
|
-
name: 'Updated Policy Name',
|
|
401
|
-
priority: 2
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// Enable/disable policies
|
|
405
|
-
await policyClient.setEnabled('policy-id', false);
|
|
406
|
-
|
|
407
|
-
// Delete a policy
|
|
408
|
-
await policyClient.delete('policy-id');
|
|
409
398
|
```
|
|
410
399
|
|
|
411
400
|
### ApprovalClient
|
|
@@ -36,15 +36,35 @@ describe('PolicyClient', () => {
|
|
|
36
36
|
describe('list', () => {
|
|
37
37
|
it('should fetch policies successfully', async () => {
|
|
38
38
|
const mockPolicies = [
|
|
39
|
-
{
|
|
40
|
-
|
|
39
|
+
{
|
|
40
|
+
id: '1',
|
|
41
|
+
name: 'Policy 1',
|
|
42
|
+
policy_type: 'cost',
|
|
43
|
+
rules: {},
|
|
44
|
+
enabled: true,
|
|
45
|
+
priority: 0,
|
|
46
|
+
status: 'ACTIVE',
|
|
47
|
+
created_at: '',
|
|
48
|
+
updated_at: '',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: '2',
|
|
52
|
+
name: 'Policy 2',
|
|
53
|
+
policy_type: 'rate',
|
|
54
|
+
rules: {},
|
|
55
|
+
enabled: false,
|
|
56
|
+
priority: 0,
|
|
57
|
+
status: 'ACTIVE',
|
|
58
|
+
created_at: '',
|
|
59
|
+
updated_at: '',
|
|
60
|
+
},
|
|
41
61
|
];
|
|
42
62
|
mockFetch.mockResolvedValueOnce({
|
|
43
63
|
ok: true,
|
|
44
|
-
json: async () => mockPolicies,
|
|
64
|
+
json: async () => ({ policies: mockPolicies, total_count: 2 }),
|
|
45
65
|
});
|
|
46
66
|
const policies = await client.list();
|
|
47
|
-
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/policies`, expect.objectContaining({
|
|
67
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/policies`, expect.objectContaining({
|
|
48
68
|
method: 'GET',
|
|
49
69
|
headers: { 'Content-Type': 'application/json' },
|
|
50
70
|
}));
|
|
@@ -59,7 +79,7 @@ describe('PolicyClient', () => {
|
|
|
59
79
|
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('status=ACTIVE'), expect.any(Object));
|
|
60
80
|
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('policy_type=cost'), expect.any(Object));
|
|
61
81
|
});
|
|
62
|
-
it('should include
|
|
82
|
+
it('should include X-API-Key header when apiKey is set', async () => {
|
|
63
83
|
const authedClient = new clients_1.PolicyClient({ baseUrl, apiKey: 'my-key' });
|
|
64
84
|
mockFetch.mockResolvedValueOnce({
|
|
65
85
|
ok: true,
|
|
@@ -68,10 +88,38 @@ describe('PolicyClient', () => {
|
|
|
68
88
|
await authedClient.list();
|
|
69
89
|
expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
|
|
70
90
|
headers: expect.objectContaining({
|
|
71
|
-
|
|
91
|
+
'X-API-Key': 'my-key',
|
|
72
92
|
}),
|
|
73
93
|
}));
|
|
74
94
|
});
|
|
95
|
+
it('should normalize hosted gateway policy records', async () => {
|
|
96
|
+
mockFetch.mockResolvedValueOnce({
|
|
97
|
+
ok: true,
|
|
98
|
+
json: async () => ({
|
|
99
|
+
policies: [
|
|
100
|
+
{
|
|
101
|
+
policy_id: 'policy-gateway-1',
|
|
102
|
+
name: 'Gateway Policy',
|
|
103
|
+
status: 'POLICY_STATUS_ACTIVE',
|
|
104
|
+
priority: 7,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
total_count: 1,
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
const policies = await client.list();
|
|
111
|
+
expect(policies).toEqual([
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
id: 'policy-gateway-1',
|
|
114
|
+
name: 'Gateway Policy',
|
|
115
|
+
policy_type: 'governance',
|
|
116
|
+
rules: {},
|
|
117
|
+
enabled: true,
|
|
118
|
+
status: 'ACTIVE',
|
|
119
|
+
priority: 7,
|
|
120
|
+
}),
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
75
123
|
it('should throw PolicyClientError on failure', async () => {
|
|
76
124
|
mockFetch.mockResolvedValueOnce({
|
|
77
125
|
ok: false,
|
|
@@ -95,13 +143,19 @@ describe('PolicyClient', () => {
|
|
|
95
143
|
id: '123',
|
|
96
144
|
name: 'Test Policy',
|
|
97
145
|
policy_type: 'cost',
|
|
146
|
+
rules: {},
|
|
147
|
+
enabled: true,
|
|
148
|
+
priority: 0,
|
|
149
|
+
status: 'ACTIVE',
|
|
150
|
+
created_at: '',
|
|
151
|
+
updated_at: '',
|
|
98
152
|
};
|
|
99
153
|
mockFetch.mockResolvedValueOnce({
|
|
100
154
|
ok: true,
|
|
101
155
|
json: async () => mockPolicy,
|
|
102
156
|
});
|
|
103
157
|
const policy = await client.get('123');
|
|
104
|
-
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/policies/123`, expect.objectContaining({ method: 'GET' }));
|
|
158
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/policies/123`, expect.objectContaining({ method: 'GET' }));
|
|
105
159
|
expect(policy).toEqual(mockPolicy);
|
|
106
160
|
});
|
|
107
161
|
it('should throw on 404', async () => {
|
|
@@ -120,13 +174,21 @@ describe('PolicyClient', () => {
|
|
|
120
174
|
policy_type: 'cost',
|
|
121
175
|
rules: { max_cost: 100 },
|
|
122
176
|
};
|
|
123
|
-
const createdPolicy = {
|
|
177
|
+
const createdPolicy = {
|
|
178
|
+
id: '456',
|
|
179
|
+
...newPolicy,
|
|
180
|
+
enabled: true,
|
|
181
|
+
priority: 0,
|
|
182
|
+
status: 'ACTIVE',
|
|
183
|
+
created_at: '',
|
|
184
|
+
updated_at: '',
|
|
185
|
+
};
|
|
124
186
|
mockFetch.mockResolvedValueOnce({
|
|
125
187
|
ok: true,
|
|
126
188
|
json: async () => createdPolicy,
|
|
127
189
|
});
|
|
128
190
|
const result = await client.create(newPolicy);
|
|
129
|
-
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/policies`, expect.objectContaining({
|
|
191
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/policies`, expect.objectContaining({
|
|
130
192
|
method: 'POST',
|
|
131
193
|
body: JSON.stringify(newPolicy),
|
|
132
194
|
}));
|
|
@@ -159,7 +221,7 @@ describe('PolicyClient', () => {
|
|
|
159
221
|
json: async () => ({ id: '123', ...updates }),
|
|
160
222
|
});
|
|
161
223
|
const result = await client.update('123', updates);
|
|
162
|
-
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/policies/123`, expect.objectContaining({
|
|
224
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/policies/123`, expect.objectContaining({
|
|
163
225
|
method: 'PATCH',
|
|
164
226
|
body: JSON.stringify(updates),
|
|
165
227
|
}));
|
|
@@ -173,7 +235,7 @@ describe('PolicyClient', () => {
|
|
|
173
235
|
json: async () => ({}),
|
|
174
236
|
});
|
|
175
237
|
await expect(client.delete('123')).resolves.toBeUndefined();
|
|
176
|
-
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/policies/123`, expect.objectContaining({ method: 'DELETE' }));
|
|
238
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/policies/123`, expect.objectContaining({ method: 'DELETE' }));
|
|
177
239
|
});
|
|
178
240
|
});
|
|
179
241
|
describe('setEnabled', () => {
|
|
@@ -183,7 +245,7 @@ describe('PolicyClient', () => {
|
|
|
183
245
|
json: async () => ({ id: '123', enabled: true }),
|
|
184
246
|
});
|
|
185
247
|
const result = await client.setEnabled('123', true);
|
|
186
|
-
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/policies/123`, expect.objectContaining({
|
|
248
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/policies/123`, expect.objectContaining({
|
|
187
249
|
method: 'PATCH',
|
|
188
250
|
body: JSON.stringify({ enabled: true }),
|
|
189
251
|
}));
|
|
@@ -586,6 +648,59 @@ describe('BudgetClient', () => {
|
|
|
586
648
|
});
|
|
587
649
|
});
|
|
588
650
|
});
|
|
651
|
+
describe('EnvelopeClient', () => {
|
|
652
|
+
const baseUrl = 'http://localhost:3000';
|
|
653
|
+
let client;
|
|
654
|
+
beforeEach(() => {
|
|
655
|
+
jest.clearAllMocks();
|
|
656
|
+
client = new clients_1.EnvelopeClient({ baseUrl, apiKey: 'test-key' });
|
|
657
|
+
});
|
|
658
|
+
it('should create envelope through the REST gateway path', async () => {
|
|
659
|
+
mockFetch.mockResolvedValueOnce({
|
|
660
|
+
ok: true,
|
|
661
|
+
json: async () => ({
|
|
662
|
+
envelope_id: 'env-1',
|
|
663
|
+
status: 'ENVELOPE_STATUS_AUTHORIZED',
|
|
664
|
+
}),
|
|
665
|
+
});
|
|
666
|
+
const envelope = await client.create({
|
|
667
|
+
adapter_type: 'typescript-sdk',
|
|
668
|
+
workflow_id: 'quickstart-agent',
|
|
669
|
+
metadata: { source: 'test' },
|
|
670
|
+
});
|
|
671
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/envelopes/create`, expect.objectContaining({ method: 'POST' }));
|
|
672
|
+
expect(envelope.envelope_id).toBe('env-1');
|
|
673
|
+
});
|
|
674
|
+
it('should update envelope status through the REST gateway path', async () => {
|
|
675
|
+
mockFetch.mockResolvedValueOnce({
|
|
676
|
+
ok: true,
|
|
677
|
+
json: async () => ({
|
|
678
|
+
envelope_id: 'env-1',
|
|
679
|
+
status: 'ENVELOPE_STATUS_COMPLETED',
|
|
680
|
+
}),
|
|
681
|
+
});
|
|
682
|
+
const envelope = await client.updateStatus({
|
|
683
|
+
envelope_id: 'env-1',
|
|
684
|
+
status: 'COMPLETED',
|
|
685
|
+
error_message: 'quickstart complete',
|
|
686
|
+
});
|
|
687
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/envelopes/status`, expect.objectContaining({ method: 'POST' }));
|
|
688
|
+
expect(envelope.status).toBe('ENVELOPE_STATUS_COMPLETED');
|
|
689
|
+
});
|
|
690
|
+
it('should fetch one envelope through the REST gateway query path', async () => {
|
|
691
|
+
mockFetch.mockResolvedValueOnce({
|
|
692
|
+
ok: true,
|
|
693
|
+
json: async () => ({
|
|
694
|
+
envelope_id: 'env-1',
|
|
695
|
+
status: 'ENVELOPE_STATUS_RUNNING',
|
|
696
|
+
metadata: {},
|
|
697
|
+
}),
|
|
698
|
+
});
|
|
699
|
+
const envelope = await client.get('env-1');
|
|
700
|
+
expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/api/v1/envelopes/get?envelope_id=env-1`, expect.objectContaining({ method: 'GET' }));
|
|
701
|
+
expect(envelope.envelope_id).toBe('env-1');
|
|
702
|
+
});
|
|
703
|
+
});
|
|
589
704
|
describe('Error classes', () => {
|
|
590
705
|
describe('PolicyClientError', () => {
|
|
591
706
|
it('should have correct name and properties', () => {
|
package/dist/clients/agent.js
CHANGED
|
@@ -42,21 +42,27 @@ const protoLoader = __importStar(require("@grpc/proto-loader"));
|
|
|
42
42
|
const util_1 = require("util");
|
|
43
43
|
const path_1 = __importDefault(require("path"));
|
|
44
44
|
const PROTO_PATH = path_1.default.join(__dirname, '../../proto/fulcrum/agent/v1/agent_service.proto');
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
45
|
+
let agentV1;
|
|
46
|
+
function getAgentV1() {
|
|
47
|
+
if (!agentV1) {
|
|
48
|
+
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
|
49
|
+
keepCase: true,
|
|
50
|
+
longs: String,
|
|
51
|
+
enums: String,
|
|
52
|
+
defaults: true,
|
|
53
|
+
oneofs: true,
|
|
54
|
+
});
|
|
55
|
+
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
|
56
|
+
const fulcrumPackage = protoDescriptor.fulcrum;
|
|
57
|
+
const agentPackage = fulcrumPackage.agent;
|
|
58
|
+
agentV1 = agentPackage.v1;
|
|
59
|
+
}
|
|
60
|
+
return agentV1;
|
|
61
|
+
}
|
|
56
62
|
class AgentClient {
|
|
57
63
|
constructor(address, credentials) {
|
|
58
64
|
const creds = credentials || grpc.credentials.createInsecure();
|
|
59
|
-
this.client = new
|
|
65
|
+
this.client = new (getAgentV1()).AgentService(address, creds);
|
|
60
66
|
}
|
|
61
67
|
/**
|
|
62
68
|
* Creates a new agent for the authenticated tenant.
|
package/dist/clients/approval.js
CHANGED
|
@@ -20,7 +20,7 @@ class ApprovalClient {
|
|
|
20
20
|
'Content-Type': 'application/json',
|
|
21
21
|
};
|
|
22
22
|
if (this.apiKey) {
|
|
23
|
-
headers['
|
|
23
|
+
headers['X-API-Key'] = this.apiKey;
|
|
24
24
|
}
|
|
25
25
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
26
26
|
method,
|
|
@@ -30,7 +30,20 @@ class ApprovalClient {
|
|
|
30
30
|
});
|
|
31
31
|
if (!response.ok) {
|
|
32
32
|
const errorData = (await response.json().catch(() => ({})));
|
|
33
|
-
|
|
33
|
+
const serverMessage = errorData.error || errorData.message || errorData.detail || '';
|
|
34
|
+
let errorMessage;
|
|
35
|
+
if (response.status === 401) {
|
|
36
|
+
errorMessage = this.apiKey
|
|
37
|
+
? `Authentication failed: ${serverMessage || 'invalid API key'}. Verify your API key is correct and active. Check your keys in the Fulcrum dashboard under Settings > API Keys.`
|
|
38
|
+
: 'Authentication required: no API key configured. Pass apiKey when creating the client. Generate a key in the Fulcrum dashboard under Settings > API Keys.';
|
|
39
|
+
}
|
|
40
|
+
else if (response.status === 403) {
|
|
41
|
+
errorMessage = `Insufficient permissions: ${serverMessage || 'access denied'}. Your API key does not have the required scopes. Update key permissions in the Fulcrum dashboard under Settings > API Keys.`;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
errorMessage = serverMessage || `Request failed with status ${response.status}`;
|
|
45
|
+
}
|
|
46
|
+
throw new ApprovalClientError(errorMessage, response.status);
|
|
34
47
|
}
|
|
35
48
|
return (await response.json());
|
|
36
49
|
}
|
package/dist/clients/budget.js
CHANGED
|
@@ -20,7 +20,7 @@ class BudgetClient {
|
|
|
20
20
|
'Content-Type': 'application/json',
|
|
21
21
|
};
|
|
22
22
|
if (this.apiKey) {
|
|
23
|
-
headers['
|
|
23
|
+
headers['X-API-Key'] = this.apiKey;
|
|
24
24
|
}
|
|
25
25
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
26
26
|
method,
|
|
@@ -30,7 +30,20 @@ class BudgetClient {
|
|
|
30
30
|
});
|
|
31
31
|
if (!response.ok) {
|
|
32
32
|
const errorData = (await response.json().catch(() => ({})));
|
|
33
|
-
|
|
33
|
+
const serverMessage = errorData.error || errorData.message || errorData.detail || '';
|
|
34
|
+
let errorMessage;
|
|
35
|
+
if (response.status === 401) {
|
|
36
|
+
errorMessage = this.apiKey
|
|
37
|
+
? `Authentication failed: ${serverMessage || 'invalid API key'}. Verify your API key is correct and active. Check your keys in the Fulcrum dashboard under Settings > API Keys.`
|
|
38
|
+
: 'Authentication required: no API key configured. Pass apiKey when creating the client. Generate a key in the Fulcrum dashboard under Settings > API Keys.';
|
|
39
|
+
}
|
|
40
|
+
else if (response.status === 403) {
|
|
41
|
+
errorMessage = `Insufficient permissions: ${serverMessage || 'access denied'}. Your API key does not have the required scopes. Update key permissions in the Fulcrum dashboard under Settings > API Keys.`;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
errorMessage = serverMessage || `Request failed with status ${response.status}`;
|
|
45
|
+
}
|
|
46
|
+
throw new BudgetClientError(errorMessage, response.status);
|
|
34
47
|
}
|
|
35
48
|
return (await response.json());
|
|
36
49
|
}
|
|
@@ -20,7 +20,7 @@ class CheckpointClient {
|
|
|
20
20
|
'Content-Type': 'application/json',
|
|
21
21
|
};
|
|
22
22
|
if (this.apiKey) {
|
|
23
|
-
headers['
|
|
23
|
+
headers['X-API-Key'] = this.apiKey;
|
|
24
24
|
}
|
|
25
25
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
26
26
|
method,
|
|
@@ -30,7 +30,20 @@ class CheckpointClient {
|
|
|
30
30
|
});
|
|
31
31
|
if (!response.ok) {
|
|
32
32
|
const errorData = (await response.json().catch(() => ({})));
|
|
33
|
-
|
|
33
|
+
const serverMessage = errorData.error || errorData.message || errorData.detail || '';
|
|
34
|
+
let errorMessage;
|
|
35
|
+
if (response.status === 401) {
|
|
36
|
+
errorMessage = this.apiKey
|
|
37
|
+
? `Authentication failed: ${serverMessage || 'invalid API key'}. Verify your API key is correct and active. Check your keys in the Fulcrum dashboard under Settings > API Keys.`
|
|
38
|
+
: 'Authentication required: no API key configured. Pass apiKey when creating the client. Generate a key in the Fulcrum dashboard under Settings > API Keys.';
|
|
39
|
+
}
|
|
40
|
+
else if (response.status === 403) {
|
|
41
|
+
errorMessage = `Insufficient permissions: ${serverMessage || 'access denied'}. Your API key does not have the required scopes. Update key permissions in the Fulcrum dashboard under Settings > API Keys.`;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
errorMessage = serverMessage || `Request failed with status ${response.status}`;
|
|
45
|
+
}
|
|
46
|
+
throw new CheckpointClientError(errorMessage, response.status);
|
|
34
47
|
}
|
|
35
48
|
const text = await response.text();
|
|
36
49
|
return text ? JSON.parse(text) : {};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Works with the Fulcrum API for managing execution envelopes that wrap AI agent operations.
|
|
5
5
|
*/
|
|
6
|
-
export type EnvelopeStatus = 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT';
|
|
6
|
+
export type EnvelopeStatus = 'PENDING' | 'AUTHORIZED' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TERMINATED' | 'TIMEOUT' | 'ENVELOPE_STATUS_AUTHORIZED' | 'ENVELOPE_STATUS_RUNNING' | 'ENVELOPE_STATUS_COMPLETED' | 'ENVELOPE_STATUS_FAILED' | 'ENVELOPE_STATUS_TERMINATED';
|
|
7
7
|
export interface EnvelopeData {
|
|
8
8
|
envelope_id: string;
|
|
9
9
|
tenant_id: string;
|
|
@@ -20,9 +20,9 @@ export interface EnvelopeData {
|
|
|
20
20
|
error_message?: string;
|
|
21
21
|
}
|
|
22
22
|
export interface CreateEnvelopeRequest {
|
|
23
|
-
tenant_id
|
|
24
|
-
adapter_type
|
|
25
|
-
workflow_id
|
|
23
|
+
tenant_id?: string;
|
|
24
|
+
adapter_type?: string;
|
|
25
|
+
workflow_id?: string;
|
|
26
26
|
budget_id?: string;
|
|
27
27
|
metadata?: Record<string, string>;
|
|
28
28
|
}
|
|
@@ -54,6 +54,7 @@ export declare class EnvelopeClient {
|
|
|
54
54
|
private timeout;
|
|
55
55
|
constructor(options: EnvelopeClientOptions);
|
|
56
56
|
private request;
|
|
57
|
+
private unwrapEnvelope;
|
|
57
58
|
/**
|
|
58
59
|
* Create a new execution envelope.
|
|
59
60
|
*/
|
package/dist/clients/envelope.js
CHANGED
|
@@ -43,8 +43,21 @@ class EnvelopeClient {
|
|
|
43
43
|
signal: controller.signal,
|
|
44
44
|
});
|
|
45
45
|
if (!response.ok) {
|
|
46
|
-
const
|
|
47
|
-
|
|
46
|
+
const errorData = await response.json().catch(() => ({}));
|
|
47
|
+
const serverMessage = errorData.error || errorData.message || errorData.detail || '';
|
|
48
|
+
let errorMessage;
|
|
49
|
+
if (response.status === 401) {
|
|
50
|
+
errorMessage = this.apiKey
|
|
51
|
+
? `Authentication failed: ${serverMessage || 'invalid API key'}. Verify your API key is correct and active. Check your keys in the Fulcrum dashboard under Settings > API Keys.`
|
|
52
|
+
: 'Authentication required: no API key configured. Pass apiKey when creating the client. Generate a key in the Fulcrum dashboard under Settings > API Keys.';
|
|
53
|
+
}
|
|
54
|
+
else if (response.status === 403) {
|
|
55
|
+
errorMessage = `Insufficient permissions: ${serverMessage || 'access denied'}. Your API key does not have the required scopes. Update key permissions in the Fulcrum dashboard under Settings > API Keys.`;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
errorMessage = serverMessage || `Request failed with status ${response.status}`;
|
|
59
|
+
}
|
|
60
|
+
throw new EnvelopeClientError(errorMessage, response.status, errorData);
|
|
48
61
|
}
|
|
49
62
|
return await response.json();
|
|
50
63
|
}
|
|
@@ -52,27 +65,43 @@ class EnvelopeClient {
|
|
|
52
65
|
clearTimeout(timeoutId);
|
|
53
66
|
}
|
|
54
67
|
}
|
|
68
|
+
unwrapEnvelope(response) {
|
|
69
|
+
return 'envelope' in response ? response.envelope : response;
|
|
70
|
+
}
|
|
55
71
|
/**
|
|
56
72
|
* Create a new execution envelope.
|
|
57
73
|
*/
|
|
58
74
|
async create(request) {
|
|
59
|
-
const
|
|
60
|
-
|
|
75
|
+
const metadata = { ...(request.metadata || {}) };
|
|
76
|
+
if (request.workflow_id) {
|
|
77
|
+
metadata.workflow_id = metadata.workflow_id || request.workflow_id;
|
|
78
|
+
}
|
|
79
|
+
const body = {
|
|
80
|
+
adapter_type: request.adapter_type || 'typescript-sdk',
|
|
81
|
+
...(request.budget_id ? { budget_id: request.budget_id } : {}),
|
|
82
|
+
metadata,
|
|
83
|
+
};
|
|
84
|
+
const response = await this.request('POST', '/api/v1/envelopes/create', body);
|
|
85
|
+
return this.unwrapEnvelope(response);
|
|
61
86
|
}
|
|
62
87
|
/**
|
|
63
88
|
* Get an envelope by ID.
|
|
64
89
|
*/
|
|
65
90
|
async get(envelopeId) {
|
|
66
|
-
const response = await this.request('GET',
|
|
67
|
-
return response
|
|
91
|
+
const response = await this.request('GET', '/api/v1/envelopes/get', undefined, { envelope_id: envelopeId });
|
|
92
|
+
return this.unwrapEnvelope(response);
|
|
68
93
|
}
|
|
69
94
|
/**
|
|
70
95
|
* Update the status of an envelope.
|
|
71
96
|
*/
|
|
72
97
|
async updateStatus(request) {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
const body = {
|
|
99
|
+
envelope_id: request.envelope_id,
|
|
100
|
+
status: request.status,
|
|
101
|
+
...(request.error_message ? { reason: request.error_message } : {}),
|
|
102
|
+
};
|
|
103
|
+
const response = await this.request('POST', '/api/v1/envelopes/status', body);
|
|
104
|
+
return this.unwrapEnvelope(response);
|
|
76
105
|
}
|
|
77
106
|
/**
|
|
78
107
|
* List envelopes with optional filters.
|
|
@@ -88,7 +117,7 @@ class EnvelopeClient {
|
|
|
88
117
|
params.workflow_id = request.workflow_id;
|
|
89
118
|
if (request.status)
|
|
90
119
|
params.status = request.status;
|
|
91
|
-
const response = await this.request('GET', '/v1/envelopes', undefined, params);
|
|
120
|
+
const response = await this.request('GET', '/api/v1/envelopes', undefined, params);
|
|
92
121
|
return response.envelopes;
|
|
93
122
|
}
|
|
94
123
|
}
|
|
@@ -43,8 +43,21 @@ class EventStoreClient {
|
|
|
43
43
|
signal: controller.signal,
|
|
44
44
|
});
|
|
45
45
|
if (!response.ok) {
|
|
46
|
-
const
|
|
47
|
-
|
|
46
|
+
const errorData = await response.json().catch(() => ({}));
|
|
47
|
+
const serverMessage = errorData.error || errorData.message || errorData.detail || '';
|
|
48
|
+
let errorMessage;
|
|
49
|
+
if (response.status === 401) {
|
|
50
|
+
errorMessage = this.apiKey
|
|
51
|
+
? `Authentication failed: ${serverMessage || 'invalid API key'}. Verify your API key is correct and active. Check your keys in the Fulcrum dashboard under Settings > API Keys.`
|
|
52
|
+
: 'Authentication required: no API key configured. Pass apiKey when creating the client. Generate a key in the Fulcrum dashboard under Settings > API Keys.';
|
|
53
|
+
}
|
|
54
|
+
else if (response.status === 403) {
|
|
55
|
+
errorMessage = `Insufficient permissions: ${serverMessage || 'access denied'}. Your API key does not have the required scopes. Update key permissions in the Fulcrum dashboard under Settings > API Keys.`;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
errorMessage = serverMessage || `Request failed with status ${response.status}`;
|
|
59
|
+
}
|
|
60
|
+
throw new EventStoreClientError(errorMessage, response.status, errorData);
|
|
48
61
|
}
|
|
49
62
|
return await response.json();
|
|
50
63
|
}
|