@bolt-foundry/gambit 0.6.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -37
- package/README.md +76 -25
- package/esm/gambit/simulator-ui/dist/bundle.js +1010 -333
- package/esm/gambit/simulator-ui/dist/bundle.js.map +3 -3
- package/esm/mod.d.ts +4 -4
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +2 -2
- package/esm/src/cli_utils.d.ts +4 -5
- package/esm/src/cli_utils.d.ts.map +1 -1
- package/esm/src/cli_utils.js +27 -7
- package/esm/src/compat/openai.d.ts +2 -0
- package/esm/src/compat/openai.d.ts.map +1 -0
- package/esm/src/compat/openai.js +1 -0
- package/esm/src/providers/openrouter.d.ts +9 -0
- package/esm/src/providers/openrouter.d.ts.map +1 -0
- package/esm/src/providers/openrouter.js +595 -0
- package/esm/src/server.d.ts.map +1 -1
- package/esm/src/server.js +261 -6
- package/esm/src/trace.d.ts.map +1 -1
- package/esm/src/trace.js +4 -1
- package/package.json +3 -2
- package/script/gambit/simulator-ui/dist/bundle.js +1010 -333
- package/script/gambit/simulator-ui/dist/bundle.js.map +3 -3
- package/script/mod.d.ts +4 -4
- package/script/mod.d.ts.map +1 -1
- package/script/mod.js +6 -6
- package/script/src/cli_utils.d.ts +4 -5
- package/script/src/cli_utils.d.ts.map +1 -1
- package/script/src/cli_utils.js +29 -9
- package/script/src/compat/openai.d.ts +2 -0
- package/script/src/compat/openai.d.ts.map +1 -0
- package/script/src/compat/openai.js +5 -0
- package/script/src/providers/openrouter.d.ts +9 -0
- package/script/src/providers/openrouter.d.ts.map +1 -0
- package/script/src/providers/openrouter.js +634 -0
- package/script/src/server.d.ts.map +1 -1
- package/script/src/server.js +261 -6
- package/script/src/trace.d.ts.map +1 -1
- package/script/src/trace.js +4 -1
package/script/src/server.js
CHANGED
|
@@ -98,6 +98,87 @@ function randomId(prefix) {
|
|
|
98
98
|
const suffix = crypto.randomUUID().replace(/-/g, "").slice(0, 24);
|
|
99
99
|
return `${prefix}-${suffix}`;
|
|
100
100
|
}
|
|
101
|
+
async function parseOpenResponseRequest(req) {
|
|
102
|
+
try {
|
|
103
|
+
return await req.json();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function formatOpenResponseSseEvent(event) {
|
|
110
|
+
return `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
|
|
111
|
+
}
|
|
112
|
+
function formatOpenResponseDoneEvent() {
|
|
113
|
+
return "data: [DONE]\n\n";
|
|
114
|
+
}
|
|
115
|
+
function createOpenResponseStream(req, provider, payload) {
|
|
116
|
+
const encoder = new TextEncoder();
|
|
117
|
+
const stream = new ReadableStream({
|
|
118
|
+
start(controller) {
|
|
119
|
+
let closed = false;
|
|
120
|
+
let completed = false;
|
|
121
|
+
const close = () => {
|
|
122
|
+
if (closed)
|
|
123
|
+
return;
|
|
124
|
+
closed = true;
|
|
125
|
+
controller.close();
|
|
126
|
+
};
|
|
127
|
+
const send = (chunk) => {
|
|
128
|
+
if (closed)
|
|
129
|
+
return;
|
|
130
|
+
controller.enqueue(encoder.encode(chunk));
|
|
131
|
+
};
|
|
132
|
+
const sendDone = () => {
|
|
133
|
+
if (closed)
|
|
134
|
+
return;
|
|
135
|
+
send(formatOpenResponseDoneEvent());
|
|
136
|
+
close();
|
|
137
|
+
};
|
|
138
|
+
const sendEvent = (event) => {
|
|
139
|
+
if (closed)
|
|
140
|
+
return;
|
|
141
|
+
send(formatOpenResponseSseEvent(event));
|
|
142
|
+
if (event.type === "response.completed") {
|
|
143
|
+
completed = true;
|
|
144
|
+
sendDone();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
req.signal.addEventListener("abort", () => {
|
|
148
|
+
close();
|
|
149
|
+
});
|
|
150
|
+
(async () => {
|
|
151
|
+
try {
|
|
152
|
+
const response = await provider.responses({
|
|
153
|
+
...payload,
|
|
154
|
+
stream: true,
|
|
155
|
+
onStreamEvent: sendEvent,
|
|
156
|
+
});
|
|
157
|
+
if (!completed) {
|
|
158
|
+
sendEvent({ type: "response.completed", response });
|
|
159
|
+
sendDone();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
164
|
+
sendEvent({
|
|
165
|
+
type: "error",
|
|
166
|
+
error: { code: "server_error", message },
|
|
167
|
+
});
|
|
168
|
+
sendDone();
|
|
169
|
+
}
|
|
170
|
+
})();
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
return new Response(stream, {
|
|
174
|
+
status: 200,
|
|
175
|
+
headers: {
|
|
176
|
+
"Content-Type": "text/event-stream",
|
|
177
|
+
"Cache-Control": "no-cache",
|
|
178
|
+
"Connection": "keep-alive",
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
101
182
|
function resolveDefaultValue(raw) {
|
|
102
183
|
if (typeof raw === "function") {
|
|
103
184
|
try {
|
|
@@ -565,6 +646,12 @@ function startWebSocketSimulator(opts) {
|
|
|
565
646
|
return "";
|
|
566
647
|
if (typeof value === "string")
|
|
567
648
|
return value;
|
|
649
|
+
if (Array.isArray(value)) {
|
|
650
|
+
return value
|
|
651
|
+
.map((part) => typeof part === "string" ? part : part.text ??
|
|
652
|
+
"")
|
|
653
|
+
.join("");
|
|
654
|
+
}
|
|
568
655
|
try {
|
|
569
656
|
return JSON.stringify(value);
|
|
570
657
|
}
|
|
@@ -624,7 +711,8 @@ function startWebSocketSimulator(opts) {
|
|
|
624
711
|
const fallbackToolInserts = [];
|
|
625
712
|
for (let i = 0; i < rawMessages.length; i++) {
|
|
626
713
|
const msg = rawMessages[i];
|
|
627
|
-
if (msg?.
|
|
714
|
+
if (msg?.type === "message" &&
|
|
715
|
+
(msg.role === "assistant" || msg.role === "user")) {
|
|
628
716
|
const content = stringifyContent(msg.content).trim();
|
|
629
717
|
if (!content)
|
|
630
718
|
continue;
|
|
@@ -637,7 +725,7 @@ function startWebSocketSimulator(opts) {
|
|
|
637
725
|
});
|
|
638
726
|
continue;
|
|
639
727
|
}
|
|
640
|
-
if (msg?.role === "tool") {
|
|
728
|
+
if (msg?.type === "message" && msg.role === "tool") {
|
|
641
729
|
const actionCallId = typeof msg.tool_call_id === "string"
|
|
642
730
|
? msg.tool_call_id
|
|
643
731
|
: undefined;
|
|
@@ -773,7 +861,7 @@ function startWebSocketSimulator(opts) {
|
|
|
773
861
|
const getLastAssistantMessage = (history) => {
|
|
774
862
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
775
863
|
const msg = history[i];
|
|
776
|
-
if (msg?.role === "assistant") {
|
|
864
|
+
if (msg?.type === "message" && msg.role === "assistant") {
|
|
777
865
|
return stringifyContent(msg.content);
|
|
778
866
|
}
|
|
779
867
|
}
|
|
@@ -1036,6 +1124,43 @@ function startWebSocketSimulator(opts) {
|
|
|
1036
1124
|
}
|
|
1037
1125
|
const server = dntShim.Deno.serve({ port, signal: opts.signal, onListen: () => { } }, async (req) => {
|
|
1038
1126
|
const url = new URL(req.url);
|
|
1127
|
+
if (url.pathname === "/v1/responses") {
|
|
1128
|
+
if (req.method !== "POST") {
|
|
1129
|
+
return new Response("Method not allowed", { status: 405 });
|
|
1130
|
+
}
|
|
1131
|
+
const payload = await parseOpenResponseRequest(req);
|
|
1132
|
+
if (!payload) {
|
|
1133
|
+
return new Response("Invalid JSON payload", { status: 400 });
|
|
1134
|
+
}
|
|
1135
|
+
const model = payload.model ?? opts.model;
|
|
1136
|
+
if (!model) {
|
|
1137
|
+
return new Response("Missing model", { status: 400 });
|
|
1138
|
+
}
|
|
1139
|
+
const requestPayload = {
|
|
1140
|
+
...payload,
|
|
1141
|
+
model,
|
|
1142
|
+
input: payload.input ?? null,
|
|
1143
|
+
};
|
|
1144
|
+
if (payload.stream) {
|
|
1145
|
+
return createOpenResponseStream(req, opts.modelProvider, requestPayload);
|
|
1146
|
+
}
|
|
1147
|
+
try {
|
|
1148
|
+
const response = await opts.modelProvider.responses({
|
|
1149
|
+
...requestPayload,
|
|
1150
|
+
stream: false,
|
|
1151
|
+
});
|
|
1152
|
+
return new Response(JSON.stringify(response), {
|
|
1153
|
+
headers: { "content-type": "application/json" },
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
catch (err) {
|
|
1157
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1158
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
1159
|
+
status: 500,
|
|
1160
|
+
headers: { "content-type": "application/json" },
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1039
1164
|
if (url.pathname.startsWith("/api/durable-streams/stream/")) {
|
|
1040
1165
|
return (0, durable_streams_js_1.handleDurableStreamRequest)(req);
|
|
1041
1166
|
}
|
|
@@ -1086,7 +1211,9 @@ function startWebSocketSimulator(opts) {
|
|
|
1086
1211
|
})();
|
|
1087
1212
|
const sessionPayload = {
|
|
1088
1213
|
messages: Array.isArray(sessionState.messages)
|
|
1089
|
-
? sessionState.messages
|
|
1214
|
+
? sessionState.messages
|
|
1215
|
+
.filter((msg) => msg.type === "message")
|
|
1216
|
+
.map((msg) => ({
|
|
1090
1217
|
role: msg.role,
|
|
1091
1218
|
content: msg.content,
|
|
1092
1219
|
name: msg.name,
|
|
@@ -1241,6 +1368,124 @@ function startWebSocketSimulator(opts) {
|
|
|
1241
1368
|
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1242
1369
|
}
|
|
1243
1370
|
}
|
|
1371
|
+
if (url.pathname === "/api/calibrate/flag") {
|
|
1372
|
+
if (req.method !== "POST") {
|
|
1373
|
+
return new Response("Method not allowed", { status: 405 });
|
|
1374
|
+
}
|
|
1375
|
+
try {
|
|
1376
|
+
const body = await req.json();
|
|
1377
|
+
if (!body.sessionId || !body.refId) {
|
|
1378
|
+
throw new Error("Missing sessionId or refId");
|
|
1379
|
+
}
|
|
1380
|
+
const state = readSessionState(body.sessionId);
|
|
1381
|
+
if (!state) {
|
|
1382
|
+
throw new Error("Session not found");
|
|
1383
|
+
}
|
|
1384
|
+
const meta = (state.meta && typeof state.meta === "object")
|
|
1385
|
+
? { ...state.meta }
|
|
1386
|
+
: {};
|
|
1387
|
+
const existingFlags = Array.isArray(meta.gradingFlags)
|
|
1388
|
+
? (meta.gradingFlags)
|
|
1389
|
+
: [];
|
|
1390
|
+
const flagIndex = existingFlags.findIndex((flag) => flag?.refId === body.refId);
|
|
1391
|
+
let nextFlags;
|
|
1392
|
+
let flagged = false;
|
|
1393
|
+
if (flagIndex >= 0) {
|
|
1394
|
+
nextFlags = existingFlags.filter((_, idx) => idx !== flagIndex);
|
|
1395
|
+
flagged = false;
|
|
1396
|
+
}
|
|
1397
|
+
else {
|
|
1398
|
+
const now = new Date().toISOString();
|
|
1399
|
+
nextFlags = [
|
|
1400
|
+
...existingFlags,
|
|
1401
|
+
{
|
|
1402
|
+
id: randomId("flag"),
|
|
1403
|
+
refId: body.refId,
|
|
1404
|
+
runId: body.runId,
|
|
1405
|
+
turnIndex: body.turnIndex,
|
|
1406
|
+
reason: body.reason?.trim() || undefined,
|
|
1407
|
+
createdAt: now,
|
|
1408
|
+
},
|
|
1409
|
+
];
|
|
1410
|
+
flagged = true;
|
|
1411
|
+
}
|
|
1412
|
+
const updated = persistSessionState({
|
|
1413
|
+
...state,
|
|
1414
|
+
meta: {
|
|
1415
|
+
...meta,
|
|
1416
|
+
gradingFlags: nextFlags,
|
|
1417
|
+
},
|
|
1418
|
+
});
|
|
1419
|
+
const sessionMeta = buildSessionMeta(body.sessionId, updated);
|
|
1420
|
+
(0, durable_streams_js_1.appendDurableStreamEvent)(CALIBRATE_STREAM_ID, {
|
|
1421
|
+
type: "calibrateSession",
|
|
1422
|
+
sessionId: body.sessionId,
|
|
1423
|
+
session: sessionMeta,
|
|
1424
|
+
});
|
|
1425
|
+
return new Response(JSON.stringify({
|
|
1426
|
+
sessionId: body.sessionId,
|
|
1427
|
+
flagged,
|
|
1428
|
+
flags: nextFlags,
|
|
1429
|
+
}), { headers: { "content-type": "application/json" } });
|
|
1430
|
+
}
|
|
1431
|
+
catch (err) {
|
|
1432
|
+
return new Response(JSON.stringify({
|
|
1433
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1434
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
if (url.pathname === "/api/calibrate/flag/reason") {
|
|
1438
|
+
if (req.method !== "POST") {
|
|
1439
|
+
return new Response("Method not allowed", { status: 405 });
|
|
1440
|
+
}
|
|
1441
|
+
try {
|
|
1442
|
+
const body = await req.json();
|
|
1443
|
+
if (!body.sessionId || !body.refId) {
|
|
1444
|
+
throw new Error("Missing sessionId or refId");
|
|
1445
|
+
}
|
|
1446
|
+
const state = readSessionState(body.sessionId);
|
|
1447
|
+
if (!state) {
|
|
1448
|
+
throw new Error("Session not found");
|
|
1449
|
+
}
|
|
1450
|
+
const meta = (state.meta && typeof state.meta === "object")
|
|
1451
|
+
? { ...state.meta }
|
|
1452
|
+
: {};
|
|
1453
|
+
const existingFlags = Array.isArray(meta.gradingFlags)
|
|
1454
|
+
? (meta.gradingFlags)
|
|
1455
|
+
: [];
|
|
1456
|
+
const flagIndex = existingFlags.findIndex((flag) => flag?.refId === body.refId);
|
|
1457
|
+
if (flagIndex < 0) {
|
|
1458
|
+
throw new Error("Flag not found");
|
|
1459
|
+
}
|
|
1460
|
+
const updatedFlag = {
|
|
1461
|
+
...existingFlags[flagIndex],
|
|
1462
|
+
reason: body.reason?.trim() || undefined,
|
|
1463
|
+
};
|
|
1464
|
+
const nextFlags = existingFlags.map((flag, idx) => idx === flagIndex ? updatedFlag : flag);
|
|
1465
|
+
const updated = persistSessionState({
|
|
1466
|
+
...state,
|
|
1467
|
+
meta: {
|
|
1468
|
+
...meta,
|
|
1469
|
+
gradingFlags: nextFlags,
|
|
1470
|
+
},
|
|
1471
|
+
});
|
|
1472
|
+
const sessionMeta = buildSessionMeta(body.sessionId, updated);
|
|
1473
|
+
(0, durable_streams_js_1.appendDurableStreamEvent)(CALIBRATE_STREAM_ID, {
|
|
1474
|
+
type: "calibrateSession",
|
|
1475
|
+
sessionId: body.sessionId,
|
|
1476
|
+
session: sessionMeta,
|
|
1477
|
+
});
|
|
1478
|
+
return new Response(JSON.stringify({
|
|
1479
|
+
sessionId: body.sessionId,
|
|
1480
|
+
flags: nextFlags,
|
|
1481
|
+
}), { headers: { "content-type": "application/json" } });
|
|
1482
|
+
}
|
|
1483
|
+
catch (err) {
|
|
1484
|
+
return new Response(JSON.stringify({
|
|
1485
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1486
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1244
1489
|
if (url.pathname === "/api/grading/reference") {
|
|
1245
1490
|
if (req.method !== "POST") {
|
|
1246
1491
|
return new Response("Method not allowed", { status: 405 });
|
|
@@ -1385,7 +1630,10 @@ function startWebSocketSimulator(opts) {
|
|
|
1385
1630
|
if (typeof body.maxTurns === "number" && Number.isFinite(body.maxTurns)) {
|
|
1386
1631
|
maxTurnsOverride = body.maxTurns;
|
|
1387
1632
|
}
|
|
1388
|
-
deckInput = body.init;
|
|
1633
|
+
deckInput = body.context ?? body.init;
|
|
1634
|
+
if (body.init !== undefined && body.context === undefined) {
|
|
1635
|
+
logger.warn('[gambit] Received deprecated "init" field in test-bot API; use "context" instead.');
|
|
1636
|
+
}
|
|
1389
1637
|
botInput = body.botInput;
|
|
1390
1638
|
await deckLoadPromise.catch(() => null);
|
|
1391
1639
|
if (typeof body.botDeckPath === "string") {
|
|
@@ -1829,6 +2077,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1829
2077
|
sessionId,
|
|
1830
2078
|
messages: state.messages,
|
|
1831
2079
|
messageRefs: state.messageRefs,
|
|
2080
|
+
feedback: state.feedback,
|
|
1832
2081
|
traces: state.traces,
|
|
1833
2082
|
notes: state.notes,
|
|
1834
2083
|
meta: state.meta,
|
|
@@ -1992,7 +2241,13 @@ function startWebSocketSimulator(opts) {
|
|
|
1992
2241
|
Array.isArray(state.messages)) {
|
|
1993
2242
|
const idx = state.messageRefs.findIndex((ref) => ref?.id === messageRefId);
|
|
1994
2243
|
if (idx >= 0) {
|
|
1995
|
-
|
|
2244
|
+
const item = state.messages[idx];
|
|
2245
|
+
if (item?.type === "message") {
|
|
2246
|
+
messageContent = stringifyContent(item.content);
|
|
2247
|
+
}
|
|
2248
|
+
else {
|
|
2249
|
+
messageContent = undefined;
|
|
2250
|
+
}
|
|
1996
2251
|
}
|
|
1997
2252
|
}
|
|
1998
2253
|
items.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/src/trace.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAI5D,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAQ7E;AAED,wBAAgB,iBAAiB,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/src/trace.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAI5D,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAQ7E;AAED,wBAAgB,iBAAiB,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAsI/D"}
|
package/script/src/trace.js
CHANGED
|
@@ -68,7 +68,10 @@ function makeConsoleTracer() {
|
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
70
|
case "message.user": {
|
|
71
|
-
|
|
71
|
+
const messageContent = event.message?.type === "message"
|
|
72
|
+
? event.message.content
|
|
73
|
+
: event.message;
|
|
74
|
+
logger.log(`[trace] message.user runId=${event.runId} actionCallId=${event.actionCallId} deck=${event.deckPath} content=${JSON.stringify(messageContent ?? "")}`);
|
|
72
75
|
break;
|
|
73
76
|
}
|
|
74
77
|
case "deck.start":
|