@agent-builder-lab/eve-kit 0.0.0-stage → 0.1.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.
@@ -0,0 +1,78 @@
1
+ import { ApprovalPolicy } from 'eve/tools/approval';
2
+
3
+ /** Chave do `clientContext` com a conta de teste do turno. */
4
+ declare const LAB_ACCOUNT_CONTEXT_KEY = "labAccount";
5
+ /** Atributo do auth do turno com a conta (JSON), para as ferramentas e os hooks lerem. */
6
+ declare const LAB_ACCOUNT_ATTRIBUTE = "labAccount";
7
+ interface LabAccount {
8
+ readonly v: 1;
9
+ readonly id: string;
10
+ readonly name: string;
11
+ readonly role: string;
12
+ readonly roleName: string;
13
+ /** Recursos liberados: `tool:agendar_consulta`, `data:agenda`, `agent:confirmador`… */
14
+ readonly grants: readonly string[];
15
+ /** Nome legível de cada recurso, para o motivo da negação. */
16
+ readonly labels: Readonly<Record<string, string>>;
17
+ /** O cadastro de quem conversa, quando a conta tem um (ex.: o paciente da conversa). */
18
+ readonly patientId?: string;
19
+ }
20
+ /** Confere o formato sem confiar em nada além do necessário; fora do formato, undefined. */
21
+ declare function readLabAccount(value: unknown): LabAccount | undefined;
22
+ /** A conta de teste que o lab mandou no `clientContext` do pedido, ou undefined. */
23
+ declare function labAccountFromContext(context: unknown): LabAccount | undefined;
24
+ type AuthLike = {
25
+ readonly attributes?: Readonly<Record<string, string | readonly string[]>>;
26
+ };
27
+ /** A conta guardada nos atributos de um auth (o `labChannel()` grava). */
28
+ declare function labAccountFromAuth(auth: AuthLike | null | undefined): LabAccount | undefined;
29
+ /**
30
+ * A conta de teste do turno, para usar dentro de uma ferramenta, de um hook ou de uma política:
31
+ * `labAccountOf(ctx)?.name`. Sem o lab (ou fora do Chat do lab), undefined.
32
+ */
33
+ declare function labAccountOf(ctx: {
34
+ readonly session: {
35
+ readonly auth: {
36
+ readonly current: AuthLike | null;
37
+ };
38
+ };
39
+ }): LabAccount | undefined;
40
+ /** Motivo da negação, igual ao que o lab espera ler (`deniedResourceOf` em @lab/access). */
41
+ declare function labDenialReason(account: LabAccount, resource: string): string;
42
+ interface LabAuthorizationOptions<TInput = Record<string, unknown>> {
43
+ /**
44
+ * Dados que a ferramenta toca: nomes (`"agenda"`) ou ids (`"data:agenda"`). Pode depender da
45
+ * entrada: `(input) => input?.cpf ? ["pacientes"] : []`.
46
+ */
47
+ readonly data?: readonly string[] | ((input: Readonly<TInput> | undefined) => readonly string[]);
48
+ /** Subagente dono da ferramenta: exige `agent:<nome>` (a conta pode delegar a ele?). */
49
+ readonly agent?: string;
50
+ /**
51
+ * Exige `tool:<nome da ferramenta>`. Padrão: sim nas ferramentas do agente principal e não nas
52
+ * de um subagente (que o lab cobre pelo próprio subagente).
53
+ */
54
+ readonly tool?: boolean;
55
+ }
56
+ /** Os recursos que uma chamada precisa, na ordem em que o harness confere. */
57
+ declare function requiredResources<TInput>(toolName: string, input: Readonly<TInput> | undefined, options?: LabAuthorizationOptions<TInput>): string[];
58
+ /**
59
+ * Política de `approval` que confere a conta de teste do turno antes de a ferramenta rodar:
60
+ *
61
+ * ```ts
62
+ * import { labAuthorization } from "@agent-builder-lab/eve-kit/authorization";
63
+ * export default defineTool({
64
+ * description: "Marca uma consulta.",
65
+ * approval: labAuthorization({ data: ["agenda", "pacientes"] }),
66
+ * async execute(input) { … },
67
+ * });
68
+ * ```
69
+ *
70
+ * Sem conta do lab no turno (o agente fora do lab, ou um pedido que não veio do Chat), não muda
71
+ * nada: devolve "not-applicable" e a ferramenta roda como sempre. `next` encadeia outra política
72
+ * (como `always()`), consultada só quando a conta pode usar a ferramenta.
73
+ */
74
+ declare function labAuthorization<TInput = Record<string, unknown>>(options?: LabAuthorizationOptions<TInput> & {
75
+ readonly next?: ApprovalPolicy<TInput>;
76
+ }): ApprovalPolicy<TInput>;
77
+
78
+ export { LAB_ACCOUNT_ATTRIBUTE, LAB_ACCOUNT_CONTEXT_KEY, type LabAccount, type LabAuthorizationOptions, labAccountFromAuth, labAccountFromContext, labAccountOf, labAuthorization, labDenialReason, readLabAccount, requiredResources };
@@ -0,0 +1,22 @@
1
+ import {
2
+ LAB_ACCOUNT_ATTRIBUTE,
3
+ LAB_ACCOUNT_CONTEXT_KEY,
4
+ labAccountFromAuth,
5
+ labAccountFromContext,
6
+ labAccountOf,
7
+ labAuthorization,
8
+ labDenialReason,
9
+ readLabAccount,
10
+ requiredResources
11
+ } from "./chunk-LQIDQBNW.js";
12
+ export {
13
+ LAB_ACCOUNT_ATTRIBUTE,
14
+ LAB_ACCOUNT_CONTEXT_KEY,
15
+ labAccountFromAuth,
16
+ labAccountFromContext,
17
+ labAccountOf,
18
+ labAuthorization,
19
+ labDenialReason,
20
+ readLabAccount,
21
+ requiredResources
22
+ };
@@ -0,0 +1,39 @@
1
+ import { AuthFn } from 'eve/channels/auth';
2
+ import { EveChannelInput, EveChannel, EveMessageContext, EveMessageResult } from 'eve/channels/eve';
3
+
4
+ interface LabChannelOptions extends Omit<EveChannelInput, "auth"> {
5
+ /**
6
+ * Autenticadores do próprio agente, verificados antes do acesso local do lab. O acesso local
7
+ * (`localDev()` do eve) entra sempre por último.
8
+ */
9
+ auth?: AuthFn<Request> | readonly AuthFn<Request>[];
10
+ /** Porta local do conector. Padrão: `LAB_CONNECTOR_URL` ou http://127.0.0.1:2100. */
11
+ connectorUrl?: string;
12
+ }
13
+ /**
14
+ * Canal HTTP do eve com o que o Agent Builder Lab precisa para reexecutar o loop e conferir a
15
+ * conta de teste, para `agent/channels/eve.ts`:
16
+ *
17
+ * ```ts
18
+ * import { labChannel } from "@agent-builder-lab/eve-kit/channel";
19
+ * export default labChannel();
20
+ * ```
21
+ *
22
+ * - Em `eve dev`, chamadas de loopback ganham `allowToolStubs`: o lab troca o resultado das
23
+ * ferramentas por saídas gravadas ou simuladas, e nada executa de verdade.
24
+ * - Quando o pedido traz um fork do lab (`clientContext.labFork.id`), busca no conector o
25
+ * histórico dos turnos anteriores e o devolve ao eve como `history`. O id vai para os
26
+ * atributos do auth do turno (para o `labReplayModel`) e sai do contexto que o modelo vê.
27
+ * - Quando o pedido traz a conta de teste ("Enviando como", `clientContext.labAccount`), ela vai
28
+ * para os atributos do auth do turno (`labAccount`), onde `labAuthorization()` confere cada
29
+ * tool call, e também sai do contexto que o modelo vê: a identidade vem do harness, nunca do
30
+ * texto.
31
+ *
32
+ * Em produção (`eve start`, Vercel) o acesso local não autentica nada e o canal se comporta como
33
+ * o padrão com os autenticadores que você passar.
34
+ */
35
+ declare function labChannel(options?: LabChannelOptions): EveChannel;
36
+ /** O `onMessage` do canal do lab (exportado para os testes e para canais montados à mão). */
37
+ declare function labOnMessage(options: Pick<LabChannelOptions, "onMessage" | "connectorUrl">): (ctx: EveMessageContext, message: unknown) => Promise<EveMessageResult>;
38
+
39
+ export { type LabChannelOptions, labChannel, labOnMessage };
@@ -0,0 +1,86 @@
1
+ import {
2
+ LAB_ACCOUNT_ATTRIBUTE,
3
+ LAB_ACCOUNT_CONTEXT_KEY,
4
+ labAccountFromContext
5
+ } from "./chunk-LQIDQBNW.js";
6
+ import {
7
+ LAB_FORK_ATTRIBUTE,
8
+ LAB_FORK_CONTEXT_KEY,
9
+ fetchForkPlan,
10
+ forkIdFromContext
11
+ } from "./chunk-3IQOFBSQ.js";
12
+
13
+ // src/channel.ts
14
+ import { localDev } from "eve/channels/auth";
15
+ import {
16
+ defaultEveAuth,
17
+ eveChannel
18
+ } from "eve/channels/eve";
19
+ function labChannel(options = {}) {
20
+ const { auth, connectorUrl, onMessage, ...rest } = options;
21
+ const own = auth === void 0 ? [] : Array.isArray(auth) ? auth : [auth];
22
+ return eveChannel({
23
+ ...rest,
24
+ auth: [...own, labLocalDev()],
25
+ onMessage: labOnMessage({ onMessage, connectorUrl })
26
+ });
27
+ }
28
+ function labOnMessage(options) {
29
+ return async (ctx, message) => {
30
+ const base = options.onMessage ? await options.onMessage(ctx, message) : { auth: defaultEveAuth(ctx) };
31
+ const context = ctx.eve.clientContext;
32
+ const forkId = forkIdFromContext(context);
33
+ const account = labAccountFromContext(context);
34
+ if (!forkId && !account && !hasLabKeys(context)) return base;
35
+ const plan = forkId ? await fetchForkPlan(forkId, options.connectorUrl) : void 0;
36
+ const attributes = {
37
+ ...forkId ? { [LAB_FORK_ATTRIBUTE]: forkId } : {},
38
+ ...account ? { [LAB_ACCOUNT_ATTRIBUTE]: JSON.stringify(account) } : {}
39
+ };
40
+ return {
41
+ ...base,
42
+ auth: withAttributes(base.auth, attributes, account),
43
+ ...plan ? { history: [...plan.history, ...base.history ?? []] } : {},
44
+ clientContext: base.clientContext ?? withoutLabKeys(context)
45
+ };
46
+ };
47
+ }
48
+ function withAttributes(auth, attributes, account) {
49
+ if (Object.keys(attributes).length === 0) return auth;
50
+ if (auth) return { ...auth, attributes: { ...auth.attributes, ...attributes } };
51
+ if (!account) return auth;
52
+ return {
53
+ authenticator: "agent-builder-lab",
54
+ principalId: `lab-account:${account.id}`,
55
+ principalType: "user",
56
+ attributes
57
+ };
58
+ }
59
+ function labLocalDev() {
60
+ const local = localDev();
61
+ return async (request) => {
62
+ const caller = await local(request);
63
+ if (!caller) return caller;
64
+ return isLoopback(request) ? { ...caller, allowToolStubs: true } : caller;
65
+ };
66
+ }
67
+ function isLoopback(request) {
68
+ const host = new URL(request.url).hostname.replace(/^\[|\]$/g, "");
69
+ return host === "127.0.0.1" || host === "localhost" || host === "::1";
70
+ }
71
+ var LAB_KEYS = [LAB_FORK_CONTEXT_KEY, LAB_ACCOUNT_CONTEXT_KEY];
72
+ function hasLabKeys(context) {
73
+ if (!context || typeof context !== "object" || Array.isArray(context)) return false;
74
+ return LAB_KEYS.some((key) => key in context);
75
+ }
76
+ function withoutLabKeys(context) {
77
+ if (!context || typeof context !== "object" || Array.isArray(context)) return [];
78
+ const others = Object.fromEntries(
79
+ Object.entries(context).filter(([key]) => !LAB_KEYS.includes(key))
80
+ );
81
+ return Object.keys(others).length > 0 ? others : [];
82
+ }
83
+ export {
84
+ labChannel,
85
+ labOnMessage
86
+ };
@@ -0,0 +1,82 @@
1
+ // src/fork.ts
2
+ var DEFAULT_CONNECTOR_URL = "http://127.0.0.1:2100";
3
+ var LAB_FORK_CONTEXT_KEY = "labFork";
4
+ var LAB_FORK_ATTRIBUTE = "labForkId";
5
+ var REPLAY_PROVIDER = "lab-replay";
6
+ function connectorUrl(explicit) {
7
+ return (explicit ?? process.env.LAB_CONNECTOR_URL ?? DEFAULT_CONNECTOR_URL).replace(/\/+$/, "");
8
+ }
9
+ function forkIdFromContext(context) {
10
+ if (!context || typeof context !== "object" || Array.isArray(context)) return void 0;
11
+ const fork = context[LAB_FORK_CONTEXT_KEY];
12
+ if (!fork || typeof fork !== "object") return void 0;
13
+ const id = fork.id;
14
+ return typeof id === "string" && id.length > 0 ? id : void 0;
15
+ }
16
+ var TTL_MS = 10 * 60 * 1e3;
17
+ var MAX_CACHED = 50;
18
+ var cache = /* @__PURE__ */ new Map();
19
+ function fetchForkPlan(forkId, baseUrl) {
20
+ const hit = cache.get(forkId);
21
+ if (hit && Date.now() - hit.at < TTL_MS) return hit.plan;
22
+ const plan = load(forkId, connectorUrl(baseUrl));
23
+ cache.set(forkId, { at: Date.now(), plan });
24
+ plan.catch(() => cache.delete(forkId));
25
+ if (cache.size > MAX_CACHED) {
26
+ const oldest = cache.keys().next().value;
27
+ if (oldest !== void 0) cache.delete(oldest);
28
+ }
29
+ return plan;
30
+ }
31
+ async function load(forkId, base) {
32
+ let response;
33
+ try {
34
+ response = await fetch(`${base}/forks/${encodeURIComponent(forkId)}`, {
35
+ headers: { accept: "application/json" },
36
+ signal: AbortSignal.timeout(1e4)
37
+ });
38
+ } catch {
39
+ throw new Error(
40
+ `Agent Builder Lab: o conector n\xE3o respondeu em ${base}. Ele est\xE1 rodando (agent-builder-lab connect)?`
41
+ );
42
+ }
43
+ const body = await response.json().catch(() => null);
44
+ if (!response.ok) {
45
+ throw new Error(`Agent Builder Lab: o lab n\xE3o entregou o fork ${forkId} (${response.status}).`);
46
+ }
47
+ return readPlan(body, forkId);
48
+ }
49
+ function readPlan(body, forkId) {
50
+ const root = body ?? {};
51
+ const history = Array.isArray(root.history) ? root.history.flatMap((m) => {
52
+ const e = m ?? {};
53
+ const role = e.role === "user" || e.role === "assistant" ? e.role : void 0;
54
+ const text = typeof e.text === "string" ? e.text : "";
55
+ if (!role || !text.trim()) return [];
56
+ return [{ role, text, ...typeof e.id === "string" && e.id ? { id: e.id } : {} }];
57
+ }) : [];
58
+ const replayRoot = root.replay;
59
+ const steps = Array.isArray(replayRoot?.steps) ? replayRoot.steps : void 0;
60
+ return {
61
+ forkId,
62
+ history,
63
+ ...steps ? {
64
+ replay: {
65
+ modelId: typeof replayRoot?.modelId === "string" ? replayRoot.modelId : void 0,
66
+ steps: steps.map((s) => ({
67
+ text: typeof s?.text === "string" ? s.text : void 0,
68
+ toolCalls: Array.isArray(s?.toolCalls) ? s.toolCalls : [],
69
+ usage: s?.usage
70
+ }))
71
+ }
72
+ } : {}
73
+ };
74
+ }
75
+
76
+ export {
77
+ LAB_FORK_CONTEXT_KEY,
78
+ LAB_FORK_ATTRIBUTE,
79
+ REPLAY_PROVIDER,
80
+ forkIdFromContext,
81
+ fetchForkPlan
82
+ };
@@ -0,0 +1,80 @@
1
+ // src/authorization.ts
2
+ var LAB_ACCOUNT_CONTEXT_KEY = "labAccount";
3
+ var LAB_ACCOUNT_ATTRIBUTE = "labAccount";
4
+ var isStrings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
5
+ function readLabAccount(value) {
6
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
7
+ const v = value;
8
+ if (v.v !== 1 || typeof v.id !== "string" || !v.id || !isStrings(v.grants)) return void 0;
9
+ const labels = {};
10
+ if (v.labels && typeof v.labels === "object" && !Array.isArray(v.labels)) {
11
+ for (const [key, label] of Object.entries(v.labels)) {
12
+ if (typeof label === "string") labels[key] = label;
13
+ }
14
+ }
15
+ return {
16
+ v: 1,
17
+ id: v.id,
18
+ name: typeof v.name === "string" && v.name ? v.name : v.id,
19
+ role: typeof v.role === "string" ? v.role : "",
20
+ roleName: typeof v.roleName === "string" && v.roleName ? v.roleName : String(v.role ?? ""),
21
+ grants: v.grants,
22
+ labels,
23
+ ...typeof v.patientId === "string" && v.patientId ? { patientId: v.patientId } : {}
24
+ };
25
+ }
26
+ function labAccountFromContext(context) {
27
+ if (!context || typeof context !== "object" || Array.isArray(context)) return void 0;
28
+ return readLabAccount(context[LAB_ACCOUNT_CONTEXT_KEY]);
29
+ }
30
+ function labAccountFromAuth(auth) {
31
+ const raw = auth?.attributes?.[LAB_ACCOUNT_ATTRIBUTE];
32
+ if (typeof raw !== "string") return void 0;
33
+ try {
34
+ return readLabAccount(JSON.parse(raw));
35
+ } catch {
36
+ return void 0;
37
+ }
38
+ }
39
+ function labAccountOf(ctx) {
40
+ return labAccountFromAuth(ctx.session.auth.current);
41
+ }
42
+ function labDenialReason(account, resource) {
43
+ const label = account.labels[resource] ?? resource.replace(/^[a-z]+:/, "");
44
+ return `403 Proibido: ${account.name} (${account.roleName}) n\xE3o tem acesso a ${label} (${resource}). O harness negou antes de executar.`;
45
+ }
46
+ var dataId = (name) => name.includes(":") ? name : `data:${name}`;
47
+ function requiredResources(toolName, input, options = {}) {
48
+ const needTool = options.tool ?? !options.agent;
49
+ const data = typeof options.data === "function" ? options.data(input) : options.data ?? [];
50
+ return [
51
+ ...options.agent ? [`agent:${options.agent}`] : [],
52
+ ...needTool ? [`tool:${toolName}`] : [],
53
+ ...data.map(dataId)
54
+ ];
55
+ }
56
+ function labAuthorization(options = {}) {
57
+ return (ctx) => {
58
+ const account = labAccountOf(ctx);
59
+ if (account) {
60
+ const input = ctx.toolInput;
61
+ const missing = requiredResources(ctx.toolName, input, options).find(
62
+ (id) => !account.grants.includes(id)
63
+ );
64
+ if (missing) return { type: "denied", reason: labDenialReason(account, missing) };
65
+ }
66
+ return options.next ? options.next(ctx) : "not-applicable";
67
+ };
68
+ }
69
+
70
+ export {
71
+ LAB_ACCOUNT_CONTEXT_KEY,
72
+ LAB_ACCOUNT_ATTRIBUTE,
73
+ readLabAccount,
74
+ labAccountFromContext,
75
+ labAccountFromAuth,
76
+ labAccountOf,
77
+ labDenialReason,
78
+ requiredResources,
79
+ labAuthorization
80
+ };
@@ -0,0 +1,128 @@
1
+ import { InstrumentationProvider } from 'eve/instrumentation';
2
+
3
+ /** A tool call do agente pai que abriu uma sessão de subagente. */
4
+ interface KitParentLineage {
5
+ sessionId: string;
6
+ turnId: string;
7
+ callId: string;
8
+ subagentName?: string;
9
+ }
10
+ /** Tokens de uma chamada ao modelo, como o eve informa (campo ausente quando o provedor omite). */
11
+ interface KitUsage {
12
+ inputTokens?: number;
13
+ outputTokens?: number;
14
+ inputTokenDetails?: {
15
+ cacheReadTokens?: number;
16
+ cacheWriteTokens?: number;
17
+ };
18
+ costUsd?: number;
19
+ }
20
+ interface KitError {
21
+ name?: string;
22
+ message: string;
23
+ }
24
+ /** Identidade comum a todo evento, para o lab casar com o stream NDJSON do eve. */
25
+ interface KitEventBase {
26
+ /** Versão deste formato. */
27
+ v: 1;
28
+ /** Liga o início ao fim da mesma operação; estável entre replays do eve. */
29
+ idempotencyKey: string;
30
+ /** Ordem de envio neste processo do agente. */
31
+ seq: number;
32
+ /** Quando a instrumentação recebeu o evento (ISO 8601). */
33
+ at: string;
34
+ sessionId: string;
35
+ rootSessionId?: string;
36
+ turnId: string;
37
+ stepIndex: number;
38
+ /** Tentativa do step (retentativas do mesmo step têm o mesmo `stepIndex`). */
39
+ attemptIndex: number;
40
+ /** Nome do agente dono da sessão (o raiz ou o subagente), quando o kit viu a sessão começar. */
41
+ agentName?: string;
42
+ /** Só em sessões de subagente. */
43
+ parentLineage?: KitParentLineage;
44
+ }
45
+ interface KitModelCallStarted extends KitEventBase {
46
+ type: "model.call.started";
47
+ model: {
48
+ modelId: string;
49
+ provider: string;
50
+ };
51
+ /** O payload final enviado ao modelo. Ausente se a política de trace não liberar entradas. */
52
+ input?: {
53
+ instructions?: unknown;
54
+ messages: unknown[];
55
+ tools?: unknown[];
56
+ };
57
+ }
58
+ interface KitModelCallCompleted extends KitEventBase {
59
+ type: "model.call.completed";
60
+ finishReason: string;
61
+ usage: KitUsage;
62
+ responseModelId?: string;
63
+ /** Texto, reasoning e tool calls da resposta. Ausente se a política não liberar saídas. */
64
+ content?: unknown[];
65
+ }
66
+ interface KitModelCallFailed extends KitEventBase {
67
+ type: "model.call.failed";
68
+ error?: KitError;
69
+ }
70
+ interface KitToolCallStarted extends KitEventBase {
71
+ type: "tool.call.started";
72
+ callId: string;
73
+ toolName: string;
74
+ /** "tool-call", "subagent-call", "remote-agent-call" ou "load-skill". */
75
+ kind?: string;
76
+ input: unknown;
77
+ startedAtMs?: number;
78
+ }
79
+ interface KitToolCallCompleted extends KitEventBase {
80
+ type: "tool.call.completed";
81
+ /** Copiados do início da mesma operação; o eve não os repete no evento final. */
82
+ callId?: string;
83
+ toolName?: string;
84
+ outcome?: string;
85
+ output: {
86
+ type: "result";
87
+ output?: unknown;
88
+ } | {
89
+ type: "error";
90
+ error?: unknown;
91
+ };
92
+ durationMs?: number;
93
+ completedAtMs?: number;
94
+ /** Em chamadas a subagente, o uso do turno filho. */
95
+ usage?: KitUsage;
96
+ }
97
+ interface KitToolCallFailed extends KitEventBase {
98
+ type: "tool.call.failed";
99
+ callId?: string;
100
+ toolName?: string;
101
+ /** "failed", "rejected", "cancelled" ou "abandoned". */
102
+ outcome?: string;
103
+ errorCode?: string;
104
+ error?: KitError;
105
+ completedAtMs?: number;
106
+ usage?: KitUsage;
107
+ }
108
+ type KitEvent = KitModelCallStarted | KitModelCallCompleted | KitModelCallFailed | KitToolCallStarted | KitToolCallCompleted | KitToolCallFailed;
109
+ type KitEventType = KitEvent["type"];
110
+
111
+ declare const DEFAULT_KIT_URL = "http://127.0.0.1:2100/kit";
112
+ interface LabInstrumentationOptions {
113
+ /** Entrada local do conector. Padrão: `LAB_KIT_URL` ou http://127.0.0.1:2100/kit. */
114
+ url?: string;
115
+ /** Tempo máximo de cada envio, em ms. */
116
+ timeoutMs?: number;
117
+ /** Manda também as saídas (resposta do modelo, resultado das tools). Padrão: sim. */
118
+ recordOutputs?: boolean;
119
+ }
120
+ /**
121
+ * Instrumentação do Agent Builder Lab para `agent/instrumentation/lab.ts`. Liga a captura das
122
+ * entradas e manda cada chamada ao modelo e cada tool call ao conector, com a identidade da
123
+ * sessão, do turno e do step para o lab casar com o stream. Se o conector não estiver ouvindo,
124
+ * os eventos são descartados sem afetar o agente.
125
+ */
126
+ declare function labInstrumentation(options?: LabInstrumentationOptions): InstrumentationProvider;
127
+
128
+ export { DEFAULT_KIT_URL, type KitError, type KitEvent, type KitEventBase, type KitEventType, type KitModelCallCompleted, type KitModelCallFailed, type KitModelCallStarted, type KitParentLineage, type KitToolCallCompleted, type KitToolCallFailed, type KitToolCallStarted, type KitUsage, type LabInstrumentationOptions, labInstrumentation };
package/dist/index.js ADDED
@@ -0,0 +1,238 @@
1
+ // src/index.ts
2
+ import {
3
+ defineInstrumentation
4
+ } from "eve/instrumentation";
5
+
6
+ // src/media.ts
7
+ var DATA_URL = /^data:[^,]*;base64,/;
8
+ var MEDIA_INLINE_LIMIT = 1024;
9
+ function withoutInlineMedia(value, limit = MEDIA_INLINE_LIMIT) {
10
+ return strip(value, limit);
11
+ }
12
+ function strip(value, limit) {
13
+ if (typeof value === "string") {
14
+ if (value.length <= limit || !DATA_URL.test(value)) return value;
15
+ const payload = value.slice(value.indexOf(",") + 1);
16
+ return `[${Math.floor(payload.length * 3 / 4)} bytes]`;
17
+ }
18
+ if (Array.isArray(value)) {
19
+ let changed = false;
20
+ const out = value.map((item) => {
21
+ const next = strip(item, limit);
22
+ if (next !== item) changed = true;
23
+ return next;
24
+ });
25
+ return changed ? out : value;
26
+ }
27
+ if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
28
+ let changed = false;
29
+ const out = {};
30
+ for (const [key, item] of Object.entries(value)) {
31
+ const next = strip(item, limit);
32
+ if (next !== item) changed = true;
33
+ out[key] = next;
34
+ }
35
+ return changed ? out : value;
36
+ }
37
+ return value;
38
+ }
39
+
40
+ // src/sender.ts
41
+ function createSender({ url, timeoutMs, maxPending }) {
42
+ let tail = Promise.resolve();
43
+ let pending = 0;
44
+ return {
45
+ send(event) {
46
+ if (pending >= maxPending) return;
47
+ pending += 1;
48
+ tail = tail.then(() => post(url, event, timeoutMs)).finally(() => {
49
+ pending -= 1;
50
+ });
51
+ },
52
+ async flush(limitMs) {
53
+ await Promise.race([tail, new Promise((resolve) => setTimeout(resolve, limitMs).unref())]);
54
+ }
55
+ };
56
+ }
57
+ async function post(url, event, timeoutMs) {
58
+ try {
59
+ const response = await fetch(url, {
60
+ method: "POST",
61
+ headers: { "content-type": "application/json" },
62
+ body: JSON.stringify(event, jsonSafe),
63
+ signal: AbortSignal.timeout(timeoutMs)
64
+ });
65
+ await response.body?.cancel();
66
+ } catch {
67
+ }
68
+ }
69
+ function jsonSafe(_key, value) {
70
+ if (value instanceof Uint8Array) return `[${value.byteLength} bytes]`;
71
+ if (value instanceof ArrayBuffer) return `[${value.byteLength} bytes]`;
72
+ if (value instanceof URL) return value.href;
73
+ if (typeof value === "bigint") return value.toString();
74
+ return value;
75
+ }
76
+
77
+ // src/index.ts
78
+ var DEFAULT_KIT_URL = "http://127.0.0.1:2100/kit";
79
+ var MAX_SESSIONS = 500;
80
+ function labInstrumentation(options = {}) {
81
+ const url = options.url ?? process.env.LAB_KIT_URL ?? DEFAULT_KIT_URL;
82
+ const recordOutputs = options.recordOutputs ?? true;
83
+ const sender = createSender({ url, timeoutMs: options.timeoutMs ?? 1e3, maxPending: 1e3 });
84
+ const sessions = /* @__PURE__ */ new Map();
85
+ let seq = 0;
86
+ function remember(sessionId, info) {
87
+ const known = sessions.get(sessionId);
88
+ sessions.delete(sessionId);
89
+ sessions.set(sessionId, {
90
+ agentName: info.agentName ?? known?.agentName,
91
+ parentLineage: info.parentLineage ? lineage(info.parentLineage) : known?.parentLineage
92
+ });
93
+ if (sessions.size > MAX_SESSIONS) {
94
+ const oldest = sessions.keys().next().value;
95
+ if (oldest !== void 0) sessions.delete(oldest);
96
+ }
97
+ }
98
+ function base(idempotencyKey, scope) {
99
+ const info = sessions.get(scope.sessionId);
100
+ seq += 1;
101
+ return {
102
+ v: 1,
103
+ idempotencyKey,
104
+ seq,
105
+ at: (/* @__PURE__ */ new Date()).toISOString(),
106
+ sessionId: scope.sessionId,
107
+ rootSessionId: scope.rootSessionId,
108
+ turnId: scope.turnId,
109
+ stepIndex: scope.stepIndex,
110
+ attemptIndex: scope.attemptIndex,
111
+ agentName: info?.agentName,
112
+ parentLineage: info?.parentLineage
113
+ };
114
+ }
115
+ function send(event) {
116
+ sender.send(event);
117
+ }
118
+ return defineInstrumentation({
119
+ tracePolicy: () => ({ emit: true, recordInputs: true, recordOutputs }),
120
+ events: {
121
+ "session.started": (event) => {
122
+ remember(event.sessionId, {
123
+ agentName: event.agentName,
124
+ parentLineage: event.parentLineage
125
+ });
126
+ },
127
+ "turn.started": (event) => {
128
+ remember(event.sessionId, {
129
+ agentName: event.agentName,
130
+ parentLineage: event.parentLineage
131
+ });
132
+ },
133
+ "model.call.started": (event) => {
134
+ send({
135
+ ...base(event.idempotencyKey, event.scope),
136
+ type: "model.call.started",
137
+ model: { modelId: event.model.modelId, provider: event.model.provider },
138
+ input: event.input && {
139
+ instructions: event.input.instructions,
140
+ messages: [...event.input.messages],
141
+ tools: event.input.tools && [...event.input.tools]
142
+ }
143
+ });
144
+ },
145
+ "model.call.completed": (event) => {
146
+ send({
147
+ ...base(event.idempotencyKey, event.scope),
148
+ type: "model.call.completed",
149
+ finishReason: event.finishReason,
150
+ usage: usage(event.usage),
151
+ responseModelId: event.responseModelId,
152
+ content: event.content && [...event.content]
153
+ });
154
+ },
155
+ "model.call.failed": (event) => {
156
+ send({
157
+ ...base(event.idempotencyKey, event.scope),
158
+ type: "model.call.failed",
159
+ error: error(event.error)
160
+ });
161
+ },
162
+ "tool.call.started": (event, ctx) => {
163
+ ctx.state.set({ callId: event.callId, toolName: event.toolName });
164
+ send({
165
+ ...base(event.idempotencyKey, event.scope),
166
+ type: "tool.call.started",
167
+ callId: event.callId,
168
+ toolName: event.toolName,
169
+ kind: event.kind,
170
+ input: event.input,
171
+ startedAtMs: event.startedAtMs
172
+ });
173
+ },
174
+ "tool.call.completed": (event, ctx) => {
175
+ const started = ctx.state.get();
176
+ send({
177
+ ...base(event.idempotencyKey, event.scope),
178
+ type: "tool.call.completed",
179
+ callId: started?.callId,
180
+ toolName: started?.toolName,
181
+ outcome: event.outcome,
182
+ // Prévias de imagem (data URLs) já chegam ao lab pelo stream do eve: aqui, só o tamanho.
183
+ output: withoutInlineMedia(event.output),
184
+ durationMs: event.durationMs,
185
+ completedAtMs: event.completedAtMs,
186
+ usage: event.usage && usage(event.usage)
187
+ });
188
+ },
189
+ "tool.call.failed": (event, ctx) => {
190
+ const started = ctx.state.get();
191
+ send({
192
+ ...base(event.idempotencyKey, event.scope),
193
+ type: "tool.call.failed",
194
+ callId: started?.callId,
195
+ toolName: started?.toolName,
196
+ outcome: event.outcome,
197
+ errorCode: event.errorCode,
198
+ error: error(event.error),
199
+ completedAtMs: event.completedAtMs,
200
+ usage: event.usage && usage(event.usage)
201
+ });
202
+ }
203
+ },
204
+ flush: () => sender.flush(2e3)
205
+ });
206
+ }
207
+ function lineage(value) {
208
+ return {
209
+ sessionId: value.sessionId,
210
+ turnId: value.turnId,
211
+ callId: value.callId,
212
+ subagentName: value.subagentName
213
+ };
214
+ }
215
+ function usage(value) {
216
+ return {
217
+ inputTokens: value.inputTokens,
218
+ outputTokens: value.outputTokens,
219
+ inputTokenDetails: value.inputTokenDetails && { ...value.inputTokenDetails },
220
+ costUsd: value.costUsd
221
+ };
222
+ }
223
+ function error(value) {
224
+ if (value === void 0 || value === null) return void 0;
225
+ if (value instanceof Error) return { name: value.name, message: value.message };
226
+ if (typeof value === "object" && "message" in value) {
227
+ const record = value;
228
+ return {
229
+ name: typeof record.name === "string" ? record.name : void 0,
230
+ message: String(record.message)
231
+ };
232
+ }
233
+ return { message: String(value) };
234
+ }
235
+ export {
236
+ DEFAULT_KIT_URL,
237
+ labInstrumentation
238
+ };
@@ -0,0 +1,36 @@
1
+ import * as eve from 'eve';
2
+ import { AgentStaticModelDefinition, DynamicResolveContext } from 'eve';
3
+
4
+ /** O modelo de verdade: fixo ou escolhido a cada step, como num `defineDynamic`. */
5
+ type LabModelChoice = AgentStaticModelDefinition | {
6
+ model: AgentStaticModelDefinition;
7
+ modelContextWindowTokens?: number;
8
+ reasoning?: "provider-default" | "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
9
+ };
10
+ type LabModelResolver = (event: unknown, ctx: DynamicResolveContext) => LabModelChoice | Promise<LabModelChoice>;
11
+ interface LabReplayOptions {
12
+ /** Porta local do conector. Padrão: `LAB_CONNECTOR_URL` ou http://127.0.0.1:2100. */
13
+ connectorUrl?: string;
14
+ /**
15
+ * Janela informada ao eve nos steps repetidos (o `mockModel` não está no catálogo do AI
16
+ * Gateway, e o eve recusa um modelo dinâmico sem janela). Padrão: 1 milhão de tokens.
17
+ */
18
+ replayContextWindowTokens?: number;
19
+ }
20
+ /**
21
+ * Modelo do agente com replay para o Agent Builder Lab, para o `model` do `agent.ts`:
22
+ *
23
+ * ```ts
24
+ * import { labReplayModel } from "@agent-builder-lab/eve-kit/replay";
25
+ * export default defineAgent({ model: labReplayModel("anthropic/claude-sonnet-5.5") });
26
+ * ```
27
+ *
28
+ * Fora de um fork do lab, devolve o seu modelo em todo step. Num fork (o canal do lab marca o
29
+ * turno com o id), repete os steps gravados antes da volta editada, inclusive ela, com um
30
+ * `mockModel`: mesmo texto, mesmas tool calls e mesmos ids. Assim as voltas anteriores saem
31
+ * iguais às originais e o modelo de verdade só entra depois do ponto editado. O `modelId` desses
32
+ * steps é `lab-replay/<modelo original>`.
33
+ */
34
+ declare function labReplayModel(model: LabModelChoice | LabModelResolver, options?: LabReplayOptions): eve.DynamicSentinel<LabModelChoice>;
35
+
36
+ export { type LabModelChoice, type LabModelResolver, type LabReplayOptions, labReplayModel };
package/dist/replay.js ADDED
@@ -0,0 +1,50 @@
1
+ import {
2
+ LAB_FORK_ATTRIBUTE,
3
+ REPLAY_PROVIDER,
4
+ fetchForkPlan
5
+ } from "./chunk-3IQOFBSQ.js";
6
+
7
+ // src/replay.ts
8
+ import { defineDynamic } from "eve";
9
+ import { mockModel } from "eve/evals";
10
+ function labReplayModel(model, options = {}) {
11
+ const real = (event, ctx) => typeof model === "function" ? model(event, ctx) : model;
12
+ return defineDynamic({
13
+ events: {
14
+ "step.started": async (event, ctx) => {
15
+ const forkId = ctx.session.auth.current?.attributes[LAB_FORK_ATTRIBUTE];
16
+ if (typeof forkId !== "string") return real(event, ctx);
17
+ const plan = await fetchForkPlan(forkId, options.connectorUrl).catch(() => void 0);
18
+ const step = plan?.replay?.steps[stepIndexOf(event)];
19
+ if (!step) return real(event, ctx);
20
+ return {
21
+ model: replayStep(step, plan?.replay?.modelId),
22
+ modelContextWindowTokens: options.replayContextWindowTokens ?? 1e6
23
+ };
24
+ }
25
+ }
26
+ });
27
+ }
28
+ function stepIndexOf(event) {
29
+ const record = event ?? {};
30
+ const index = record.data?.stepIndex ?? record.stepIndex;
31
+ return typeof index === "number" ? index : -1;
32
+ }
33
+ function replayStep(step, modelId = "modelo") {
34
+ return mockModel({
35
+ provider: REPLAY_PROVIDER,
36
+ modelId,
37
+ respond: () => ({
38
+ text: step.text || void 0,
39
+ toolCalls: step.toolCalls.map((call) => ({
40
+ id: call.id,
41
+ name: call.name,
42
+ input: call.input
43
+ })),
44
+ usage: step.usage
45
+ })
46
+ });
47
+ }
48
+ export {
49
+ labReplayModel
50
+ };
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@agent-builder-lab/eve-kit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Liga um agente eve ao Agent Builder Lab: instrumentação da janela de contexto, canal com history e stubs, replay e autorização por conta de teste.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./authorization": {
12
+ "types": "./dist/authorization.d.ts",
13
+ "import": "./dist/authorization.js"
14
+ },
15
+ "./channel": {
16
+ "types": "./dist/channel.d.ts",
17
+ "import": "./dist/channel.js"
18
+ },
19
+ "./replay": {
20
+ "types": "./dist/replay.d.ts",
21
+ "import": "./dist/replay.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "engines": {
28
+ "node": ">=24"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "peerDependencies": {
34
+ "eve": "^0.83.2"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "24.19.2",
38
+ "eve": "0.83.2",
39
+ "tsup": "8.5.1",
40
+ "typescript": "6.0.3",
41
+ "vitest": "5.0.5",
42
+ "@lab/config": "0.0.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "typecheck": "tsc --noEmit",
47
+ "test": "vitest run"
48
+ }
6
49
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.