@shogo-ai/core 1.10.0 → 2.4.1
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/dist/chunk-H625AGFN.js +466 -0
- package/dist/chunk-MDRCI2CN.js +197 -0
- package/dist/chunk-NTAY4E43.js +72 -0
- package/dist/{chunk-24AL3V6R.js → chunk-VW6ZGCBI.js} +35 -1
- package/dist/{chunk-IDGVRGAL.js → chunk-WRHTXHTH.js} +81 -11
- package/dist/file-types.cjs +79 -5
- package/dist/file-types.d.cts +23 -4
- package/dist/file-types.d.ts +23 -4
- package/dist/file-types.js +7 -1
- package/dist/index.cjs +464 -17
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +35 -7
- package/dist/instrumentation.cjs +95 -10
- package/dist/instrumentation.js +2 -1
- package/dist/logger.cjs +39 -2
- package/dist/logger.d.cts +71 -1
- package/dist/logger.d.ts +71 -1
- package/dist/logger.js +9 -3
- package/dist/stream-buffer.cjs +242 -5
- package/dist/stream-buffer.d.cts +97 -1
- package/dist/stream-buffer.d.ts +97 -1
- package/dist/stream-buffer.js +13 -3
- package/dist/tech-stack-registry.cjs +36 -0
- package/dist/tech-stack-registry.d.cts +48 -1
- package/dist/tech-stack-registry.d.ts +48 -1
- package/dist/tech-stack-registry.js +7 -1
- package/package.json +16 -1
- package/dist/chunk-ELMG6OXT.js +0 -236
- package/dist/chunk-HKX3MEJN.js +0 -126
- package/dist/chunk-KTDDFWXM.js +0 -39
package/dist/index.cjs
CHANGED
|
@@ -20,19 +20,33 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
BINARY_FILE_BASENAMES: () => BINARY_FILE_BASENAMES,
|
|
23
24
|
BINARY_FILE_EXTENSIONS: () => BINARY_FILE_EXTENSIONS,
|
|
25
|
+
BINARY_FILE_TRANSIENT_SUFFIXES: () => BINARY_FILE_TRANSIENT_SUFFIXES,
|
|
24
26
|
StreamBufferStore: () => StreamBufferStore,
|
|
25
27
|
TECH_STACK_REGISTRY: () => TECH_STACK_REGISTRY,
|
|
28
|
+
TURN_COMPLETE_EVENT_TYPE: () => TURN_COMPLETE_EVENT_TYPE,
|
|
29
|
+
TURN_SEQ_EVENT_TYPE: () => TURN_SEQ_EVENT_TYPE,
|
|
30
|
+
compactSseFrames: () => compactSseFrames,
|
|
26
31
|
createBufferingTransform: () => createBufferingTransform,
|
|
27
32
|
createLogger: () => createLogger,
|
|
33
|
+
emitLogToSink: () => emitLogToSink,
|
|
34
|
+
encodeTurnCompleteFrame: () => encodeTurnCompleteFrame,
|
|
35
|
+
encodeTurnSeqFrame: () => encodeTurnSeqFrame,
|
|
28
36
|
extractUserText: () => extractUserText,
|
|
29
37
|
findLastUserMessage: () => findLastUserMessage,
|
|
38
|
+
getDeclaredPorts: () => getDeclaredPorts,
|
|
39
|
+
getMinimumInstanceSize: () => getMinimumInstanceSize,
|
|
30
40
|
getStackEntry: () => getStackEntry,
|
|
31
41
|
initInstrumentation: () => initInstrumentation,
|
|
42
|
+
isBinaryBuffer: () => isBinaryBuffer,
|
|
32
43
|
isBinaryFilePath: () => isBinaryFilePath,
|
|
44
|
+
isDockerTechStack: () => isDockerTechStack,
|
|
33
45
|
isMacOSJunkName: () => isMacOSJunkName,
|
|
34
46
|
isMacOSJunkPath: () => isMacOSJunkPath,
|
|
35
47
|
isMobileTechStack: () => isMobileTechStack,
|
|
48
|
+
setOtelLogSink: () => setOtelLogSink,
|
|
49
|
+
setTraceContextProvider: () => setTraceContextProvider,
|
|
36
50
|
shutdownInstrumentation: () => shutdownInstrumentation,
|
|
37
51
|
stackSeedsItself: () => stackSeedsItself,
|
|
38
52
|
traceOperation: () => traceOperation,
|
|
@@ -60,6 +74,21 @@ function formatEntry(entry) {
|
|
|
60
74
|
const extraStr = Object.keys(extra).length > 0 ? " " + JSON.stringify(extra) : "";
|
|
61
75
|
return `[${service}] ${msg}${extraStr}`;
|
|
62
76
|
}
|
|
77
|
+
var logSink = null;
|
|
78
|
+
function setOtelLogSink(sink) {
|
|
79
|
+
logSink = sink;
|
|
80
|
+
}
|
|
81
|
+
function emitLogToSink(entry) {
|
|
82
|
+
if (!logSink) return;
|
|
83
|
+
try {
|
|
84
|
+
logSink({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var traceContextProvider = null;
|
|
89
|
+
function setTraceContextProvider(provider) {
|
|
90
|
+
traceContextProvider = provider;
|
|
91
|
+
}
|
|
63
92
|
function createLogger(service, defaultExtra) {
|
|
64
93
|
function log(level, msg, extra) {
|
|
65
94
|
if (!shouldLog(level)) return;
|
|
@@ -71,6 +100,16 @@ function createLogger(service, defaultExtra) {
|
|
|
71
100
|
...defaultExtra,
|
|
72
101
|
...extra
|
|
73
102
|
};
|
|
103
|
+
if (traceContextProvider) {
|
|
104
|
+
try {
|
|
105
|
+
const tc = traceContextProvider();
|
|
106
|
+
if (tc) {
|
|
107
|
+
entry.trace_id = tc.trace_id;
|
|
108
|
+
entry.span_id = tc.span_id;
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
74
113
|
const formatted = formatEntry(entry);
|
|
75
114
|
switch (level) {
|
|
76
115
|
case "error":
|
|
@@ -83,6 +122,12 @@ function createLogger(service, defaultExtra) {
|
|
|
83
122
|
console.log(formatted);
|
|
84
123
|
break;
|
|
85
124
|
}
|
|
125
|
+
if (logSink) {
|
|
126
|
+
try {
|
|
127
|
+
logSink(entry);
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
86
131
|
}
|
|
87
132
|
return {
|
|
88
133
|
debug: (msg, extra) => log("debug", msg, extra),
|
|
@@ -95,13 +140,23 @@ function createLogger(service, defaultExtra) {
|
|
|
95
140
|
|
|
96
141
|
// src/instrumentation.ts
|
|
97
142
|
var import_api = require("@opentelemetry/api");
|
|
143
|
+
var import_api_logs = require("@opentelemetry/api-logs");
|
|
98
144
|
var import_sdk_node = require("@opentelemetry/sdk-node");
|
|
99
145
|
var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
146
|
+
var import_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
|
|
100
147
|
var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
148
|
+
var import_sdk_logs = require("@opentelemetry/sdk-logs");
|
|
101
149
|
var import_resources = require("@opentelemetry/resources");
|
|
102
150
|
var import_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
151
|
+
var SEVERITY_BY_LEVEL = {
|
|
152
|
+
debug: { number: import_api_logs.SeverityNumber.DEBUG, text: "DEBUG" },
|
|
153
|
+
info: { number: import_api_logs.SeverityNumber.INFO, text: "INFO" },
|
|
154
|
+
warn: { number: import_api_logs.SeverityNumber.WARN, text: "WARN" },
|
|
155
|
+
error: { number: import_api_logs.SeverityNumber.ERROR, text: "ERROR" }
|
|
156
|
+
};
|
|
103
157
|
var sdk = null;
|
|
104
158
|
function initInstrumentation(config) {
|
|
159
|
+
if (sdk) return;
|
|
105
160
|
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
106
161
|
if (!endpoint) {
|
|
107
162
|
console.log(`[OTEL] Tracing disabled for ${config.serviceName} (OTEL_EXPORTER_OTLP_ENDPOINT not set)`);
|
|
@@ -115,18 +170,43 @@ function initInstrumentation(config) {
|
|
|
115
170
|
}
|
|
116
171
|
const serviceName = process.env.OTEL_SERVICE_NAME || config.serviceName;
|
|
117
172
|
console.log(`[OTEL] Configuring ${serviceName} \u2192 ${endpoint}/v1/traces`);
|
|
173
|
+
const exportTimeoutMs = Number(process.env.OTEL_EXPORTER_TIMEOUT_MS ?? 3e3);
|
|
118
174
|
let traceExporter;
|
|
119
175
|
try {
|
|
120
176
|
traceExporter = new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
121
177
|
url: `${endpoint}/v1/traces`,
|
|
122
|
-
headers
|
|
178
|
+
headers,
|
|
179
|
+
timeoutMillis: exportTimeoutMs
|
|
123
180
|
});
|
|
124
181
|
} catch (err) {
|
|
125
182
|
console.error(`[OTEL] Failed to create exporter: ${err.message}`);
|
|
126
183
|
return;
|
|
127
184
|
}
|
|
128
185
|
const isProduction2 = process.env.NODE_ENV === "production" || process.env.NODE_ENV === "staging";
|
|
129
|
-
const spanProcessor = isProduction2 ? new import_sdk_trace_base.BatchSpanProcessor(traceExporter
|
|
186
|
+
const spanProcessor = isProduction2 ? new import_sdk_trace_base.BatchSpanProcessor(traceExporter, {
|
|
187
|
+
maxQueueSize: 2048,
|
|
188
|
+
maxExportBatchSize: 512,
|
|
189
|
+
scheduledDelayMillis: 5e3,
|
|
190
|
+
exportTimeoutMillis: exportTimeoutMs
|
|
191
|
+
}) : new import_sdk_trace_base.SimpleSpanProcessor(traceExporter);
|
|
192
|
+
let logRecordProcessors = [];
|
|
193
|
+
try {
|
|
194
|
+
const logExporter = new import_exporter_logs_otlp_http.OTLPLogExporter({
|
|
195
|
+
url: `${endpoint}/v1/logs`,
|
|
196
|
+
headers,
|
|
197
|
+
timeoutMillis: exportTimeoutMs
|
|
198
|
+
});
|
|
199
|
+
logRecordProcessors = [
|
|
200
|
+
isProduction2 ? new import_sdk_logs.BatchLogRecordProcessor(logExporter, {
|
|
201
|
+
maxQueueSize: 2048,
|
|
202
|
+
maxExportBatchSize: 512,
|
|
203
|
+
scheduledDelayMillis: 5e3,
|
|
204
|
+
exportTimeoutMillis: exportTimeoutMs
|
|
205
|
+
}) : new import_sdk_logs.SimpleLogRecordProcessor(logExporter)
|
|
206
|
+
];
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.warn(`[OTEL] Failed to create log exporter for ${serviceName}: ${err.message}`);
|
|
209
|
+
}
|
|
130
210
|
sdk = new import_sdk_node.NodeSDK({
|
|
131
211
|
resource: (0, import_resources.resourceFromAttributes)({
|
|
132
212
|
[import_semantic_conventions.ATTR_SERVICE_NAME]: serviceName,
|
|
@@ -134,6 +214,7 @@ function initInstrumentation(config) {
|
|
|
134
214
|
"deployment.environment.name": process.env.NODE_ENV || "development"
|
|
135
215
|
}),
|
|
136
216
|
spanProcessors: [spanProcessor],
|
|
217
|
+
logRecordProcessors,
|
|
137
218
|
instrumentations: []
|
|
138
219
|
});
|
|
139
220
|
try {
|
|
@@ -144,6 +225,24 @@ function initInstrumentation(config) {
|
|
|
144
225
|
sdk = null;
|
|
145
226
|
return;
|
|
146
227
|
}
|
|
228
|
+
setTraceContextProvider(() => {
|
|
229
|
+
const ctx = import_api.trace.getActiveSpan()?.spanContext();
|
|
230
|
+
return ctx && (0, import_api.isSpanContextValid)(ctx) ? { trace_id: ctx.traceId, span_id: ctx.spanId } : null;
|
|
231
|
+
});
|
|
232
|
+
if (logRecordProcessors.length > 0) {
|
|
233
|
+
const otelLogger = import_api_logs.logs.getLogger(serviceName);
|
|
234
|
+
setOtelLogSink((entry) => {
|
|
235
|
+
const { level, msg, service, timestamp, ...attrs } = entry;
|
|
236
|
+
const severity = SEVERITY_BY_LEVEL[level] ?? SEVERITY_BY_LEVEL.info;
|
|
237
|
+
otelLogger.emit({
|
|
238
|
+
severityNumber: severity.number,
|
|
239
|
+
severityText: severity.text,
|
|
240
|
+
body: msg,
|
|
241
|
+
attributes: { service, ...attrs }
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
console.log(`[OTEL] Logs exporter enabled for ${serviceName} \u2192 ${endpoint}/v1/logs`);
|
|
245
|
+
}
|
|
147
246
|
try {
|
|
148
247
|
const tracer = import_api.trace.getTracer(serviceName);
|
|
149
248
|
const testSpan = tracer.startSpan("otel.startup-test", {
|
|
@@ -164,13 +263,24 @@ function initInstrumentation(config) {
|
|
|
164
263
|
}
|
|
165
264
|
}
|
|
166
265
|
async function shutdownInstrumentation() {
|
|
167
|
-
if (sdk)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
266
|
+
if (!sdk) return;
|
|
267
|
+
setOtelLogSink(null);
|
|
268
|
+
setTraceContextProvider(null);
|
|
269
|
+
const shutdownTimeoutMs = Number(process.env.OTEL_SHUTDOWN_TIMEOUT_MS ?? 2e3);
|
|
270
|
+
let timer;
|
|
271
|
+
try {
|
|
272
|
+
await Promise.race([
|
|
273
|
+
sdk.shutdown(),
|
|
274
|
+
new Promise((resolve) => {
|
|
275
|
+
timer = setTimeout(resolve, shutdownTimeoutMs);
|
|
276
|
+
})
|
|
277
|
+
]);
|
|
278
|
+
console.log("[OTEL] Tracing shut down");
|
|
279
|
+
} catch (err) {
|
|
280
|
+
console.error("[OTEL] Error shutting down:", err.message);
|
|
281
|
+
} finally {
|
|
282
|
+
if (timer) clearTimeout(timer);
|
|
283
|
+
sdk = null;
|
|
174
284
|
}
|
|
175
285
|
}
|
|
176
286
|
function traceOperation(tracerName, spanName, attributes, fn) {
|
|
@@ -190,13 +300,126 @@ function traceOperation(tracerName, spanName, attributes, fn) {
|
|
|
190
300
|
});
|
|
191
301
|
}
|
|
192
302
|
|
|
303
|
+
// src/stream-compaction.ts
|
|
304
|
+
var TURN_SEQ_EVENT_TYPE = "data-turn-seq";
|
|
305
|
+
var DELTA_PART = {
|
|
306
|
+
"text-delta": "text",
|
|
307
|
+
"reasoning-delta": "reasoning"
|
|
308
|
+
};
|
|
309
|
+
var PART_BOUNDARY = {
|
|
310
|
+
"text-end": "text",
|
|
311
|
+
"reasoning-end": "reasoning",
|
|
312
|
+
"text-start": "text",
|
|
313
|
+
"reasoning-start": "reasoning"
|
|
314
|
+
};
|
|
315
|
+
function compactSseFrames(frames) {
|
|
316
|
+
if (frames.length === 0) return new Uint8Array(0);
|
|
317
|
+
const decoder = new TextDecoder();
|
|
318
|
+
let text = "";
|
|
319
|
+
for (const frame of frames) text += decoder.decode(frame, { stream: true });
|
|
320
|
+
text += decoder.decode();
|
|
321
|
+
const events = text.split("\n\n");
|
|
322
|
+
const tail = events.pop() ?? "";
|
|
323
|
+
const out = [];
|
|
324
|
+
const openParts = /* @__PURE__ */ new Map();
|
|
325
|
+
const openToolInputs = /* @__PURE__ */ new Map();
|
|
326
|
+
const preliminaryOutputs = /* @__PURE__ */ new Map();
|
|
327
|
+
for (const event of events) {
|
|
328
|
+
const chunk = parseEvent(event);
|
|
329
|
+
if (!chunk) {
|
|
330
|
+
out.push({ raw: event });
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const type = chunk.type;
|
|
334
|
+
if (type === TURN_SEQ_EVENT_TYPE) continue;
|
|
335
|
+
if (type in PART_BOUNDARY) {
|
|
336
|
+
openParts.delete(`${PART_BOUNDARY[type]}:${chunk.id}`);
|
|
337
|
+
out.push({ chunk });
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (type in DELTA_PART && typeof chunk.delta === "string") {
|
|
341
|
+
const key = `${DELTA_PART[type]}:${chunk.id}`;
|
|
342
|
+
const merged = openParts.get(key);
|
|
343
|
+
if (merged) {
|
|
344
|
+
merged.delta += chunk.delta;
|
|
345
|
+
if (chunk.providerMetadata !== void 0) merged.providerMetadata = chunk.providerMetadata;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const copy = { ...chunk };
|
|
349
|
+
openParts.set(key, copy);
|
|
350
|
+
out.push({ chunk: copy });
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (type === "tool-input-delta" && typeof chunk.inputTextDelta === "string") {
|
|
354
|
+
const index = openToolInputs.get(chunk.toolCallId);
|
|
355
|
+
const existing = index !== void 0 ? out[index] : null;
|
|
356
|
+
if (existing && "chunk" in existing) {
|
|
357
|
+
existing.chunk.inputTextDelta += chunk.inputTextDelta;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
openToolInputs.set(chunk.toolCallId, out.length);
|
|
361
|
+
out.push({ chunk: { ...chunk } });
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (type === "tool-input-available" || type === "tool-input-error") {
|
|
365
|
+
const index = openToolInputs.get(chunk.toolCallId);
|
|
366
|
+
if (index !== void 0) out[index] = null;
|
|
367
|
+
openToolInputs.delete(chunk.toolCallId);
|
|
368
|
+
} else if (type === "tool-input-start") {
|
|
369
|
+
openToolInputs.delete(chunk.toolCallId);
|
|
370
|
+
} else if (type === "tool-output-available" || type === "tool-output-error") {
|
|
371
|
+
const superseded = preliminaryOutputs.get(chunk.toolCallId);
|
|
372
|
+
if (superseded !== void 0) {
|
|
373
|
+
out[superseded] = null;
|
|
374
|
+
preliminaryOutputs.delete(chunk.toolCallId);
|
|
375
|
+
}
|
|
376
|
+
if (type === "tool-output-available" && chunk.preliminary === true) {
|
|
377
|
+
preliminaryOutputs.set(chunk.toolCallId, out.length);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
out.push({ chunk });
|
|
381
|
+
}
|
|
382
|
+
let result = "";
|
|
383
|
+
for (const entry of out) {
|
|
384
|
+
if (!entry) continue;
|
|
385
|
+
result += "chunk" in entry ? encodeEvent(entry.chunk) : `${entry.raw}
|
|
386
|
+
|
|
387
|
+
`;
|
|
388
|
+
}
|
|
389
|
+
result += tail;
|
|
390
|
+
return new TextEncoder().encode(result);
|
|
391
|
+
}
|
|
392
|
+
function encodeTurnSeqFrame(turnId, seq) {
|
|
393
|
+
return new TextEncoder().encode(
|
|
394
|
+
encodeEvent({ type: TURN_SEQ_EVENT_TYPE, data: { turnId, seq }, transient: true })
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
function encodeEvent(chunk) {
|
|
398
|
+
return `data: ${JSON.stringify(chunk)}
|
|
399
|
+
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
function parseEvent(event) {
|
|
403
|
+
if (!event.startsWith("data: ") || event.includes("\n")) return null;
|
|
404
|
+
const payload = event.slice(6);
|
|
405
|
+
if (!payload.startsWith("{")) return null;
|
|
406
|
+
try {
|
|
407
|
+
const chunk = JSON.parse(payload);
|
|
408
|
+
return chunk && typeof chunk.type === "string" ? chunk : null;
|
|
409
|
+
} catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
193
414
|
// src/stream-buffer.ts
|
|
194
415
|
var CLEANUP_INTERVAL_MS = 6e4;
|
|
195
416
|
var MAX_BUFFER_AGE_MS = 30 * 6e4;
|
|
196
417
|
var COMPLETED_GRACE_MS = 3e4;
|
|
418
|
+
var TURN_REPLY_TTL_MS = 60 * 6e4;
|
|
197
419
|
var StreamBufferStore = class {
|
|
198
420
|
constructor() {
|
|
199
421
|
this.buffers = /* @__PURE__ */ new Map();
|
|
422
|
+
this.replies = /* @__PURE__ */ new Map();
|
|
200
423
|
this.cleanupTimer = null;
|
|
201
424
|
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);
|
|
202
425
|
const handle = this.cleanupTimer;
|
|
@@ -310,6 +533,44 @@ var StreamBufferStore = class {
|
|
|
310
533
|
lastEventAt: buf.lastEventAt
|
|
311
534
|
};
|
|
312
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* Keep the reply a turn finished with, so it can still be read after the
|
|
538
|
+
* buffer is evicted (30 s grace) — e.g. by the API's call follower, or by a
|
|
539
|
+
* reconciliation pass long after the stream is gone. Keyed by the same
|
|
540
|
+
* session key as the buffer.
|
|
541
|
+
*/
|
|
542
|
+
recordTurnReply(key, reply) {
|
|
543
|
+
const trimmed = reply?.trim();
|
|
544
|
+
if (!trimmed) return;
|
|
545
|
+
const now = Date.now();
|
|
546
|
+
this.pruneReplies(now);
|
|
547
|
+
this.replies.set(key, {
|
|
548
|
+
turnId: this.buffers.get(key)?.turnId ?? "",
|
|
549
|
+
reply,
|
|
550
|
+
completedAt: now
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* The terminal reply for a session, or null when none was recorded or it
|
|
555
|
+
* expired. A reply belonging to an older turn of a session that has since
|
|
556
|
+
* started a new one is never returned.
|
|
557
|
+
*/
|
|
558
|
+
turnReply(key) {
|
|
559
|
+
const entry = this.replies.get(key);
|
|
560
|
+
if (!entry) return null;
|
|
561
|
+
if (Date.now() - entry.completedAt > TURN_REPLY_TTL_MS) {
|
|
562
|
+
this.replies.delete(key);
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
const current = this.buffers.get(key);
|
|
566
|
+
if (current && entry.turnId && current.turnId !== entry.turnId) return null;
|
|
567
|
+
return entry;
|
|
568
|
+
}
|
|
569
|
+
pruneReplies(now) {
|
|
570
|
+
for (const [key, entry] of this.replies) {
|
|
571
|
+
if (now - entry.completedAt > TURN_REPLY_TTL_MS) this.replies.delete(key);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
313
574
|
/**
|
|
314
575
|
* Create a ReadableStream that first replays all buffered chunks past
|
|
315
576
|
* `opts.fromSeq` (default: 0 → replay everything), then subscribes for
|
|
@@ -322,15 +583,29 @@ var StreamBufferStore = class {
|
|
|
322
583
|
if (!buf) return null;
|
|
323
584
|
const fromSeq = Math.max(0, opts.fromSeq ?? 0);
|
|
324
585
|
let subscribedController = null;
|
|
586
|
+
if (opts.buildReplay) return this.createBuiltReplayStream(buf, fromSeq, opts.buildReplay);
|
|
325
587
|
return new ReadableStream({
|
|
326
588
|
start(controller) {
|
|
327
|
-
|
|
328
|
-
|
|
589
|
+
if (opts.compact) {
|
|
590
|
+
const pending = buf.frames.filter((frame) => frame.seq > fromSeq);
|
|
591
|
+
const compacted = compactSseFrames(pending.map((frame) => frame.chunk));
|
|
329
592
|
try {
|
|
330
|
-
controller.enqueue(
|
|
593
|
+
if (compacted.byteLength > 0) controller.enqueue(compacted);
|
|
594
|
+
if (pending.length > 0 && buf.status === "active") {
|
|
595
|
+
controller.enqueue(encodeTurnSeqFrame(buf.turnId, buf.nextSeq - 1));
|
|
596
|
+
}
|
|
331
597
|
} catch {
|
|
332
598
|
return;
|
|
333
599
|
}
|
|
600
|
+
} else {
|
|
601
|
+
for (const frame of buf.frames) {
|
|
602
|
+
if (frame.seq <= fromSeq) continue;
|
|
603
|
+
try {
|
|
604
|
+
controller.enqueue(frame.chunk);
|
|
605
|
+
} catch {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
334
609
|
}
|
|
335
610
|
if (buf.status !== "active") {
|
|
336
611
|
try {
|
|
@@ -350,8 +625,59 @@ var StreamBufferStore = class {
|
|
|
350
625
|
}
|
|
351
626
|
});
|
|
352
627
|
}
|
|
628
|
+
createBuiltReplayStream(buf, fromSeq, buildReplay) {
|
|
629
|
+
const pending = buf.frames.filter((frame) => frame.seq > fromSeq).map((frame) => frame.chunk);
|
|
630
|
+
const ctx = { turnId: buf.turnId, lastSeq: buf.nextSeq - 1 };
|
|
631
|
+
const held = [];
|
|
632
|
+
let ended = false;
|
|
633
|
+
let target = null;
|
|
634
|
+
const proxy = {
|
|
635
|
+
enqueue(chunk) {
|
|
636
|
+
if (target) target.enqueue(chunk);
|
|
637
|
+
else held.push(chunk);
|
|
638
|
+
},
|
|
639
|
+
close() {
|
|
640
|
+
if (target) target.close();
|
|
641
|
+
else ended = true;
|
|
642
|
+
},
|
|
643
|
+
error(reason) {
|
|
644
|
+
if (target) target.error(reason);
|
|
645
|
+
else ended = true;
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
const active = buf.status === "active";
|
|
649
|
+
if (active) buf.subscribers.add(proxy);
|
|
650
|
+
return new ReadableStream({
|
|
651
|
+
async start(controller) {
|
|
652
|
+
let replay;
|
|
653
|
+
try {
|
|
654
|
+
replay = await buildReplay(pending, ctx);
|
|
655
|
+
} catch {
|
|
656
|
+
replay = compactSseFrames(pending);
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
if (replay.byteLength > 0) controller.enqueue(replay);
|
|
660
|
+
if (pending.length > 0 && active) controller.enqueue(encodeTurnSeqFrame(ctx.turnId, ctx.lastSeq));
|
|
661
|
+
for (const chunk of held) controller.enqueue(chunk);
|
|
662
|
+
held.length = 0;
|
|
663
|
+
if (!active || ended || buf.status !== "active") {
|
|
664
|
+
controller.close();
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
target = controller;
|
|
668
|
+
} catch {
|
|
669
|
+
buf.subscribers.delete(proxy);
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
cancel() {
|
|
673
|
+
buf.subscribers.delete(proxy);
|
|
674
|
+
target = null;
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
353
678
|
cleanup() {
|
|
354
679
|
const now = Date.now();
|
|
680
|
+
this.pruneReplies(now);
|
|
355
681
|
for (const [key, buf] of this.buffers) {
|
|
356
682
|
if (buf.status !== "active" && buf.completedAt && now - buf.completedAt > COMPLETED_GRACE_MS) {
|
|
357
683
|
this.buffers.delete(key);
|
|
@@ -370,6 +696,7 @@ var StreamBufferStore = class {
|
|
|
370
696
|
this.completeBuffer(buf, "disposed");
|
|
371
697
|
}
|
|
372
698
|
this.buffers.clear();
|
|
699
|
+
this.replies.clear();
|
|
373
700
|
}
|
|
374
701
|
completeBuffer(buf, reason) {
|
|
375
702
|
if (buf.status !== "active") return;
|
|
@@ -400,6 +727,14 @@ var StreamBufferStore = class {
|
|
|
400
727
|
buf.subscribers.clear();
|
|
401
728
|
}
|
|
402
729
|
};
|
|
730
|
+
var TURN_COMPLETE_EVENT_TYPE = "data-turn-complete";
|
|
731
|
+
function encodeTurnCompleteFrame(data) {
|
|
732
|
+
return new TextEncoder().encode(
|
|
733
|
+
`data: ${JSON.stringify({ type: TURN_COMPLETE_EVENT_TYPE, data })}
|
|
734
|
+
|
|
735
|
+
`
|
|
736
|
+
);
|
|
737
|
+
}
|
|
403
738
|
function generateTurnId() {
|
|
404
739
|
try {
|
|
405
740
|
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
@@ -467,14 +802,35 @@ var TECH_STACK_REGISTRY = {
|
|
|
467
802
|
"react-app": { id: "react-app", target: "web" },
|
|
468
803
|
"threejs-game": { id: "threejs-game", target: "web" },
|
|
469
804
|
"phaser-game": { id: "phaser-game", target: "web" },
|
|
805
|
+
// Video (Remotion). `target: 'web'` because the canvas preview is the
|
|
806
|
+
// Remotion Player page the runtime serves from `dist/`; the stack's own
|
|
807
|
+
// `stack.json` declares `runtime.devServer: 'static-build'`, so no new
|
|
808
|
+
// target value (and therefore no new sizing/publish rules) is needed.
|
|
809
|
+
"video-editor": { id: "video-editor", target: "web", seedsOwnTemplate: true },
|
|
470
810
|
// Mobile (Metro/Expo) — agent-runtime owns the seed.
|
|
471
811
|
"expo-app": { id: "expo-app", target: "mobile", metro: true, seedsOwnTemplate: true },
|
|
472
812
|
"expo-three": { id: "expo-three", target: "mobile", metro: true, seedsOwnTemplate: true },
|
|
473
813
|
"react-native": { id: "react-native", target: "mobile", metro: true, seedsOwnTemplate: true },
|
|
474
814
|
// Data / scripting
|
|
475
815
|
"python-data": { id: "python-data", target: "data", seedsOwnTemplate: true },
|
|
816
|
+
// Multi-service backend (dockerd + docker compose). Requires the
|
|
817
|
+
// Docker-capable metal VM class; gated separately by
|
|
818
|
+
// `runtime.docker_class_enabled` (see apps/api/src/lib/runtime-class-setting.ts).
|
|
819
|
+
"docker-compose": {
|
|
820
|
+
id: "docker-compose",
|
|
821
|
+
target: "data",
|
|
822
|
+
seedsOwnTemplate: true,
|
|
823
|
+
vmClass: "docker",
|
|
824
|
+
minimumInstanceSize: "large",
|
|
825
|
+
ports: [
|
|
826
|
+
{ port: 8e3, label: "app", protocol: "http", defaultVisibility: "preview" },
|
|
827
|
+
{ port: 5432, label: "postgres", protocol: "tcp", defaultVisibility: "tunnel" }
|
|
828
|
+
]
|
|
829
|
+
},
|
|
476
830
|
// Native (full game engines)
|
|
477
831
|
"unity-game": { id: "unity-game", target: "native", seedsOwnTemplate: true },
|
|
832
|
+
// Bring-your-own framework: the preview runs the project's static build.
|
|
833
|
+
custom: { id: "custom", target: "web", seedsOwnTemplate: true },
|
|
478
834
|
// Bare / no-stack
|
|
479
835
|
none: { id: "none", target: "none", seedsOwnTemplate: true }
|
|
480
836
|
};
|
|
@@ -491,6 +847,15 @@ function usesMetroBundler(techStackId) {
|
|
|
491
847
|
function stackSeedsItself(techStackId) {
|
|
492
848
|
return getStackEntry(techStackId)?.seedsOwnTemplate === true;
|
|
493
849
|
}
|
|
850
|
+
function isDockerTechStack(techStackId) {
|
|
851
|
+
return getStackEntry(techStackId)?.vmClass === "docker";
|
|
852
|
+
}
|
|
853
|
+
function getMinimumInstanceSize(techStackId) {
|
|
854
|
+
return getStackEntry(techStackId)?.minimumInstanceSize ?? null;
|
|
855
|
+
}
|
|
856
|
+
function getDeclaredPorts(techStackId) {
|
|
857
|
+
return getStackEntry(techStackId)?.ports ?? [];
|
|
858
|
+
}
|
|
494
859
|
|
|
495
860
|
// src/file-types.ts
|
|
496
861
|
var BINARY_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -538,6 +903,24 @@ var BINARY_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
538
903
|
"aac",
|
|
539
904
|
"opus",
|
|
540
905
|
"pdf",
|
|
906
|
+
// Office / document containers
|
|
907
|
+
"xls",
|
|
908
|
+
"xlsx",
|
|
909
|
+
"xlsm",
|
|
910
|
+
"xlsb",
|
|
911
|
+
"doc",
|
|
912
|
+
"docx",
|
|
913
|
+
"docm",
|
|
914
|
+
"ppt",
|
|
915
|
+
"pptx",
|
|
916
|
+
"pptm",
|
|
917
|
+
"odt",
|
|
918
|
+
"ods",
|
|
919
|
+
"odp",
|
|
920
|
+
"pages",
|
|
921
|
+
"numbers",
|
|
922
|
+
"key",
|
|
923
|
+
"epub",
|
|
541
924
|
// Fonts
|
|
542
925
|
"woff",
|
|
543
926
|
"woff2",
|
|
@@ -605,28 +988,92 @@ var BINARY_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
605
988
|
"nc",
|
|
606
989
|
"tfrecord"
|
|
607
990
|
]);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
991
|
+
var BINARY_FILE_TRANSIENT_SUFFIXES = /* @__PURE__ */ new Set([
|
|
992
|
+
"part",
|
|
993
|
+
"partial",
|
|
994
|
+
"crdownload",
|
|
995
|
+
"download"
|
|
996
|
+
]);
|
|
997
|
+
var BINARY_FILE_BASENAMES = /* @__PURE__ */ new Set([
|
|
998
|
+
"aapt",
|
|
999
|
+
"aapt2",
|
|
1000
|
+
"adb",
|
|
1001
|
+
"apksigner",
|
|
1002
|
+
"bcc_compat",
|
|
1003
|
+
"dexdump",
|
|
1004
|
+
"dmtracedump",
|
|
1005
|
+
"etc1tool",
|
|
1006
|
+
"fastboot",
|
|
1007
|
+
"hprof-conv",
|
|
1008
|
+
"llvm-rs-cc",
|
|
1009
|
+
"mksdcard",
|
|
1010
|
+
"qemu-aarch64-static",
|
|
1011
|
+
"qemu-arm-static",
|
|
1012
|
+
"qemu-i386-static",
|
|
1013
|
+
"qemu-x86_64-static",
|
|
1014
|
+
"split-select",
|
|
1015
|
+
"sqlite3",
|
|
1016
|
+
"zipalign"
|
|
1017
|
+
]);
|
|
1018
|
+
function basenameOf(path) {
|
|
611
1019
|
const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
612
|
-
|
|
613
|
-
|
|
1020
|
+
return path.slice(slash + 1);
|
|
1021
|
+
}
|
|
1022
|
+
function extensionOfBasename(basename) {
|
|
1023
|
+
const dot = basename.lastIndexOf(".");
|
|
1024
|
+
if (dot <= 0 || dot === basename.length - 1) return null;
|
|
1025
|
+
return basename.slice(dot + 1).toLowerCase();
|
|
1026
|
+
}
|
|
1027
|
+
function isBinaryFilePath(path) {
|
|
1028
|
+
let basename = basenameOf(path).toLowerCase();
|
|
1029
|
+
if (BINARY_FILE_BASENAMES.has(basename)) return true;
|
|
1030
|
+
for (let i = 0; i < 2; i++) {
|
|
1031
|
+
const ext = extensionOfBasename(basename);
|
|
1032
|
+
if (!ext) return false;
|
|
1033
|
+
if (BINARY_FILE_EXTENSIONS.has(ext)) return true;
|
|
1034
|
+
if (!BINARY_FILE_TRANSIENT_SUFFIXES.has(ext)) return false;
|
|
1035
|
+
basename = basename.slice(0, -(ext.length + 1));
|
|
1036
|
+
}
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
function isBinaryBuffer(bytes, inspectBytes = 8192) {
|
|
1040
|
+
const head = bytes.subarray(0, Math.min(bytes.length, inspectBytes));
|
|
1041
|
+
if (head.includes(0)) return true;
|
|
1042
|
+
const startsWith = (...signature) => signature.every((value, index) => head[index] === value);
|
|
1043
|
+
const ascii = (value) => Array.from(value).every(
|
|
1044
|
+
(char, index) => head[index] === char.charCodeAt(0)
|
|
1045
|
+
);
|
|
1046
|
+
return startsWith(80, 75, 3, 4) || startsWith(80, 75, 5, 6) || startsWith(208, 207, 17, 224, 161, 177, 26, 225) || ascii("%PDF-") || startsWith(137, 80, 78, 71, 13, 10, 26, 10) || startsWith(255, 216, 255) || startsWith(71, 73, 70, 56) || startsWith(82, 73, 70, 70);
|
|
614
1047
|
}
|
|
615
1048
|
// Annotate the CommonJS export names for ESM import in node:
|
|
616
1049
|
0 && (module.exports = {
|
|
1050
|
+
BINARY_FILE_BASENAMES,
|
|
617
1051
|
BINARY_FILE_EXTENSIONS,
|
|
1052
|
+
BINARY_FILE_TRANSIENT_SUFFIXES,
|
|
618
1053
|
StreamBufferStore,
|
|
619
1054
|
TECH_STACK_REGISTRY,
|
|
1055
|
+
TURN_COMPLETE_EVENT_TYPE,
|
|
1056
|
+
TURN_SEQ_EVENT_TYPE,
|
|
1057
|
+
compactSseFrames,
|
|
620
1058
|
createBufferingTransform,
|
|
621
1059
|
createLogger,
|
|
1060
|
+
emitLogToSink,
|
|
1061
|
+
encodeTurnCompleteFrame,
|
|
1062
|
+
encodeTurnSeqFrame,
|
|
622
1063
|
extractUserText,
|
|
623
1064
|
findLastUserMessage,
|
|
1065
|
+
getDeclaredPorts,
|
|
1066
|
+
getMinimumInstanceSize,
|
|
624
1067
|
getStackEntry,
|
|
625
1068
|
initInstrumentation,
|
|
1069
|
+
isBinaryBuffer,
|
|
626
1070
|
isBinaryFilePath,
|
|
1071
|
+
isDockerTechStack,
|
|
627
1072
|
isMacOSJunkName,
|
|
628
1073
|
isMacOSJunkPath,
|
|
629
1074
|
isMobileTechStack,
|
|
1075
|
+
setOtelLogSink,
|
|
1076
|
+
setTraceContextProvider,
|
|
630
1077
|
shutdownInstrumentation,
|
|
631
1078
|
stackSeedsItself,
|
|
632
1079
|
traceOperation,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { LogLevel, Logger, createLogger } from './logger.cjs';
|
|
1
|
+
export { LogEntry, LogLevel, LogSink, Logger, TraceContext, TraceContextProvider, createLogger, emitLogToSink, setOtelLogSink, setTraceContextProvider } from './logger.cjs';
|
|
2
2
|
export { InstrumentationConfig, initInstrumentation, shutdownInstrumentation, traceOperation } from './instrumentation.cjs';
|
|
3
|
-
export { ReplayOptions, StreamBufferStore, StreamBufferWriter, TurnSnapshot, TurnStatus, TurnTerminal, createBufferingTransform } from './stream-buffer.cjs';
|
|
3
|
+
export { ReplayOptions, StreamBufferStore, StreamBufferWriter, TURN_COMPLETE_EVENT_TYPE, TURN_SEQ_EVENT_TYPE, TurnCompleteFrameData, TurnReply, TurnSnapshot, TurnStatus, TurnTerminal, compactSseFrames, createBufferingTransform, encodeTurnCompleteFrame, encodeTurnSeqFrame } from './stream-buffer.cjs';
|
|
4
4
|
export { extractUserText, findLastUserMessage } from './chat-message.cjs';
|
|
5
5
|
export { isMacOSJunkName, isMacOSJunkPath } from './macos-junk.cjs';
|
|
6
|
-
export { StackRegistryEntry, StackTarget, TECH_STACK_REGISTRY, getStackEntry, isMobileTechStack, stackSeedsItself, usesMetroBundler } from './tech-stack-registry.cjs';
|
|
7
|
-
export { BINARY_FILE_EXTENSIONS, isBinaryFilePath } from './file-types.cjs';
|
|
6
|
+
export { StackRegistryEntry, StackTarget, TECH_STACK_REGISTRY, getDeclaredPorts, getMinimumInstanceSize, getStackEntry, isDockerTechStack, isMobileTechStack, stackSeedsItself, usesMetroBundler } from './tech-stack-registry.cjs';
|
|
7
|
+
export { BINARY_FILE_BASENAMES, BINARY_FILE_EXTENSIONS, BINARY_FILE_TRANSIENT_SUFFIXES, isBinaryBuffer, isBinaryFilePath } from './file-types.cjs';
|
|
8
8
|
import '@opentelemetry/api';
|