/
agent-hosting-provider.ts
134 lines (119 loc) · 4.14 KB
/
agent-hosting-provider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//
// Copyright 2023 DXOS.org
//
import { type Config } from '@dxos/config';
import { invariant } from '@dxos/invariant';
import { log } from '@dxos/log';
export type AgentHostingProvider = {
name: string;
baseURL: string;
username: string;
password?: string;
};
/**
* Cannot communicate with, or decode response from hosting provider.
*/
export class ProviderApiError extends Error {}
// TODO: Load from config or dynamically discover
const defaultConfig: AgentHostingProvider = {
name: 'default',
baseURL: 'http://localhost:8082/v1alpha1/',
username: 'dxos',
};
export interface AgentHostingProviderClient {
createAgent(invitationCode: string, identityKey: string): Promise<string>;
getAgent(agentID: string): Promise<string | null>;
destroyAgent(agentID: string): Promise<boolean>;
}
// Interface to REST API to manage agent deployments
// TODO(nf): for now API just simply returns created k8s CRD objects, define backend-agnostic API
export class EldonAgentHostingProviderClient implements AgentHostingProviderClient {
private readonly _config: AgentHostingProvider;
constructor(private readonly _clientConfig: Config) {
const runtimeAgentHostingConfig = this._clientConfig.get('runtime.services.agentHosting');
invariant(runtimeAgentHostingConfig, 'agentHosting config not found');
invariant(runtimeAgentHostingConfig.server, 'agentHosting server not found');
this._config = {
...defaultConfig,
baseURL: runtimeAgentHostingConfig.server,
password: this._clientConfig.get('runtime.app.env.DX_ELDON_PASSWORD'),
};
log.info('EldonAgentHostingProviderClient initialized', { config: this._config });
}
public requestInitWithCredentials(req: RequestInit): RequestInit {
return {
...req,
headers: {
...req.headers,
Authorization: 'Basic ' + Buffer.from(`${this._config.username}:${this._config.password}`).toString('base64'),
},
};
}
public async createAgent(invitationCode: string, identityKey: string) {
const res = await fetch(
new URL('agent', this._config.baseURL),
this.requestInitWithCredentials({
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
invitation: invitationCode,
identityKey,
}),
}),
);
try {
const agent = await res.json();
return agent.metadata.uid;
} catch (err) {
if (err instanceof TypeError) {
log.warn('failed to parse response from agent create', { res });
throw new ProviderApiError('failed to parse response from hosting provider');
}
log.warn('bad response from agent create', { res });
throw new ProviderApiError('bad response from hosting provider');
}
}
public async getAgent(agentID: string) {
const res = await fetch(
new URL('agent/' + agentID, this._config.baseURL),
this.requestInitWithCredentials({
method: 'GET',
}),
);
// TODO(nf): is Sentry logging this and causing the log message?
if (res.status === 404) {
return null;
}
if (res.status !== 200) {
log.warn('request to agent get failed', { res });
throw new ProviderApiError('bad response from hosting provider');
}
log.info('getAgent', { res });
try {
const agent = await res.json();
return agent.metadata.uid;
} catch (err) {
if (err instanceof TypeError) {
log.warn('failed to parse response from agent get', { err });
throw new ProviderApiError('failed to parse response from hosting provider');
}
log.warn('bad response from agent get', { res });
throw new ProviderApiError('bad response from hosting provider');
}
}
public async destroyAgent(agentID: string) {
const res = await fetch(
new URL('agent/' + agentID, this._config.baseURL),
this.requestInitWithCredentials({
method: 'DELETE',
}),
);
if (res.status === 204) {
return true;
}
log.warn('failed to send destroy request', { status: res.status, statusText: res.statusText });
throw new ProviderApiError('bad response from hosting provider');
}
}