langwatch 1.18.0 → 1.20.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/README.md +25 -11
- package/dist/agent/index.d.mts +122 -79
- package/dist/agent/index.d.ts +122 -79
- package/dist/agent/index.js +523 -300
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +522 -301
- package/dist/agent/index.mjs.map +1 -1
- package/dist/chunk-5VYEG3RS.js +117 -0
- package/dist/chunk-5VYEG3RS.js.map +1 -0
- package/dist/{chunk-O4J3ZPZY.js → chunk-5ZNEXSAB.js} +7912 -2564
- package/dist/chunk-5ZNEXSAB.js.map +1 -0
- package/dist/chunk-BDQ2RG7R.mjs +852 -0
- package/dist/chunk-BDQ2RG7R.mjs.map +1 -0
- package/dist/chunk-BL3GHEKF.js +59 -0
- package/dist/chunk-BL3GHEKF.js.map +1 -0
- package/dist/chunk-C7IWJ2GJ.mjs +59 -0
- package/dist/chunk-C7IWJ2GJ.mjs.map +1 -0
- package/dist/chunk-EQ5CFQGJ.js +852 -0
- package/dist/chunk-EQ5CFQGJ.js.map +1 -0
- package/dist/{chunk-NPEK4RSD.mjs → chunk-ET5KYCG2.mjs} +7757 -2409
- package/dist/chunk-ET5KYCG2.mjs.map +1 -0
- package/dist/{chunk-MVQJMRM6.mjs → chunk-FKYYEGJ7.mjs} +120 -112
- package/dist/chunk-FKYYEGJ7.mjs.map +1 -0
- package/dist/{chunk-JJASOJOK.js → chunk-HCK7TCSK.js} +138 -130
- package/dist/chunk-HCK7TCSK.js.map +1 -0
- package/dist/{chunk-LCG5Y2EG.js → chunk-N76BG37W.js} +15 -101
- package/dist/chunk-N76BG37W.js.map +1 -0
- package/dist/chunk-SERULD4Z.mjs +117 -0
- package/dist/chunk-SERULD4Z.mjs.map +1 -0
- package/dist/{chunk-73QOHSHU.js → chunk-WDUUAIG3.js} +2 -2
- package/dist/chunk-WDUUAIG3.js.map +1 -0
- package/dist/{chunk-WMDQVWA6.mjs → chunk-WSGVZ3J2.mjs} +22 -108
- package/dist/chunk-WSGVZ3J2.mjs.map +1 -0
- package/dist/{chunk-57L2K66L.mjs → chunk-XQ4CP2JK.mjs} +2 -2
- package/dist/chunk-XQ4CP2JK.mjs.map +1 -0
- package/dist/cli/bundle.js +766 -479
- package/dist/implementation-B4Z2ksnA.d.mts +261 -0
- package/dist/implementation-BFUgzz5C.d.ts +261 -0
- package/dist/index.d.mts +398 -1123
- package/dist/index.d.ts +398 -1123
- package/dist/index.js +7613 -3566
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7419 -3372
- package/dist/index.mjs.map +1 -1
- package/dist/observability-sdk/index.d.mts +16 -76
- package/dist/observability-sdk/index.d.ts +16 -76
- package/dist/observability-sdk/index.js +8 -5
- package/dist/observability-sdk/index.js.map +1 -1
- package/dist/observability-sdk/index.mjs +8 -5
- package/dist/observability-sdk/instrumentation/langchain/index.d.mts +3 -5
- package/dist/observability-sdk/instrumentation/langchain/index.d.ts +3 -5
- package/dist/observability-sdk/instrumentation/langchain/index.js +160 -112
- package/dist/observability-sdk/instrumentation/langchain/index.js.map +1 -1
- package/dist/observability-sdk/instrumentation/langchain/index.mjs +148 -100
- package/dist/observability-sdk/instrumentation/langchain/index.mjs.map +1 -1
- package/dist/observability-sdk/semconv/index.d.mts +37 -0
- package/dist/observability-sdk/semconv/index.d.ts +37 -0
- package/dist/observability-sdk/semconv/index.js +121 -0
- package/dist/observability-sdk/semconv/index.js.map +1 -0
- package/dist/observability-sdk/semconv/index.mjs +121 -0
- package/dist/observability-sdk/semconv/index.mjs.map +1 -0
- package/dist/observability-sdk/setup/node/index.d.mts +73 -313
- package/dist/observability-sdk/setup/node/index.d.ts +73 -313
- package/dist/observability-sdk/setup/node/index.js +6 -4
- package/dist/observability-sdk/setup/node/index.js.map +1 -1
- package/dist/observability-sdk/setup/node/index.mjs +5 -3
- package/dist/types-B696jWYQ.d.mts +16010 -0
- package/dist/types-Bo6OvFct.d.ts +16010 -0
- package/dist/{types-VOZv9LYO.d.mts → types-CoS5PmeU.d.mts} +22 -59
- package/dist/{types-VOZv9LYO.d.ts → types-CoS5PmeU.d.ts} +22 -59
- package/dist/{types-DLfhJb38.d.mts → types-RkiBQZ9j.d.mts} +1 -6
- package/dist/{types-DLfhJb38.d.ts → types-RkiBQZ9j.d.ts} +1 -6
- package/dist/values-1yj9l0Sa.d.mts +112 -0
- package/dist/values-1yj9l0Sa.d.ts +112 -0
- package/package.json +95 -64
- package/dist/chunk-57L2K66L.mjs.map +0 -1
- package/dist/chunk-73QOHSHU.js.map +0 -1
- package/dist/chunk-HY2N6TO5.mjs +0 -566
- package/dist/chunk-HY2N6TO5.mjs.map +0 -1
- package/dist/chunk-JJASOJOK.js.map +0 -1
- package/dist/chunk-LCG5Y2EG.js.map +0 -1
- package/dist/chunk-MVQJMRM6.mjs.map +0 -1
- package/dist/chunk-NPEK4RSD.mjs.map +0 -1
- package/dist/chunk-O4J3ZPZY.js.map +0 -1
- package/dist/chunk-UEJSGC3P.js +0 -566
- package/dist/chunk-UEJSGC3P.js.map +0 -1
- package/dist/chunk-WMDQVWA6.mjs.map +0 -1
- package/dist/implementation-BvX-7vox.d.ts +0 -716
- package/dist/implementation-CQ__V0Vc.d.mts +0 -716
- package/dist/types-CNEUr6Hm.d.ts +0 -36682
- package/dist/types-DfPAHmAh.d.mts +0 -36682
package/dist/agent/index.js
CHANGED
|
@@ -32,9 +32,11 @@ var agent_exports = {};
|
|
|
32
32
|
__export(agent_exports, {
|
|
33
33
|
AGENT_TRANSPORTS: () => AGENT_TRANSPORTS,
|
|
34
34
|
AgentParameterError: () => AgentParameterError,
|
|
35
|
+
AgentStateError: () => AgentStateError,
|
|
35
36
|
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
36
37
|
MAX_TIMEOUT_MS: () => MAX_TIMEOUT_MS,
|
|
37
38
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
39
|
+
STATE_MAX_BYTES: () => STATE_MAX_BYTES,
|
|
38
40
|
connectAgent: () => connectAgent,
|
|
39
41
|
normalizeReply: () => normalizeReply,
|
|
40
42
|
parameterSpecsFromSchema: () => parameterSpecsFromSchema,
|
|
@@ -83,11 +85,11 @@ var ConsoleLogger = class {
|
|
|
83
85
|
var import_api = require("@opentelemetry/api");
|
|
84
86
|
|
|
85
87
|
// src/agent/identity.ts
|
|
86
|
-
var os = __toESM(require("os"));
|
|
87
88
|
var import_node_crypto = require("crypto");
|
|
89
|
+
var os = __toESM(require("os"));
|
|
88
90
|
|
|
89
91
|
// package.json
|
|
90
|
-
var version = "1.
|
|
92
|
+
var version = "1.20.0";
|
|
91
93
|
|
|
92
94
|
// src/internal/constants.ts
|
|
93
95
|
var LANGWATCH_SDK_VERSION = version;
|
|
@@ -145,7 +147,9 @@ function resolveEnabled({
|
|
|
145
147
|
env = process.env
|
|
146
148
|
}) {
|
|
147
149
|
const flag = env.LANGWATCH_AGENT_CONNECT;
|
|
148
|
-
if (isSet2(flag)
|
|
150
|
+
if (isSet2(flag)) {
|
|
151
|
+
if (!isTruthy(flag)) return false;
|
|
152
|
+
}
|
|
149
153
|
if (explicit !== void 0) return explicit;
|
|
150
154
|
return !isTruthy(env.CI);
|
|
151
155
|
}
|
|
@@ -228,24 +232,37 @@ var isStringList = (value) => Array.isArray(value) && value.every(isString);
|
|
|
228
232
|
var readScope = (value) => {
|
|
229
233
|
if (!isRecord(value)) return { kind: "shared" };
|
|
230
234
|
if (value.kind === "owner") return { kind: "owner" };
|
|
231
|
-
if (value.kind === "host" && isString(value.hostLabel))
|
|
235
|
+
if (value.kind === "host" && isString(value.hostLabel))
|
|
236
|
+
return { kind: "host", hostLabel: value.hostLabel };
|
|
232
237
|
return { kind: "shared" };
|
|
233
238
|
};
|
|
239
|
+
var readRegisteredAgent = (entry) => {
|
|
240
|
+
if (!isRecord(entry)) return null;
|
|
241
|
+
if (!isString(entry.name)) return null;
|
|
242
|
+
let id = null;
|
|
243
|
+
if (isString(entry.id)) {
|
|
244
|
+
id = entry.id;
|
|
245
|
+
} else if (isString(entry.agentId)) {
|
|
246
|
+
id = entry.agentId;
|
|
247
|
+
}
|
|
248
|
+
if (id === null) return null;
|
|
249
|
+
return {
|
|
250
|
+
name: entry.name,
|
|
251
|
+
environment: isString(entry.environment) ? entry.environment : "",
|
|
252
|
+
id,
|
|
253
|
+
url: isString(entry.url) ? entry.url : "",
|
|
254
|
+
parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
|
|
255
|
+
scope: readScope(entry.scope)
|
|
256
|
+
};
|
|
257
|
+
};
|
|
234
258
|
var readRegistered = (frame) => {
|
|
235
|
-
if (!Array.isArray(frame.agents)
|
|
259
|
+
if (!Array.isArray(frame.agents)) return null;
|
|
260
|
+
if (!isString(frame.instanceId)) return null;
|
|
236
261
|
const agents = [];
|
|
237
262
|
for (const entry of frame.agents) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
agents.push({
|
|
242
|
-
name: entry.name,
|
|
243
|
-
environment: isString(entry.environment) ? entry.environment : "",
|
|
244
|
-
id,
|
|
245
|
-
url: isString(entry.url) ? entry.url : "",
|
|
246
|
-
parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
|
|
247
|
-
scope: readScope(entry.scope)
|
|
248
|
-
});
|
|
263
|
+
const agent = readRegisteredAgent(entry);
|
|
264
|
+
if (agent === null) return null;
|
|
265
|
+
agents.push(agent);
|
|
249
266
|
}
|
|
250
267
|
return {
|
|
251
268
|
type: "registered",
|
|
@@ -281,12 +298,15 @@ var readDeadline = (value) => {
|
|
|
281
298
|
return null;
|
|
282
299
|
};
|
|
283
300
|
var readCall = (frame) => {
|
|
284
|
-
if (!isString(frame.callId)
|
|
301
|
+
if (!isString(frame.callId)) return null;
|
|
302
|
+
if (!isString(frame.agentId)) return null;
|
|
285
303
|
const messages = isMessageList(frame.messages) ? frame.messages : [];
|
|
286
304
|
const run = isRecord(frame.run) ? frame.run : {};
|
|
287
305
|
return {
|
|
288
306
|
type: "call",
|
|
289
307
|
protocol: typeof frame.protocol === "number" ? frame.protocol : PROTOCOL_VERSION,
|
|
308
|
+
// A platform that predates hooks sends no kind: every call is a turn.
|
|
309
|
+
kind: isString(frame.kind) ? frame.kind : "turn",
|
|
290
310
|
callId: frame.callId,
|
|
291
311
|
agentId: frame.agentId,
|
|
292
312
|
threadId: isString(frame.threadId) ? frame.threadId : "",
|
|
@@ -294,6 +314,7 @@ var readCall = (frame) => {
|
|
|
294
314
|
newMessages: isMessageList(frame.newMessages) ? frame.newMessages : messages,
|
|
295
315
|
params: readParams(frame.params),
|
|
296
316
|
session: frame.session === void 0 ? null : frame.session,
|
|
317
|
+
state: isRecord(frame.state) ? frame.state : null,
|
|
297
318
|
traceparent: isString(frame.traceparent) ? frame.traceparent : null,
|
|
298
319
|
deadlineAt: readDeadline(frame.deadlineAt),
|
|
299
320
|
run: {
|
|
@@ -310,7 +331,8 @@ function parseServerFrame(raw) {
|
|
|
310
331
|
} catch {
|
|
311
332
|
return null;
|
|
312
333
|
}
|
|
313
|
-
if (!isRecord(parsed)
|
|
334
|
+
if (!isRecord(parsed)) return null;
|
|
335
|
+
if (!isString(parsed.type)) return null;
|
|
314
336
|
switch (parsed.type) {
|
|
315
337
|
case "registered":
|
|
316
338
|
return readRegistered(parsed);
|
|
@@ -339,164 +361,6 @@ function traceIdFromTraceparent(traceparent) {
|
|
|
339
361
|
return traceId.toLowerCase();
|
|
340
362
|
}
|
|
341
363
|
|
|
342
|
-
// src/agent/schema.ts
|
|
343
|
-
var AgentParameterError = class extends Error {
|
|
344
|
-
constructor(message) {
|
|
345
|
-
super(message);
|
|
346
|
-
this.code = "agent_parameter_invalid";
|
|
347
|
-
this.name = "AgentParameterError";
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
var ACCEPTED_FORMS = `parameters must be a definition map ({ model: { options: [...], default: '...' } }), a Standard JSON Schema object (one with "~standard".jsonSchema), or a JSON Schema object ({ type: 'object', properties })`;
|
|
351
|
-
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
352
|
-
var isStandardJsonSchema = (value) => {
|
|
353
|
-
if (!isRecord2(value)) return false;
|
|
354
|
-
const standard = value["~standard"];
|
|
355
|
-
return isRecord2(standard) && isRecord2(standard.jsonSchema);
|
|
356
|
-
};
|
|
357
|
-
var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
|
|
358
|
-
var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
|
|
359
|
-
var definitionType = (definition) => {
|
|
360
|
-
if (definition.type) return definition.type;
|
|
361
|
-
if (definition.options) return "string";
|
|
362
|
-
const value = definition.default;
|
|
363
|
-
if (typeof value === "number") return "number";
|
|
364
|
-
if (typeof value === "boolean") return "boolean";
|
|
365
|
-
return "string";
|
|
366
|
-
};
|
|
367
|
-
var definitionMapToSchema = (definitions) => {
|
|
368
|
-
const properties = {};
|
|
369
|
-
const required = [];
|
|
370
|
-
for (const [name, definition] of Object.entries(definitions)) {
|
|
371
|
-
const property = { type: definitionType(definition) };
|
|
372
|
-
if (definition.options) property.enum = [...definition.options];
|
|
373
|
-
if (definition.default !== void 0) property.default = definition.default;
|
|
374
|
-
else required.push(name);
|
|
375
|
-
if (definition.description !== void 0) property.description = definition.description;
|
|
376
|
-
properties[name] = property;
|
|
377
|
-
}
|
|
378
|
-
const schema = { type: "object", properties };
|
|
379
|
-
if (required.length > 0) schema.required = required;
|
|
380
|
-
return schema;
|
|
381
|
-
};
|
|
382
|
-
var readStandardJsonSchema = (input) => {
|
|
383
|
-
const converter = input["~standard"].jsonSchema;
|
|
384
|
-
const options = { target: "draft-2020-12" };
|
|
385
|
-
const schema = converter.input?.(options) ?? converter.output?.(options);
|
|
386
|
-
if (schema === void 0) {
|
|
387
|
-
throw new AgentParameterError(`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`);
|
|
388
|
-
}
|
|
389
|
-
if (!isRecord2(schema)) {
|
|
390
|
-
throw new AgentParameterError(`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`);
|
|
391
|
-
}
|
|
392
|
-
return schema;
|
|
393
|
-
};
|
|
394
|
-
function toParameterSchema(input) {
|
|
395
|
-
if (input === void 0) return { type: "object", properties: {} };
|
|
396
|
-
if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
|
|
397
|
-
if (isJsonSchemaObject(input)) return input;
|
|
398
|
-
if (isDefinitionMap(input)) return definitionMapToSchema(input);
|
|
399
|
-
throw new AgentParameterError(ACCEPTED_FORMS);
|
|
400
|
-
}
|
|
401
|
-
var specType = (property) => {
|
|
402
|
-
const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
|
|
403
|
-
if (type === "number" || type === "integer") return "number";
|
|
404
|
-
if (type === "boolean") return "boolean";
|
|
405
|
-
return "string";
|
|
406
|
-
};
|
|
407
|
-
var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
|
|
408
|
-
function parameterSpecsFromSchema(schema) {
|
|
409
|
-
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
410
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
411
|
-
const specs = [];
|
|
412
|
-
for (const [name, raw] of Object.entries(properties)) {
|
|
413
|
-
const property = isRecord2(raw) ? raw : {};
|
|
414
|
-
const spec = { name, type: specType(property) };
|
|
415
|
-
const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
|
|
416
|
-
if (options && options.length > 0) spec.options = options;
|
|
417
|
-
const fallback = scalar(property.default);
|
|
418
|
-
if (fallback !== void 0) spec.default = fallback;
|
|
419
|
-
if (typeof property.description === "string") spec.description = property.description;
|
|
420
|
-
spec.required = fallback === void 0 && required.has(name);
|
|
421
|
-
specs.push(spec);
|
|
422
|
-
}
|
|
423
|
-
return specs;
|
|
424
|
-
}
|
|
425
|
-
var coerce = ({
|
|
426
|
-
spec,
|
|
427
|
-
value
|
|
428
|
-
}) => {
|
|
429
|
-
if (spec.type === "number") {
|
|
430
|
-
const asNumber = typeof value === "number" ? value : Number(value);
|
|
431
|
-
if (typeof value === "boolean" || !Number.isFinite(asNumber) || String(value).trim() === "") {
|
|
432
|
-
throw new AgentParameterError(`parameter "${spec.name}" must be a number, got ${JSON.stringify(value)}`);
|
|
433
|
-
}
|
|
434
|
-
return asNumber;
|
|
435
|
-
}
|
|
436
|
-
if (spec.type === "boolean") {
|
|
437
|
-
if (typeof value === "boolean") return value;
|
|
438
|
-
if (value === "true") return true;
|
|
439
|
-
if (value === "false") return false;
|
|
440
|
-
throw new AgentParameterError(`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`);
|
|
441
|
-
}
|
|
442
|
-
const asString = typeof value === "string" ? value : String(value);
|
|
443
|
-
if (spec.options && !spec.options.includes(asString)) {
|
|
444
|
-
throw new AgentParameterError(
|
|
445
|
-
`parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
return asString;
|
|
449
|
-
};
|
|
450
|
-
function resolveParameterValues({
|
|
451
|
-
specs,
|
|
452
|
-
supplied
|
|
453
|
-
}) {
|
|
454
|
-
const values = { ...supplied ?? {} };
|
|
455
|
-
for (const spec of specs) {
|
|
456
|
-
const value = values[spec.name];
|
|
457
|
-
if (value === void 0) {
|
|
458
|
-
if (spec.default !== void 0) {
|
|
459
|
-
values[spec.name] = spec.default;
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
if (!spec.required) continue;
|
|
463
|
-
throw new AgentParameterError(`parameter "${spec.name}" is required and the run did not supply it`);
|
|
464
|
-
}
|
|
465
|
-
values[spec.name] = coerce({ spec, value });
|
|
466
|
-
}
|
|
467
|
-
return values;
|
|
468
|
-
}
|
|
469
|
-
var issuePath = (issue) => (issue.path ?? []).map(
|
|
470
|
-
(segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
|
|
471
|
-
).join(".");
|
|
472
|
-
async function validateParameterValues({
|
|
473
|
-
schema,
|
|
474
|
-
values
|
|
475
|
-
}) {
|
|
476
|
-
const standard = schema["~standard"];
|
|
477
|
-
const result = await standard.validate?.(values);
|
|
478
|
-
if (result === void 0) return values;
|
|
479
|
-
if (result.issues) {
|
|
480
|
-
const detail = result.issues.map((issue) => {
|
|
481
|
-
const path = issuePath(issue);
|
|
482
|
-
return path ? `${path}: ${issue.message}` : issue.message;
|
|
483
|
-
}).join("; ");
|
|
484
|
-
throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
|
|
485
|
-
}
|
|
486
|
-
const parsed = isRecord2(result.value) ? result.value : {};
|
|
487
|
-
return { ...values, ...parsed };
|
|
488
|
-
}
|
|
489
|
-
function createParameterReader({
|
|
490
|
-
input,
|
|
491
|
-
specs
|
|
492
|
-
}) {
|
|
493
|
-
const schema = isStandardJsonSchema(input) ? input : void 0;
|
|
494
|
-
return async (supplied) => {
|
|
495
|
-
const values = resolveParameterValues({ specs, supplied });
|
|
496
|
-
return schema ? validateParameterValues({ schema, values }) : values;
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
|
|
500
364
|
// src/agent/transport.ts
|
|
501
365
|
var import_node_module = require("module");
|
|
502
366
|
|
|
@@ -513,11 +377,7 @@ var CREDENTIAL_HEADERS = [
|
|
|
513
377
|
"x-project-id"
|
|
514
378
|
];
|
|
515
379
|
var LangWatchRedirectError = class extends Error {
|
|
516
|
-
constructor({
|
|
517
|
-
url,
|
|
518
|
-
location,
|
|
519
|
-
status
|
|
520
|
-
}) {
|
|
380
|
+
constructor({ url, location, status }) {
|
|
521
381
|
super(
|
|
522
382
|
`LangWatch refused to follow a redirect from ${url} to ${location ?? "an unknown location"} (HTTP ${status}). Set the endpoint to the final URL.`
|
|
523
383
|
);
|
|
@@ -664,7 +524,19 @@ var upgrade = async ({
|
|
|
664
524
|
const location = first.headers.get("location");
|
|
665
525
|
const refused = refusalOf({ url, response: first });
|
|
666
526
|
const target = location === null ? null : schemeUpgradeTarget({ url, location });
|
|
667
|
-
if (location === null
|
|
527
|
+
if (location === null) {
|
|
528
|
+
discard(spare);
|
|
529
|
+
throw refused;
|
|
530
|
+
}
|
|
531
|
+
if (first.status === 303) {
|
|
532
|
+
discard(spare);
|
|
533
|
+
throw refused;
|
|
534
|
+
}
|
|
535
|
+
if (target === null) {
|
|
536
|
+
discard(spare);
|
|
537
|
+
throw refused;
|
|
538
|
+
}
|
|
539
|
+
if (isStream(init?.body)) {
|
|
668
540
|
discard(spare);
|
|
669
541
|
throw refused;
|
|
670
542
|
}
|
|
@@ -714,7 +586,10 @@ function resolveTransport({
|
|
|
714
586
|
env = process.env
|
|
715
587
|
}) {
|
|
716
588
|
const candidate = isSet3(explicit) ? explicit : env.LANGWATCH_AGENT_TRANSPORT;
|
|
717
|
-
|
|
589
|
+
if (!isSet3(candidate)) return "websocket";
|
|
590
|
+
const normalized = candidate.trim().toLowerCase();
|
|
591
|
+
if (normalized === "http") return "http";
|
|
592
|
+
return "websocket";
|
|
718
593
|
}
|
|
719
594
|
var NoWebSocketError = class extends Error {
|
|
720
595
|
constructor() {
|
|
@@ -811,14 +686,14 @@ var HttpLongPollSocket = class {
|
|
|
811
686
|
return;
|
|
812
687
|
}
|
|
813
688
|
if (frame.type === "ack" && typeof frame.callId === "string") this.inFlight.add(frame.callId);
|
|
814
|
-
if (frame.type === "result" && typeof frame.callId === "string")
|
|
689
|
+
if (frame.type === "result" && typeof frame.callId === "string")
|
|
690
|
+
this.inFlight.delete(frame.callId);
|
|
815
691
|
this.outbox = this.outbox.then(() => this.registered).then(() => this.post(data)).catch(() => void 0);
|
|
816
692
|
}
|
|
817
693
|
/**
|
|
818
|
-
* Stops polling, lets
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
* hold the connection open.
|
|
694
|
+
* Stops polling, lets queued frames go out, then reports close. Bounded: on
|
|
695
|
+
* the deadline the frame requests are aborted and close is reported anyway,
|
|
696
|
+
* so an unanswering request cannot hold the connection open.
|
|
822
697
|
*/
|
|
823
698
|
close(code = 1e3) {
|
|
824
699
|
this.closed = true;
|
|
@@ -873,7 +748,8 @@ var HttpLongPollSocket = class {
|
|
|
873
748
|
signal: this.polls.signal
|
|
874
749
|
});
|
|
875
750
|
} catch (error) {
|
|
876
|
-
if (!this.closed)
|
|
751
|
+
if (!this.closed)
|
|
752
|
+
this.fail(`could not reach ${this.url}/register (${describe(error)})`, 1006);
|
|
877
753
|
return;
|
|
878
754
|
}
|
|
879
755
|
const body = await this.jsonOf(response);
|
|
@@ -894,46 +770,66 @@ var HttpLongPollSocket = class {
|
|
|
894
770
|
}
|
|
895
771
|
async pollLoop() {
|
|
896
772
|
while (!this.closed && this.token) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
this.fail(`the poll was answered with HTTP ${response.status}`, 1006);
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
const frames = Array.isArray(body?.frames) ? body.frames : [];
|
|
926
|
-
for (const frame of frames) {
|
|
927
|
-
const entry = frame;
|
|
928
|
-
if (entry.type === "cancel" && typeof entry.callId === "string") this.inFlight.delete(entry.callId);
|
|
929
|
-
this.emitMessage(JSON.stringify(frame));
|
|
930
|
-
}
|
|
931
|
-
for (const listener of this.pingListeners) listener();
|
|
932
|
-
if (frames.length === 0) {
|
|
933
|
-
const elapsedMs = Date.now() - startedAt;
|
|
934
|
-
if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
|
|
935
|
-
}
|
|
773
|
+
if (!await this.pollOnce()) return;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/** One long poll: delivers what it answered, and says whether polling goes on. */
|
|
777
|
+
async pollOnce() {
|
|
778
|
+
const query = this.inFlight.size > 0 ? `?inFlight=${encodeURIComponent([...this.inFlight].join(","))}` : "";
|
|
779
|
+
const startedAt = Date.now();
|
|
780
|
+
let response;
|
|
781
|
+
try {
|
|
782
|
+
response = await this.fetchImpl(`${this.url}/poll${query}`, {
|
|
783
|
+
method: "GET",
|
|
784
|
+
headers: this.requestHeaders(),
|
|
785
|
+
signal: this.polls.signal
|
|
786
|
+
});
|
|
787
|
+
} catch (error) {
|
|
788
|
+
if (!this.closed) this.fail(`the poll failed (${describe(error)})`, 1006);
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
if (this.closed) return false;
|
|
792
|
+
if (response.status === 410) {
|
|
793
|
+
this.fail(
|
|
794
|
+
"the platform no longer knows this instance, registering again",
|
|
795
|
+
SESSION_LOST_CLOSE_CODE
|
|
796
|
+
);
|
|
797
|
+
return false;
|
|
936
798
|
}
|
|
799
|
+
const body = await this.jsonOf(response);
|
|
800
|
+
if (!response.ok) {
|
|
801
|
+
this.refusePoll({ status: response.status, body });
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
const delivered = this.deliverFrames(body);
|
|
805
|
+
for (const listener of this.pingListeners) listener();
|
|
806
|
+
if (delivered === 0) {
|
|
807
|
+
const elapsedMs = Date.now() - startedAt;
|
|
808
|
+
if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
|
|
809
|
+
}
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
refusePoll({
|
|
813
|
+
status,
|
|
814
|
+
body
|
|
815
|
+
}) {
|
|
816
|
+
const answered = body && typeof body.frame === "object" && body.frame !== null ? body.frame : null;
|
|
817
|
+
if (answered) this.emitMessage(JSON.stringify(answered));
|
|
818
|
+
if (answered?.type === "refused") {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
this.fail(`the poll was answered with HTTP ${status}`, 1006);
|
|
822
|
+
}
|
|
823
|
+
/** Emits every frame a poll answered and returns how many there were. */
|
|
824
|
+
deliverFrames(body) {
|
|
825
|
+
const frames = Array.isArray(body?.frames) ? body.frames : [];
|
|
826
|
+
for (const frame of frames) {
|
|
827
|
+
const entry = frame;
|
|
828
|
+
if (entry.type === "cancel" && typeof entry.callId === "string")
|
|
829
|
+
this.inFlight.delete(entry.callId);
|
|
830
|
+
this.emitMessage(JSON.stringify(frame));
|
|
831
|
+
}
|
|
832
|
+
return frames.length;
|
|
937
833
|
}
|
|
938
834
|
async post(data) {
|
|
939
835
|
if (!this.token) return;
|
|
@@ -951,12 +847,7 @@ var HttpLongPollSocket = class {
|
|
|
951
847
|
if (this.frames.signal.aborted) return;
|
|
952
848
|
response = null;
|
|
953
849
|
}
|
|
954
|
-
if (response
|
|
955
|
-
if (response?.status === 410) {
|
|
956
|
-
this.fail("the platform no longer knows this instance, registering again", SESSION_LOST_CLOSE_CODE);
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
if (response && response.status < 500) return;
|
|
850
|
+
if (this.postSettled(response)) return;
|
|
960
851
|
const delay = POST_RETRY_DELAYS_MS[attempt];
|
|
961
852
|
if (delay === void 0) {
|
|
962
853
|
this.fail(`a frame could not be posted after ${attempt} retries`, 1006);
|
|
@@ -965,6 +856,18 @@ var HttpLongPollSocket = class {
|
|
|
965
856
|
await wait(delay);
|
|
966
857
|
}
|
|
967
858
|
}
|
|
859
|
+
/** Whether a post's response ends its retries: delivered, refused, or the session lost. */
|
|
860
|
+
postSettled(response) {
|
|
861
|
+
if (response?.ok) return true;
|
|
862
|
+
if (response?.status === 410) {
|
|
863
|
+
this.fail(
|
|
864
|
+
"the platform no longer knows this instance, registering again",
|
|
865
|
+
SESSION_LOST_CLOSE_CODE
|
|
866
|
+
);
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
return response !== null && response.status < 500;
|
|
870
|
+
}
|
|
968
871
|
async jsonOf(response) {
|
|
969
872
|
try {
|
|
970
873
|
const parsed = await response.json();
|
|
@@ -1018,19 +921,257 @@ function openTransportSocket({
|
|
|
1018
921
|
return transport === "http" ? new HttpLongPollSocket({ url: httpUrl, headers }) : socketFactory({ url: websocketUrl, headers });
|
|
1019
922
|
}
|
|
1020
923
|
|
|
924
|
+
// src/agent/schema.ts
|
|
925
|
+
var AgentParameterError = class extends Error {
|
|
926
|
+
constructor(message) {
|
|
927
|
+
super(message);
|
|
928
|
+
this.code = "agent_parameter_invalid";
|
|
929
|
+
this.name = "AgentParameterError";
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
var ACCEPTED_FORMS = `parameters must be a definition map ({ model: { options: [...], default: '...' } }), a Standard JSON Schema object (one with "~standard".jsonSchema), or a JSON Schema object ({ type: 'object', properties })`;
|
|
933
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
934
|
+
var isStandardJsonSchema = (value) => {
|
|
935
|
+
if (!isRecord2(value)) return false;
|
|
936
|
+
const standard = value["~standard"];
|
|
937
|
+
return isRecord2(standard) && isRecord2(standard.jsonSchema);
|
|
938
|
+
};
|
|
939
|
+
var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
|
|
940
|
+
var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
|
|
941
|
+
var definitionType = (definition) => {
|
|
942
|
+
if (definition.type) return definition.type;
|
|
943
|
+
if (definition.options) return "string";
|
|
944
|
+
const value = definition.default;
|
|
945
|
+
if (typeof value === "number") return "number";
|
|
946
|
+
if (typeof value === "boolean") return "boolean";
|
|
947
|
+
return "string";
|
|
948
|
+
};
|
|
949
|
+
var definitionMapToSchema = (definitions) => {
|
|
950
|
+
const properties = {};
|
|
951
|
+
const required = [];
|
|
952
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
953
|
+
const property = { type: definitionType(definition) };
|
|
954
|
+
if (definition.options) property.enum = [...definition.options];
|
|
955
|
+
if (definition.default !== void 0) property.default = definition.default;
|
|
956
|
+
else required.push(name);
|
|
957
|
+
if (definition.description !== void 0) property.description = definition.description;
|
|
958
|
+
properties[name] = property;
|
|
959
|
+
}
|
|
960
|
+
const schema = { type: "object", properties };
|
|
961
|
+
if (required.length > 0) schema.required = required;
|
|
962
|
+
return schema;
|
|
963
|
+
};
|
|
964
|
+
var readStandardJsonSchema = (input) => {
|
|
965
|
+
const converter = input["~standard"].jsonSchema;
|
|
966
|
+
const options = { target: "draft-2020-12" };
|
|
967
|
+
const schema = converter.input?.(options) ?? converter.output?.(options);
|
|
968
|
+
if (schema === void 0) {
|
|
969
|
+
throw new AgentParameterError(
|
|
970
|
+
`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
if (!isRecord2(schema)) {
|
|
974
|
+
throw new AgentParameterError(
|
|
975
|
+
`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
return schema;
|
|
979
|
+
};
|
|
980
|
+
function toParameterSchema(input) {
|
|
981
|
+
if (input === void 0) return { type: "object", properties: {} };
|
|
982
|
+
if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
|
|
983
|
+
if (isJsonSchemaObject(input)) return input;
|
|
984
|
+
if (isDefinitionMap(input)) return definitionMapToSchema(input);
|
|
985
|
+
throw new AgentParameterError(ACCEPTED_FORMS);
|
|
986
|
+
}
|
|
987
|
+
var specType = (property) => {
|
|
988
|
+
const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
|
|
989
|
+
if (type === "number" || type === "integer") return "number";
|
|
990
|
+
if (type === "boolean") return "boolean";
|
|
991
|
+
return "string";
|
|
992
|
+
};
|
|
993
|
+
var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
|
|
994
|
+
function parameterSpecsFromSchema(schema) {
|
|
995
|
+
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
996
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
997
|
+
const specs = [];
|
|
998
|
+
for (const [name, raw] of Object.entries(properties)) {
|
|
999
|
+
const property = isRecord2(raw) ? raw : {};
|
|
1000
|
+
const spec = { name, type: specType(property) };
|
|
1001
|
+
const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
|
|
1002
|
+
if (options && options.length > 0) spec.options = options;
|
|
1003
|
+
const fallback = scalar(property.default);
|
|
1004
|
+
if (fallback !== void 0) spec.default = fallback;
|
|
1005
|
+
if (typeof property.description === "string") spec.description = property.description;
|
|
1006
|
+
spec.required = fallback === void 0 && required.has(name);
|
|
1007
|
+
specs.push(spec);
|
|
1008
|
+
}
|
|
1009
|
+
return specs;
|
|
1010
|
+
}
|
|
1011
|
+
var coerce = ({
|
|
1012
|
+
spec,
|
|
1013
|
+
value
|
|
1014
|
+
}) => {
|
|
1015
|
+
if (spec.type === "number") {
|
|
1016
|
+
return coerceNumber(spec.name, value);
|
|
1017
|
+
}
|
|
1018
|
+
if (spec.type === "boolean") {
|
|
1019
|
+
if (typeof value === "boolean") return value;
|
|
1020
|
+
if (value === "true") return true;
|
|
1021
|
+
if (value === "false") return false;
|
|
1022
|
+
throw new AgentParameterError(
|
|
1023
|
+
`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
const asString = typeof value === "string" ? value : String(value);
|
|
1027
|
+
if (spec.options && !spec.options.includes(asString)) {
|
|
1028
|
+
throw new AgentParameterError(
|
|
1029
|
+
`parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
return asString;
|
|
1033
|
+
};
|
|
1034
|
+
function resolveParameterValues({
|
|
1035
|
+
specs,
|
|
1036
|
+
supplied
|
|
1037
|
+
}) {
|
|
1038
|
+
const values = { ...supplied };
|
|
1039
|
+
for (const spec of specs) {
|
|
1040
|
+
const value = values[spec.name];
|
|
1041
|
+
if (value === void 0) {
|
|
1042
|
+
if (spec.default !== void 0) {
|
|
1043
|
+
values[spec.name] = spec.default;
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
if (!spec.required) continue;
|
|
1047
|
+
throw new AgentParameterError(
|
|
1048
|
+
`parameter "${spec.name}" is required and the run did not supply it`
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
values[spec.name] = coerce({ spec, value });
|
|
1052
|
+
}
|
|
1053
|
+
return values;
|
|
1054
|
+
}
|
|
1055
|
+
var issuePath = (issue) => (issue.path ?? []).map(
|
|
1056
|
+
(segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
|
|
1057
|
+
).join(".");
|
|
1058
|
+
async function validateParameterValues({
|
|
1059
|
+
schema,
|
|
1060
|
+
values
|
|
1061
|
+
}) {
|
|
1062
|
+
const standard = schema["~standard"];
|
|
1063
|
+
const result = await standard.validate?.(values);
|
|
1064
|
+
if (result === void 0) return values;
|
|
1065
|
+
if (result.issues) {
|
|
1066
|
+
const detail = result.issues.map((issue) => {
|
|
1067
|
+
const path = issuePath(issue);
|
|
1068
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
1069
|
+
}).join("; ");
|
|
1070
|
+
throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
|
|
1071
|
+
}
|
|
1072
|
+
const parsed = isRecord2(result.value) ? result.value : {};
|
|
1073
|
+
return { ...values, ...parsed };
|
|
1074
|
+
}
|
|
1075
|
+
function createParameterReader({
|
|
1076
|
+
input,
|
|
1077
|
+
specs
|
|
1078
|
+
}) {
|
|
1079
|
+
const schema = isStandardJsonSchema(input) ? input : void 0;
|
|
1080
|
+
return async (supplied) => {
|
|
1081
|
+
const values = resolveParameterValues({ specs, supplied });
|
|
1082
|
+
return schema ? validateParameterValues({ schema, values }) : values;
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
function coerceNumber(name, value) {
|
|
1086
|
+
const asNumber = typeof value === "number" ? value : Number(value);
|
|
1087
|
+
if (typeof value === "boolean") {
|
|
1088
|
+
throw new AgentParameterError(
|
|
1089
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
if (!Number.isFinite(asNumber)) {
|
|
1093
|
+
throw new AgentParameterError(
|
|
1094
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
const stringValue2 = String(value).trim();
|
|
1098
|
+
if (stringValue2 === "") {
|
|
1099
|
+
throw new AgentParameterError(
|
|
1100
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
return asNumber;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// src/agent/state.ts
|
|
1107
|
+
var STATE_MAX_BYTES = 64 * 1024;
|
|
1108
|
+
var AgentStateError = class extends Error {
|
|
1109
|
+
constructor(code, message) {
|
|
1110
|
+
super(message);
|
|
1111
|
+
this.code = code;
|
|
1112
|
+
this.name = "AgentStateError";
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1116
|
+
function checkState(value) {
|
|
1117
|
+
if (value === void 0 || value === null) return void 0;
|
|
1118
|
+
if (!isPlainObject(value)) {
|
|
1119
|
+
throw new AgentStateError(
|
|
1120
|
+
"agent_call_failed",
|
|
1121
|
+
`the state must be a JSON object, got ${Array.isArray(value) ? "an array" : typeof value}`
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
let encoded;
|
|
1125
|
+
try {
|
|
1126
|
+
encoded = JSON.stringify(value);
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
throw new AgentStateError(
|
|
1129
|
+
"agent_call_failed",
|
|
1130
|
+
`the state is not JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
const sizeBytes = Buffer.byteLength(encoded, "utf8");
|
|
1134
|
+
if (sizeBytes > STATE_MAX_BYTES) {
|
|
1135
|
+
throw new AgentStateError(
|
|
1136
|
+
"agent_payload_too_large",
|
|
1137
|
+
`The state is ${sizeBytes} bytes, above the limit of ${STATE_MAX_BYTES} bytes.`
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
return value;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1021
1143
|
// src/agent/client.ts
|
|
1144
|
+
var kindRefusal = (runtime, kind) => {
|
|
1145
|
+
if (kind === "turn") return null;
|
|
1146
|
+
if (kind !== "setup" && kind !== "teardown") {
|
|
1147
|
+
return { code: "agent_call_failed", message: `unknown call kind "${kind}"` };
|
|
1148
|
+
}
|
|
1149
|
+
if (runtime.hooks[kind]) return null;
|
|
1150
|
+
return {
|
|
1151
|
+
code: "hook_not_declared",
|
|
1152
|
+
message: `agent "${runtime.name}" declares no ${kind} hook`
|
|
1153
|
+
};
|
|
1154
|
+
};
|
|
1155
|
+
var errorCodeOf = (error) => error instanceof AgentParameterError || error instanceof AgentStateError ? error.code : "agent_call_failed";
|
|
1156
|
+
var answerCall = async ({
|
|
1157
|
+
runtime,
|
|
1158
|
+
frame,
|
|
1159
|
+
call
|
|
1160
|
+
}) => {
|
|
1161
|
+
const hook = frame.kind === "setup" || frame.kind === "teardown" ? runtime.hooks[frame.kind] : void 0;
|
|
1162
|
+
if (!hook) {
|
|
1163
|
+
const result = await runtime.run(call);
|
|
1164
|
+
return { ...result, state: checkState(result.state) };
|
|
1165
|
+
}
|
|
1166
|
+
const state = await hook({ run: frame.run, state: frame.state, params: call.params });
|
|
1167
|
+
return { state: checkState(frame.kind === "setup" ? state ?? {} : state) };
|
|
1168
|
+
};
|
|
1022
1169
|
var FAILURE_NOTICE_INTERVAL_MS = 5 * 6e4;
|
|
1023
1170
|
var NOT_CONNECTED = "not connected to LangWatch";
|
|
1024
1171
|
function refusalAdvice(frame) {
|
|
1025
1172
|
switch (frame.code) {
|
|
1026
1173
|
case "project_required": {
|
|
1027
|
-
const
|
|
1028
|
-
const listed = projects.map((project) => {
|
|
1029
|
-
const entry = project;
|
|
1030
|
-
const id = typeof entry.id === "string" ? entry.id : "";
|
|
1031
|
-
const name = typeof entry.name === "string" ? entry.name : "";
|
|
1032
|
-
return name && id ? `${name} (${id})` : id || name;
|
|
1033
|
-
}).filter((line) => line !== "");
|
|
1174
|
+
const listed = projectNames(frame.meta?.projects);
|
|
1034
1175
|
return `the API key reaches more than one project. Set LANGWATCH_PROJECT_ID to one of: ${listed.length > 0 ? listed.join(", ") : "the projects the key reaches"}.`;
|
|
1035
1176
|
}
|
|
1036
1177
|
case "api_key_invalid":
|
|
@@ -1050,8 +1191,30 @@ function refusalAdvice(frame) {
|
|
|
1050
1191
|
return `${frame.message} (${frame.code})`;
|
|
1051
1192
|
}
|
|
1052
1193
|
}
|
|
1194
|
+
function projectNames(value) {
|
|
1195
|
+
if (!Array.isArray(value)) return [];
|
|
1196
|
+
return value.map(projectName).filter((name) => name !== "");
|
|
1197
|
+
}
|
|
1198
|
+
function projectName(value) {
|
|
1199
|
+
if (!isRecord3(value)) return "";
|
|
1200
|
+
const id = stringValue(value.id);
|
|
1201
|
+
const name = stringValue(value.name);
|
|
1202
|
+
if (name && id) return `${name} (${id})`;
|
|
1203
|
+
return id || name;
|
|
1204
|
+
}
|
|
1205
|
+
function isRecord3(value) {
|
|
1206
|
+
return typeof value === "object" && value !== null;
|
|
1207
|
+
}
|
|
1208
|
+
function stringValue(value) {
|
|
1209
|
+
return typeof value === "string" ? value : "";
|
|
1210
|
+
}
|
|
1053
1211
|
var SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
1054
1212
|
var CLOSE_GRACE_MS = 500;
|
|
1213
|
+
var readCallTraceContext = (frame) => {
|
|
1214
|
+
const parent = frame.traceparent ? import_api.propagation.extract(import_api.context.active(), { traceparent: frame.traceparent }) : import_api.context.active();
|
|
1215
|
+
const traceId = import_api.trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
|
|
1216
|
+
return { parent, traceId };
|
|
1217
|
+
};
|
|
1055
1218
|
var AgentClient = class {
|
|
1056
1219
|
constructor(config) {
|
|
1057
1220
|
this.agents = [];
|
|
@@ -1092,7 +1255,7 @@ var AgentClient = class {
|
|
|
1092
1255
|
get isRegistered() {
|
|
1093
1256
|
return this.registered;
|
|
1094
1257
|
}
|
|
1095
|
-
/** True once the client gave up: refused, or no socket implementation
|
|
1258
|
+
/** True once the client gave up: refused, or no socket implementation available. */
|
|
1096
1259
|
get isStopped() {
|
|
1097
1260
|
return this.stopped;
|
|
1098
1261
|
}
|
|
@@ -1109,7 +1272,9 @@ var AgentClient = class {
|
|
|
1109
1272
|
addAgent(runtime) {
|
|
1110
1273
|
this.agents.push(runtime);
|
|
1111
1274
|
if (this.gaveUp) {
|
|
1112
|
-
this.logger.debug(
|
|
1275
|
+
this.logger.debug(
|
|
1276
|
+
`agent "${runtime.name}" ${NOT_CONNECTED}: the connection gave up earlier in this process`
|
|
1277
|
+
);
|
|
1113
1278
|
return;
|
|
1114
1279
|
}
|
|
1115
1280
|
this.stopped = false;
|
|
@@ -1119,6 +1284,10 @@ var AgentClient = class {
|
|
|
1119
1284
|
}
|
|
1120
1285
|
this.scheduleConnect(0);
|
|
1121
1286
|
}
|
|
1287
|
+
/** Registers the agents again after one changed: a hook declared once the socket opened. */
|
|
1288
|
+
refreshAgents() {
|
|
1289
|
+
if (this.socket) this.restartSocket();
|
|
1290
|
+
}
|
|
1122
1291
|
/** Closes the socket and connects again at once, keeping the reconnect loop. */
|
|
1123
1292
|
restartSocket() {
|
|
1124
1293
|
const socket = this.socket;
|
|
@@ -1278,7 +1447,10 @@ var AgentClient = class {
|
|
|
1278
1447
|
* connection is lost, and silence while the retries run: the same notice
|
|
1279
1448
|
* repeats only after the notice interval, and a reconnect resets it.
|
|
1280
1449
|
*/
|
|
1281
|
-
noteDisconnected({
|
|
1450
|
+
noteDisconnected({
|
|
1451
|
+
wasRegistered,
|
|
1452
|
+
code
|
|
1453
|
+
}) {
|
|
1282
1454
|
const now = Date.now();
|
|
1283
1455
|
if (wasRegistered) {
|
|
1284
1456
|
this.logger.warn(
|
|
@@ -1371,7 +1543,8 @@ var AgentClient = class {
|
|
|
1371
1543
|
this.logger.info(`connected to LangWatch over HTTP long polling at ${this.httpUrl}`);
|
|
1372
1544
|
}
|
|
1373
1545
|
this.heartbeatIntervalMs = frame.heartbeatIntervalMs;
|
|
1374
|
-
if (frame.instanceId && frame.instanceId !== this.instance.id)
|
|
1546
|
+
if (frame.instanceId && frame.instanceId !== this.instance.id)
|
|
1547
|
+
this.instance.id = frame.instanceId;
|
|
1375
1548
|
this.byId.clear();
|
|
1376
1549
|
for (const entry of frame.agents) {
|
|
1377
1550
|
const runtime = this.agents.find(
|
|
@@ -1398,21 +1571,9 @@ var AgentClient = class {
|
|
|
1398
1571
|
});
|
|
1399
1572
|
return;
|
|
1400
1573
|
}
|
|
1401
|
-
const
|
|
1402
|
-
if (
|
|
1403
|
-
this.sendError({
|
|
1404
|
-
callId: frame.callId,
|
|
1405
|
-
code: "agent_busy",
|
|
1406
|
-
message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
|
|
1407
|
-
});
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
|
|
1411
|
-
this.sendError({
|
|
1412
|
-
callId: frame.callId,
|
|
1413
|
-
code: "agent_call_timeout",
|
|
1414
|
-
message: "the call deadline passed before it started"
|
|
1415
|
-
});
|
|
1574
|
+
const refusal = this.callRefusal({ frame, runtime });
|
|
1575
|
+
if (refusal) {
|
|
1576
|
+
this.sendError({ callId: frame.callId, ...refusal });
|
|
1416
1577
|
return;
|
|
1417
1578
|
}
|
|
1418
1579
|
const entry = { runtime, cancelled: false, timer: null };
|
|
@@ -1435,18 +1596,18 @@ var AgentClient = class {
|
|
|
1435
1596
|
}
|
|
1436
1597
|
this.send({ type: "ack", protocol: PROTOCOL_VERSION, callId: frame.callId });
|
|
1437
1598
|
this.armCallDeadline({ frame, entry, runtime });
|
|
1438
|
-
const parent
|
|
1439
|
-
const traceId = import_api.trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
|
|
1599
|
+
const { parent, traceId } = readCallTraceContext(frame);
|
|
1440
1600
|
const call = {
|
|
1441
1601
|
messages: frame.messages,
|
|
1442
1602
|
newMessages: frame.newMessages,
|
|
1443
1603
|
threadId: frame.threadId,
|
|
1444
1604
|
session: frame.session,
|
|
1605
|
+
state: frame.state,
|
|
1445
1606
|
params,
|
|
1446
1607
|
traceId
|
|
1447
1608
|
};
|
|
1448
1609
|
try {
|
|
1449
|
-
const result = await import_api.context.with(parent, () => runtime
|
|
1610
|
+
const result = await import_api.context.with(parent, () => answerCall({ runtime, frame, call }));
|
|
1450
1611
|
if (entry.cancelled) return;
|
|
1451
1612
|
this.releaseCall({ callId: frame.callId, entry });
|
|
1452
1613
|
await this.flushSpans();
|
|
@@ -1454,12 +1615,13 @@ var AgentClient = class {
|
|
|
1454
1615
|
type: "result",
|
|
1455
1616
|
protocol: PROTOCOL_VERSION,
|
|
1456
1617
|
callId: frame.callId,
|
|
1457
|
-
output: result.output,
|
|
1458
|
-
...result.session === void 0 ? {} : { session: result.session }
|
|
1618
|
+
...result.output === void 0 ? {} : { output: result.output },
|
|
1619
|
+
...result.session === void 0 ? {} : { session: result.session },
|
|
1620
|
+
...result.state === void 0 ? {} : { state: result.state }
|
|
1459
1621
|
});
|
|
1460
1622
|
} catch (error) {
|
|
1461
1623
|
if (entry.cancelled) return;
|
|
1462
|
-
const code = error
|
|
1624
|
+
const code = errorCodeOf(error);
|
|
1463
1625
|
const message = describeError(error);
|
|
1464
1626
|
this.logger.warn(`agent "${runtime.name}" call ${frame.callId} failed: ${message}`);
|
|
1465
1627
|
this.releaseCall({ callId: frame.callId, entry });
|
|
@@ -1469,15 +1631,32 @@ var AgentClient = class {
|
|
|
1469
1631
|
this.releaseCall({ callId: frame.callId, entry });
|
|
1470
1632
|
}
|
|
1471
1633
|
}
|
|
1634
|
+
/** Why the runtime cannot take this call now, or null when it can. */
|
|
1635
|
+
callRefusal({
|
|
1636
|
+
frame,
|
|
1637
|
+
runtime
|
|
1638
|
+
}) {
|
|
1639
|
+
const kind = kindRefusal(runtime, frame.kind);
|
|
1640
|
+
if (kind) return kind;
|
|
1641
|
+
const busy = [...this.inFlight.values()].filter((call) => call.runtime === runtime).length;
|
|
1642
|
+
if (busy >= runtime.concurrency) {
|
|
1643
|
+
return {
|
|
1644
|
+
code: "agent_busy",
|
|
1645
|
+
message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
|
|
1649
|
+
return {
|
|
1650
|
+
code: "agent_call_timeout",
|
|
1651
|
+
message: "the call deadline passed before it started"
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1472
1656
|
/**
|
|
1473
|
-
* Exports the spans of the call now
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
1476
|
-
* made the judge report the spans missing.
|
|
1477
|
-
*
|
|
1478
|
-
* The call awaits this before it sends its result or its error: the frame is
|
|
1479
|
-
* what tells the platform the turn is over, so a frame that goes out first
|
|
1480
|
-
* lets the judge read the call while its spans are still in the exporter.
|
|
1657
|
+
* Exports the spans of the call now, since a batch exporter would
|
|
1658
|
+
* otherwise hold them for seconds after the judge reads them. Awaited
|
|
1659
|
+
* before the result or error frame goes out.
|
|
1481
1660
|
*/
|
|
1482
1661
|
async flushSpans() {
|
|
1483
1662
|
const provider = import_api.trace.getTracerProvider();
|
|
@@ -1509,26 +1688,33 @@ var AgentClient = class {
|
|
|
1509
1688
|
runtime
|
|
1510
1689
|
}) {
|
|
1511
1690
|
const fromDeadline = frame.deadlineAt === null ? Infinity : frame.deadlineAt - Date.now();
|
|
1512
|
-
const limit = Math.min(fromDeadline, runtime.timeoutMs);
|
|
1691
|
+
const limit = frame.kind !== "turn" && frame.deadlineAt !== null ? fromDeadline : Math.min(fromDeadline, runtime.timeoutMs);
|
|
1513
1692
|
if (!Number.isFinite(limit)) return;
|
|
1514
|
-
const timer = setTimeout(
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1693
|
+
const timer = setTimeout(
|
|
1694
|
+
() => {
|
|
1695
|
+
entry.timer = null;
|
|
1696
|
+
if (entry.cancelled) return;
|
|
1697
|
+
entry.cancelled = true;
|
|
1698
|
+
this.releaseCall({ callId: frame.callId, entry });
|
|
1699
|
+
this.logger.warn(
|
|
1700
|
+
`agent "${runtime.name}" call ${frame.callId} passed its ${limit} ms limit`
|
|
1701
|
+
);
|
|
1702
|
+
this.sendError({
|
|
1703
|
+
callId: frame.callId,
|
|
1704
|
+
code: "agent_call_timeout",
|
|
1705
|
+
message: `the call passed the ${limit} ms limit of agent "${runtime.name}"`
|
|
1706
|
+
});
|
|
1707
|
+
},
|
|
1708
|
+
Math.max(0, limit)
|
|
1709
|
+
);
|
|
1528
1710
|
timer.unref();
|
|
1529
1711
|
entry.timer = timer;
|
|
1530
1712
|
}
|
|
1531
|
-
sendError({
|
|
1713
|
+
sendError({
|
|
1714
|
+
callId,
|
|
1715
|
+
code,
|
|
1716
|
+
message
|
|
1717
|
+
}) {
|
|
1532
1718
|
this.send({ type: "result", protocol: PROTOCOL_VERSION, callId, error: { code, message } });
|
|
1533
1719
|
}
|
|
1534
1720
|
guard(action) {
|
|
@@ -1571,7 +1757,11 @@ var installShutdownHooks = () => {
|
|
|
1571
1757
|
}
|
|
1572
1758
|
process.on("beforeExit", onBeforeExit);
|
|
1573
1759
|
};
|
|
1574
|
-
function warnOnce2({
|
|
1760
|
+
function warnOnce2({
|
|
1761
|
+
logger,
|
|
1762
|
+
key,
|
|
1763
|
+
message
|
|
1764
|
+
}) {
|
|
1575
1765
|
if (noticesGiven.has(key)) {
|
|
1576
1766
|
logger.debug(message);
|
|
1577
1767
|
return;
|
|
@@ -1614,22 +1804,33 @@ var DEFAULT_CONCURRENCY = 10;
|
|
|
1614
1804
|
var isMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.role === "string";
|
|
1615
1805
|
function normalizeReply(reply) {
|
|
1616
1806
|
if (typeof reply === "string") return { output: reply };
|
|
1617
|
-
if (Array.isArray(reply)
|
|
1807
|
+
if (Array.isArray(reply)) {
|
|
1808
|
+
if (reply.every(isMessage)) return { output: reply };
|
|
1809
|
+
}
|
|
1618
1810
|
if (isMessage(reply)) return { output: reply };
|
|
1619
1811
|
if (typeof reply === "object" && reply !== null && "output" in reply) {
|
|
1620
|
-
const { output, session } = reply;
|
|
1621
|
-
|
|
1622
|
-
|
|
1812
|
+
const { output, session, state } = reply;
|
|
1813
|
+
return {
|
|
1814
|
+
output: normalizeReply(output).output,
|
|
1815
|
+
...session === void 0 ? {} : { session },
|
|
1816
|
+
...state === void 0 ? {} : { state }
|
|
1817
|
+
};
|
|
1623
1818
|
}
|
|
1624
1819
|
throw new Error(
|
|
1625
|
-
"the agent handler must return a string, a message, a list of messages, or { output, session }"
|
|
1820
|
+
"the agent handler must return a string, a message, a list of messages, or { output, session, state }"
|
|
1626
1821
|
);
|
|
1627
1822
|
}
|
|
1628
|
-
var clampTimeout = ({
|
|
1823
|
+
var clampTimeout = ({
|
|
1824
|
+
timeoutMs,
|
|
1825
|
+
logger,
|
|
1826
|
+
name
|
|
1827
|
+
}) => {
|
|
1629
1828
|
if (timeoutMs === void 0) return DEFAULT_TIMEOUT_MS;
|
|
1630
1829
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return DEFAULT_TIMEOUT_MS;
|
|
1631
1830
|
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
1632
|
-
logger.warn(
|
|
1831
|
+
logger.warn(
|
|
1832
|
+
`agent "${name}": timeoutMs ${timeoutMs} is above the ${MAX_TIMEOUT_MS} cap, using the cap`
|
|
1833
|
+
);
|
|
1633
1834
|
return MAX_TIMEOUT_MS;
|
|
1634
1835
|
}
|
|
1635
1836
|
return Math.floor(timeoutMs);
|
|
@@ -1660,10 +1861,12 @@ function connectAgent(options, handler) {
|
|
|
1660
1861
|
newMessages: call.newMessages ?? call.messages,
|
|
1661
1862
|
threadId: call.threadId ?? `local_${Date.now().toString(36)}`,
|
|
1662
1863
|
session: call.session ?? null,
|
|
1864
|
+
state: call.state ?? null,
|
|
1663
1865
|
params,
|
|
1664
1866
|
traceId: call.traceId ?? ""
|
|
1665
1867
|
});
|
|
1666
1868
|
};
|
|
1869
|
+
const hooks = {};
|
|
1667
1870
|
const runtime = {
|
|
1668
1871
|
name,
|
|
1669
1872
|
environment,
|
|
@@ -1673,14 +1876,29 @@ function connectAgent(options, handler) {
|
|
|
1673
1876
|
parameters,
|
|
1674
1877
|
concurrency,
|
|
1675
1878
|
timeoutMs,
|
|
1676
|
-
...options.sticky ? { sticky: true } : {}
|
|
1879
|
+
...options.sticky ? { sticky: true } : {},
|
|
1880
|
+
hooks: { setup: false, teardown: false }
|
|
1677
1881
|
},
|
|
1678
1882
|
readParams: readParams2,
|
|
1679
1883
|
concurrency,
|
|
1680
1884
|
timeoutMs,
|
|
1681
|
-
run: runHandler
|
|
1885
|
+
run: runHandler,
|
|
1886
|
+
hooks
|
|
1682
1887
|
};
|
|
1683
1888
|
let detach;
|
|
1889
|
+
let refresh;
|
|
1890
|
+
const scenario = {
|
|
1891
|
+
setup: (hook) => {
|
|
1892
|
+
hooks.setup = async ({ run, params }) => hook(run, params);
|
|
1893
|
+
runtime.register.hooks = { setup: true, teardown: Boolean(hooks.teardown) };
|
|
1894
|
+
refresh?.();
|
|
1895
|
+
},
|
|
1896
|
+
teardown: (hook) => {
|
|
1897
|
+
hooks.teardown = async ({ run, state, params }) => hook(run, state, params);
|
|
1898
|
+
runtime.register.hooks = { setup: Boolean(hooks.setup), teardown: true };
|
|
1899
|
+
refresh?.();
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1684
1902
|
if (!resolveEnabled({ explicit: options.enabled })) {
|
|
1685
1903
|
logger.debug(`agent "${name}" not connected to LangWatch: the connection is disabled`);
|
|
1686
1904
|
} else {
|
|
@@ -1702,15 +1920,18 @@ function connectAgent(options, handler) {
|
|
|
1702
1920
|
});
|
|
1703
1921
|
client.addAgent(runtime);
|
|
1704
1922
|
detach = () => client.removeAgent(runtime);
|
|
1923
|
+
refresh = () => client.refreshAgents();
|
|
1705
1924
|
}
|
|
1706
1925
|
}
|
|
1707
1926
|
const connected = Object.assign(invoke, {
|
|
1708
1927
|
environment,
|
|
1709
1928
|
parameters,
|
|
1929
|
+
scenario,
|
|
1710
1930
|
disconnect: async () => {
|
|
1711
1931
|
if (!detach) return;
|
|
1712
1932
|
const release = detach;
|
|
1713
1933
|
detach = void 0;
|
|
1934
|
+
refresh = void 0;
|
|
1714
1935
|
await release();
|
|
1715
1936
|
}
|
|
1716
1937
|
});
|
|
@@ -1721,9 +1942,11 @@ function connectAgent(options, handler) {
|
|
|
1721
1942
|
0 && (module.exports = {
|
|
1722
1943
|
AGENT_TRANSPORTS,
|
|
1723
1944
|
AgentParameterError,
|
|
1945
|
+
AgentStateError,
|
|
1724
1946
|
DEFAULT_TIMEOUT_MS,
|
|
1725
1947
|
MAX_TIMEOUT_MS,
|
|
1726
1948
|
PROTOCOL_VERSION,
|
|
1949
|
+
STATE_MAX_BYTES,
|
|
1727
1950
|
connectAgent,
|
|
1728
1951
|
normalizeReply,
|
|
1729
1952
|
parameterSpecsFromSchema,
|