@lensmcp/protocol-types 1.25.32 → 1.25.33
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/index.d.ts +3 -0
- package/index.js +1 -1
- package/lib/ai-call-row.d.ts +172 -0
- package/lib/ai-call-row.js +3 -0
- package/lib/ai-call.d.ts +144 -0
- package/lib/ai-call.js +19 -0
- package/lib/ai-playground.d.ts +76 -0
- package/lib/ai-playground.js +1 -0
- package/lib/event-loop.d.ts +48 -0
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ export * from './lib/log-query.js';
|
|
|
17
17
|
export * from './lib/error-dismissal.js';
|
|
18
18
|
export * from './lib/ai-content.js';
|
|
19
19
|
export * from './lib/http-exchange.js';
|
|
20
|
+
export * from './lib/ai-call.js';
|
|
21
|
+
export * from './lib/ai-call-row.js';
|
|
22
|
+
export * from './lib/ai-playground.js';
|
|
20
23
|
export * from './lib/pod-memory.js';
|
|
21
24
|
export * from './lib/perf-rules.js';
|
|
22
25
|
export * from './lib/sources.js';
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export*from"./lib/schema-version.js";export*from"./lib/source-location.js";export*from"./lib/context.js";export*from"./lib/event.js";export*from"./lib/node.js";export*from"./lib/edge.js";export*from"./lib/attributes.js";export*from"./lib/producer-health.js";export*from"./lib/resources.js";export*from"./lib/tokens.js";export*from"./lib/capture-demand.js";export*from"./lib/bounded-clone.js";export*from"./lib/bounded-map.js";export*from"./lib/flood-guard.js";export*from"./lib/dashboard.js";export*from"./lib/log-query.js";export*from"./lib/error-dismissal.js";export*from"./lib/ai-content.js";export*from"./lib/http-exchange.js";export*from"./lib/pod-memory.js";export*from"./lib/perf-rules.js";export*from"./lib/sources.js";export*from"./lib/page-visit.js";export*from"./lib/replay.js";export*from"./lib/event-loop.js";export*from"./lib/workspace-actions.js";
|
|
1
|
+
"use strict";export*from"./lib/schema-version.js";export*from"./lib/source-location.js";export*from"./lib/context.js";export*from"./lib/event.js";export*from"./lib/node.js";export*from"./lib/edge.js";export*from"./lib/attributes.js";export*from"./lib/producer-health.js";export*from"./lib/resources.js";export*from"./lib/tokens.js";export*from"./lib/capture-demand.js";export*from"./lib/bounded-clone.js";export*from"./lib/bounded-map.js";export*from"./lib/flood-guard.js";export*from"./lib/dashboard.js";export*from"./lib/log-query.js";export*from"./lib/error-dismissal.js";export*from"./lib/ai-content.js";export*from"./lib/http-exchange.js";export*from"./lib/ai-call.js";export*from"./lib/ai-call-row.js";export*from"./lib/ai-playground.js";export*from"./lib/pod-memory.js";export*from"./lib/perf-rules.js";export*from"./lib/sources.js";export*from"./lib/page-visit.js";export*from"./lib/replay.js";export*from"./lib/event-loop.js";export*from"./lib/workspace-actions.js";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { HttpExchange } from './http-exchange.js';
|
|
2
|
+
import { type AiApi, type AiShape } from './ai-call.js';
|
|
3
|
+
/**
|
|
4
|
+
* One AI provider's call as a line of the AI calls page — and what it belongs to: the agent that made it, the run
|
|
5
|
+
* (one turn of an agent's tool loop, its rounds) it is part of, the action it was for, its flow.
|
|
6
|
+
*
|
|
7
|
+
* Names come from what the app said, in order: its own labels (OpenAI `metadata` — FoodGuard's conductor sends
|
|
8
|
+
* `agent`, `run`, `act`, `round`, `attempt`), the `x-lensmcp-agent` / `-run` / `-action` headers, else what the call
|
|
9
|
+
* itself says — the system prompt's title (`# Critical Limits Analyst — …`, `You are a compliance gate analyst …`),
|
|
10
|
+
* the structured answer's schema (`product_flow`), the tools' MCP server (`conductor__…`) — else the service. A run the
|
|
11
|
+
* app did not name is found from the calls themselves: one continuing another's answer (`previous_response_id`), or
|
|
12
|
+
* carrying the results of the tools another's answer called. What a person renames or merges ({@link AiNames}) wins.
|
|
13
|
+
*
|
|
14
|
+
* Pure, and shared: the server indexes the kept exchanges with it, the page groups by it.
|
|
15
|
+
*/
|
|
16
|
+
/** Where a name came from: renamed by a person, said by the app, or read off the call. */
|
|
17
|
+
export type AiNameSource = 'renamed' | 'metadata' | 'header' | 'prompt' | 'schema' | 'tools' | 'service';
|
|
18
|
+
/** How the call's run was found: the app named it, it continues an answer, it carries tool results — or it is alone. */
|
|
19
|
+
export type AiRunSource = 'metadata' | 'header' | 'chain' | 'tools' | 'alone';
|
|
20
|
+
export interface AiNamed {
|
|
21
|
+
/** Stable across the calls it names (`agent:onboarding-chat-conductor`); what a rename or a merge is keyed by. */
|
|
22
|
+
key: string;
|
|
23
|
+
name: string;
|
|
24
|
+
from: AiNameSource;
|
|
25
|
+
}
|
|
26
|
+
export interface AiCallRow {
|
|
27
|
+
/** The exchange's id (`hx_…`): the call's page is `/ai/calls/<id>`. */
|
|
28
|
+
id: string;
|
|
29
|
+
at: number;
|
|
30
|
+
durationMs?: number;
|
|
31
|
+
status?: number;
|
|
32
|
+
/** The provider said it failed, or no answer came. */
|
|
33
|
+
failed?: true;
|
|
34
|
+
service: string;
|
|
35
|
+
flowId?: string;
|
|
36
|
+
requestId?: string;
|
|
37
|
+
provider: string;
|
|
38
|
+
host: string;
|
|
39
|
+
api: AiApi;
|
|
40
|
+
shape: AiShape;
|
|
41
|
+
model?: string;
|
|
42
|
+
streamed?: true;
|
|
43
|
+
tokens?: {
|
|
44
|
+
input?: number;
|
|
45
|
+
cached?: number;
|
|
46
|
+
output?: number;
|
|
47
|
+
reasoning?: number;
|
|
48
|
+
};
|
|
49
|
+
/** What it cost, estimated from its tokens by the lens's price table (the server's) — none for a model it has no price for. */
|
|
50
|
+
costUsd?: number;
|
|
51
|
+
agent: AiNamed;
|
|
52
|
+
run: {
|
|
53
|
+
key: string;
|
|
54
|
+
from: AiRunSource;
|
|
55
|
+
};
|
|
56
|
+
/** Its place in its run, from 1 — set when the rows are linked ({@link linkAiRuns}). */
|
|
57
|
+
step?: number;
|
|
58
|
+
/** The app's own round and attempt (`metadata.round`, `metadata.attempt`). */
|
|
59
|
+
round?: number;
|
|
60
|
+
attempt?: number;
|
|
61
|
+
action?: AiNamed;
|
|
62
|
+
/** The structured answer's schema name. */
|
|
63
|
+
schema?: string;
|
|
64
|
+
/** The tools it was offered, by name (at most {@link MAX_TOOLS}). */
|
|
65
|
+
tools?: string[];
|
|
66
|
+
/** The tools its answer called, by name. */
|
|
67
|
+
called?: string[];
|
|
68
|
+
/** The ids of the tool calls in its answer: the next call of its run carries their results. */
|
|
69
|
+
callIds?: string[];
|
|
70
|
+
/** The ids of tool calls whose results it carries: what joins it to the call that asked for them. */
|
|
71
|
+
answers?: string[];
|
|
72
|
+
responseId?: string;
|
|
73
|
+
previousResponseId?: string;
|
|
74
|
+
/** The app's own labels, as strings (at most {@link MAX_META} of them). */
|
|
75
|
+
meta?: Record<string, string>;
|
|
76
|
+
/** A body was cut by the capture: some of the call is not readable. */
|
|
77
|
+
cut?: true;
|
|
78
|
+
/** What it was last given and what it answered, a line each — only where content may be shown. */
|
|
79
|
+
asked?: string;
|
|
80
|
+
said?: string;
|
|
81
|
+
}
|
|
82
|
+
/** A person's names for groups, kept in `.lensmcp/ai-names/names.json` and committed: the team sees the same names. */
|
|
83
|
+
export interface AiNames {
|
|
84
|
+
version: 1;
|
|
85
|
+
/** A group's key (`agent:…`, `action:…`) → its name. */
|
|
86
|
+
names: Record<string, string>;
|
|
87
|
+
/** A group's key → the key it was merged into: its calls are shown as that group's. */
|
|
88
|
+
merge: Record<string, string>;
|
|
89
|
+
}
|
|
90
|
+
export declare const EMPTY_AI_NAMES: AiNames;
|
|
91
|
+
/** `GET /api/ai/calls?from=&to=&rev=` — the AI calls the lens kept, in a window, newest first, named and in their runs. */
|
|
92
|
+
export interface AiCallsView {
|
|
93
|
+
/** Changes when a call, or a name, does: sent back as `rev`, an unchanged answer is `{ same: true }`. */
|
|
94
|
+
rev: string;
|
|
95
|
+
at: number;
|
|
96
|
+
rows: AiCallRow[];
|
|
97
|
+
/** Calls kept in all — the window may hold fewer — and when the first of them was made. */
|
|
98
|
+
total: number;
|
|
99
|
+
oldest?: number;
|
|
100
|
+
/** The kept calls are still being read for the first time: how far. */
|
|
101
|
+
reading?: {
|
|
102
|
+
done: number;
|
|
103
|
+
total: number;
|
|
104
|
+
};
|
|
105
|
+
names: AiNames;
|
|
106
|
+
/** What was said is in the rows (`asked`, `said`): a loopback-bound dashboard. */
|
|
107
|
+
content: boolean;
|
|
108
|
+
}
|
|
109
|
+
/** `GET /api/ai/call?id=` — one call: its row, the other calls of its run, and the exchange itself. */
|
|
110
|
+
export interface AiCallView {
|
|
111
|
+
row: AiCallRow;
|
|
112
|
+
/** Its run's calls by step, itself among them. */
|
|
113
|
+
run: AiCallRow[];
|
|
114
|
+
exchange?: HttpExchange;
|
|
115
|
+
/** Why the exchange is not here: a dashboard not bound to loopback, or a file no longer kept. */
|
|
116
|
+
withheld?: string;
|
|
117
|
+
names: AiNames;
|
|
118
|
+
}
|
|
119
|
+
/** `POST /api/ai/names` — rename a group (an empty name takes the rename back), merge one into another, or undo a merge. */
|
|
120
|
+
export type AiNamesChange = {
|
|
121
|
+
op: 'rename';
|
|
122
|
+
key: string;
|
|
123
|
+
name: string;
|
|
124
|
+
} | {
|
|
125
|
+
op: 'merge';
|
|
126
|
+
key: string;
|
|
127
|
+
into: string;
|
|
128
|
+
} | {
|
|
129
|
+
op: 'unmerge';
|
|
130
|
+
key: string;
|
|
131
|
+
};
|
|
132
|
+
export declare const MAX_TOOLS = 40;
|
|
133
|
+
export declare const MAX_META = 24;
|
|
134
|
+
/** A key out of a name: lower case, words joined by `-` (`Critical Limits Analyst` → `critical-limits-analyst`). */
|
|
135
|
+
export declare const aiKeyOf: (kind: "agent" | "action", name: string) => string;
|
|
136
|
+
/** A name as a person reads it: `onboarding-chat-conductor` → `Onboarding chat conductor`; one already written for people stays. */
|
|
137
|
+
export declare function humanName(s: string): string;
|
|
138
|
+
/**
|
|
139
|
+
* What a system prompt calls the one it instructs: its heading (`# Critical Limits Analyst — establish …` →
|
|
140
|
+
* `Critical Limits Analyst`), or who it says the model is (`You are a compliance gate analyst for …` →
|
|
141
|
+
* `Compliance gate analyst`). `undefined` when it says neither — its first words are not a name.
|
|
142
|
+
*/
|
|
143
|
+
export declare function promptTitle(text: string): string | undefined;
|
|
144
|
+
/**
|
|
145
|
+
* A prompt that names no one, by how it opens — its first words (`You look at ONE product and say …`): two prompts
|
|
146
|
+
* that open alike are one agent, until a person names or merges it.
|
|
147
|
+
*/
|
|
148
|
+
export declare function promptOpening(text: string): string | undefined;
|
|
149
|
+
/** An action's name without the ids in it: `ask:products:material.source-adoption.01a163b4-…::509c` → `ask:products:material.source-adoption`. */
|
|
150
|
+
export declare function actionName(s: string): string;
|
|
151
|
+
/**
|
|
152
|
+
* The row of one exchange — `undefined` when it is not an AI provider's call. `content: false` leaves out what was
|
|
153
|
+
* said (`asked`, `said`) for a dashboard not bound to loopback, as the Request & response panel does.
|
|
154
|
+
*/
|
|
155
|
+
export declare function aiCallRow(x: HttpExchange, opts: {
|
|
156
|
+
content: boolean;
|
|
157
|
+
}): AiCallRow | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Joins the calls the app did not put in a run to the run they continue — the call whose answer they continue
|
|
160
|
+
* (`previous_response_id`) or whose tool calls they carry the results of — and numbers each run's calls by time
|
|
161
|
+
* (`step`). Rows are changed in place; any order in, the same result.
|
|
162
|
+
*/
|
|
163
|
+
export declare function linkAiRuns(rows: AiCallRow[]): AiCallRow[];
|
|
164
|
+
/** Follows a merge to the group it ended in (a chain of merges, never a loop). */
|
|
165
|
+
export declare function mergedKey(names: AiNames, key: string): string;
|
|
166
|
+
/**
|
|
167
|
+
* A person's renames and merges over the rows: a merged group's calls take the key and name of the group they were
|
|
168
|
+
* merged into; a renamed group its new name (`from: 'renamed'`). Rows are changed in place.
|
|
169
|
+
*/
|
|
170
|
+
export declare function applyAiNames(rows: AiCallRow[], names: AiNames): AiCallRow[];
|
|
171
|
+
/** A names file's content, read leniently: anything not a name or a merge is dropped. */
|
|
172
|
+
export declare function aiNamesOf(v: unknown): AiNames;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";var N=Object.defineProperty;var l=(e,n)=>N(e,"name",{value:n,configurable:!0});var B=Object.defineProperty,i=l((e,n)=>B(e,"name",{value:n,configurable:!0}),"a");import{aiCallOf as _}from"./ai-call.js";export const EMPTY_AI_NAMES={version:1,names:{},merge:{}},MAX_TOOLS=40,MAX_META=24;const R=160,x="unknown service";export const aiKeyOf=i((e,n)=>`${e}:${n.normalize("NFKD").toLowerCase().replace(/[^a-z0-9:.]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"unnamed"}`,"aiKeyOf");export function humanName(e){const n=e.trim();if(/[A-Z]/.test(n)||/ /.test(n))return n;const t=n.replace(/[-_.]+/g," ").trim();return t?t[0].toUpperCase()+t.slice(1):n}l(humanName,"humanName"),i(humanName,"humanName");const d=i((e,n=R)=>{if(!e)return;const t=e.replace(/\s+/g," ").trim();if(t)return t.length>n?`${t.slice(0,n-1)}\u2026`:t},"line");export function promptTitle(e){const n=e.split(`
|
|
2
|
+
`).map(o=>o.trim()).find(Boolean)??"",t=/^#{1,6}\s+(.+)$/.exec(n);if(t){const o=t[1].split(/\s+[—–]\s+|\s+-\s+|:\s/)[0].replace(/[*_`]/g,"").trim();return o?o.slice(0,60):void 0}const r=/^you are (?:an? |the )?(.+?)(?:[.,;:!]|\s+(?:who|that|which|for|in|on|whose|with|and|following|using|working|based|at|to)\b|$)/i.exec(n);if(r){const o=r[1].trim().split(/\s+/);if(o.length>6||/^(?:going|about|given|now|here|responsible|able|allowed|expected|tasked|asked|helping|working|being|not)$/i.test(o[0]))return;const s=o.join(" ");return s[0].toUpperCase()+s.slice(1)}}l(promptTitle,"promptTitle"),i(promptTitle,"promptTitle");export function promptOpening(e){const n=e.split(`
|
|
3
|
+
`).map(r=>r.trim()).find(Boolean);if(!n)return;const t=n.replace(/^[#>*\s-]+/,"").split(/\s+/).filter(Boolean);if(!(t.length<3))return t.length>7?`${t.slice(0,7).join(" ")} \u2026`:t.join(" ")}l(promptOpening,"promptOpening"),i(promptOpening,"promptOpening");export function actionName(e){const n=e.split("::")[0],t=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{8,}|\d{4,})$/i;return n.split(":").map(r=>r.split(".").filter(o=>!t.test(o)).join(".")).filter((r,o,s)=>r||o<s.length-1).join(":").slice(0,120)}l(actionName,"actionName"),i(actionName,"actionName");const h=i((e,n)=>e.request.headers.find(([t])=>t.toLowerCase()===n)?.[1],"header"),I=i(e=>e!==void 0&&/^\d{1,6}$/.test(e.trim())?Number(e.trim()):void 0,"intOf"),k=i((e,...n)=>{for(const t of n)if(e[t]?.trim())return e[t].trim()},"first");function O(e){const n={};for(const t of e.settings)if(t.name.startsWith("metadata.")){if(Object.keys(n).length>=MAX_META)break;n[t.name.slice(9)]=t.value.slice(0,200)}return n}l(O,"A"),i(O,"metaOf");function j(e,n,t){const r=i((p,g)=>({key:aiKeyOf("agent",p),name:humanName(p),from:g}),"named"),o=k(t,"agent","agent_name","agentName","assistant");if(o)return r(o,"metadata");const s=h(e,"x-lensmcp-agent");if(s?.trim())return r(s.trim(),"header");const a=n.system.map(p=>promptTitle(p.text)).find(Boolean);if(a)return r(a,"prompt");const u=n.answer.format?.name;if(u)return r(u,"schema");const f=[...n.tools,...n.answer.toolCalls].map(p=>p.server).find(Boolean);if(f)return r(f,"tools");const m=n.system.map(p=>promptOpening(p.text)).find(Boolean);return m?{key:aiKeyOf("agent",`prompt ${m.replace(/ …$/,"")}`),name:m,from:"prompt"}:r(`${e.service??x} ${n.api==="embeddings"?"embeddings":n.model??n.api}`,"service")}l(j,"T"),i(j,"agentOf");function C(e,n,t){const r=i((a,u)=>({key:aiKeyOf("action",a),name:a,from:u}),"named"),o=k(t,"act","action","task","step","operation");if(o&&actionName(o))return r(actionName(o),"metadata");const s=h(e,"x-lensmcp-action");if(s?.trim())return r(actionName(s.trim()),"header");if(n.answer.format?.name)return r(n.answer.format.name,"schema")}l(C,"_"),i(C,"actionOf");function A(e){if(e.kind==="message")return d(e.text);if(e.kind==="tool")return d(e.output!==void 0?`${e.tool} \u2192 ${e.output}`:`${e.tool}(${e.arguments})`)}l(A,"B"),i(A,"turnLine");function y(e){const n=e.answer;return n.error?d(`error: ${n.error}`):n.refusal?d(`refused: ${n.refusal}`):n.toolCalls.length?d(n.toolCalls.map(t=>`${t.tool}(${t.arguments})`).join(" \xB7 ")):e.embedding?`${e.embedding.vectors??e.embedding.count} vector${(e.embedding.vectors??e.embedding.count)===1?"":"s"}${e.embedding.dimensions?` \xD7 ${e.embedding.dimensions}`:""}`:d(n.text)}l(y,"O"),i(y,"saidLine");export function aiCallRow(e,n){const t=_(e);if(!t||!e.ai)return;const r=O(t),o=new Map(t.settings.map(c=>[c.name,c.value])),s=k(r,"run","run_id","runId"),a=h(e,"x-lensmcp-run")?.trim(),u=t.turns.filter(c=>c.kind==="tool").filter(c=>c.output!==void 0&&c.callId).map(c=>c.callId),f=t.answer.toolCalls.map(c=>c.callId).filter(c=>!!c),m=t.usage,p=m?{...m.input!==void 0?{input:m.input}:{},...m.cached?{cached:m.cached}:{},...m.output!==void 0?{output:m.output}:{},...m.reasoning?{reasoning:m.reasoning}:{}}:void 0,g=C(e,t,r),v=I(r.round),w=I(r.attempt),$=o.get("continues response"),b=[...t.turns].reverse().map(A).find(Boolean),M=t.answer.status==="failed"||!!t.answer.error||(e.status??0)>=400||!!e.error;return{id:e.id,at:e.startedAt,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.status!==void 0?{status:e.status}:{},...M?{failed:!0}:{},service:e.service??x,...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},provider:e.ai.provider,host:e.host,api:t.api,shape:t.shape,...t.model?{model:t.model}:{},...e.response.stream?{streamed:!0}:{},...p&&Object.keys(p).length?{tokens:p}:{},agent:j(e,t,r),run:s?{key:`run:${s}`,from:"metadata"}:a?{key:`run:${a}`,from:"header"}:{key:`run:${e.id}`,from:"alone"},...v!==void 0?{round:v}:{},...w!==void 0?{attempt:w}:{},...g?{action:g}:{},...t.answer.format?.name?{schema:t.answer.format.name}:{},...t.tools.length?{tools:t.tools.slice(0,MAX_TOOLS).map(c=>c.name)}:{},...t.answer.toolCalls.length?{called:t.answer.toolCalls.map(c=>c.name)}:{},...f.length?{callIds:f}:{},...u.length?{answers:u}:{},...t.responseId?{responseId:t.responseId}:{},...$?{previousResponseId:$}:{},...Object.keys(r).length?{meta:r}:{},...t.gaps.some(c=>/kept to its first|cut/.test(c))?{cut:!0}:{},...n.content&&b?{asked:b}:{},...n.content&&y(t)?{said:y(t)}:{}}}l(aiCallRow,"aiCallRow"),i(aiCallRow,"aiCallRow");export function linkAiRuns(e){const n=[...e].sort((s,a)=>s.at-a.at||s.id.localeCompare(a.id)),t=new Map,r=new Map;for(const s of n){if(s.run.from==="alone"){const a=s.previousResponseId?t.get(s.previousResponseId):void 0,u=a?void 0:s.answers?.map(m=>r.get(m)).find(Boolean),f=a??u;if(f){const m=a?"chain":"tools";s.run={key:f.run.key,from:f.run.from==="alone"||f.run.from===m?m:f.run.from},f.run.from==="alone"&&(f.run={key:f.run.key,from:m})}}s.responseId&&t.set(s.responseId,s);for(const a of s.callIds??[])r.set(a,s)}const o=new Map;for(const s of n){const a=(o.get(s.run.key)??0)+1;o.set(s.run.key,a),s.step=a}return e}l(linkAiRuns,"linkAiRuns"),i(linkAiRuns,"linkAiRuns");export function mergedKey(e,n){let t=n;for(let r=0;r<8&&e.merge[t]&&e.merge[t]!==n;r++)t=e.merge[t];return t}l(mergedKey,"mergedKey"),i(mergedKey,"mergedKey");export function applyAiNames(e,n){if(!Object.keys(n.names).length&&!Object.keys(n.merge).length)return e;const t=new Map;for(const o of e)t.has(o.agent.key)||t.set(o.agent.key,o.agent),o.action&&!t.has(o.action.key)&&t.set(o.action.key,o.action);const r=i(o=>{const s=mergedKey(n,o.key),a=n.names[s];if(a)return{key:s,name:a,from:"renamed"};if(s===o.key)return o;const u=t.get(s);return{key:s,name:u?.name??humanName(s.replace(/^[a-z]+:/,"")),from:u?.from??o.from}},"resolve");for(const o of e)o.agent=r(o.agent),o.action&&(o.action=r(o.action));return e}l(applyAiNames,"applyAiNames"),i(applyAiNames,"applyAiNames");export function aiNamesOf(e){const n=e&&typeof e=="object"?e:{},t=i(r=>{const o={};if(r&&typeof r=="object"&&!Array.isArray(r))for(const[s,a]of Object.entries(r))/^(agent|action):/.test(s)&&typeof a=="string"&&a.trim()&&(o[s]=a.trim().slice(0,120));return o},"pick");return{version:1,names:t(n.names),merge:t(n.merge)}}l(aiNamesOf,"aiNamesOf"),i(aiNamesOf,"aiNamesOf");
|
package/lib/ai-call.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { HttpExchange } from './http-exchange.js';
|
|
2
|
+
/**
|
|
3
|
+
* An AI provider's call read for a person (Data › External APIs › Request & response): what the model was told — its
|
|
4
|
+
* system prompt, the context the app put in, the conversation so far with each tool call and what it returned, the
|
|
5
|
+
* tools it was offered — and what it answered: text, a structured decision, the tools it chose to call. Read from the
|
|
6
|
+
* bodies the egress tap kept: OpenAI Responses and Chat Completions, embeddings, Anthropic Messages.
|
|
7
|
+
*
|
|
8
|
+
* A body the capture cut (at 64 KB, before AI calls were kept whole) is read as far as it goes (`parseJsonPrefix`), and
|
|
9
|
+
* says so (`gaps`). A streamed answer is put back together from its events, the way the providers' SDKs do.
|
|
10
|
+
*
|
|
11
|
+
* Pure: run on the exchange the panel already holds — no wire change, nothing more fetched.
|
|
12
|
+
*/
|
|
13
|
+
export type AiShape = 'prompt' | 'chat' | 'tools' | 'decision' | 'embedding';
|
|
14
|
+
export type AiApi = 'responses' | 'chat' | 'embeddings' | 'messages';
|
|
15
|
+
export interface AiCall {
|
|
16
|
+
provider: string;
|
|
17
|
+
api: AiApi;
|
|
18
|
+
/** What kind of call it is, read from its request: one prompt, a conversation, a tool loop, a structured decision, an embedding. */
|
|
19
|
+
shape: AiShape;
|
|
20
|
+
model?: string;
|
|
21
|
+
/** The provider's id of its answer (`resp_…`, `chatcmpl-…`, `msg_…`): what a later call continuing it names. */
|
|
22
|
+
responseId?: string;
|
|
23
|
+
/** How it was asked — reasoning effort, the output cap, tool choice, temperature… — as written. */
|
|
24
|
+
settings: Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
value: string;
|
|
27
|
+
}>;
|
|
28
|
+
/** The system prompt — `instructions`, a system message — in order. */
|
|
29
|
+
system: AiText[];
|
|
30
|
+
/** Everything the model was given after the system prompt, oldest first. */
|
|
31
|
+
turns: AiTurn[];
|
|
32
|
+
tools: AiTool[];
|
|
33
|
+
answer: AiAnswer;
|
|
34
|
+
usage?: AiUsage;
|
|
35
|
+
embedding?: {
|
|
36
|
+
inputs: string[];
|
|
37
|
+
count: number;
|
|
38
|
+
dimensions?: number;
|
|
39
|
+
vectors?: number;
|
|
40
|
+
};
|
|
41
|
+
/** What could not be read, in words: a body the capture cut, a stream whose events were not kept. */
|
|
42
|
+
gaps: string[];
|
|
43
|
+
}
|
|
44
|
+
export interface AiText {
|
|
45
|
+
source: string;
|
|
46
|
+
text: string;
|
|
47
|
+
cut?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export type AiRole = 'user' | 'assistant' | 'developer' | 'system';
|
|
50
|
+
export type AiTurn = {
|
|
51
|
+
kind: 'message';
|
|
52
|
+
role: AiRole;
|
|
53
|
+
text: string;
|
|
54
|
+
attachments?: string[];
|
|
55
|
+
} | ({
|
|
56
|
+
kind: 'tool';
|
|
57
|
+
} & AiToolCall) | {
|
|
58
|
+
kind: 'reasoning';
|
|
59
|
+
text?: string;
|
|
60
|
+
};
|
|
61
|
+
export interface AiToolCall {
|
|
62
|
+
callId?: string;
|
|
63
|
+
/** As the model named it (`conductor__fill_question`). */
|
|
64
|
+
name: string;
|
|
65
|
+
/** An MCP tool's server (`conductor`): a hosted `mcp` tool's label, or the `server__tool` convention's first part. */
|
|
66
|
+
server?: string;
|
|
67
|
+
/** The tool's own name (`fill_question`). */
|
|
68
|
+
tool: string;
|
|
69
|
+
/** What the model sent, as written (JSON for a function tool). */
|
|
70
|
+
arguments: string;
|
|
71
|
+
/** What came back — present when this call's conversation carries it. */
|
|
72
|
+
output?: string;
|
|
73
|
+
error?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface AiTool {
|
|
76
|
+
name: string;
|
|
77
|
+
server?: string;
|
|
78
|
+
tool: string;
|
|
79
|
+
/** `function`, `mcp`, `web_search`… */
|
|
80
|
+
type: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
/** Its input schema, as offered. */
|
|
83
|
+
parameters?: unknown;
|
|
84
|
+
strict?: boolean;
|
|
85
|
+
/** Calls of it in the conversation, and in the answer. */
|
|
86
|
+
called: number;
|
|
87
|
+
}
|
|
88
|
+
export interface AiAnswer {
|
|
89
|
+
/** `completed`, `incomplete`, `failed`… */
|
|
90
|
+
status?: string;
|
|
91
|
+
/** Why it stopped: `stop`, `tool_calls`, `max_output_tokens`, `end_turn`… */
|
|
92
|
+
finish?: string;
|
|
93
|
+
text?: string;
|
|
94
|
+
/** The structured decision, parsed — when one was asked for (a JSON schema, a JSON object). */
|
|
95
|
+
json?: unknown;
|
|
96
|
+
format?: {
|
|
97
|
+
type: string;
|
|
98
|
+
name?: string;
|
|
99
|
+
schema?: unknown;
|
|
100
|
+
strict?: boolean;
|
|
101
|
+
};
|
|
102
|
+
/** The tools it chose to call — the app runs them and answers in its next call. */
|
|
103
|
+
toolCalls: AiToolCall[];
|
|
104
|
+
reasoning?: string;
|
|
105
|
+
refusal?: string;
|
|
106
|
+
error?: string;
|
|
107
|
+
}
|
|
108
|
+
export interface AiUsage {
|
|
109
|
+
input?: number;
|
|
110
|
+
cached?: number;
|
|
111
|
+
output?: number;
|
|
112
|
+
reasoning?: number;
|
|
113
|
+
total?: number;
|
|
114
|
+
}
|
|
115
|
+
/** Where a cut string ends, in what is shown. */
|
|
116
|
+
export declare const CUT_MARK = "\u2026";
|
|
117
|
+
/**
|
|
118
|
+
* JSON that may have been cut anywhere — a body the capture kept to its first 64 KB, a stream event kept to its first
|
|
119
|
+
* 4 KB: what parses of it, closed where it stopped. A string cut in the middle keeps what arrived (marked `…`); a key, a
|
|
120
|
+
* number or a literal cut in the middle is dropped with its member. `undefined` when it is not JSON at all.
|
|
121
|
+
*/
|
|
122
|
+
export declare function parseJsonPrefix(text: string): {
|
|
123
|
+
value: unknown;
|
|
124
|
+
cut: boolean;
|
|
125
|
+
} | undefined;
|
|
126
|
+
/** Which API a call went to, by its path. */
|
|
127
|
+
export declare function aiApiOf(path: string): AiApi | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* A tool's server and its own name. A hosted MCP tool names its server (`server_label`); a client that hands MCP tools to
|
|
130
|
+
* the model as functions names them `server__tool` (`conductor__fill_question`), or `mcp__server__tool`.
|
|
131
|
+
*/
|
|
132
|
+
export declare function splitToolName(name: string, server?: string): {
|
|
133
|
+
server?: string;
|
|
134
|
+
tool: string;
|
|
135
|
+
};
|
|
136
|
+
/** The call, read — or `undefined` when the exchange is not an AI provider's call this reads. */
|
|
137
|
+
export declare function aiCallOf(x: HttpExchange): AiCall | undefined;
|
|
138
|
+
/** How each kind of call reads, for a person. */
|
|
139
|
+
export declare const SHAPE_LABEL: Record<AiShape, string>;
|
|
140
|
+
/** A JSON text pretty — or as it was, when it is not JSON. */
|
|
141
|
+
export declare function prettyJson(text: string): {
|
|
142
|
+
value?: unknown;
|
|
143
|
+
text: string;
|
|
144
|
+
};
|
package/lib/ai-call.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";var se=Object.defineProperty;var v=(t,s)=>se(t,"name",{value:s,configurable:!0});var oe=Object.defineProperty,g=v((t,s)=>oe(t,"name",{value:s,configurable:!0}),"d");const b=g(t=>!!t&&typeof t=="object"&&!Array.isArray(t),"isObj"),e=g(t=>typeof t=="string"?t:void 0,"str"),y=g(t=>typeof t=="number"&&Number.isFinite(t)?t:void 0,"num"),k=g(t=>Array.isArray(t)?t:[],"arr"),c=g(t=>b(t)?t:{},"obj"),A=g(t=>typeof t=="string"?t:t===void 0?"":JSON.stringify(t),"json"),U=g(t=>t>=1024*1024?`${(t/1024/1024).toFixed(1)} MB`:`${Math.round(t/1024)} KB`,"kb");export const CUT_MARK="\u2026";export function parseJsonPrefix(t){try{return{value:JSON.parse(t),cut:!1}}catch{}const s=[];let n=0,l=!1,a=!1,r=!1;const o=t.length,i=g(m=>{const d=s[s.length-1];d&&(d.state="after"),n=m},"ended");for(let m=0;m<o;m++){const d=t[m];if(l){r?r=!1:d==="\\"?r=!0:d==='"'&&(l=!1,a?s[s.length-1].state="colon":i(m+1));continue}if(d===" "||d===`
|
|
2
|
+
`||d==="\r"||d===" ")continue;const f=s[s.length-1];if(d==='"'){l=!0,a=!!f&&f.c==="{"&&f.state==="key";continue}if(d==="{"||d==="["){s.push({c:d,state:d==="{"?"key":"value"}),n=m+1;continue}if(d==="}"||d==="]"){s.pop(),i(m+1);continue}if(d===":"){f&&(f.state="value");continue}if(d===","){f&&(f.state=f.c==="{"?"key":"value");continue}let _=m;for(;_<o&&!`,}]
|
|
3
|
+
\r `.includes(t[_]);)_++;if(_>=o)break;i(_),m=_-1}const u=s.map(m=>m.c==="{"?"}":"]").reverse().join("");if(l&&!a)for(let m=0;m<=6;m++)try{return{value:JSON.parse(`${t.slice(0,o-m)}${CUT_MARK}"${u}`),cut:!0}}catch{}if(n)try{return{value:JSON.parse(`${t.slice(0,n)}${u}`),cut:!0}}catch{return}}v(parseJsonPrefix,"parseJsonPrefix"),g(parseJsonPrefix,"parseJsonPrefix");export function aiApiOf(t){const s=t.split("?")[0].replace(/\/+$/,"");if(/\/responses$/.test(s))return"responses";if(/\/chat\/completions$/.test(s))return"chat";if(/\/embeddings$/.test(s))return"embeddings";if(/\/messages$/.test(s))return"messages"}v(aiApiOf,"aiApiOf"),g(aiApiOf,"aiApiOf");export function splitToolName(t,s){if(s)return{server:s,tool:t};const n=/^(?:mcp__)?([A-Za-z0-9-]+(?:_[A-Za-z0-9-]+)*)__(.+)$/.exec(t);return n?{server:n[1],tool:n[2]}:{tool:t}}v(splitToolName,"splitToolName"),g(splitToolName,"splitToolName");const $=g((t,s,n={})=>({...n.callId?{callId:n.callId}:{},name:t,...splitToolName(t,n.server),arguments:s,...n.output!==void 0?{output:n.output}:{},...n.error?{error:n.error}:{}}),"toolCall");function M(t,s,n){if(!t)return;if(t.omitted==="ai"){n.push(`The ${s==="request"?"prompt":"answer"} was not kept \u2014 an AI provider\u2019s bodies are kept only when asked for: Settings \u203A Features \u203A Capture \u203A AI calls, for the calls made after.`);return}if(t.omitted||t.text===void 0)return;const l=parseJsonPrefix(t.text);if(l)return(l.cut||t.truncated)&&n.push(`The ${s} was kept to its first ${U(t.kept)} of ${U(t.size)} \u2014 read as far as it goes; what follows is not shown (the ${s==="request"?"end of the conversation":"rest of the answer"}, marked ${CUT_MARK}).`),l.value}v(M,"E"),g(M,"readBody");function J(t){if(t.data===void 0||t.data==="[DONE]")return;const s=parseJsonPrefix(t.data);return s&&b(s.value)?{v:s.value,cut:s.cut}:void 0}v(J,"M"),g(J,"eventJson");function I(t){if(typeof t=="string")return{text:t,attachments:[]};const s=[],n=[];for(const l of k(t)){if(typeof l=="string"){s.push(l);continue}const a=c(l),r=e(a.type)??"",o=e(a.text)??e(a.refusal);o!==void 0?s.push(o):/image/.test(r)?n.push(e(a.detail)?`image (${e(a.detail)})`:"image"):/file|document/.test(r)?n.push(`file ${e(a.filename)??e(c(a.source).media_type)??e(a.file_id)??""}`.trim()):/audio/.test(r)?n.push("audio"):r&&n.push(r)}return{text:s.join(`
|
|
4
|
+
|
|
5
|
+
`),attachments:n}}v(I,"I"),g(I,"contentOf");const N=g(t=>typeof t=="string"?t:Array.isArray(t)&&I(t).text||A(t),"outputOf");function C(t,s){const n=[];for(const[a,r]of s){let o=t;for(const u of a.split("."))o=c(o)[u];if(o==null)continue;const i=typeof o=="string"?o:typeof o=="number"||typeof o=="boolean"?String(o):b(o)&&e(o.name)?`${e(o.type)??"tool"} ${e(o.name)}`:b(o)&&e(o.type)?e(o.type):JSON.stringify(o);n.push({name:r,value:i})}const l=c(t.metadata);for(const[a,r]of Object.entries(l))n.push({name:`metadata.${a}`,value:typeof r=="string"?r:JSON.stringify(r)});return n}v(C,"O"),g(C,"settingsOf");function O(t,s,n,l){const a=s?t.byCallId.get(s):void 0;if(a){a.output=n,l&&(a.error=l);return}t.call.turns.push({kind:"tool",...$("(an earlier call)","",{callId:s,output:n,error:l})})}v(O,"T"),g(O,"addOutput");function j(t,s){const n={kind:"tool",...s};t.call.turns.push(n),s.callId&&t.byCallId.set(s.callId,n)}v(j,"S"),g(j,"addCall");const ne=[["reasoning.effort","reasoning effort"],["reasoning.summary","reasoning summary"],["max_output_tokens","max output tokens"],["temperature","temperature"],["top_p","top_p"],["tool_choice","tool choice"],["parallel_tool_calls","parallel tool calls"],["text.verbosity","verbosity"],["truncation","truncation"],["previous_response_id","continues response"],["store","store"],["service_tier","service tier"],["stream","stream"],["prompt_cache_key","prompt cache key"],["user","user"],["safety_identifier","safety identifier"]],W=g(t=>A(Object.fromEntries(Object.entries(t).filter(([s])=>s!=="type"&&s!=="id"&&s!=="status"))),"builtInCall");function Z(t,s){const n=e(s.type)??(s.role?"message":"");if(n==="message"){const l=e(s.role)??"user",{text:a,attachments:r}=I(s.content);l==="system"?t.call.system.push({source:"system message",text:a}):t.call.turns.push({kind:"message",role:l,text:a,...r.length?{attachments:r}:{}})}else if(n==="function_call"||n==="custom_tool_call")j(t,$(e(s.name)??"?",e(s.arguments)??A(s.input),{callId:e(s.call_id)}));else if(n==="function_call_output"||n==="custom_tool_call_output"||n==="computer_call_output"||n==="local_shell_call_output")O(t,e(s.call_id),N(s.output));else if(n==="mcp_call")j(t,$(e(s.name)??"?",e(s.arguments)??"",{callId:e(s.id),server:e(s.server_label),...s.output!==void 0&&s.output!==null?{output:N(s.output)}:{},error:e(s.error)}));else if(n==="mcp_list_tools"){const l=k(s.tools).map(a=>e(c(a).name)).filter(Boolean);j(t,$("list_tools","",{server:e(s.server_label),output:l.join(", ")}))}else if(n==="reasoning"){const l=k(s.summary).map(a=>e(c(a).text)).filter(Boolean).join(`
|
|
6
|
+
|
|
7
|
+
`);t.call.turns.push({kind:"reasoning",...l?{text:l}:{}})}else n&&j(t,$(n.replace(/_call$/,""),W(s),{callId:e(s.call_id)??e(s.id)}))}v(Z,"R"),g(Z,"responsesItem");function D(t){return k(t.tools).map(s=>{const n=c(s),l=e(n.type)??"function";if(l==="mcp"){const r=e(n.server_label)??"mcp",o=k(n.allowed_tools).map(i=>e(i)).filter(Boolean);return{name:r,server:r,tool:o.length?o.join(", "):"every tool",type:l,...e(n.server_description)??e(n.server_url)?{description:e(n.server_description)??e(n.server_url)}:{},called:0}}const a=e(n.name)??l;return{name:a,...splitToolName(a),type:l,...e(n.description)?{description:e(n.description)}:{},...n.parameters!==void 0?{parameters:n.parameters}:{},...typeof n.strict=="boolean"?{strict:n.strict}:{},called:0}})}v(D,"W"),g(D,"responsesTools");function q(t,s){const n=t.call.answer;e(s.status)&&(n.status=e(s.status));const l=e(c(s.incomplete_details).reason);l&&(n.finish=l);const a=c(s.error);e(a.message)&&(n.error=e(a.message)),e(s.model)&&(t.call.model??=e(s.model)),e(s.id)&&(t.call.responseId=e(s.id)),E(t,k(s.output).map(c));const r=c(s.usage);Object.keys(r).length&&(t.call.usage={...y(r.input_tokens)!==void 0?{input:y(r.input_tokens)}:{},...y(c(r.input_tokens_details).cached_tokens)!==void 0?{cached:y(c(r.input_tokens_details).cached_tokens)}:{},...y(r.output_tokens)!==void 0?{output:y(r.output_tokens)}:{},...y(c(r.output_tokens_details).reasoning_tokens)!==void 0?{reasoning:y(c(r.output_tokens_details).reasoning_tokens)}:{},...y(r.total_tokens)!==void 0?{total:y(r.total_tokens)}:{}})}v(q,"z"),g(q,"responsesAnswer");function E(t,s){const n=t.call.answer,l=[],a=[];for(const r of s){const o=e(r.type);if(o==="message")for(const i of k(r.content).map(c))e(i.refusal)?n.refusal=e(i.refusal):e(i.text)!==void 0&&l.push(e(i.text));else if(o==="function_call"||o==="custom_tool_call")n.toolCalls.push($(e(r.name)??"?",e(r.arguments)??A(r.input),{callId:e(r.call_id)}));else if(o==="mcp_call")n.toolCalls.push($(e(r.name)??"?",e(r.arguments)??"",{callId:e(r.id),server:e(r.server_label),...r.output!==void 0&&r.output!==null?{output:N(r.output)}:{},error:e(r.error)}));else if(o==="reasoning"){const i=k(r.summary).map(u=>e(c(u).text)).filter(Boolean).join(`
|
|
8
|
+
|
|
9
|
+
`);i&&a.push(i)}else o&&o!=="mcp_list_tools"&&n.toolCalls.push($(o.replace(/_call$/,""),W(r),{callId:e(r.call_id)??e(r.id)}))}l.length&&(n.text=l.join(`
|
|
10
|
+
|
|
11
|
+
`)),a.length&&(n.reasoning=a.join(`
|
|
12
|
+
|
|
13
|
+
`))}v(E,"F"),g(E,"responsesOutput");function H(t,s,n){let l,a=0;const r=new Map,o=new Set,i=new Map,u=new Map,m=new Map;for(const f of s){const _=J(f);if(!_)continue;_.cut&&a++;const p=_.v,h=e(p.type)??f.event,w=y(p.output_index)??0;if(h==="response.completed"||h==="response.incomplete"||h==="response.failed"){if(!_.cut&&b(p.response))l=p.response;else if(b(p.response)){const x=p.response;e(x.status)&&(t.call.answer.status=e(x.status))}}else if(h==="response.created"||h==="response.in_progress"){const x=c(p.response);e(x.model)&&(t.call.model??=e(x.model)),e(x.id)&&(t.call.responseId??=e(x.id))}else h==="response.output_item.added"||h==="response.output_item.done"?b(p.item)&&(!o.has(w)||!_.cut)&&(r.set(w,{...r.get(w)??{},...p.item}),h.endsWith(".done")&&!_.cut&&o.add(w)):h==="response.output_text.delta"||h==="response.refusal.delta"?i.set(w,(i.get(w)??"")+(e(p.delta)??"")):h==="response.output_text.done"&&!_.cut&&e(p.text)!==void 0?i.set(w,e(p.text)):h==="response.function_call_arguments.delta"||h==="response.custom_tool_call_input.delta"||h==="response.mcp_call_arguments.delta"?u.set(w,(u.get(w)??"")+(e(p.delta)??"")):(h==="response.function_call_arguments.done"||h==="response.mcp_call_arguments.done")&&!_.cut&&e(p.arguments)!==void 0?u.set(w,e(p.arguments)):h==="response.reasoning_summary_text.delta"?m.set(w,(m.get(w)??"")+(e(p.delta)??"")):(h==="error"||h==="response.error")&&(t.call.answer.error=e(p.message)??e(c(p.error).message)??"the stream reported an error")}if(l){q(t,l);return}const d=[...r.entries()].sort(([f],[_])=>f-_).map(([f,_])=>{if(o.has(f))return _;const p=e(_.type);return p==="message"&&i.has(f)?{..._,content:[{type:"output_text",text:i.get(f)}]}:(p==="function_call"||p==="mcp_call")&&u.has(f)?{..._,arguments:u.get(f)}:p==="custom_tool_call"&&u.has(f)?{..._,input:u.get(f)}:p==="reasoning"&&m.has(f)?{..._,summary:[{type:"summary_text",text:m.get(f)}]}:_});for(const[f,_]of i)r.has(f)||d.push({type:"message",content:[{type:"output_text",text:_}]});E(t,d),t.call.answer.status||(t.call.answer.status=d.length?"completed":"unknown"),a&&n.push(`${a} of the stream\u2019s events were kept to their first 4 KB, so the answer was rebuilt from its pieces${t.call.usage?"":" \u2014 its token counts came in the last event, which was cut"}.`)}v(H,"Z"),g(H,"responsesStream");function R(t,s,n,l){const a=t.call;e(s.model)&&(a.model=e(s.model)),e(s.instructions)&&a.system.push({source:"instructions",text:e(s.instructions)});const r=s.input;if(typeof r=="string")a.turns.push({kind:"message",role:"user",text:r});else for(const i of k(r))b(i)&&Z(t,i);a.tools=D(s),a.settings=C(s,ne);const o=c(c(s.text).format);e(o.type)&&o.type!=="text"&&(a.answer.format={type:e(o.type),...e(o.name)?{name:e(o.name)}:{},...o.schema!==void 0?{schema:o.schema}:{},...typeof o.strict=="boolean"?{strict:o.strict}:{}}),T(t,n,l,i=>q(t,i),i=>H(t,i,l))}v(R,"D"),g(R,"responsesCall");const re=[["reasoning_effort","reasoning effort"],["max_completion_tokens","max completion tokens"],["max_tokens","max tokens"],["temperature","temperature"],["top_p","top_p"],["tool_choice","tool choice"],["parallel_tool_calls","parallel tool calls"],["n","n"],["seed","seed"],["stop","stop"],["presence_penalty","presence penalty"],["frequency_penalty","frequency penalty"],["verbosity","verbosity"],["store","store"],["service_tier","service tier"],["stream","stream"],["user","user"]];function K(t){const s=k(t.tool_calls).map(c).map(l=>{const a=c(l.function);return $(e(a.name)??e(c(l.custom).name)??"?",e(a.arguments)??e(c(l.custom).input)??"",{callId:e(l.id)})}),n=c(t.function_call);return e(n.name)&&s.push($(e(n.name),e(n.arguments)??"",{})),s}v(K,"G"),g(K,"chatCalls");function z(t,s){const n=t.call.answer;e(s.model)&&(t.call.model??=e(s.model)),e(s.id)&&(t.call.responseId??=e(s.id));const l=c(k(s.choices)[0]),a=c(l.message),r=I(a.content).text;r&&(n.text=r),e(a.refusal)&&(n.refusal=e(a.refusal));const o=e(a.reasoning_content)??e(a.reasoning);o&&(n.reasoning=o),n.toolCalls.push(...K(a)),e(l.finish_reason)&&(n.finish=e(l.finish_reason)),n.status=n.finish?"completed":n.status??"unknown";const i=c(s.error);e(i.message)&&(n.error=e(i.message));const u=c(s.usage);Object.keys(u).length&&(t.call.usage={...y(u.prompt_tokens)!==void 0?{input:y(u.prompt_tokens)}:{},...y(c(u.prompt_tokens_details).cached_tokens)!==void 0?{cached:y(c(u.prompt_tokens_details).cached_tokens)}:{},...y(u.completion_tokens)!==void 0?{output:y(u.completion_tokens)}:{},...y(c(u.completion_tokens_details).reasoning_tokens)!==void 0?{reasoning:y(c(u.completion_tokens_details).reasoning_tokens)}:{},...y(u.total_tokens)!==void 0?{total:y(u.total_tokens)}:{}})}v(z,"L"),g(z,"chatAnswer");function G(t,s,n){let l="",a="",r="",o,i,u,m,d=0;const f=new Map;for(const p of s){const h=J(p);if(!h)continue;h.cut&&d++,u??=e(h.v.model),m??=e(h.v.id),b(h.v.usage)&&(i=h.v.usage),b(h.v.error)&&(t.call.answer.error=e(h.v.error.message)??"the stream reported an error");const w=c(k(h.v.choices)[0]),x=c(w.delta);l+=e(x.content)??"",a+=e(x.reasoning_content)??e(x.reasoning)??"",r+=e(x.refusal)??"";for(const S of k(x.tool_calls).map(c)){const L=y(S.index)??0,B=f.get(L)??{name:"",args:""},P=c(S.function);e(S.id)&&(B.id=e(S.id)),B.name+=e(P.name)??"",B.args+=e(P.arguments)??"",f.set(L,B)}e(w.finish_reason)&&(o=e(w.finish_reason))}const _={content:l,...a?{reasoning_content:a}:{},...r?{refusal:r}:{},tool_calls:[...f.entries()].sort(([p],[h])=>p-h).map(([,p])=>({id:p.id,function:{name:p.name,arguments:p.args}}))};z(t,{...u?{model:u}:{},...m?{id:m}:{},choices:[{message:_,...o?{finish_reason:o}:{}}],...i?{usage:i}:{}}),d&&n.push(`${d} of the stream\u2019s chunks were kept to their first 4 KB \u2014 the answer was joined from what was kept of them.`)}v(G,"V"),g(G,"chatStream");function Q(t,s,n,l){const a=t.call;e(s.model)&&(a.model=e(s.model));for(const o of k(s.messages).map(c)){const i=e(o.role)??"user",{text:u,attachments:m}=I(o.content);if(i==="system")a.system.push({source:"system message",text:u});else if(i==="tool")O(t,e(o.tool_call_id),u);else if(i==="function")O(t,void 0,u);else{(u||m.length||i!=="assistant")&&a.turns.push({kind:"message",role:i,text:u,...m.length?{attachments:m}:{}});for(const d of K(o))j(t,d)}}a.tools=[...k(s.tools).map(c),...k(s.functions).map(o=>({type:"function",function:o}))].map(o=>{const i=c(o.function),u=e(o.type)??"function",m=e(i.name)??e(c(o.custom).name)??u;return{name:m,...splitToolName(m),type:u,...e(i.description)??e(c(o.custom).description)?{description:e(i.description)??e(c(o.custom).description)}:{},...i.parameters!==void 0?{parameters:i.parameters}:{},...typeof i.strict=="boolean"?{strict:i.strict}:{},called:0}}),a.settings=C(s,re);const r=c(s.response_format);if(e(r.type)&&r.type!=="text"){const o=c(r.json_schema);a.answer.format={type:e(r.type),...e(o.name)?{name:e(o.name)}:{},...o.schema!==void 0?{schema:o.schema}:{},...typeof o.strict=="boolean"?{strict:o.strict}:{}}}T(t,n,l,o=>z(t,o),o=>G(t,o,l))}v(Q,"X"),g(Q,"chatCall");const ae=[["max_tokens","max tokens"],["thinking.budget_tokens","thinking budget"],["temperature","temperature"],["top_p","top_p"],["top_k","top_k"],["tool_choice","tool choice"],["stop_sequences","stop sequences"],["stream","stream"],["metadata.user_id","user"]];function V(t,s,n){if(typeof n=="string"){t.call.turns.push({kind:"message",role:s,text:n});return}const l=[],a=[],r=g(()=>{(l.length||a.length)&&t.call.turns.push({kind:"message",role:s,text:l.join(`
|
|
14
|
+
|
|
15
|
+
`),...a.length?{attachments:[...a]}:{}}),l.length=0,a.length=0},"flush");for(const o of k(n).map(c)){const i=e(o.type)??"";i==="text"?l.push(e(o.text)??""):i==="tool_use"||i==="server_tool_use"||i==="mcp_tool_use"?(r(),j(t,$(e(o.name)??"?",A(o.input),{callId:e(o.id),server:e(o.server_name)}))):i==="tool_result"||i==="mcp_tool_result"||/_tool_result$/.test(i)?(r(),O(t,e(o.tool_use_id),N(o.content),o.is_error===!0?"the tool reported an error":void 0)):i==="thinking"||i==="redacted_thinking"?(r(),t.call.turns.push({kind:"reasoning",...e(o.thinking)?{text:e(o.thinking)}:{}})):a.push(i==="image"?"image":i==="document"?`document ${e(o.title)??""}`.trim():i)}r()}v(V,"ee"),g(V,"anthropicBlocks");function F(t,s){const n=t.call.answer;e(s.model)&&(t.call.model??=e(s.model)),e(s.id)&&(t.call.responseId??=e(s.id));const l=[],a=[];for(const i of k(s.content).map(c)){const u=e(i.type);u==="text"?l.push(e(i.text)??""):u==="tool_use"||u==="server_tool_use"||u==="mcp_tool_use"?n.toolCalls.push($(e(i.name)??"?",A(i.input),{callId:e(i.id),server:e(i.server_name)})):u==="thinking"&&a.push(e(i.thinking)??"")}l.length&&(n.text=l.join(`
|
|
16
|
+
|
|
17
|
+
`)),a.length&&(n.reasoning=a.join(`
|
|
18
|
+
|
|
19
|
+
`)),e(s.stop_reason)&&(n.finish=e(s.stop_reason)),n.status=s.type==="error"?"failed":n.finish?"completed":n.status??"unknown";const r=c(s.error);e(r.message)&&(n.error=e(r.message));const o=c(s.usage);if(Object.keys(o).length){const i=y(o.input_tokens),u=y(o.cache_read_input_tokens),m=y(o.output_tokens);t.call.usage={...i!==void 0?{input:i+(u??0)+(y(o.cache_creation_input_tokens)??0)}:{},...u!==void 0?{cached:u}:{},...m!==void 0?{output:m}:{}}}}v(F,"P"),g(F,"anthropicAnswer");function X(t,s,n){let l={};const a=new Map,r=new Map;let o={},i=0;for(const u of s){const m=J(u);if(!m)continue;m.cut&&i++;const d=m.v,f=e(d.type)??u.event,_=y(d.index)??0;if(f==="message_start")l=c(d.message),o={...c(l.usage)};else if(f==="content_block_start")a.set(_,{...c(d.content_block)});else if(f==="content_block_delta"){const p=c(d.delta),h=a.get(_)??{};e(p.text)!==void 0&&(h.text=(e(h.text)??"")+e(p.text)),e(p.thinking)!==void 0&&(h.thinking=(e(h.thinking)??"")+e(p.thinking)),e(p.partial_json)!==void 0&&r.set(_,(r.get(_)??"")+e(p.partial_json)),a.set(_,h)}else f==="message_delta"?(l={...l,...c(d.delta)},o={...o,...c(d.usage)}):f==="error"&&(t.call.answer.error=e(c(d.error).message)??"the stream reported an error")}for(const[u,m]of r){const d=a.get(u);d&&(d.input=parseJsonPrefix(m)?.value??m)}F(t,{...l,content:[...a.entries()].sort(([u],[m])=>u-m).map(([,u])=>u),usage:o}),i&&n.push(`${i} of the stream\u2019s events were kept to their first 4 KB \u2014 the answer was joined from what was kept of them.`)}v(X,"te"),g(X,"anthropicStream");function Y(t,s,n,l){const a=t.call;e(s.model)&&(a.model=e(s.model));const r=s.system;if(typeof r=="string"&&r)a.system.push({source:"system",text:r});else for(const o of k(r).map(c))e(o.text)&&a.system.push({source:"system",text:e(o.text)});for(const o of k(s.messages).map(c))V(t,e(o.role)??"user",o.content);a.tools=[...k(s.tools).map(c).map(o=>{const i=e(o.name)??e(o.type)??"tool";return{name:i,...splitToolName(i),type:e(o.type)??"custom",...e(o.description)?{description:e(o.description)}:{},...o.input_schema!==void 0?{parameters:o.input_schema}:{},called:0}}),...k(s.mcp_servers).map(c).map(o=>({name:e(o.name)??"mcp",server:e(o.name)??"mcp",tool:"every tool",type:"mcp",...e(o.url)?{description:e(o.url)}:{},called:0}))],a.settings=C(s,ae),T(t,n,l,o=>F(t,o),o=>X(t,o,l))}v(Y,"ne"),g(Y,"anthropicCall");function ee(t,s,n,l){const a=t.call;e(s.model)&&(a.model=e(s.model));const r=s.input,o=typeof r=="string"?[r]:k(r),i=o.slice(0,200).map(p=>typeof p=="string"?p:typeof p=="number"?`token ${p}`:Array.isArray(p)?`${p.length} tokens`:A(p)),u=o.length>0&&o.every(p=>typeof p=="number");a.embedding={inputs:u?[`${o.length} tokens`]:i,count:u?1:o.length,...y(s.dimensions)!==void 0?{dimensions:y(s.dimensions)}:{}},a.settings=C(s,[["dimensions","dimensions"],["encoding_format","encoding"],["user","user"]]);const m=c(M(n.response.body,"answer",[])),d=k(m.data);if(d.length){a.embedding.vectors=d.length;const p=c(d[0]).embedding;Array.isArray(p)&&a.embedding.dimensions===void 0&&(a.embedding.dimensions=p.length)}const f=c(m.usage);y(f.prompt_tokens)!==void 0&&(a.usage={input:y(f.prompt_tokens),...y(f.total_tokens)!==void 0?{total:y(f.total_tokens)}:{}}),a.answer.status=n.status!==void 0&&n.status<400?"completed":"failed";const _=c(m.error);e(_.message)&&(a.answer.error=e(_.message))}v(ee,"se"),g(ee,"embeddingsCall");function T(t,s,n,l,a){const r=s.response.stream;if(r){if(r.events.length&&r.events.every(i=>i.data===void 0)){n.push("The answer streamed, and only each event\u2019s name, time and size were kept \u2014 an AI provider\u2019s bodies are kept only when asked for: Settings \u203A Features \u203A Capture \u203A AI calls.");return}a(r.events),r.cut&&n.push(`The stream was cut after ${r.events.length.toLocaleString("en-US")} of its ${r.total.toLocaleString("en-US")} events \u2014 what came after is not shown.`);return}const o=M(s.response.body,"answer",n);b(o)?l(o):s.error&&(t.call.answer.error=s.error),t.call.answer.status||(t.call.answer.status=s.error||(s.status??0)>=400?"failed":"unknown")}v(T,"N"),g(T,"answerOf");function te(t){return t.api==="embeddings"?"embedding":t.answer.format&&/json/.test(t.answer.format.type)?"decision":t.tools.length||t.answer.toolCalls.length||t.turns.some(s=>s.kind==="tool")?"tools":t.turns.filter(s=>s.kind==="message"&&(s.role==="user"||s.role==="assistant")).length>1?"chat":"prompt"}v(te,"oe"),g(te,"shapeOf");export function aiCallOf(t){if(!t.ai)return;const s=aiApiOf(t.path);if(!s)return;const n=[],l={call:{provider:t.ai.provider,api:s,shape:"prompt",settings:[],system:[],turns:[],tools:[],answer:{toolCalls:[]},gaps:n},byCallId:new Map},a=c(M(t.request.body,"request",n));s==="responses"?R(l,a,t,n):s==="chat"?Q(l,a,t,n):s==="messages"?Y(l,a,t,n):ee(l,a,t,n);const r=l.call;if(r.answer.text!==void 0&&(r.answer.format?/json/.test(r.answer.format.type):/^\s*[{[]/.test(r.answer.text))){const i=parseJsonPrefix(r.answer.text);i&&(b(i.value)||Array.isArray(i.value))&&(r.answer.json=i.value)}!r.answer.finish&&r.answer.toolCalls.length&&(r.answer.finish="tool calls");const o=new Map;for(const i of r.turns)i.kind==="tool"&&o.set(i.name,(o.get(i.name)??0)+1);for(const i of r.answer.toolCalls)o.set(i.name,(o.get(i.name)??0)+1);for(const i of r.tools)i.called=o.get(i.name)??0;r.shape=te(r);for(const i of r.system)i.text.endsWith(CUT_MARK)&&(i.cut=!0);return r}v(aiCallOf,"aiCallOf"),g(aiCallOf,"aiCallOf");export const SHAPE_LABEL={prompt:"Single prompt",chat:"Conversation",tools:"Tool calling",decision:"Structured decision",embedding:"Embedding"};export function prettyJson(t){const s=t.trim()?parseJsonPrefix(t):void 0;return s&&(b(s.value)||Array.isArray(s.value))?{value:s.value,text:t}:{text:t}}v(prettyJson,"prettyJson"),g(prettyJson,"prettyJson");
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AI calls page's playground (planning/ai-calls D1, D4): a kept call sent again — to another model, its prompt,
|
|
3
|
+
* input or tools changed — on the lens's own keys, paid from the click allowance. A tool the model calls gets the
|
|
4
|
+
* result recorded in the original run (or one a person typed); nothing reaches the running app.
|
|
5
|
+
*/
|
|
6
|
+
/** A tool's result the playground gives back: recorded in the call's run, or typed by a person. */
|
|
7
|
+
export interface AiToolResult {
|
|
8
|
+
name: string;
|
|
9
|
+
/** The arguments it answers, as sent (JSON); absent on a person's: it answers any call of that tool. */
|
|
10
|
+
arguments?: string;
|
|
11
|
+
output: string;
|
|
12
|
+
}
|
|
13
|
+
/** `GET /api/ai/playground?id=` — what the playground can do with one call. */
|
|
14
|
+
export interface AiPlaygroundInfo {
|
|
15
|
+
/** The models the lens's keys can send to, the call's own first; the cost of a step estimated from the call's tokens. */
|
|
16
|
+
models: Array<{
|
|
17
|
+
model: string;
|
|
18
|
+
provider: 'openai' | 'anthropic';
|
|
19
|
+
estimateUsd?: number;
|
|
20
|
+
own?: true;
|
|
21
|
+
}>;
|
|
22
|
+
/** The call's request as it was sent (the Responses API body), to start from. */
|
|
23
|
+
body?: Record<string, unknown>;
|
|
24
|
+
/** Every tool result its run recorded, oldest first. */
|
|
25
|
+
recorded: Array<AiToolResult & {
|
|
26
|
+
at: number;
|
|
27
|
+
}>;
|
|
28
|
+
/** Today's spend on clicks, and the allowance. */
|
|
29
|
+
spentTodayUsd: number;
|
|
30
|
+
capUsd: number;
|
|
31
|
+
/** Why the playground cannot send this call. */
|
|
32
|
+
unavailable?: string;
|
|
33
|
+
}
|
|
34
|
+
/** `POST /api/ai/playground` — one run of the playground, on one model. */
|
|
35
|
+
export interface AiPlaygroundRun {
|
|
36
|
+
id: string;
|
|
37
|
+
model: string;
|
|
38
|
+
/** The request as edited (the Responses API body); absent: as it was sent. */
|
|
39
|
+
body?: Record<string, unknown>;
|
|
40
|
+
/** Results a person typed: theirs before the recorded ones. */
|
|
41
|
+
results?: AiToolResult[];
|
|
42
|
+
/** Steps of the tool loop at most: 1–8 (default 6). */
|
|
43
|
+
maxSteps?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface AiPlaygroundStep {
|
|
46
|
+
/** The provider's API, its request and its answer as sent and received — read like a kept call. */
|
|
47
|
+
api: 'responses' | 'messages';
|
|
48
|
+
sent: Record<string, unknown>;
|
|
49
|
+
response: Record<string, unknown>;
|
|
50
|
+
model: string;
|
|
51
|
+
usage: {
|
|
52
|
+
input: number;
|
|
53
|
+
output: number;
|
|
54
|
+
cacheRead?: number;
|
|
55
|
+
};
|
|
56
|
+
costUsd?: number;
|
|
57
|
+
latencyMs: number;
|
|
58
|
+
/** The tools it called this step: each with the result given back, and where that came from. */
|
|
59
|
+
tools: Array<{
|
|
60
|
+
name: string;
|
|
61
|
+
arguments: string;
|
|
62
|
+
output?: string;
|
|
63
|
+
from: 'recorded' | 'edited' | 'missing';
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
export interface AiPlaygroundAnswer {
|
|
67
|
+
model: string;
|
|
68
|
+
provider: 'openai' | 'anthropic';
|
|
69
|
+
/** The key that paid. */
|
|
70
|
+
keyLabel?: string;
|
|
71
|
+
steps: AiPlaygroundStep[];
|
|
72
|
+
/** It answered; a tool's result is missing (type one, run again); the step limit; a call failed. */
|
|
73
|
+
stopped: 'answered' | 'missing-result' | 'max-steps' | 'failed';
|
|
74
|
+
error?: string;
|
|
75
|
+
costUsd: number;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";export{};
|
package/lib/event-loop.d.ts
CHANGED
|
@@ -63,3 +63,51 @@ export interface EventLoopsView {
|
|
|
63
63
|
persistence: 'disk' | 'memory';
|
|
64
64
|
retentionDays: number;
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* A loop's delay over one short window — `[end of the window (epoch ms), mean, p99, max (ms), utilization (0–1)]`.
|
|
68
|
+
* A Node pod measures one every 5 s (`LENSMCP_EVENT_LOOP_STEP_MS`) and carries every one since its last report in the
|
|
69
|
+
* next `event-loop-lag` event (`raw.samples`): the history costs the bus no event of its own.
|
|
70
|
+
*/
|
|
71
|
+
export type EventLoopSample = [at: number, meanMs: number, p99Ms: number, maxMs: number, utilization: number];
|
|
72
|
+
/**
|
|
73
|
+
* One bucket of a loop's samples — `[bucket start (epoch ms), mean, p99, max, utilization]`. max and p99 are the
|
|
74
|
+
* bucket's highest, so a spike survives any bucket width; mean and utilization are its average.
|
|
75
|
+
*/
|
|
76
|
+
export type EventLoopPoint = [at: number, meanMs: number, p99Ms: number, maxMs: number, utilization: number];
|
|
77
|
+
/** One loop — a process lifetime, so a restart or a hot swap starts a new series — over the asked window. */
|
|
78
|
+
export interface EventLoopSeries {
|
|
79
|
+
loop: EventLoopIdentity;
|
|
80
|
+
/** The first and the last sample the lens holds of this loop (all time, not only the window). */
|
|
81
|
+
firstAt: number;
|
|
82
|
+
lastAt: number;
|
|
83
|
+
points: EventLoopPoint[];
|
|
84
|
+
}
|
|
85
|
+
/** The stall incidents of one loop in one bucket, for a marker: what a click opens is read from `/api/event-loops`. */
|
|
86
|
+
export interface EventLoopStallMark {
|
|
87
|
+
loopId: string;
|
|
88
|
+
/** The loop's service, so a loop with stalls and no samples (a browser page) still gets its chart. */
|
|
89
|
+
project: string;
|
|
90
|
+
runtime: EventLoopIdentity['runtime'];
|
|
91
|
+
/** The bucket's start, as the points'. */
|
|
92
|
+
at: number;
|
|
93
|
+
count: number;
|
|
94
|
+
longestMs: number;
|
|
95
|
+
/** One of them was not responding when last observed. */
|
|
96
|
+
unresponsive: boolean;
|
|
97
|
+
}
|
|
98
|
+
/** `/api/event-loops/history`: every loop's delay over a window, bucketed, with its stalls. */
|
|
99
|
+
export interface EventLoopHistory {
|
|
100
|
+
from: number;
|
|
101
|
+
to: number;
|
|
102
|
+
/** The bucket width. */
|
|
103
|
+
stepMs: number;
|
|
104
|
+
/** What the buckets were made of: the 5 s samples (kept `samplesHours`), or the 5-minute rollups (kept `retentionDays`). */
|
|
105
|
+
source: 'samples' | 'rollups';
|
|
106
|
+
series: EventLoopSeries[];
|
|
107
|
+
/** More loops had samples in the window than are listed: the most recently seen are. */
|
|
108
|
+
seriesTruncated?: boolean;
|
|
109
|
+
stalls: EventLoopStallMark[];
|
|
110
|
+
persistence: 'disk' | 'memory';
|
|
111
|
+
samplesHours: number;
|
|
112
|
+
retentionDays: number;
|
|
113
|
+
}
|