@tonbo/cli 0.3.1 → 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 +1224 -1162
- package/package.json +1 -1
package/dist/bin/tonbo.js
CHANGED
|
@@ -32,1232 +32,1290 @@ 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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
}
|
|
349
436
|
}
|
|
350
437
|
};
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
+
}
|
|
357
477
|
}
|
|
358
|
-
|
|
359
|
-
|
|
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));
|
|
360
496
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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));
|
|
366
510
|
}
|
|
367
|
-
return
|
|
511
|
+
return candidate;
|
|
368
512
|
}
|
|
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
|
-
};
|
|
513
|
+
function assertManagedDeploymentSpec(value) {
|
|
514
|
+
if (!validateDeploymentSpec(value)) {
|
|
515
|
+
throw new Error(validationMessage("Managed Deployment spec", validateDeploymentSpec.errors));
|
|
388
516
|
}
|
|
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
517
|
}
|
|
436
518
|
|
|
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."
|
|
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;
|
|
468
525
|
}
|
|
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;
|
|
526
|
+
async listRegions(oauthToken) {
|
|
527
|
+
return parseRegionDiscovery(
|
|
528
|
+
await requestJson(this.fetcher, `${this.accountOrigin}/api/cli/regions`, {
|
|
529
|
+
headers: { authorization: `Bearer ${oauthToken}` }
|
|
530
|
+
})
|
|
531
|
+
);
|
|
486
532
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
+
});
|
|
495
542
|
}
|
|
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;
|
|
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);
|
|
507
551
|
}
|
|
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;
|
|
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);
|
|
524
560
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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);
|
|
531
574
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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);
|
|
538
592
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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);
|
|
544
606
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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);
|
|
550
616
|
}
|
|
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.
|
|
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 };
|
|
595
678
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
+
}
|
|
615
708
|
);
|
|
616
|
-
if (!refreshed.refresh_token) refreshed.refresh_token = tokens.refresh_token;
|
|
617
|
-
await this.credentials.save(refreshed);
|
|
618
|
-
return refreshed.access_token;
|
|
619
709
|
}
|
|
620
|
-
|
|
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()
|
|
621
727
|
}) {
|
|
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
|
-
)
|
|
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
|
|
657
759
|
);
|
|
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
760
|
}
|
|
664
|
-
|
|
665
|
-
return
|
|
761
|
+
listAgentSecrets(agentId, token) {
|
|
762
|
+
return this.management(
|
|
763
|
+
"GET",
|
|
764
|
+
`/v1/agents/${agentId}/secrets`,
|
|
765
|
+
token
|
|
766
|
+
).then((body) => body.data);
|
|
666
767
|
}
|
|
667
|
-
|
|
668
|
-
return
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
768
|
+
setAgentSecret(agentId, name, value, token) {
|
|
769
|
+
return this.management(
|
|
770
|
+
"PUT",
|
|
771
|
+
`/v1/agents/${agentId}/secrets/${encodeURIComponent(name)}`,
|
|
772
|
+
token,
|
|
773
|
+
{ value }
|
|
774
|
+
);
|
|
673
775
|
}
|
|
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;
|
|
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()
|
|
729
791
|
}
|
|
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
|
-
});
|
|
792
|
+
},
|
|
793
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
750
794
|
});
|
|
751
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
|
+
}
|
|
752
811
|
};
|
|
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
812
|
|
|
771
|
-
//
|
|
772
|
-
|
|
813
|
+
// src/agent-source.ts
|
|
814
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
815
|
+
import path from "node:path";
|
|
773
816
|
|
|
774
|
-
// ../../packages/agent-source-
|
|
775
|
-
|
|
776
|
-
|
|
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;
|
|
777
859
|
}
|
|
778
|
-
function
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
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) {
|
|
786
916
|
try {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
} catch {
|
|
792
|
-
return null;
|
|
917
|
+
return await lstat(path.join(this.root, filename));
|
|
918
|
+
} catch (error) {
|
|
919
|
+
if (error.code === "ENOENT") return null;
|
|
920
|
+
throw error;
|
|
793
921
|
}
|
|
794
922
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
return null;
|
|
799
|
-
return `https://github.com/${parts[0]}/${parts[1]}`;
|
|
923
|
+
};
|
|
924
|
+
function inspectLocalAgentSource(root) {
|
|
925
|
+
return inspectAgentSource(new LocalAgentSource(root));
|
|
800
926
|
}
|
|
801
927
|
|
|
802
|
-
// src/
|
|
803
|
-
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";
|
|
804
933
|
|
|
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
|
-
}
|
|
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."
|
|
900
959
|
}
|
|
901
960
|
};
|
|
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
|
-
}
|
|
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;
|
|
934
977
|
}
|
|
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"
|
|
978
|
+
@media (prefers-color-scheme: dark) {
|
|
979
|
+
:root {
|
|
980
|
+
--paper: #1d1b17;
|
|
981
|
+
--ink: #f3f2ee;
|
|
982
|
+
--rule: #3a362e;
|
|
983
|
+
--quiet: #a49e8f;
|
|
984
|
+
--signal: #f08b4b;
|
|
1012
985
|
}
|
|
1013
986
|
}
|
|
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
|
-
}
|
|
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;
|
|
1074
998
|
}
|
|
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
|
-
|
|
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"
|
|
1120
1061
|
},
|
|
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
|
-
|
|
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
|
+
});
|
|
1231
1242
|
}
|
|
1232
1243
|
};
|
|
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);
|
|
1249
|
+
}
|
|
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]);
|
|
1255
|
+
}
|
|
1233
1256
|
|
|
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}` : "."}`;
|
|
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";
|
|
1249
1268
|
}
|
|
1250
|
-
function
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
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
|
+
}
|
|
1254
1285
|
}
|
|
1255
|
-
|
|
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]}`;
|
|
1256
1291
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1292
|
+
|
|
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
|
+
});
|
|
1261
1319
|
}
|
|
1262
1320
|
|
|
1263
1321
|
// ../../packages/agent-source-bundler/dist/src/native-pi.js
|
|
@@ -2647,6 +2705,10 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2647
2705
|
async (options) => initCommand(dependencies(jsonOutput(program)), options)
|
|
2648
2706
|
);
|
|
2649
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
|
+
});
|
|
2650
2712
|
const machine = program.command("machine").description("manage independent account-owned Machines");
|
|
2651
2713
|
machine.command("list").option("--account <account>", "account UUID").action(async (options) => {
|
|
2652
2714
|
const deps = dependencies(jsonOutput(program));
|
|
@@ -2657,7 +2719,7 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2657
2719
|
)
|
|
2658
2720
|
);
|
|
2659
2721
|
});
|
|
2660
|
-
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) => {
|
|
2661
2723
|
const deps = dependencies(jsonOutput(program));
|
|
2662
2724
|
deps.output(
|
|
2663
2725
|
await deps.api.machineRequest(await deps.auth.accessToken(), "", "POST", {
|