@tonbo/cli 0.3.0 → 0.3.2
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 +1 -0
- package/dist/bin/tonbo.js +1258 -1215
- package/package.json +1 -1
package/dist/bin/tonbo.js
CHANGED
|
@@ -32,1246 +32,1293 @@ async function requestJson(fetcher, url, init = {}) {
|
|
|
32
32
|
return body;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// src/
|
|
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
|
-
listOrganizations(oauthToken) {
|
|
62
|
-
return requestJson(
|
|
63
|
-
this.fetcher,
|
|
64
|
-
`${this.accountOrigin}/api/cli/accounts`,
|
|
65
|
-
{
|
|
66
|
-
headers: { authorization: `Bearer ${oauthToken}` }
|
|
67
|
-
}
|
|
68
|
-
).then((body) => body.organizations);
|
|
69
|
-
}
|
|
70
|
-
createAgent(oauthToken, name, organizationId) {
|
|
71
|
-
return requestJson(
|
|
72
|
-
this.fetcher,
|
|
73
|
-
`${this.accountOrigin}/api/cli/agents`,
|
|
74
|
-
{
|
|
75
|
-
method: "POST",
|
|
76
|
-
headers: {
|
|
77
|
-
authorization: `Bearer ${oauthToken}`,
|
|
78
|
-
"content-type": "application/json"
|
|
79
|
-
},
|
|
80
|
-
body: JSON.stringify(organizationId ? { name, orgId: organizationId } : { name })
|
|
35
|
+
// src/contracts.ts
|
|
36
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
37
|
+
|
|
38
|
+
// src/generated/contracts.ts
|
|
39
|
+
var piAgentSchema = {
|
|
40
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
41
|
+
"$id": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json",
|
|
42
|
+
"title": "PI Agent v1",
|
|
43
|
+
"type": "object",
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"required": [
|
|
46
|
+
"runtime",
|
|
47
|
+
"driver"
|
|
48
|
+
],
|
|
49
|
+
"properties": {
|
|
50
|
+
"runtime": {
|
|
51
|
+
"const": "pi"
|
|
52
|
+
},
|
|
53
|
+
"secrets": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"maxItems": 32,
|
|
56
|
+
"uniqueItems": true,
|
|
57
|
+
"items": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"pattern": "^[A-Z_][A-Z0-9_]{0,127}$"
|
|
81
60
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
61
|
+
},
|
|
62
|
+
"driver": {
|
|
63
|
+
"oneOf": [
|
|
64
|
+
{
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"required": [
|
|
68
|
+
"kind"
|
|
69
|
+
],
|
|
70
|
+
"properties": {
|
|
71
|
+
"kind": {
|
|
72
|
+
"const": "native"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
93
75
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
76
|
+
{
|
|
77
|
+
"type": "object",
|
|
78
|
+
"additionalProperties": false,
|
|
79
|
+
"required": [
|
|
80
|
+
"kind",
|
|
81
|
+
"protocol",
|
|
82
|
+
"command"
|
|
83
|
+
],
|
|
84
|
+
"properties": {
|
|
85
|
+
"kind": {
|
|
86
|
+
"const": "command"
|
|
87
|
+
},
|
|
88
|
+
"protocol": {
|
|
89
|
+
"const": "pi-rpc-v1"
|
|
90
|
+
},
|
|
91
|
+
"command": {
|
|
92
|
+
"type": "array",
|
|
93
|
+
"minItems": 1,
|
|
94
|
+
"maxItems": 64,
|
|
95
|
+
"items": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"minLength": 1,
|
|
98
|
+
"maxLength": 1024
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
101
105
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
};
|
|
107
|
+
var agentServiceSchema = {
|
|
108
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
109
|
+
"$id": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json",
|
|
110
|
+
"title": "Tonbo Agent application service v1",
|
|
111
|
+
"type": "object",
|
|
112
|
+
"additionalProperties": false,
|
|
113
|
+
"required": [
|
|
114
|
+
"command"
|
|
115
|
+
],
|
|
116
|
+
"properties": {
|
|
117
|
+
"command": {
|
|
118
|
+
"type": "array",
|
|
119
|
+
"minItems": 1,
|
|
120
|
+
"maxItems": 64,
|
|
121
|
+
"items": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"minLength": 1,
|
|
124
|
+
"maxLength": 4096
|
|
113
125
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
headers: { authorization: `Bearer ${oauthToken}` }
|
|
126
|
+
},
|
|
127
|
+
"secrets": {
|
|
128
|
+
"type": "array",
|
|
129
|
+
"maxItems": 32,
|
|
130
|
+
"uniqueItems": true,
|
|
131
|
+
"items": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"pattern": "^[A-Z_][A-Z0-9_]{0,127}$"
|
|
123
134
|
}
|
|
124
|
-
|
|
135
|
+
},
|
|
136
|
+
"kubernetes": {
|
|
137
|
+
"$ref": "https://contracts.tonbo.dev/agents/kubernetes-profiles-v1.schema.json#/$defs/request"
|
|
138
|
+
}
|
|
125
139
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const prepared = await this.management("PUT", `${bundlesPath}/${bundle.sha256}`, token, {
|
|
141
|
-
format: descriptor.format,
|
|
142
|
-
size_bytes: descriptor.size_bytes
|
|
143
|
-
});
|
|
144
|
-
if (prepared.status === "upload") {
|
|
145
|
-
if (!prepared.upload_url) throw new Error("Tonbo did not return a source upload URL.");
|
|
146
|
-
if (!prepared.upload_headers || prepared.upload_headers["if-none-match"] !== "*")
|
|
147
|
-
throw new Error("Missing immutable source upload headers.");
|
|
148
|
-
const uploaded = await this.fetcher(prepared.upload_url, {
|
|
149
|
-
redirect: "error",
|
|
150
|
-
method: "PUT",
|
|
151
|
-
headers: prepared.upload_headers,
|
|
152
|
-
body: new Blob([new Uint8Array(bundle.bytes)])
|
|
153
|
-
});
|
|
154
|
-
let uploadError = null;
|
|
155
|
-
if (!uploaded.ok) {
|
|
156
|
-
uploadError = new Error(`Source upload failed with HTTP ${uploaded.status}.`);
|
|
157
|
-
}
|
|
158
|
-
try {
|
|
159
|
-
await this.management("POST", `${bundlesPath}/${bundle.sha256}/complete`, token, {
|
|
160
|
-
format: bundle.format,
|
|
161
|
-
size_bytes: bundle.size_bytes
|
|
162
|
-
});
|
|
163
|
-
} catch (error) {
|
|
164
|
-
if (uploadError)
|
|
165
|
-
throw new AggregateError([uploadError, error], "Source bundle upload did not complete.");
|
|
166
|
-
throw error;
|
|
140
|
+
};
|
|
141
|
+
var kubernetesProfilesSchema = {
|
|
142
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
143
|
+
"$id": "https://contracts.tonbo.dev/agents/kubernetes-profiles-v1.schema.json",
|
|
144
|
+
"title": "Tonbo managed Kubernetes profiles v1",
|
|
145
|
+
"$defs": {
|
|
146
|
+
"request": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"required": [
|
|
150
|
+
"profile"
|
|
151
|
+
],
|
|
152
|
+
"properties": {
|
|
153
|
+
"profile": false
|
|
167
154
|
}
|
|
168
155
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
"GET",
|
|
202
|
-
`/v1/agents/${agentId}/deployments/${deploymentId}`,
|
|
203
|
-
token
|
|
204
|
-
).then((response) => response.data);
|
|
205
|
-
}
|
|
206
|
-
getProduction(agentId, token) {
|
|
207
|
-
return this.management(
|
|
208
|
-
"GET",
|
|
209
|
-
`/v1/agents/${agentId}/production`,
|
|
210
|
-
token
|
|
211
|
-
).then(
|
|
212
|
-
(response) => response.data,
|
|
213
|
-
(error) => {
|
|
214
|
-
if (error.status === 404) return null;
|
|
215
|
-
throw error;
|
|
156
|
+
},
|
|
157
|
+
"x-tonbo-profiles": {}
|
|
158
|
+
};
|
|
159
|
+
var declarationSchema = {
|
|
160
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
161
|
+
"$id": "https://contracts.tonbo.dev/agents/tonbo-declaration-v2.schema.json",
|
|
162
|
+
"title": "Tonbo Agent declaration v2",
|
|
163
|
+
"type": "object",
|
|
164
|
+
"additionalProperties": false,
|
|
165
|
+
"required": [
|
|
166
|
+
"version",
|
|
167
|
+
"harness"
|
|
168
|
+
],
|
|
169
|
+
"properties": {
|
|
170
|
+
"version": {
|
|
171
|
+
"const": 2
|
|
172
|
+
},
|
|
173
|
+
"inference": {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"additionalProperties": false,
|
|
176
|
+
"default": {
|
|
177
|
+
"model": "claude-sonnet-4-5"
|
|
178
|
+
},
|
|
179
|
+
"required": [
|
|
180
|
+
"model"
|
|
181
|
+
],
|
|
182
|
+
"properties": {
|
|
183
|
+
"model": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"minLength": 1,
|
|
186
|
+
"maxLength": 160
|
|
187
|
+
}
|
|
216
188
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
token,
|
|
235
|
-
turnId = randomUUID()
|
|
236
|
-
}) {
|
|
237
|
-
const agentPath = `/v1/agents/${agentId}`;
|
|
238
|
-
const production = await this.getProduction(agentId, token);
|
|
239
|
-
if (!production)
|
|
240
|
-
throw new Error("Agent has no Production Deployment; run `tonbo deploy` first.");
|
|
241
|
-
if (production.observed_state !== "running")
|
|
242
|
-
throw new Error(
|
|
243
|
-
`Production is ${production.observed_state}${production.last_error ? ` (${production.last_error})` : ""}; wait for it to be running.`
|
|
244
|
-
);
|
|
245
|
-
const session = sessionId ? { id: sessionId } : (await this.management("POST", `${agentPath}/sessions`, token, {
|
|
246
|
-
deployment_id: production.deployment_id
|
|
247
|
-
})).data;
|
|
248
|
-
const turn = (await this.management(
|
|
249
|
-
"POST",
|
|
250
|
-
`${agentPath}/sessions/${session.id}/turns`,
|
|
251
|
-
token,
|
|
252
|
-
{ prompt },
|
|
253
|
-
turnId
|
|
254
|
-
)).data;
|
|
255
|
-
return { production, session, turn };
|
|
256
|
-
}
|
|
257
|
-
turnEvents({
|
|
258
|
-
after = 0,
|
|
259
|
-
agentId,
|
|
260
|
-
sessionId,
|
|
261
|
-
token,
|
|
262
|
-
turnId
|
|
263
|
-
}) {
|
|
264
|
-
return this.management(
|
|
265
|
-
"GET",
|
|
266
|
-
`/v1/agents/${agentId}/sessions/${sessionId}/turns/${turnId}/events?after=${after}`,
|
|
267
|
-
token
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
listAgentSecrets(agentId, token) {
|
|
271
|
-
return this.management(
|
|
272
|
-
"GET",
|
|
273
|
-
`/v1/agents/${agentId}/secrets`,
|
|
274
|
-
token
|
|
275
|
-
).then((body) => body.data);
|
|
276
|
-
}
|
|
277
|
-
setAgentSecret(agentId, name, value, token) {
|
|
278
|
-
return this.management(
|
|
279
|
-
"PUT",
|
|
280
|
-
`/v1/agents/${agentId}/secrets/${encodeURIComponent(name)}`,
|
|
281
|
-
token,
|
|
282
|
-
{ value }
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
deleteAgentSecret(agentId, name, token) {
|
|
286
|
-
return this.management(
|
|
287
|
-
"DELETE",
|
|
288
|
-
`/v1/agents/${agentId}/secrets/${encodeURIComponent(name)}`,
|
|
289
|
-
token
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
management(method, path6, token, body, idempotencyKey) {
|
|
293
|
-
return requestJson(this.fetcher, `${this.managementOrigin}${path6}`, {
|
|
294
|
-
method,
|
|
295
|
-
headers: {
|
|
296
|
-
authorization: `Bearer ${token}`,
|
|
297
|
-
...body === void 0 ? {} : {
|
|
298
|
-
"content-type": "application/json",
|
|
299
|
-
"idempotency-key": idempotencyKey ?? randomUUID()
|
|
189
|
+
},
|
|
190
|
+
"build": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"additionalProperties": false,
|
|
193
|
+
"required": [
|
|
194
|
+
"command"
|
|
195
|
+
],
|
|
196
|
+
"properties": {
|
|
197
|
+
"command": {
|
|
198
|
+
"type": "array",
|
|
199
|
+
"minItems": 1,
|
|
200
|
+
"maxItems": 64,
|
|
201
|
+
"items": {
|
|
202
|
+
"type": "string",
|
|
203
|
+
"minLength": 1,
|
|
204
|
+
"maxLength": 1024
|
|
205
|
+
}
|
|
300
206
|
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"service": {
|
|
210
|
+
"$ref": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json"
|
|
211
|
+
},
|
|
212
|
+
"agent": {
|
|
213
|
+
"$ref": "https://contracts.tonbo.dev/agents/agent-name-v1.json#/$defs/publicHostname"
|
|
214
|
+
},
|
|
215
|
+
"harness": {
|
|
216
|
+
"$ref": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json"
|
|
217
|
+
}
|
|
304
218
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
219
|
+
};
|
|
220
|
+
var deploymentSchema = {
|
|
221
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
222
|
+
"$id": "https://contracts.tonbo.dev/agents/managed-deployment-v1.schema.json",
|
|
223
|
+
"title": "Managed Agent Deployment v1",
|
|
224
|
+
"type": "object",
|
|
225
|
+
"additionalProperties": false,
|
|
226
|
+
"required": [
|
|
227
|
+
"version",
|
|
228
|
+
"agent",
|
|
229
|
+
"source",
|
|
230
|
+
"inference"
|
|
231
|
+
],
|
|
232
|
+
"properties": {
|
|
233
|
+
"version": {
|
|
234
|
+
"const": 1
|
|
235
|
+
},
|
|
236
|
+
"agent": {
|
|
237
|
+
"$ref": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json"
|
|
238
|
+
},
|
|
239
|
+
"source": {
|
|
240
|
+
"type": "object",
|
|
241
|
+
"additionalProperties": false,
|
|
242
|
+
"required": [
|
|
243
|
+
"format",
|
|
244
|
+
"sha256",
|
|
245
|
+
"size_bytes"
|
|
246
|
+
],
|
|
247
|
+
"properties": {
|
|
248
|
+
"format": {
|
|
249
|
+
"const": "tar-v1"
|
|
250
|
+
},
|
|
251
|
+
"sha256": {
|
|
252
|
+
"type": "string",
|
|
253
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
254
|
+
},
|
|
255
|
+
"size_bytes": {
|
|
256
|
+
"type": "integer",
|
|
257
|
+
"minimum": 1,
|
|
258
|
+
"maximum": 67108864
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"inference": {
|
|
263
|
+
"type": "object",
|
|
264
|
+
"additionalProperties": false,
|
|
265
|
+
"required": [
|
|
266
|
+
"model"
|
|
267
|
+
],
|
|
268
|
+
"properties": {
|
|
269
|
+
"model": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"minLength": 1,
|
|
272
|
+
"maxLength": 160
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
"service": {
|
|
277
|
+
"$ref": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json"
|
|
278
|
+
}
|
|
319
279
|
}
|
|
320
280
|
};
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
281
|
+
var agentNameContract = {
|
|
282
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
283
|
+
"$id": "https://contracts.tonbo.dev/agents/agent-name-v1.json",
|
|
284
|
+
"x-tonbo-version": 1,
|
|
285
|
+
"x-tonbo-public-hostname-apex": "tonbo.sh",
|
|
286
|
+
"x-tonbo-public-hostname-template": "<agent>-<organization>-<word>-<word>.tonbo.sh",
|
|
287
|
+
"$defs": {
|
|
288
|
+
"name": {
|
|
289
|
+
"type": "string",
|
|
290
|
+
"minLength": 1,
|
|
291
|
+
"maxLength": 40,
|
|
292
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
293
|
+
"not": {
|
|
294
|
+
"enum": [
|
|
295
|
+
"api",
|
|
296
|
+
"artifacts",
|
|
297
|
+
"auth",
|
|
298
|
+
"inference",
|
|
299
|
+
"network-health",
|
|
300
|
+
"sandbox-control",
|
|
301
|
+
"status",
|
|
302
|
+
"streams",
|
|
303
|
+
"www"
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"publicHostname": {
|
|
308
|
+
"type": "string",
|
|
309
|
+
"minLength": 16,
|
|
310
|
+
"maxLength": 72,
|
|
311
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)+\\.tonbo\\.sh$",
|
|
312
|
+
"not": {
|
|
313
|
+
"enum": [
|
|
314
|
+
"api.tonbo.sh",
|
|
315
|
+
"artifacts.tonbo.sh",
|
|
316
|
+
"auth.tonbo.sh",
|
|
317
|
+
"inference.tonbo.sh",
|
|
318
|
+
"network-health.tonbo.sh",
|
|
319
|
+
"sandbox-control.tonbo.sh",
|
|
320
|
+
"status.tonbo.sh",
|
|
321
|
+
"streams.tonbo.sh",
|
|
322
|
+
"www.tonbo.sh"
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
"hostnameWord": {
|
|
327
|
+
"type": "string",
|
|
328
|
+
"enum": [
|
|
329
|
+
"arch",
|
|
330
|
+
"bark",
|
|
331
|
+
"beam",
|
|
332
|
+
"bell",
|
|
333
|
+
"bird",
|
|
334
|
+
"blue",
|
|
335
|
+
"boat",
|
|
336
|
+
"bold",
|
|
337
|
+
"book",
|
|
338
|
+
"calm",
|
|
339
|
+
"cave",
|
|
340
|
+
"clay",
|
|
341
|
+
"coal",
|
|
342
|
+
"cool",
|
|
343
|
+
"cove",
|
|
344
|
+
"dawn",
|
|
345
|
+
"deer",
|
|
346
|
+
"dove",
|
|
347
|
+
"dune",
|
|
348
|
+
"dusk",
|
|
349
|
+
"echo",
|
|
350
|
+
"fern",
|
|
351
|
+
"fire",
|
|
352
|
+
"flax",
|
|
353
|
+
"flow",
|
|
354
|
+
"foam",
|
|
355
|
+
"fawn",
|
|
356
|
+
"frog",
|
|
357
|
+
"gate",
|
|
358
|
+
"glow",
|
|
359
|
+
"gold",
|
|
360
|
+
"gray",
|
|
361
|
+
"gulf",
|
|
362
|
+
"hail",
|
|
363
|
+
"halo",
|
|
364
|
+
"hare",
|
|
365
|
+
"hawk",
|
|
366
|
+
"hill",
|
|
367
|
+
"iris",
|
|
368
|
+
"jade",
|
|
369
|
+
"kite",
|
|
370
|
+
"lake",
|
|
371
|
+
"lamb",
|
|
372
|
+
"leaf",
|
|
373
|
+
"lime",
|
|
374
|
+
"lion",
|
|
375
|
+
"lobe",
|
|
376
|
+
"loft",
|
|
377
|
+
"loom",
|
|
378
|
+
"luma",
|
|
379
|
+
"lynx",
|
|
380
|
+
"mint",
|
|
381
|
+
"mist",
|
|
382
|
+
"moon",
|
|
383
|
+
"moss",
|
|
384
|
+
"navy",
|
|
385
|
+
"nest",
|
|
386
|
+
"nova",
|
|
387
|
+
"oak",
|
|
388
|
+
"ocean",
|
|
389
|
+
"onyx",
|
|
390
|
+
"opal",
|
|
391
|
+
"otter",
|
|
392
|
+
"palm",
|
|
393
|
+
"peak",
|
|
394
|
+
"pine",
|
|
395
|
+
"pond",
|
|
396
|
+
"pool",
|
|
397
|
+
"rain",
|
|
398
|
+
"reed",
|
|
399
|
+
"reef",
|
|
400
|
+
"rice",
|
|
401
|
+
"ring",
|
|
402
|
+
"rise",
|
|
403
|
+
"road",
|
|
404
|
+
"rock",
|
|
405
|
+
"rose",
|
|
406
|
+
"ruby",
|
|
407
|
+
"sage",
|
|
408
|
+
"sand",
|
|
409
|
+
"seal",
|
|
410
|
+
"seed",
|
|
411
|
+
"silk",
|
|
412
|
+
"sky",
|
|
413
|
+
"snow",
|
|
414
|
+
"soft",
|
|
415
|
+
"star",
|
|
416
|
+
"stem",
|
|
417
|
+
"stone",
|
|
418
|
+
"surf",
|
|
419
|
+
"swan",
|
|
420
|
+
"teal",
|
|
421
|
+
"tide",
|
|
422
|
+
"tree",
|
|
423
|
+
"vale",
|
|
424
|
+
"vine",
|
|
425
|
+
"wave",
|
|
426
|
+
"west",
|
|
427
|
+
"wind",
|
|
428
|
+
"wing",
|
|
429
|
+
"wolf",
|
|
430
|
+
"wood",
|
|
431
|
+
"wren",
|
|
432
|
+
"yarn",
|
|
433
|
+
"zest"
|
|
434
|
+
]
|
|
435
|
+
}
|
|
347
436
|
}
|
|
348
437
|
};
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
438
|
+
var regionDiscoverySchema = {
|
|
439
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
440
|
+
"$id": "https://contracts.tonbo.dev/regions/discovery-v1.schema.json",
|
|
441
|
+
"title": "Regional allocation discovery v1",
|
|
442
|
+
"type": "object",
|
|
443
|
+
"additionalProperties": false,
|
|
444
|
+
"required": [
|
|
445
|
+
"version",
|
|
446
|
+
"regions"
|
|
447
|
+
],
|
|
448
|
+
"properties": {
|
|
449
|
+
"version": {
|
|
450
|
+
"const": 1
|
|
451
|
+
},
|
|
452
|
+
"regions": {
|
|
453
|
+
"type": "array",
|
|
454
|
+
"items": {
|
|
455
|
+
"type": "object",
|
|
456
|
+
"additionalProperties": false,
|
|
457
|
+
"required": [
|
|
458
|
+
"id",
|
|
459
|
+
"display_name",
|
|
460
|
+
"allocation_enabled"
|
|
461
|
+
],
|
|
462
|
+
"properties": {
|
|
463
|
+
"id": {
|
|
464
|
+
"type": "string",
|
|
465
|
+
"pattern": "^[a-z]{2}(-gov)?-[a-z]+-[0-9]+$"
|
|
466
|
+
},
|
|
467
|
+
"display_name": {
|
|
468
|
+
"type": "string",
|
|
469
|
+
"minLength": 1
|
|
470
|
+
},
|
|
471
|
+
"allocation_enabled": {
|
|
472
|
+
"type": "boolean"
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
360
477
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// src/contracts.ts
|
|
481
|
+
var ajv = new Ajv2020({ allErrors: true, useDefaults: true });
|
|
482
|
+
ajv.addKeyword({ keyword: "x-tonbo-profiles" });
|
|
483
|
+
ajv.addKeyword({ keyword: "x-tonbo-version" });
|
|
484
|
+
ajv.addKeyword({ keyword: "x-tonbo-public-hostname-apex" });
|
|
485
|
+
ajv.addKeyword({ keyword: "x-tonbo-public-hostname-template" });
|
|
486
|
+
ajv.addSchema(kubernetesProfilesSchema);
|
|
487
|
+
ajv.addSchema(piAgentSchema);
|
|
488
|
+
ajv.addSchema(agentNameContract);
|
|
489
|
+
ajv.addSchema(agentServiceSchema);
|
|
490
|
+
var validateDeclaration = ajv.compile(declarationSchema);
|
|
491
|
+
var validateDeploymentSpec = ajv.compile(deploymentSchema);
|
|
492
|
+
var validateRegionDiscovery = ajv.compile(regionDiscoverySchema);
|
|
493
|
+
function parseRegionDiscovery(value) {
|
|
494
|
+
if (!validateRegionDiscovery(value)) {
|
|
495
|
+
throw new Error(validationMessage("Region discovery", validateRegionDiscovery.errors));
|
|
367
496
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
497
|
+
const result = value;
|
|
498
|
+
if (new Set(result.regions.map((region) => region.id)).size !== result.regions.length)
|
|
499
|
+
throw new Error("Region discovery contains duplicate region identities.");
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
502
|
+
function validationMessage(label, errors) {
|
|
503
|
+
const detail = errors?.map((error) => `${error.instancePath || "/"} ${error.message}`).join("; ");
|
|
504
|
+
return `${label} is invalid${detail ? `: ${detail}` : "."}`;
|
|
505
|
+
}
|
|
506
|
+
function parseDeclaration(value) {
|
|
507
|
+
const candidate = structuredClone(value);
|
|
508
|
+
if (!validateDeclaration(candidate)) {
|
|
509
|
+
throw new Error(validationMessage(".tonbo TOML", validateDeclaration.errors));
|
|
376
510
|
}
|
|
377
|
-
return
|
|
511
|
+
return candidate;
|
|
378
512
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
source.readText(PI_SETTINGS_PATH, MAX_HARNESS_CONFIG_BYTES)
|
|
383
|
-
]);
|
|
384
|
-
if (piSettings !== null) {
|
|
385
|
-
return {
|
|
386
|
-
version: 1,
|
|
387
|
-
instructions: { agentsMd },
|
|
388
|
-
harness: {
|
|
389
|
-
id: "pi",
|
|
390
|
-
reason: "pi_settings_found",
|
|
391
|
-
state: "identified"
|
|
392
|
-
},
|
|
393
|
-
pi: {
|
|
394
|
-
packageCount: parsePiPackageCount(piSettings),
|
|
395
|
-
settingsFound: true
|
|
396
|
-
}
|
|
397
|
-
};
|
|
513
|
+
function assertManagedDeploymentSpec(value) {
|
|
514
|
+
if (!validateDeploymentSpec(value)) {
|
|
515
|
+
throw new Error(validationMessage("Managed Deployment spec", validateDeploymentSpec.errors));
|
|
398
516
|
}
|
|
399
|
-
return {
|
|
400
|
-
version: 1,
|
|
401
|
-
instructions: { agentsMd },
|
|
402
|
-
harness: {
|
|
403
|
-
candidates: supportedHarnesses.map((harness) => harness.id),
|
|
404
|
-
reason: "no_harness_specific_config",
|
|
405
|
-
state: "selection_required"
|
|
406
|
-
},
|
|
407
|
-
pi: {
|
|
408
|
-
packageCount: 0,
|
|
409
|
-
settingsFound: false
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// src/agent-source.ts
|
|
415
|
-
var LocalAgentSource = class {
|
|
416
|
-
constructor(root) {
|
|
417
|
-
this.root = root;
|
|
418
|
-
}
|
|
419
|
-
async has(filename) {
|
|
420
|
-
const metadata = await this.metadata(filename);
|
|
421
|
-
return metadata?.isFile() === true;
|
|
422
|
-
}
|
|
423
|
-
async readText(filename, maxBytes) {
|
|
424
|
-
const metadata = await this.metadata(filename);
|
|
425
|
-
if (!metadata) return null;
|
|
426
|
-
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
427
|
-
throw new Error(`${filename} must be a regular file.`);
|
|
428
|
-
}
|
|
429
|
-
if (metadata.size > maxBytes) {
|
|
430
|
-
throw new Error(`${filename} exceeds the ${maxBytes}-byte inspection limit.`);
|
|
431
|
-
}
|
|
432
|
-
return readFile(path.join(this.root, filename), "utf8");
|
|
433
|
-
}
|
|
434
|
-
async metadata(filename) {
|
|
435
|
-
try {
|
|
436
|
-
return await lstat(path.join(this.root, filename));
|
|
437
|
-
} catch (error) {
|
|
438
|
-
if (error.code === "ENOENT") return null;
|
|
439
|
-
throw error;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
function inspectLocalAgentSource(root) {
|
|
444
|
-
return inspectAgentSource(new LocalAgentSource(root));
|
|
445
517
|
}
|
|
446
518
|
|
|
447
|
-
// src/
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
// src/callback-page.ts
|
|
454
|
-
var WORDMARK = [
|
|
455
|
-
"\u2588\u2588\u2588\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
456
|
-
"\u2588\u2588\u2584 \u2584\u2588\u2588\u2580\u2584 \u2588\u2588 \u2584\u2580\u2588\u2588 \u2584 \u2588\u2588 \u2584\u2580\u2588",
|
|
457
|
-
"\u2588\u2588\u2588\u2584\u2584\u2588\u2588\u2584\u2584\u2588\u2588\u2588\u2584\u2588\u2584\u2588\u2588\u2584\u2584\u2588\u2588\u2588\u2588\u2584\u2584\u2588"
|
|
458
|
-
].join("\n");
|
|
459
|
-
var OUTCOMES = {
|
|
460
|
-
complete: {
|
|
461
|
-
status: 200,
|
|
462
|
-
title: "You are signed in.",
|
|
463
|
-
body: "Return to your terminal. This tab can be closed."
|
|
464
|
-
},
|
|
465
|
-
denied: {
|
|
466
|
-
status: 400,
|
|
467
|
-
title: "Login was not completed.",
|
|
468
|
-
body: "Nothing was authorized. Return to your terminal to try again."
|
|
469
|
-
},
|
|
470
|
-
invalid: {
|
|
471
|
-
// The state did not match, the code was missing, or the path was wrong.
|
|
472
|
-
// Say that it could not be verified rather than which check failed: the
|
|
473
|
-
// person who can read this page is not necessarily the one who started
|
|
474
|
-
// the login.
|
|
475
|
-
status: 400,
|
|
476
|
-
title: "This callback could not be verified.",
|
|
477
|
-
body: "Return to your terminal and start the login again."
|
|
519
|
+
// src/api.ts
|
|
520
|
+
var TonboApi = class {
|
|
521
|
+
constructor(fetcher, accountOrigin = "https://tonbo.dev", managementOrigin = "https://api.tonbo.dev") {
|
|
522
|
+
this.fetcher = fetcher;
|
|
523
|
+
this.accountOrigin = accountOrigin;
|
|
524
|
+
this.managementOrigin = managementOrigin;
|
|
478
525
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
<meta charset="utf-8">
|
|
486
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
487
|
-
<meta name="robots" content="noindex">
|
|
488
|
-
<title>Tonbo CLI</title>
|
|
489
|
-
<style>
|
|
490
|
-
:root {
|
|
491
|
-
--paper: #f3f2ee;
|
|
492
|
-
--ink: #1d1b17;
|
|
493
|
-
--rule: #c9c5ba;
|
|
494
|
-
--quiet: #5c564a;
|
|
495
|
-
--signal: #d64a03;
|
|
526
|
+
async listRegions(oauthToken) {
|
|
527
|
+
return parseRegionDiscovery(
|
|
528
|
+
await requestJson(this.fetcher, `${this.accountOrigin}/api/cli/regions`, {
|
|
529
|
+
headers: { authorization: `Bearer ${oauthToken}` }
|
|
530
|
+
})
|
|
531
|
+
);
|
|
496
532
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
533
|
+
machineRequest(oauthToken, path6 = "", method = "GET", body) {
|
|
534
|
+
return requestJson(this.fetcher, `${this.accountOrigin}/api/cli/machines${path6}`, {
|
|
535
|
+
method,
|
|
536
|
+
headers: {
|
|
537
|
+
authorization: `Bearer ${oauthToken}`,
|
|
538
|
+
...body === void 0 ? {} : { "content-type": "application/json" }
|
|
539
|
+
},
|
|
540
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
541
|
+
});
|
|
505
542
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
color: var(--ink);
|
|
515
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
516
|
-
-webkit-font-smoothing: antialiased;
|
|
543
|
+
listAgents(oauthToken) {
|
|
544
|
+
return requestJson(
|
|
545
|
+
this.fetcher,
|
|
546
|
+
`${this.accountOrigin}/api/cli/agents`,
|
|
547
|
+
{
|
|
548
|
+
headers: { authorization: `Bearer ${oauthToken}` }
|
|
549
|
+
}
|
|
550
|
+
).then((body) => body.agents);
|
|
517
551
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
font-size: 8px;
|
|
527
|
-
font-weight: 400;
|
|
528
|
-
line-height: 10px;
|
|
529
|
-
letter-spacing: 0;
|
|
530
|
-
white-space: pre;
|
|
531
|
-
color: var(--signal);
|
|
532
|
-
transform: scaleY(0.8);
|
|
533
|
-
transform-origin: left top;
|
|
552
|
+
listOrganizations(oauthToken) {
|
|
553
|
+
return requestJson(
|
|
554
|
+
this.fetcher,
|
|
555
|
+
`${this.accountOrigin}/api/cli/accounts`,
|
|
556
|
+
{
|
|
557
|
+
headers: { authorization: `Bearer ${oauthToken}` }
|
|
558
|
+
}
|
|
559
|
+
).then((body) => body.organizations);
|
|
534
560
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
561
|
+
createAgent(oauthToken, name, organizationId) {
|
|
562
|
+
return requestJson(
|
|
563
|
+
this.fetcher,
|
|
564
|
+
`${this.accountOrigin}/api/cli/agents`,
|
|
565
|
+
{
|
|
566
|
+
method: "POST",
|
|
567
|
+
headers: {
|
|
568
|
+
authorization: `Bearer ${oauthToken}`,
|
|
569
|
+
"content-type": "application/json"
|
|
570
|
+
},
|
|
571
|
+
body: JSON.stringify(organizationId ? { name, orgId: organizationId } : { name })
|
|
572
|
+
}
|
|
573
|
+
).then((body) => body.agent);
|
|
541
574
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
575
|
+
registerSshKey(oauthToken, key) {
|
|
576
|
+
return requestJson(
|
|
577
|
+
this.fetcher,
|
|
578
|
+
`${this.accountOrigin}/api/cli/ssh-keys`,
|
|
579
|
+
{
|
|
580
|
+
method: "POST",
|
|
581
|
+
headers: {
|
|
582
|
+
authorization: `Bearer ${oauthToken}`,
|
|
583
|
+
"content-type": "application/json"
|
|
584
|
+
},
|
|
585
|
+
body: JSON.stringify({
|
|
586
|
+
algorithm: key.algorithm,
|
|
587
|
+
key_base64: key.keyBase64,
|
|
588
|
+
label: key.label
|
|
589
|
+
})
|
|
590
|
+
}
|
|
591
|
+
).then((body) => body.key);
|
|
548
592
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
593
|
+
revokeSshKey(oauthToken, fingerprint) {
|
|
594
|
+
return requestJson(
|
|
595
|
+
this.fetcher,
|
|
596
|
+
`${this.accountOrigin}/api/cli/ssh-keys`,
|
|
597
|
+
{
|
|
598
|
+
method: "DELETE",
|
|
599
|
+
headers: {
|
|
600
|
+
authorization: `Bearer ${oauthToken}`,
|
|
601
|
+
"content-type": "application/json"
|
|
602
|
+
},
|
|
603
|
+
body: JSON.stringify({ fingerprint })
|
|
604
|
+
}
|
|
605
|
+
).then((body) => body.key);
|
|
554
606
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
607
|
+
exchangeManagementToken(oauthToken, agentId) {
|
|
608
|
+
return requestJson(
|
|
609
|
+
this.fetcher,
|
|
610
|
+
`${this.accountOrigin}/api/cli/agents/${agentId}/token`,
|
|
611
|
+
{
|
|
612
|
+
method: "POST",
|
|
613
|
+
headers: { authorization: `Bearer ${oauthToken}` }
|
|
614
|
+
}
|
|
615
|
+
).then((body) => body.access_token);
|
|
560
616
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
this.
|
|
617
|
+
async deploy({
|
|
618
|
+
bundle,
|
|
619
|
+
origin,
|
|
620
|
+
agentId,
|
|
621
|
+
promote,
|
|
622
|
+
spec,
|
|
623
|
+
token
|
|
624
|
+
}) {
|
|
625
|
+
const descriptor = {
|
|
626
|
+
format: bundle.format,
|
|
627
|
+
sha256: bundle.sha256,
|
|
628
|
+
size_bytes: bundle.size_bytes
|
|
629
|
+
};
|
|
630
|
+
const bundlesPath = `/v1/agents/${agentId}/source-bundles`;
|
|
631
|
+
const prepared = await this.management("PUT", `${bundlesPath}/${bundle.sha256}`, token, {
|
|
632
|
+
format: descriptor.format,
|
|
633
|
+
size_bytes: descriptor.size_bytes
|
|
634
|
+
});
|
|
635
|
+
if (prepared.status === "upload") {
|
|
636
|
+
if (!prepared.upload_url) throw new Error("Tonbo did not return a source upload URL.");
|
|
637
|
+
if (!prepared.upload_headers || prepared.upload_headers["if-none-match"] !== "*")
|
|
638
|
+
throw new Error("Missing immutable source upload headers.");
|
|
639
|
+
const uploaded = await this.fetcher(prepared.upload_url, {
|
|
640
|
+
redirect: "error",
|
|
641
|
+
method: "PUT",
|
|
642
|
+
headers: prepared.upload_headers,
|
|
643
|
+
body: new Blob([new Uint8Array(bundle.bytes)])
|
|
644
|
+
});
|
|
645
|
+
let uploadError = null;
|
|
646
|
+
if (!uploaded.ok) {
|
|
647
|
+
uploadError = new Error(`Source upload failed with HTTP ${uploaded.status}.`);
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
await this.management("POST", `${bundlesPath}/${bundle.sha256}/complete`, token, {
|
|
651
|
+
format: bundle.format,
|
|
652
|
+
size_bytes: bundle.size_bytes
|
|
653
|
+
});
|
|
654
|
+
} catch (error) {
|
|
655
|
+
if (uploadError)
|
|
656
|
+
throw new AggregateError([uploadError, error], "Source bundle upload did not complete.");
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
let deployment = await this.createDeployment(agentId, { spec, origin }, token);
|
|
661
|
+
const previous = await this.getProduction(agentId, token);
|
|
662
|
+
const previousDeployment = previous ? await this.getDeployment(agentId, previous.deployment_id, token) : null;
|
|
663
|
+
const unchanged = previousDeployment?.spec_sha256 === deployment.spec_sha256;
|
|
664
|
+
let production = previous;
|
|
665
|
+
if (promote) {
|
|
666
|
+
production = await this.putProduction(
|
|
667
|
+
agentId,
|
|
668
|
+
{
|
|
669
|
+
deployment_id: deployment.id,
|
|
670
|
+
desired_state: "running",
|
|
671
|
+
expected_generation: previous ? previous.generation : null
|
|
672
|
+
},
|
|
673
|
+
token
|
|
674
|
+
);
|
|
675
|
+
deployment = await this.getDeployment(agentId, deployment.id, token);
|
|
676
|
+
}
|
|
677
|
+
return { deployment, production, unchanged };
|
|
605
678
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
679
|
+
createDeployment(agentId, body, token) {
|
|
680
|
+
return this.management(
|
|
681
|
+
"POST",
|
|
682
|
+
`/v1/agents/${agentId}/deployments`,
|
|
683
|
+
token,
|
|
684
|
+
body
|
|
685
|
+
).then((response) => response.data);
|
|
686
|
+
}
|
|
687
|
+
listDeployments(agentId, token) {
|
|
688
|
+
return this.managementList(`/v1/agents/${agentId}/deployments`, token);
|
|
689
|
+
}
|
|
690
|
+
getDeployment(agentId, deploymentId, token) {
|
|
691
|
+
return this.management(
|
|
692
|
+
"GET",
|
|
693
|
+
`/v1/agents/${agentId}/deployments/${deploymentId}`,
|
|
694
|
+
token
|
|
695
|
+
).then((response) => response.data);
|
|
696
|
+
}
|
|
697
|
+
getProduction(agentId, token) {
|
|
698
|
+
return this.management(
|
|
699
|
+
"GET",
|
|
700
|
+
`/v1/agents/${agentId}/production`,
|
|
701
|
+
token
|
|
702
|
+
).then(
|
|
703
|
+
(response) => response.data,
|
|
704
|
+
(error) => {
|
|
705
|
+
if (error.status === 404) return null;
|
|
706
|
+
throw error;
|
|
707
|
+
}
|
|
625
708
|
);
|
|
626
|
-
if (!refreshed.refresh_token) refreshed.refresh_token = tokens.refresh_token;
|
|
627
|
-
await this.credentials.save(refreshed);
|
|
628
|
-
return refreshed.access_token;
|
|
629
709
|
}
|
|
630
|
-
|
|
710
|
+
putProduction(agentId, body, token) {
|
|
711
|
+
return this.management(
|
|
712
|
+
"PUT",
|
|
713
|
+
`/v1/agents/${agentId}/production`,
|
|
714
|
+
token,
|
|
715
|
+
body
|
|
716
|
+
).then((response) => response.data);
|
|
717
|
+
}
|
|
718
|
+
listRollouts(agentId, token) {
|
|
719
|
+
return this.managementList(`/v1/agents/${agentId}/production/rollouts`, token);
|
|
720
|
+
}
|
|
721
|
+
async run({
|
|
722
|
+
agentId,
|
|
723
|
+
prompt,
|
|
724
|
+
sessionId,
|
|
725
|
+
token,
|
|
726
|
+
turnId = randomUUID()
|
|
631
727
|
}) {
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
grant_type: "authorization_code",
|
|
664
|
-
redirect_uri: config.redirect_uri
|
|
665
|
-
})
|
|
666
|
-
)
|
|
728
|
+
const agentPath = `/v1/agents/${agentId}`;
|
|
729
|
+
const production = await this.getProduction(agentId, token);
|
|
730
|
+
if (!production)
|
|
731
|
+
throw new Error("Agent has no Production Deployment; run `tonbo deploy` first.");
|
|
732
|
+
if (production.observed_state !== "running")
|
|
733
|
+
throw new Error(
|
|
734
|
+
`Production is ${production.observed_state}${production.last_error ? ` (${production.last_error})` : ""}; wait for it to be running.`
|
|
735
|
+
);
|
|
736
|
+
const session = sessionId ? { id: sessionId } : (await this.management("POST", `${agentPath}/sessions`, token, {
|
|
737
|
+
deployment_id: production.deployment_id
|
|
738
|
+
})).data;
|
|
739
|
+
const turn = (await this.management(
|
|
740
|
+
"POST",
|
|
741
|
+
`${agentPath}/sessions/${session.id}/turns`,
|
|
742
|
+
token,
|
|
743
|
+
{ prompt },
|
|
744
|
+
turnId
|
|
745
|
+
)).data;
|
|
746
|
+
return { production, session, turn };
|
|
747
|
+
}
|
|
748
|
+
turnEvents({
|
|
749
|
+
after = 0,
|
|
750
|
+
agentId,
|
|
751
|
+
sessionId,
|
|
752
|
+
token,
|
|
753
|
+
turnId
|
|
754
|
+
}) {
|
|
755
|
+
return this.management(
|
|
756
|
+
"GET",
|
|
757
|
+
`/v1/agents/${agentId}/sessions/${sessionId}/turns/${turnId}/events?after=${after}`,
|
|
758
|
+
token
|
|
667
759
|
);
|
|
668
|
-
progress({ status: "completed", step: "token-exchange" });
|
|
669
|
-
progress({ status: "started", step: "credential-store" });
|
|
670
|
-
await this.credentials.save(tokens);
|
|
671
|
-
progress({ status: "completed", step: "credential-store" });
|
|
672
|
-
return tokens;
|
|
673
760
|
}
|
|
674
|
-
|
|
675
|
-
return
|
|
761
|
+
listAgentSecrets(agentId, token) {
|
|
762
|
+
return this.management(
|
|
763
|
+
"GET",
|
|
764
|
+
`/v1/agents/${agentId}/secrets`,
|
|
765
|
+
token
|
|
766
|
+
).then((body) => body.data);
|
|
676
767
|
}
|
|
677
|
-
|
|
678
|
-
return
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
768
|
+
setAgentSecret(agentId, name, value, token) {
|
|
769
|
+
return this.management(
|
|
770
|
+
"PUT",
|
|
771
|
+
`/v1/agents/${agentId}/secrets/${encodeURIComponent(name)}`,
|
|
772
|
+
token,
|
|
773
|
+
{ value }
|
|
774
|
+
);
|
|
683
775
|
}
|
|
684
|
-
|
|
685
|
-
return
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
const complete = (closeError) => {
|
|
700
|
-
if (closeError) reject(closeError);
|
|
701
|
-
else if ("code" in result) {
|
|
702
|
-
progress({ status: "completed", step: "callback-close" });
|
|
703
|
-
resolve(result.code);
|
|
704
|
-
} else reject(result.error);
|
|
705
|
-
};
|
|
706
|
-
if ("code" in result) progress({ status: "started", step: "callback-close" });
|
|
707
|
-
if (!server.listening) {
|
|
708
|
-
complete();
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
server.close(complete);
|
|
712
|
-
for (const socket of sockets) {
|
|
713
|
-
if (socket !== completedResponseSocket) socket.destroy();
|
|
714
|
-
}
|
|
715
|
-
server.closeIdleConnections();
|
|
716
|
-
};
|
|
717
|
-
const timeout = setTimeout(
|
|
718
|
-
() => {
|
|
719
|
-
finish({ error: new Error("Timed out waiting for browser login.") });
|
|
720
|
-
},
|
|
721
|
-
5 * 60 * 1e3
|
|
722
|
-
);
|
|
723
|
-
const server = createServer((request, response) => {
|
|
724
|
-
const url = new URL(request.url ?? "/", LOOPBACK_REDIRECT);
|
|
725
|
-
const code = url.searchParams.get("code");
|
|
726
|
-
const oauthError = url.searchParams.get("error_description") ?? url.searchParams.get("error");
|
|
727
|
-
if (oauthError) {
|
|
728
|
-
const responseSocket2 = response.socket;
|
|
729
|
-
respond(
|
|
730
|
-
response,
|
|
731
|
-
"denied",
|
|
732
|
-
() => finish({ error: new Error(oauthError) }, responseSocket2)
|
|
733
|
-
);
|
|
734
|
-
return;
|
|
735
|
-
}
|
|
736
|
-
if (url.pathname !== "/callback" || url.searchParams.get("state") !== expectedState || !code) {
|
|
737
|
-
respond(response, "invalid");
|
|
738
|
-
return;
|
|
776
|
+
deleteAgentSecret(agentId, name, token) {
|
|
777
|
+
return this.management(
|
|
778
|
+
"DELETE",
|
|
779
|
+
`/v1/agents/${agentId}/secrets/${encodeURIComponent(name)}`,
|
|
780
|
+
token
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
management(method, path6, token, body, idempotencyKey) {
|
|
784
|
+
return requestJson(this.fetcher, `${this.managementOrigin}${path6}`, {
|
|
785
|
+
method,
|
|
786
|
+
headers: {
|
|
787
|
+
authorization: `Bearer ${token}`,
|
|
788
|
+
...body === void 0 ? {} : {
|
|
789
|
+
"content-type": "application/json",
|
|
790
|
+
"idempotency-key": idempotencyKey ?? randomUUID()
|
|
739
791
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
const responseSocket = response.socket;
|
|
743
|
-
respond(response, "complete", () => finish({ code }, responseSocket));
|
|
744
|
-
});
|
|
745
|
-
server.on("connection", (socket) => {
|
|
746
|
-
sockets.add(socket);
|
|
747
|
-
socket.once("close", () => sockets.delete(socket));
|
|
748
|
-
});
|
|
749
|
-
server.once("error", (error) => finish({ error }));
|
|
750
|
-
progress({ status: "started", step: "callback-server" });
|
|
751
|
-
server.listen(17655, "localhost", () => {
|
|
752
|
-
progress({ status: "completed", step: "callback-server" });
|
|
753
|
-
progress({ status: "started", step: "browser" });
|
|
754
|
-
void ready().then(startAuthorization).catch(
|
|
755
|
-
(error) => finish({
|
|
756
|
-
error: error instanceof Error ? error : new Error("Could not open the login browser.")
|
|
757
|
-
})
|
|
758
|
-
);
|
|
759
|
-
});
|
|
792
|
+
},
|
|
793
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
760
794
|
});
|
|
761
795
|
}
|
|
796
|
+
async managementList(path6, token) {
|
|
797
|
+
const values = [];
|
|
798
|
+
let cursor = null;
|
|
799
|
+
do {
|
|
800
|
+
const separator = path6.includes("?") ? "&" : "?";
|
|
801
|
+
const page = await this.management(
|
|
802
|
+
"GET",
|
|
803
|
+
`${path6}${separator}limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`,
|
|
804
|
+
token
|
|
805
|
+
);
|
|
806
|
+
values.push(...page.data);
|
|
807
|
+
cursor = page.next_cursor;
|
|
808
|
+
} while (cursor);
|
|
809
|
+
return values;
|
|
810
|
+
}
|
|
762
811
|
};
|
|
763
|
-
function respond(response, outcome, finished) {
|
|
764
|
-
const page = callbackResponse(outcome);
|
|
765
|
-
if (finished) response.once("finish", finished);
|
|
766
|
-
response.writeHead(page.status, { ...page.headers, connection: "close" });
|
|
767
|
-
response.end(page.body);
|
|
768
|
-
}
|
|
769
|
-
async function openBrowser(url) {
|
|
770
|
-
if (process.platform === "darwin") return void await exec("open", [url]);
|
|
771
|
-
if (process.platform === "win32")
|
|
772
|
-
return void await exec("rundll32", ["url.dll,FileProtocolHandler", url]);
|
|
773
|
-
return void await exec("xdg-open", [url]);
|
|
774
|
-
}
|
|
775
812
|
|
|
776
|
-
// src/
|
|
777
|
-
import {
|
|
778
|
-
|
|
779
|
-
// ../../packages/agent-source-bundler/src/generated/contracts.ts
|
|
780
|
-
var sourceBundleContract = { "version": 1, "format": "tar-v1", "key_prefix": "agent-source-bundles", "content_type": "application/vnd.tonbo.source+tar", "max_bytes": 67108864 };
|
|
781
|
-
var piSessionContract = { "version": 1, "adapter": "pi-jsonl-v3", "format_version": 3, "durable_completion_timeout_seconds": 30, "session_directory": "/sessions", "path_template": "/sessions/{session_id}.jsonl", "preflight_command": ["/usr/local/bin/artifacts", "runtime", "session-preflight"] };
|
|
813
|
+
// src/agent-source.ts
|
|
814
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
815
|
+
import path from "node:path";
|
|
782
816
|
|
|
783
|
-
// ../../packages/agent-source-
|
|
784
|
-
|
|
785
|
-
|
|
817
|
+
// ../../packages/agent-source-inspector/dist/src/index.js
|
|
818
|
+
var AGENTS_INSTRUCTIONS_PATH = "AGENTS.md";
|
|
819
|
+
var PI_SETTINGS_PATH = ".pi/settings.json";
|
|
820
|
+
var MAX_HARNESS_CONFIG_BYTES = 256 * 1024;
|
|
821
|
+
var supportedHarnesses = [
|
|
822
|
+
{
|
|
823
|
+
command: "pi",
|
|
824
|
+
id: "pi",
|
|
825
|
+
name: "PI"
|
|
826
|
+
}
|
|
827
|
+
];
|
|
828
|
+
var supportedExecutionTargets = [
|
|
829
|
+
{ driver: "native", harness: "pi" },
|
|
830
|
+
{ driver: "command", harness: "pi" }
|
|
831
|
+
];
|
|
832
|
+
var AgentSourceInspectionError = class extends Error {
|
|
833
|
+
code;
|
|
834
|
+
path;
|
|
835
|
+
constructor(code, path6, message, options) {
|
|
836
|
+
super(message, options);
|
|
837
|
+
this.code = code;
|
|
838
|
+
this.path = path6;
|
|
839
|
+
this.name = "AgentSourceInspectionError";
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
function parsePiPackageCount(contents) {
|
|
843
|
+
let settings;
|
|
844
|
+
try {
|
|
845
|
+
settings = JSON.parse(contents);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
throw new AgentSourceInspectionError("invalid_pi_settings", PI_SETTINGS_PATH, `${PI_SETTINGS_PATH} must contain valid JSON.`, { cause: error });
|
|
848
|
+
}
|
|
849
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) {
|
|
850
|
+
throw new AgentSourceInspectionError("invalid_pi_settings", PI_SETTINGS_PATH, `${PI_SETTINGS_PATH} must contain a JSON object.`);
|
|
851
|
+
}
|
|
852
|
+
if (!("packages" in settings))
|
|
853
|
+
return 0;
|
|
854
|
+
const packages = settings.packages;
|
|
855
|
+
if (!Array.isArray(packages)) {
|
|
856
|
+
throw new AgentSourceInspectionError("invalid_pi_settings", PI_SETTINGS_PATH, `${PI_SETTINGS_PATH} packages must be an array.`);
|
|
857
|
+
}
|
|
858
|
+
return packages.length;
|
|
786
859
|
}
|
|
787
|
-
function
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (
|
|
793
|
-
|
|
860
|
+
async function inspectAgentSource(source) {
|
|
861
|
+
const [agentsMd, piSettings] = await Promise.all([
|
|
862
|
+
source.has(AGENTS_INSTRUCTIONS_PATH),
|
|
863
|
+
source.readText(PI_SETTINGS_PATH, MAX_HARNESS_CONFIG_BYTES)
|
|
864
|
+
]);
|
|
865
|
+
if (piSettings !== null) {
|
|
866
|
+
return {
|
|
867
|
+
version: 1,
|
|
868
|
+
instructions: { agentsMd },
|
|
869
|
+
harness: {
|
|
870
|
+
id: "pi",
|
|
871
|
+
reason: "pi_settings_found",
|
|
872
|
+
state: "identified"
|
|
873
|
+
},
|
|
874
|
+
pi: {
|
|
875
|
+
packageCount: parsePiPackageCount(piSettings),
|
|
876
|
+
settingsFound: true
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
return {
|
|
881
|
+
version: 1,
|
|
882
|
+
instructions: { agentsMd },
|
|
883
|
+
harness: {
|
|
884
|
+
candidates: supportedHarnesses.map((harness) => harness.id),
|
|
885
|
+
reason: "no_harness_specific_config",
|
|
886
|
+
state: "selection_required"
|
|
887
|
+
},
|
|
888
|
+
pi: {
|
|
889
|
+
packageCount: 0,
|
|
890
|
+
settingsFound: false
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/agent-source.ts
|
|
896
|
+
var LocalAgentSource = class {
|
|
897
|
+
constructor(root) {
|
|
898
|
+
this.root = root;
|
|
899
|
+
}
|
|
900
|
+
async has(filename) {
|
|
901
|
+
const metadata = await this.metadata(filename);
|
|
902
|
+
return metadata?.isFile() === true;
|
|
903
|
+
}
|
|
904
|
+
async readText(filename, maxBytes) {
|
|
905
|
+
const metadata = await this.metadata(filename);
|
|
906
|
+
if (!metadata) return null;
|
|
907
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
908
|
+
throw new Error(`${filename} must be a regular file.`);
|
|
909
|
+
}
|
|
910
|
+
if (metadata.size > maxBytes) {
|
|
911
|
+
throw new Error(`${filename} exceeds the ${maxBytes}-byte inspection limit.`);
|
|
912
|
+
}
|
|
913
|
+
return readFile(path.join(this.root, filename), "utf8");
|
|
914
|
+
}
|
|
915
|
+
async metadata(filename) {
|
|
794
916
|
try {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
} catch {
|
|
800
|
-
return null;
|
|
917
|
+
return await lstat(path.join(this.root, filename));
|
|
918
|
+
} catch (error) {
|
|
919
|
+
if (error.code === "ENOENT") return null;
|
|
920
|
+
throw error;
|
|
801
921
|
}
|
|
802
922
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
return null;
|
|
807
|
-
return `https://github.com/${parts[0]}/${parts[1]}`;
|
|
923
|
+
};
|
|
924
|
+
function inspectLocalAgentSource(root) {
|
|
925
|
+
return inspectAgentSource(new LocalAgentSource(root));
|
|
808
926
|
}
|
|
809
927
|
|
|
810
|
-
// src/
|
|
811
|
-
import
|
|
928
|
+
// src/auth.ts
|
|
929
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
930
|
+
import { createServer } from "node:http";
|
|
931
|
+
import { execFile } from "node:child_process";
|
|
932
|
+
import { promisify } from "node:util";
|
|
812
933
|
|
|
813
|
-
// src/
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
// src/contracts.ts
|
|
839
|
-
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
840
|
-
|
|
841
|
-
// src/generated/contracts.ts
|
|
842
|
-
var piAgentSchema = {
|
|
843
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
844
|
-
"$id": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json",
|
|
845
|
-
"title": "PI Agent v1",
|
|
846
|
-
"type": "object",
|
|
847
|
-
"additionalProperties": false,
|
|
848
|
-
"required": [
|
|
849
|
-
"runtime",
|
|
850
|
-
"driver"
|
|
851
|
-
],
|
|
852
|
-
"properties": {
|
|
853
|
-
"runtime": {
|
|
854
|
-
"const": "pi"
|
|
855
|
-
},
|
|
856
|
-
"secrets": {
|
|
857
|
-
"type": "array",
|
|
858
|
-
"maxItems": 32,
|
|
859
|
-
"uniqueItems": true,
|
|
860
|
-
"items": {
|
|
861
|
-
"type": "string",
|
|
862
|
-
"pattern": "^[A-Z_][A-Z0-9_]{0,127}$"
|
|
863
|
-
}
|
|
864
|
-
},
|
|
865
|
-
"driver": {
|
|
866
|
-
"oneOf": [
|
|
867
|
-
{
|
|
868
|
-
"type": "object",
|
|
869
|
-
"additionalProperties": false,
|
|
870
|
-
"required": [
|
|
871
|
-
"kind"
|
|
872
|
-
],
|
|
873
|
-
"properties": {
|
|
874
|
-
"kind": {
|
|
875
|
-
"const": "native"
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
},
|
|
879
|
-
{
|
|
880
|
-
"type": "object",
|
|
881
|
-
"additionalProperties": false,
|
|
882
|
-
"required": [
|
|
883
|
-
"kind",
|
|
884
|
-
"protocol",
|
|
885
|
-
"command"
|
|
886
|
-
],
|
|
887
|
-
"properties": {
|
|
888
|
-
"kind": {
|
|
889
|
-
"const": "command"
|
|
890
|
-
},
|
|
891
|
-
"protocol": {
|
|
892
|
-
"const": "pi-rpc-v1"
|
|
893
|
-
},
|
|
894
|
-
"command": {
|
|
895
|
-
"type": "array",
|
|
896
|
-
"minItems": 1,
|
|
897
|
-
"maxItems": 64,
|
|
898
|
-
"items": {
|
|
899
|
-
"type": "string",
|
|
900
|
-
"minLength": 1,
|
|
901
|
-
"maxLength": 1024
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
]
|
|
907
|
-
}
|
|
934
|
+
// src/callback-page.ts
|
|
935
|
+
var WORDMARK = [
|
|
936
|
+
"\u2588\u2588\u2588\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
937
|
+
"\u2588\u2588\u2584 \u2584\u2588\u2588\u2580\u2584 \u2588\u2588 \u2584\u2580\u2588\u2588 \u2584 \u2588\u2588 \u2584\u2580\u2588",
|
|
938
|
+
"\u2588\u2588\u2588\u2584\u2584\u2588\u2588\u2584\u2584\u2588\u2588\u2588\u2584\u2588\u2584\u2588\u2588\u2584\u2584\u2588\u2588\u2588\u2588\u2584\u2584\u2588"
|
|
939
|
+
].join("\n");
|
|
940
|
+
var OUTCOMES = {
|
|
941
|
+
complete: {
|
|
942
|
+
status: 200,
|
|
943
|
+
title: "You are signed in.",
|
|
944
|
+
body: "Return to your terminal. This tab can be closed."
|
|
945
|
+
},
|
|
946
|
+
denied: {
|
|
947
|
+
status: 400,
|
|
948
|
+
title: "Login was not completed.",
|
|
949
|
+
body: "Nothing was authorized. Return to your terminal to try again."
|
|
950
|
+
},
|
|
951
|
+
invalid: {
|
|
952
|
+
// The state did not match, the code was missing, or the path was wrong.
|
|
953
|
+
// Say that it could not be verified rather than which check failed: the
|
|
954
|
+
// person who can read this page is not necessarily the one who started
|
|
955
|
+
// the login.
|
|
956
|
+
status: 400,
|
|
957
|
+
title: "This callback could not be verified.",
|
|
958
|
+
body: "Return to your terminal and start the login again."
|
|
908
959
|
}
|
|
909
960
|
};
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
"minLength": 1,
|
|
927
|
-
"maxLength": 4096
|
|
928
|
-
}
|
|
929
|
-
},
|
|
930
|
-
"secrets": {
|
|
931
|
-
"type": "array",
|
|
932
|
-
"maxItems": 32,
|
|
933
|
-
"uniqueItems": true,
|
|
934
|
-
"items": {
|
|
935
|
-
"type": "string",
|
|
936
|
-
"pattern": "^[A-Z_][A-Z0-9_]{0,127}$"
|
|
937
|
-
}
|
|
938
|
-
},
|
|
939
|
-
"kubernetes": {
|
|
940
|
-
"$ref": "https://contracts.tonbo.dev/agents/kubernetes-profiles-v1.schema.json#/$defs/request"
|
|
941
|
-
}
|
|
961
|
+
function renderCallbackPage(outcome) {
|
|
962
|
+
const { title, body } = OUTCOMES[outcome];
|
|
963
|
+
return `<!doctype html>
|
|
964
|
+
<html lang="en">
|
|
965
|
+
<head>
|
|
966
|
+
<meta charset="utf-8">
|
|
967
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
968
|
+
<meta name="robots" content="noindex">
|
|
969
|
+
<title>Tonbo CLI</title>
|
|
970
|
+
<style>
|
|
971
|
+
:root {
|
|
972
|
+
--paper: #f3f2ee;
|
|
973
|
+
--ink: #1d1b17;
|
|
974
|
+
--rule: #c9c5ba;
|
|
975
|
+
--quiet: #5c564a;
|
|
976
|
+
--signal: #d64a03;
|
|
942
977
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
"type": "object",
|
|
951
|
-
"additionalProperties": false,
|
|
952
|
-
"required": [
|
|
953
|
-
"profile"
|
|
954
|
-
],
|
|
955
|
-
"properties": {
|
|
956
|
-
"profile": false
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
},
|
|
960
|
-
"x-tonbo-profiles": {}
|
|
961
|
-
};
|
|
962
|
-
var declarationSchema = {
|
|
963
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
964
|
-
"$id": "https://contracts.tonbo.dev/agents/tonbo-declaration-v2.schema.json",
|
|
965
|
-
"title": "Tonbo Agent declaration v2",
|
|
966
|
-
"type": "object",
|
|
967
|
-
"additionalProperties": false,
|
|
968
|
-
"required": [
|
|
969
|
-
"version",
|
|
970
|
-
"harness"
|
|
971
|
-
],
|
|
972
|
-
"properties": {
|
|
973
|
-
"version": {
|
|
974
|
-
"const": 2
|
|
975
|
-
},
|
|
976
|
-
"inference": {
|
|
977
|
-
"type": "object",
|
|
978
|
-
"additionalProperties": false,
|
|
979
|
-
"default": {
|
|
980
|
-
"model": "claude-sonnet-4-5"
|
|
981
|
-
},
|
|
982
|
-
"required": [
|
|
983
|
-
"model"
|
|
984
|
-
],
|
|
985
|
-
"properties": {
|
|
986
|
-
"model": {
|
|
987
|
-
"type": "string",
|
|
988
|
-
"minLength": 1,
|
|
989
|
-
"maxLength": 160
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
},
|
|
993
|
-
"build": {
|
|
994
|
-
"type": "object",
|
|
995
|
-
"additionalProperties": false,
|
|
996
|
-
"required": [
|
|
997
|
-
"command"
|
|
998
|
-
],
|
|
999
|
-
"properties": {
|
|
1000
|
-
"command": {
|
|
1001
|
-
"type": "array",
|
|
1002
|
-
"minItems": 1,
|
|
1003
|
-
"maxItems": 64,
|
|
1004
|
-
"items": {
|
|
1005
|
-
"type": "string",
|
|
1006
|
-
"minLength": 1,
|
|
1007
|
-
"maxLength": 1024
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
},
|
|
1012
|
-
"service": {
|
|
1013
|
-
"$ref": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json"
|
|
1014
|
-
},
|
|
1015
|
-
"agent": {
|
|
1016
|
-
"$ref": "https://contracts.tonbo.dev/agents/agent-name-v1.json#/$defs/publicHostname"
|
|
1017
|
-
},
|
|
1018
|
-
"harness": {
|
|
1019
|
-
"$ref": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json"
|
|
978
|
+
@media (prefers-color-scheme: dark) {
|
|
979
|
+
:root {
|
|
980
|
+
--paper: #1d1b17;
|
|
981
|
+
--ink: #f3f2ee;
|
|
982
|
+
--rule: #3a362e;
|
|
983
|
+
--quiet: #a49e8f;
|
|
984
|
+
--signal: #f08b4b;
|
|
1020
985
|
}
|
|
1021
986
|
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
"inference"
|
|
1034
|
-
],
|
|
1035
|
-
"properties": {
|
|
1036
|
-
"version": {
|
|
1037
|
-
"const": 1
|
|
1038
|
-
},
|
|
1039
|
-
"agent": {
|
|
1040
|
-
"$ref": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json"
|
|
1041
|
-
},
|
|
1042
|
-
"source": {
|
|
1043
|
-
"type": "object",
|
|
1044
|
-
"additionalProperties": false,
|
|
1045
|
-
"required": [
|
|
1046
|
-
"format",
|
|
1047
|
-
"sha256",
|
|
1048
|
-
"size_bytes"
|
|
1049
|
-
],
|
|
1050
|
-
"properties": {
|
|
1051
|
-
"format": {
|
|
1052
|
-
"const": "tar-v1"
|
|
1053
|
-
},
|
|
1054
|
-
"sha256": {
|
|
1055
|
-
"type": "string",
|
|
1056
|
-
"pattern": "^[0-9a-f]{64}$"
|
|
1057
|
-
},
|
|
1058
|
-
"size_bytes": {
|
|
1059
|
-
"type": "integer",
|
|
1060
|
-
"minimum": 1,
|
|
1061
|
-
"maximum": 67108864
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
},
|
|
1065
|
-
"inference": {
|
|
1066
|
-
"type": "object",
|
|
1067
|
-
"additionalProperties": false,
|
|
1068
|
-
"required": [
|
|
1069
|
-
"model"
|
|
1070
|
-
],
|
|
1071
|
-
"properties": {
|
|
1072
|
-
"model": {
|
|
1073
|
-
"type": "string",
|
|
1074
|
-
"minLength": 1,
|
|
1075
|
-
"maxLength": 160
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
},
|
|
1079
|
-
"service": {
|
|
1080
|
-
"$ref": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json"
|
|
1081
|
-
}
|
|
987
|
+
* { box-sizing: border-box; }
|
|
988
|
+
body {
|
|
989
|
+
margin: 0;
|
|
990
|
+
min-height: 100vh;
|
|
991
|
+
display: grid;
|
|
992
|
+
grid-template-rows: auto 1fr;
|
|
993
|
+
padding: 0 clamp(20px, 5vw, 72px);
|
|
994
|
+
background: var(--paper);
|
|
995
|
+
color: var(--ink);
|
|
996
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
997
|
+
-webkit-font-smoothing: antialiased;
|
|
1082
998
|
}
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
999
|
+
header { padding: 28px 0; }
|
|
1000
|
+
/* Same metrics as .auth-wordmark in the app: the glyphs are the ground
|
|
1001
|
+
and the letters are the gaps, so the 8px/10px/scaleY(0.8) combination is
|
|
1002
|
+
what makes the rows meet without seams. Iosevka is not available here,
|
|
1003
|
+
so it falls back to the platform monospace. */
|
|
1004
|
+
pre.wordmark {
|
|
1005
|
+
margin: 0;
|
|
1006
|
+
font-family: 'Iosevka', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
|
|
1007
|
+
font-size: 8px;
|
|
1008
|
+
font-weight: 400;
|
|
1009
|
+
line-height: 10px;
|
|
1010
|
+
letter-spacing: 0;
|
|
1011
|
+
white-space: pre;
|
|
1012
|
+
color: var(--signal);
|
|
1013
|
+
transform: scaleY(0.8);
|
|
1014
|
+
transform-origin: left top;
|
|
1015
|
+
}
|
|
1016
|
+
main {
|
|
1017
|
+
display: flex;
|
|
1018
|
+
flex-direction: column;
|
|
1019
|
+
justify-content: center;
|
|
1020
|
+
max-width: 46ch;
|
|
1021
|
+
padding-bottom: 12vh;
|
|
1022
|
+
}
|
|
1023
|
+
h1 {
|
|
1024
|
+
margin: 0;
|
|
1025
|
+
font-size: 26px;
|
|
1026
|
+
font-weight: 300;
|
|
1027
|
+
line-height: 1.16;
|
|
1028
|
+
letter-spacing: -0.018em;
|
|
1029
|
+
}
|
|
1030
|
+
p {
|
|
1031
|
+
margin: 14px 0 0;
|
|
1032
|
+
font-size: 16px;
|
|
1033
|
+
line-height: 1.55;
|
|
1034
|
+
color: var(--quiet);
|
|
1035
|
+
}
|
|
1036
|
+
hr {
|
|
1037
|
+
width: 40px;
|
|
1038
|
+
margin: 28px 0 0;
|
|
1039
|
+
border: 0;
|
|
1040
|
+
border-top: 1px solid var(--rule);
|
|
1041
|
+
}
|
|
1042
|
+
</style>
|
|
1043
|
+
</head>
|
|
1044
|
+
<body>
|
|
1045
|
+
<header><pre class="wordmark" aria-hidden="true">${WORDMARK}</pre></header>
|
|
1046
|
+
<main>
|
|
1047
|
+
<h1>${title}</h1>
|
|
1048
|
+
<p>${body}</p>
|
|
1049
|
+
<hr>
|
|
1050
|
+
</main>
|
|
1051
|
+
</body>
|
|
1052
|
+
</html>
|
|
1053
|
+
`;
|
|
1054
|
+
}
|
|
1055
|
+
function callbackResponse(outcome) {
|
|
1056
|
+
return {
|
|
1057
|
+
status: OUTCOMES[outcome].status,
|
|
1058
|
+
headers: {
|
|
1059
|
+
"content-type": "text/html; charset=utf-8",
|
|
1060
|
+
"cache-control": "no-store"
|
|
1128
1061
|
},
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1062
|
+
body: renderCallbackPage(outcome)
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// src/auth.ts
|
|
1067
|
+
var exec = promisify(execFile);
|
|
1068
|
+
var LOOPBACK_REDIRECT = "http://localhost:17655/callback";
|
|
1069
|
+
function base64url(value) {
|
|
1070
|
+
return value.toString("base64url");
|
|
1071
|
+
}
|
|
1072
|
+
function withAbsoluteExpiry(tokens) {
|
|
1073
|
+
if (tokens.expires_at || !Number.isInteger(tokens.expires_in) || (tokens.expires_in ?? 0) <= 0)
|
|
1074
|
+
return tokens;
|
|
1075
|
+
return {
|
|
1076
|
+
...tokens,
|
|
1077
|
+
expires_at: Math.floor(Date.now() / 1e3) + (tokens.expires_in ?? 0)
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
var AuthClient = class {
|
|
1081
|
+
constructor(credentials, fetcher, accountOrigin = "https://tonbo.dev", browserOpener = openBrowser) {
|
|
1082
|
+
this.credentials = credentials;
|
|
1083
|
+
this.fetcher = fetcher;
|
|
1084
|
+
this.accountOrigin = accountOrigin;
|
|
1085
|
+
this.browserOpener = browserOpener;
|
|
1086
|
+
}
|
|
1087
|
+
async accessToken() {
|
|
1088
|
+
const injected = process.env.TONBO_ACCESS_TOKEN?.trim();
|
|
1089
|
+
if (injected) return injected;
|
|
1090
|
+
const tokens = await this.credentials.load();
|
|
1091
|
+
if (!tokens) throw new Error("Not logged in. Run `tonbo login`.");
|
|
1092
|
+
const legacyRelativeExpiry = !tokens.expires_at && tokens.expires_in;
|
|
1093
|
+
if (!legacyRelativeExpiry && (!tokens.expires_at || tokens.expires_at > Math.floor(Date.now() / 1e3) + 30))
|
|
1094
|
+
return tokens.access_token;
|
|
1095
|
+
if (!tokens.refresh_token) throw new Error("Tonbo login expired. Run `tonbo login` again.");
|
|
1096
|
+
const config = await this.config();
|
|
1097
|
+
const refreshed = withAbsoluteExpiry(
|
|
1098
|
+
await this.exchange(
|
|
1099
|
+
config.token_endpoint,
|
|
1100
|
+
new URLSearchParams({
|
|
1101
|
+
client_id: config.client_id,
|
|
1102
|
+
grant_type: "refresh_token",
|
|
1103
|
+
refresh_token: tokens.refresh_token
|
|
1104
|
+
})
|
|
1105
|
+
)
|
|
1106
|
+
);
|
|
1107
|
+
if (!refreshed.refresh_token) refreshed.refresh_token = tokens.refresh_token;
|
|
1108
|
+
await this.credentials.save(refreshed);
|
|
1109
|
+
return refreshed.access_token;
|
|
1110
|
+
}
|
|
1111
|
+
async login(progress = () => {
|
|
1112
|
+
}) {
|
|
1113
|
+
progress({ status: "started", step: "account-config" });
|
|
1114
|
+
const config = await this.config();
|
|
1115
|
+
progress({ status: "completed", step: "account-config" });
|
|
1116
|
+
if (config.redirect_uri !== LOOPBACK_REDIRECT)
|
|
1117
|
+
throw new Error(`Unsupported OAuth redirect URI: ${config.redirect_uri}`);
|
|
1118
|
+
const verifier = base64url(randomBytes(48));
|
|
1119
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
1120
|
+
const state = base64url(randomBytes(24));
|
|
1121
|
+
const authorization = new URL(config.authorization_endpoint);
|
|
1122
|
+
authorization.search = new URLSearchParams({
|
|
1123
|
+
client_id: config.client_id,
|
|
1124
|
+
code_challenge: challenge,
|
|
1125
|
+
code_challenge_method: "S256",
|
|
1126
|
+
redirect_uri: config.redirect_uri,
|
|
1127
|
+
response_type: "code",
|
|
1128
|
+
scope: "openid profile email",
|
|
1129
|
+
state
|
|
1130
|
+
}).toString();
|
|
1131
|
+
const code = await this.listenForCode(
|
|
1132
|
+
state,
|
|
1133
|
+
() => this.browserOpener(authorization.toString()),
|
|
1134
|
+
progress
|
|
1135
|
+
);
|
|
1136
|
+
progress({ status: "started", step: "token-exchange" });
|
|
1137
|
+
const tokens = withAbsoluteExpiry(
|
|
1138
|
+
await this.exchange(
|
|
1139
|
+
config.token_endpoint,
|
|
1140
|
+
new URLSearchParams({
|
|
1141
|
+
client_id: config.client_id,
|
|
1142
|
+
code,
|
|
1143
|
+
code_verifier: verifier,
|
|
1144
|
+
grant_type: "authorization_code",
|
|
1145
|
+
redirect_uri: config.redirect_uri
|
|
1146
|
+
})
|
|
1147
|
+
)
|
|
1148
|
+
);
|
|
1149
|
+
progress({ status: "completed", step: "token-exchange" });
|
|
1150
|
+
progress({ status: "started", step: "credential-store" });
|
|
1151
|
+
await this.credentials.save(tokens);
|
|
1152
|
+
progress({ status: "completed", step: "credential-store" });
|
|
1153
|
+
return tokens;
|
|
1154
|
+
}
|
|
1155
|
+
config() {
|
|
1156
|
+
return requestJson(this.fetcher, `${this.accountOrigin}/api/cli/config`);
|
|
1157
|
+
}
|
|
1158
|
+
exchange(endpoint, body) {
|
|
1159
|
+
return requestJson(this.fetcher, endpoint, {
|
|
1160
|
+
method: "POST",
|
|
1161
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
1162
|
+
body
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
listenForCode(expectedState, ready, progress) {
|
|
1166
|
+
return new Promise((resolve, reject) => {
|
|
1167
|
+
let settled = false;
|
|
1168
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
1169
|
+
let authorizationStarted = false;
|
|
1170
|
+
const startAuthorization = () => {
|
|
1171
|
+
if (authorizationStarted) return;
|
|
1172
|
+
authorizationStarted = true;
|
|
1173
|
+
progress({ status: "completed", step: "browser" });
|
|
1174
|
+
progress({ status: "started", step: "browser-authorization" });
|
|
1175
|
+
};
|
|
1176
|
+
const finish = (result, completedResponseSocket) => {
|
|
1177
|
+
if (settled) return;
|
|
1178
|
+
settled = true;
|
|
1179
|
+
clearTimeout(timeout);
|
|
1180
|
+
const complete = (closeError) => {
|
|
1181
|
+
if (closeError) reject(closeError);
|
|
1182
|
+
else if ("code" in result) {
|
|
1183
|
+
progress({ status: "completed", step: "callback-close" });
|
|
1184
|
+
resolve(result.code);
|
|
1185
|
+
} else reject(result.error);
|
|
1186
|
+
};
|
|
1187
|
+
if ("code" in result) progress({ status: "started", step: "callback-close" });
|
|
1188
|
+
if (!server.listening) {
|
|
1189
|
+
complete();
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
server.close(complete);
|
|
1193
|
+
for (const socket of sockets) {
|
|
1194
|
+
if (socket !== completedResponseSocket) socket.destroy();
|
|
1195
|
+
}
|
|
1196
|
+
server.closeIdleConnections();
|
|
1197
|
+
};
|
|
1198
|
+
const timeout = setTimeout(
|
|
1199
|
+
() => {
|
|
1200
|
+
finish({ error: new Error("Timed out waiting for browser login.") });
|
|
1201
|
+
},
|
|
1202
|
+
5 * 60 * 1e3
|
|
1203
|
+
);
|
|
1204
|
+
const server = createServer((request, response) => {
|
|
1205
|
+
const url = new URL(request.url ?? "/", LOOPBACK_REDIRECT);
|
|
1206
|
+
const code = url.searchParams.get("code");
|
|
1207
|
+
const oauthError = url.searchParams.get("error_description") ?? url.searchParams.get("error");
|
|
1208
|
+
if (oauthError) {
|
|
1209
|
+
const responseSocket2 = response.socket;
|
|
1210
|
+
respond(
|
|
1211
|
+
response,
|
|
1212
|
+
"denied",
|
|
1213
|
+
() => finish({ error: new Error(oauthError) }, responseSocket2)
|
|
1214
|
+
);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (url.pathname !== "/callback" || url.searchParams.get("state") !== expectedState || !code) {
|
|
1218
|
+
respond(response, "invalid");
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
startAuthorization();
|
|
1222
|
+
progress({ status: "completed", step: "browser-authorization" });
|
|
1223
|
+
const responseSocket = response.socket;
|
|
1224
|
+
respond(response, "complete", () => finish({ code }, responseSocket));
|
|
1225
|
+
});
|
|
1226
|
+
server.on("connection", (socket) => {
|
|
1227
|
+
sockets.add(socket);
|
|
1228
|
+
socket.once("close", () => sockets.delete(socket));
|
|
1229
|
+
});
|
|
1230
|
+
server.once("error", (error) => finish({ error }));
|
|
1231
|
+
progress({ status: "started", step: "callback-server" });
|
|
1232
|
+
server.listen(17655, "localhost", () => {
|
|
1233
|
+
progress({ status: "completed", step: "callback-server" });
|
|
1234
|
+
progress({ status: "started", step: "browser" });
|
|
1235
|
+
void ready().then(startAuthorization).catch(
|
|
1236
|
+
(error) => finish({
|
|
1237
|
+
error: error instanceof Error ? error : new Error("Could not open the login browser.")
|
|
1238
|
+
})
|
|
1239
|
+
);
|
|
1240
|
+
});
|
|
1241
|
+
});
|
|
1239
1242
|
}
|
|
1240
1243
|
};
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
ajv.addKeyword({ keyword: "x-tonbo-public-hostname-apex" });
|
|
1247
|
-
ajv.addKeyword({ keyword: "x-tonbo-public-hostname-template" });
|
|
1248
|
-
ajv.addSchema(kubernetesProfilesSchema);
|
|
1249
|
-
ajv.addSchema(piAgentSchema);
|
|
1250
|
-
ajv.addSchema(agentNameContract);
|
|
1251
|
-
ajv.addSchema(agentServiceSchema);
|
|
1252
|
-
var validateDeclaration = ajv.compile(declarationSchema);
|
|
1253
|
-
var validateDeploymentSpec = ajv.compile(deploymentSchema);
|
|
1254
|
-
function validationMessage(label, errors) {
|
|
1255
|
-
const detail = errors?.map((error) => `${error.instancePath || "/"} ${error.message}`).join("; ");
|
|
1256
|
-
return `${label} is invalid${detail ? `: ${detail}` : "."}`;
|
|
1244
|
+
function respond(response, outcome, finished) {
|
|
1245
|
+
const page = callbackResponse(outcome);
|
|
1246
|
+
if (finished) response.once("finish", finished);
|
|
1247
|
+
response.writeHead(page.status, { ...page.headers, connection: "close" });
|
|
1248
|
+
response.end(page.body);
|
|
1257
1249
|
}
|
|
1258
|
-
function
|
|
1259
|
-
|
|
1260
|
-
if (
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
return candidate;
|
|
1250
|
+
async function openBrowser(url) {
|
|
1251
|
+
if (process.platform === "darwin") return void await exec("open", [url]);
|
|
1252
|
+
if (process.platform === "win32")
|
|
1253
|
+
return void await exec("rundll32", ["url.dll,FileProtocolHandler", url]);
|
|
1254
|
+
return void await exec("xdg-open", [url]);
|
|
1264
1255
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1256
|
+
|
|
1257
|
+
// ../../packages/agent-source-bundler/dist/src/generated/contracts.js
|
|
1258
|
+
var inferenceDefault = { "model": "claude-sonnet-5" };
|
|
1259
|
+
var sourceBundleContract = { "version": 1, "format": "tar-v1", "key_prefix": "agent-source-bundles", "content_type": "application/vnd.tonbo.source+tar", "max_bytes": 67108864 };
|
|
1260
|
+
var piSessionContract = { "version": 1, "adapter": "pi-jsonl-v3", "format_version": 3, "durable_completion_timeout_seconds": 30, "turn_claim_seconds": 30, "turn_claim_renew_seconds": 10, "session_directory": "/sessions", "path_template": "/sessions/{session_id}.jsonl", "preflight_command": ["/usr/local/bin/artifacts", "runtime", "session-preflight"] };
|
|
1261
|
+
|
|
1262
|
+
// ../../packages/agent-source-bundler/dist/src/inference-default.js
|
|
1263
|
+
var DEFAULT_INFERENCE_MODEL = inferenceDefault.model;
|
|
1264
|
+
|
|
1265
|
+
// ../../packages/agent-source-bundler/dist/src/deployment-origin.js
|
|
1266
|
+
function deploymentOriginLabel(actor) {
|
|
1267
|
+
return actor === "onboarding" ? "Setup guide" : "tonbo deploy";
|
|
1268
|
+
}
|
|
1269
|
+
function githubRepositoryUrl(remote) {
|
|
1270
|
+
if (typeof remote !== "string" || !remote || /\s/.test(remote) || [...remote].some((character) => character.charCodeAt(0) < 32))
|
|
1271
|
+
return null;
|
|
1272
|
+
let path6;
|
|
1273
|
+
const ssh = /^git@github\.com:([^?#]+)$/.exec(remote);
|
|
1274
|
+
if (ssh)
|
|
1275
|
+
path6 = ssh[1];
|
|
1276
|
+
else {
|
|
1277
|
+
try {
|
|
1278
|
+
const url = new URL(remote);
|
|
1279
|
+
if (url.hostname !== "github.com" || url.port || !["https:", "ssh:"].includes(url.protocol))
|
|
1280
|
+
return null;
|
|
1281
|
+
path6 = url.pathname.slice(1);
|
|
1282
|
+
} catch {
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1268
1285
|
}
|
|
1286
|
+
path6 = path6.replace(/\/$/, "").replace(/\.git$/, "");
|
|
1287
|
+
const parts = path6.split("/");
|
|
1288
|
+
if (parts.length !== 2 || !/^[a-z\d](?:[a-z\d-]{0,38})$/i.test(parts[0]) || !/^[a-z\d._-]{1,100}$/i.test(parts[1]) || [".", ".."].includes(parts[1]))
|
|
1289
|
+
return null;
|
|
1290
|
+
return `https://github.com/${parts[0]}/${parts[1]}`;
|
|
1269
1291
|
}
|
|
1270
1292
|
|
|
1271
|
-
// src/
|
|
1272
|
-
import
|
|
1293
|
+
// src/commands.ts
|
|
1294
|
+
import path4 from "node:path";
|
|
1295
|
+
|
|
1296
|
+
// src/build.ts
|
|
1297
|
+
import { spawn } from "node:child_process";
|
|
1298
|
+
async function runBuildCommand(root, command) {
|
|
1299
|
+
if (command.length === 0) throw new Error("Build command must not be empty.");
|
|
1300
|
+
await new Promise((resolve, reject) => {
|
|
1301
|
+
const child = spawn(command[0], command.slice(1), {
|
|
1302
|
+
cwd: root,
|
|
1303
|
+
env: process.env,
|
|
1304
|
+
// Command output is progress, not the CLI result. Keep stdout available
|
|
1305
|
+
// for the single JSON document emitted by `tonbo --json deploy`.
|
|
1306
|
+
stdio: ["inherit", process.stderr, process.stderr]
|
|
1307
|
+
});
|
|
1308
|
+
child.once("error", reject);
|
|
1309
|
+
child.once("exit", (code, signal) => {
|
|
1310
|
+
if (code === 0) resolve();
|
|
1311
|
+
else
|
|
1312
|
+
reject(
|
|
1313
|
+
new Error(
|
|
1314
|
+
`Build command failed${signal ? ` with signal ${signal}` : ` with status ${code}`}.`
|
|
1315
|
+
)
|
|
1316
|
+
);
|
|
1317
|
+
});
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1273
1320
|
|
|
1274
|
-
// ../../packages/agent-source-bundler/src/native-pi.
|
|
1321
|
+
// ../../packages/agent-source-bundler/dist/src/native-pi.js
|
|
1275
1322
|
import { stringify } from "smol-toml";
|
|
1276
1323
|
function nativePiDeclaration(model) {
|
|
1277
1324
|
return {
|
|
@@ -1382,7 +1429,7 @@ function buildDeploymentSpec(declaration, source) {
|
|
|
1382
1429
|
return spec;
|
|
1383
1430
|
}
|
|
1384
1431
|
|
|
1385
|
-
// ../../packages/agent-source-bundler/src/index.
|
|
1432
|
+
// ../../packages/agent-source-bundler/dist/src/index.js
|
|
1386
1433
|
import { createHash as createHash2 } from "node:crypto";
|
|
1387
1434
|
import { lstat as lstat3, readFile as readFile3, readdir } from "node:fs/promises";
|
|
1388
1435
|
import path3 from "node:path";
|
|
@@ -1405,7 +1452,8 @@ var DEFAULT_IGNORES = [
|
|
|
1405
1452
|
var EXACT_NPM_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
1406
1453
|
var GIT_COMMIT = /^[0-9a-f]{40}$/i;
|
|
1407
1454
|
function packageSource(value, index) {
|
|
1408
|
-
if (typeof value === "string")
|
|
1455
|
+
if (typeof value === "string")
|
|
1456
|
+
return value;
|
|
1409
1457
|
if (value && typeof value === "object" && "source" in value && typeof value.source === "string")
|
|
1410
1458
|
return value.source;
|
|
1411
1459
|
throw new Error(`.pi/settings.json packages[${index}] must be a source string or object.`);
|
|
@@ -1415,9 +1463,7 @@ async function validatePackageSource(root, source) {
|
|
|
1415
1463
|
const specifier = source.slice(4);
|
|
1416
1464
|
const separator = specifier.lastIndexOf("@");
|
|
1417
1465
|
if (separator <= 0 || !EXACT_NPM_VERSION.test(specifier.slice(separator + 1))) {
|
|
1418
|
-
throw new Error(
|
|
1419
|
-
`PI package ${source} must pin an exact npm version, for example npm:my-agent@1.2.3.`
|
|
1420
|
-
);
|
|
1466
|
+
throw new Error(`PI package ${source} must pin an exact npm version, for example npm:my-agent@1.2.3.`);
|
|
1421
1467
|
}
|
|
1422
1468
|
return;
|
|
1423
1469
|
}
|
|
@@ -1449,9 +1495,7 @@ async function validatePackageSource(root, source) {
|
|
|
1449
1495
|
}
|
|
1450
1496
|
return;
|
|
1451
1497
|
}
|
|
1452
|
-
throw new Error(
|
|
1453
|
-
`PI package ${source} must use an exact npm version, a full Git commit, or a agent-local path.`
|
|
1454
|
-
);
|
|
1498
|
+
throw new Error(`PI package ${source} must use an exact npm version, a full Git commit, or a agent-local path.`);
|
|
1455
1499
|
}
|
|
1456
1500
|
async function validatePiPackages(root) {
|
|
1457
1501
|
const filename = path3.join(root, ".pi", "settings.json");
|
|
@@ -1459,23 +1503,26 @@ async function validatePiPackages(root) {
|
|
|
1459
1503
|
try {
|
|
1460
1504
|
settings = JSON.parse(await readFile3(filename, "utf8"));
|
|
1461
1505
|
} catch (error) {
|
|
1462
|
-
if (error.code === "ENOENT")
|
|
1506
|
+
if (error.code === "ENOENT")
|
|
1507
|
+
return;
|
|
1463
1508
|
throw new Error(`Could not read ${filename} as JSON.`, { cause: error });
|
|
1464
1509
|
}
|
|
1465
|
-
if (!settings || typeof settings !== "object" || !("packages" in settings))
|
|
1510
|
+
if (!settings || typeof settings !== "object" || !("packages" in settings))
|
|
1511
|
+
return;
|
|
1466
1512
|
const packages = settings.packages;
|
|
1467
|
-
if (!Array.isArray(packages))
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
);
|
|
1513
|
+
if (!Array.isArray(packages))
|
|
1514
|
+
throw new Error(`${filename} packages must be an array.`);
|
|
1515
|
+
await Promise.all(packages.map((value, index) => validatePackageSource(root, packageSource(value, index))));
|
|
1471
1516
|
}
|
|
1472
1517
|
async function findDeclarationRoot(start) {
|
|
1473
1518
|
let candidate = path3.resolve(start);
|
|
1474
1519
|
for (; ; ) {
|
|
1475
1520
|
try {
|
|
1476
|
-
if ((await lstat3(path3.join(candidate, ".tonbo"))).isFile())
|
|
1521
|
+
if ((await lstat3(path3.join(candidate, ".tonbo"))).isFile())
|
|
1522
|
+
return candidate;
|
|
1477
1523
|
} catch (error) {
|
|
1478
|
-
if (error.code !== "ENOENT")
|
|
1524
|
+
if (error.code !== "ENOENT")
|
|
1525
|
+
throw error;
|
|
1479
1526
|
}
|
|
1480
1527
|
const parent = path3.dirname(candidate);
|
|
1481
1528
|
if (parent === candidate) {
|
|
@@ -1489,7 +1536,8 @@ async function sourceIgnore(root) {
|
|
|
1489
1536
|
try {
|
|
1490
1537
|
matcher.add(await readFile3(path3.join(root, ".tonboignore"), "utf8"));
|
|
1491
1538
|
} catch (error) {
|
|
1492
|
-
if (error.code !== "ENOENT")
|
|
1539
|
+
if (error.code !== "ENOENT")
|
|
1540
|
+
throw error;
|
|
1493
1541
|
}
|
|
1494
1542
|
return matcher;
|
|
1495
1543
|
}
|
|
@@ -1502,11 +1550,10 @@ async function collectFiles(root) {
|
|
|
1502
1550
|
for (const entry of entries) {
|
|
1503
1551
|
const relative = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
|
|
1504
1552
|
const ignored = matcher.ignores(relative + (entry.isDirectory() ? "/" : ""));
|
|
1505
|
-
if (ignored)
|
|
1553
|
+
if (ignored)
|
|
1554
|
+
continue;
|
|
1506
1555
|
if (relative === SESSION_SOURCE_DIRECTORY || relative.startsWith(`${SESSION_SOURCE_DIRECTORY}/`)) {
|
|
1507
|
-
throw new Error(
|
|
1508
|
-
`Source path ${SESSION_SOURCE_DIRECTORY}/ is reserved for durable PI history. Rename it or exclude it with .tonboignore.`
|
|
1509
|
-
);
|
|
1556
|
+
throw new Error(`Source path ${SESSION_SOURCE_DIRECTORY}/ is reserved for durable PI history. Rename it or exclude it with .tonboignore.`);
|
|
1510
1557
|
}
|
|
1511
1558
|
const absolute = path3.join(directory, entry.name);
|
|
1512
1559
|
if (entry.isDirectory()) {
|
|
@@ -1515,9 +1562,7 @@ async function collectFiles(root) {
|
|
|
1515
1562
|
}
|
|
1516
1563
|
const metadata = await lstat3(absolute);
|
|
1517
1564
|
if (!metadata.isFile()) {
|
|
1518
|
-
throw new Error(
|
|
1519
|
-
`Source path ${relative} is not a regular file. V1 does not follow symlinks or special files.`
|
|
1520
|
-
);
|
|
1565
|
+
throw new Error(`Source path ${relative} is not a regular file. V1 does not follow symlinks or special files.`);
|
|
1521
1566
|
}
|
|
1522
1567
|
files.push({
|
|
1523
1568
|
absolute,
|
|
@@ -1535,19 +1580,15 @@ async function collectFiles(root) {
|
|
|
1535
1580
|
}
|
|
1536
1581
|
function addEntry(pack, file, contents) {
|
|
1537
1582
|
return new Promise((resolve, reject) => {
|
|
1538
|
-
pack.entry(
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
},
|
|
1548
|
-
contents,
|
|
1549
|
-
(error) => error ? reject(error) : resolve()
|
|
1550
|
-
);
|
|
1583
|
+
pack.entry({
|
|
1584
|
+
gid: 0,
|
|
1585
|
+
mode: file.mode,
|
|
1586
|
+
mtime: /* @__PURE__ */ new Date(0),
|
|
1587
|
+
name: file.relative,
|
|
1588
|
+
size: contents.length,
|
|
1589
|
+
type: "file",
|
|
1590
|
+
uid: 0
|
|
1591
|
+
}, contents, (error) => error ? reject(error) : resolve());
|
|
1551
1592
|
});
|
|
1552
1593
|
}
|
|
1553
1594
|
async function buildSourceBundle(root) {
|
|
@@ -1564,9 +1605,7 @@ async function buildSourceBundle(root) {
|
|
|
1564
1605
|
pack.on("data", (chunk) => {
|
|
1565
1606
|
size += chunk.length;
|
|
1566
1607
|
if (size > MAX_BUNDLE_BYTES) {
|
|
1567
|
-
pack.destroy(
|
|
1568
|
-
new Error(`Source bundle exceeds ${MAX_BUNDLE_BYTES} bytes after ignore rules.`)
|
|
1569
|
-
);
|
|
1608
|
+
pack.destroy(new Error(`Source bundle exceeds ${MAX_BUNDLE_BYTES} bytes after ignore rules.`));
|
|
1570
1609
|
return;
|
|
1571
1610
|
}
|
|
1572
1611
|
chunks.push(chunk);
|
|
@@ -1727,7 +1766,7 @@ async function initCommand(deps, options) {
|
|
|
1727
1766
|
entry = (await deps.prompt("PI SDK entry file [dist/agent.mjs]: ")).trim();
|
|
1728
1767
|
}
|
|
1729
1768
|
entry ||= "dist/agent.mjs";
|
|
1730
|
-
let model = options.model?.trim() || existingDeclaration?.inference.model ||
|
|
1769
|
+
let model = options.model?.trim() || existingDeclaration?.inference.model || DEFAULT_INFERENCE_MODEL;
|
|
1731
1770
|
let buildCommand = driver === "command" ? options.buildCommand ?? existingDeclaration?.build?.command ?? ["npm", "run", "build"] : void 0;
|
|
1732
1771
|
let agentPlan = existingDeclaration?.agent ? {
|
|
1733
1772
|
kind: "existing",
|
|
@@ -2666,6 +2705,10 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2666
2705
|
async (options) => initCommand(dependencies(jsonOutput(program)), options)
|
|
2667
2706
|
);
|
|
2668
2707
|
program.command("login").description("sign in through the browser and store the session in the user config").action(async () => loginCommand(dependencies(jsonOutput(program))));
|
|
2708
|
+
program.command("region").description("discover regions and Machine allocation availability").command("list").action(async () => {
|
|
2709
|
+
const deps = dependencies(jsonOutput(program));
|
|
2710
|
+
deps.output(await deps.api.listRegions(await deps.auth.accessToken()));
|
|
2711
|
+
});
|
|
2669
2712
|
const machine = program.command("machine").description("manage independent account-owned Machines");
|
|
2670
2713
|
machine.command("list").option("--account <account>", "account UUID").action(async (options) => {
|
|
2671
2714
|
const deps = dependencies(jsonOutput(program));
|
|
@@ -2676,7 +2719,7 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2676
2719
|
)
|
|
2677
2720
|
);
|
|
2678
2721
|
});
|
|
2679
|
-
machine.command("allocate").requiredOption("--account <account>", "owning account UUID").requiredOption("--region <region>", "compute region (
|
|
2722
|
+
machine.command("allocate").requiredOption("--account <account>", "owning account UUID").requiredOption("--region <region>", "compute region (see tonbo region list)").action(async (options) => {
|
|
2680
2723
|
const deps = dependencies(jsonOutput(program));
|
|
2681
2724
|
deps.output(
|
|
2682
2725
|
await deps.api.machineRequest(await deps.auth.accessToken(), "", "POST", {
|