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