agentfootprint 7.12.0 → 7.14.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/dist/core/Agent.js +118 -7
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +59 -1
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +6 -0
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +14 -0
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/stages/callLLM.js +10 -4
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/stages/seed.js +16 -0
- package/dist/core/agent/stages/seed.js.map +1 -1
- package/dist/core/slots/buildSystemPromptSlot.js +2 -1
- package/dist/core/slots/buildSystemPromptSlot.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +54 -3
- package/dist/esm/core/Agent.js +118 -7
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +48 -1
- package/dist/esm/core/agent/AgentBuilder.js +59 -1
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.js +6 -0
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +14 -0
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/stages/callLLM.d.ts +9 -0
- package/dist/esm/core/agent/stages/callLLM.js +10 -4
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/stages/seed.d.ts +10 -1
- package/dist/esm/core/agent/stages/seed.js +16 -0
- package/dist/esm/core/agent/stages/seed.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +44 -0
- package/dist/esm/core/slots/buildSystemPromptSlot.d.ts +19 -4
- package/dist/esm/core/slots/buildSystemPromptSlot.js +2 -1
- package/dist/esm/core/slots/buildSystemPromptSlot.js.map +1 -1
- package/dist/esm/hosting/envelope.d.ts +33 -0
- package/dist/esm/hosting/envelope.js +49 -0
- package/dist/esm/hosting/envelope.js.map +1 -0
- package/dist/esm/hosting/errors.d.ts +78 -0
- package/dist/esm/hosting/errors.js +119 -0
- package/dist/esm/hosting/errors.js.map +1 -0
- package/dist/esm/hosting/index.d.ts +53 -0
- package/dist/esm/hosting/index.js +52 -0
- package/dist/esm/hosting/index.js.map +1 -0
- package/dist/esm/hosting/memorySessions.d.ts +22 -0
- package/dist/esm/hosting/memorySessions.js +31 -0
- package/dist/esm/hosting/memorySessions.js.map +1 -0
- package/dist/esm/hosting/nodeHost.d.ts +64 -0
- package/dist/esm/hosting/nodeHost.js +245 -0
- package/dist/esm/hosting/nodeHost.js.map +1 -0
- package/dist/esm/hosting/standingAgent.d.ts +62 -0
- package/dist/esm/hosting/standingAgent.js +192 -0
- package/dist/esm/hosting/standingAgent.js.map +1 -0
- package/dist/esm/hosting/types.d.ts +206 -0
- package/dist/esm/hosting/types.js +21 -0
- package/dist/esm/hosting/types.js.map +1 -0
- package/dist/esm/lib/injection-engine/index.d.ts +1 -0
- package/dist/esm/lib/injection-engine/index.js +4 -0
- package/dist/esm/lib/injection-engine/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillsFromDir.d.ts +80 -0
- package/dist/esm/lib/injection-engine/skillsFromDir.js +243 -0
- package/dist/esm/lib/injection-engine/skillsFromDir.js.map +1 -0
- package/dist/esm/lib/mcp/index.d.ts +6 -5
- package/dist/esm/lib/mcp/index.js +5 -4
- package/dist/esm/lib/mcp/index.js.map +1 -1
- package/dist/esm/lib/mcp/mcpClient.js +21 -9
- package/dist/esm/lib/mcp/mcpClient.js.map +1 -1
- package/dist/esm/lib/mcp/mcpServe.d.ts +70 -0
- package/dist/esm/lib/mcp/mcpServe.js +374 -0
- package/dist/esm/lib/mcp/mcpServe.js.map +1 -0
- package/dist/esm/lib/mcp/types.d.ts +130 -17
- package/dist/esm/lib/mcp/types.js +10 -10
- package/dist/esm/tool-providers/index.d.ts +8 -2
- package/dist/esm/tool-providers/index.js +7 -1
- package/dist/esm/tool-providers/index.js.map +1 -1
- package/dist/hosting/envelope.js +54 -0
- package/dist/hosting/envelope.js.map +1 -0
- package/dist/hosting/errors.js +126 -0
- package/dist/hosting/errors.js.map +1 -0
- package/dist/hosting/index.js +64 -0
- package/dist/hosting/index.js.map +1 -0
- package/dist/hosting/memorySessions.js +35 -0
- package/dist/hosting/memorySessions.js.map +1 -0
- package/dist/hosting/nodeHost.js +272 -0
- package/dist/hosting/nodeHost.js.map +1 -0
- package/dist/hosting/standingAgent.js +196 -0
- package/dist/hosting/standingAgent.js.map +1 -0
- package/dist/hosting/types.js +22 -0
- package/dist/hosting/types.js.map +1 -0
- package/dist/lib/injection-engine/index.js +6 -1
- package/dist/lib/injection-engine/index.js.map +1 -1
- package/dist/lib/injection-engine/skillsFromDir.js +270 -0
- package/dist/lib/injection-engine/skillsFromDir.js.map +1 -0
- package/dist/lib/mcp/index.js +7 -5
- package/dist/lib/mcp/index.js.map +1 -1
- package/dist/lib/mcp/mcpClient.js +21 -9
- package/dist/lib/mcp/mcpClient.js.map +1 -1
- package/dist/lib/mcp/mcpServe.js +401 -0
- package/dist/lib/mcp/mcpServe.js.map +1 -0
- package/dist/lib/mcp/types.js +10 -10
- package/dist/tool-providers/index.js +8 -1
- package/dist/tool-providers/index.js.map +1 -1
- package/dist/types/core/Agent.d.ts +54 -3
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +48 -1
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/stages/callLLM.d.ts +9 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/stages/seed.d.ts +10 -1
- package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +44 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/slots/buildSystemPromptSlot.d.ts +19 -4
- package/dist/types/core/slots/buildSystemPromptSlot.d.ts.map +1 -1
- package/dist/types/hosting/envelope.d.ts +34 -0
- package/dist/types/hosting/envelope.d.ts.map +1 -0
- package/dist/types/hosting/errors.d.ts +79 -0
- package/dist/types/hosting/errors.d.ts.map +1 -0
- package/dist/types/hosting/index.d.ts +54 -0
- package/dist/types/hosting/index.d.ts.map +1 -0
- package/dist/types/hosting/memorySessions.d.ts +23 -0
- package/dist/types/hosting/memorySessions.d.ts.map +1 -0
- package/dist/types/hosting/nodeHost.d.ts +65 -0
- package/dist/types/hosting/nodeHost.d.ts.map +1 -0
- package/dist/types/hosting/standingAgent.d.ts +63 -0
- package/dist/types/hosting/standingAgent.d.ts.map +1 -0
- package/dist/types/hosting/types.d.ts +207 -0
- package/dist/types/hosting/types.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/index.d.ts +1 -0
- package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillsFromDir.d.ts +81 -0
- package/dist/types/lib/injection-engine/skillsFromDir.d.ts.map +1 -0
- package/dist/types/lib/mcp/index.d.ts +6 -5
- package/dist/types/lib/mcp/index.d.ts.map +1 -1
- package/dist/types/lib/mcp/mcpClient.d.ts.map +1 -1
- package/dist/types/lib/mcp/mcpServe.d.ts +71 -0
- package/dist/types/lib/mcp/mcpServe.d.ts.map +1 -0
- package/dist/types/lib/mcp/types.d.ts +130 -17
- package/dist/types/lib/mcp/types.d.ts.map +1 -1
- package/dist/types/tool-providers/index.d.ts +8 -2
- package/dist/types/tool-providers/index.d.ts.map +1 -1
- package/package.json +15 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosting/types — the two ports an agent needs to stand up and stay up.
|
|
3
|
+
*
|
|
4
|
+
* `AgentHost` is "something can call me". `SessionLifecycle` is "the
|
|
5
|
+
* conversation outlives the request". Both are deliberately written in the
|
|
6
|
+
* vocabulary every transport and every store already has — an input, a reply,
|
|
7
|
+
* a session id, a stored blob — and in nothing else.
|
|
8
|
+
*
|
|
9
|
+
* **The rule these types are written under:** no runtime, product or protocol
|
|
10
|
+
* gets a field, a name or an assumption here. A port shaped around one
|
|
11
|
+
* provider's request envelope stops being a port and becomes that provider's
|
|
12
|
+
* SDK with extra steps, and every later adapter pays for it. If a decision only
|
|
13
|
+
* makes sense for one place you might deploy, it belongs in the adapter for
|
|
14
|
+
* that place, not in this file. `nodeHost` is the first adapter and it does not
|
|
15
|
+
* get special treatment either: its paths, its status codes and its JSON body
|
|
16
|
+
* shape all live in `nodeHost.ts`, and nothing in this file knows they exist.
|
|
17
|
+
*
|
|
18
|
+
* Pattern: Ports & adapters (hexagonal). Role: the port side, exclusively.
|
|
19
|
+
*/
|
|
20
|
+
import type { Agent } from '../core/Agent.js';
|
|
21
|
+
import type { AgentRunCheckpoint } from '../core/runCheckpoint.js';
|
|
22
|
+
/**
|
|
23
|
+
* Something a host can do BEYOND the baseline of "accept a request, deliver one
|
|
24
|
+
* reply". Read it from {@link AgentHost.capabilities} and branch on it — never
|
|
25
|
+
* assume it, and never infer it from the adapter's name.
|
|
26
|
+
*
|
|
27
|
+
* The union starts at exactly what a shipped adapter can honour today. A name
|
|
28
|
+
* is added when an adapter can actually keep the promise, never in anticipation
|
|
29
|
+
* of a transport that does not exist yet: a capability nobody implements is a
|
|
30
|
+
* promise the library cannot keep, and pre-minting one for an imagined future
|
|
31
|
+
* transport would bake that transport's assumptions in before it arrives.
|
|
32
|
+
*/
|
|
33
|
+
export type HostCapability = 'streaming';
|
|
34
|
+
/**
|
|
35
|
+
* One inbound request, as the transport described it.
|
|
36
|
+
*/
|
|
37
|
+
export interface HostRequest {
|
|
38
|
+
/** What the caller is asking. */
|
|
39
|
+
readonly input: string;
|
|
40
|
+
/**
|
|
41
|
+
* The conversation this request CLAIMS to belong to — caller data, exactly as
|
|
42
|
+
* the transport declared it (a JSON field, a header, a path segment).
|
|
43
|
+
*
|
|
44
|
+
* It is **not identity** and must never be trusted as identity on its own:
|
|
45
|
+
* anyone who can reach the host can put any string here, including someone
|
|
46
|
+
* else's. Authenticate the caller by your own means, then check that the
|
|
47
|
+
* authenticated principal is allowed this session, before you serve it.
|
|
48
|
+
*/
|
|
49
|
+
readonly sessionId?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Transport headers with lower-cased names, as delivered. Present so a
|
|
52
|
+
* handler can map its own conventions (a correlation id, a tenant) without
|
|
53
|
+
* the port having to guess which ones matter.
|
|
54
|
+
*/
|
|
55
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
56
|
+
/** Aborted when the caller goes away. */
|
|
57
|
+
readonly signal?: AbortSignal;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The one reply a request gets. Exactly one of {@link HostReply.complete} or
|
|
61
|
+
* {@link HostReply.fail} ends it; a second call is ignored rather than allowed
|
|
62
|
+
* to corrupt the wire.
|
|
63
|
+
*/
|
|
64
|
+
export interface HostReply {
|
|
65
|
+
/** Deliver the final answer and end the reply. */
|
|
66
|
+
complete(output: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* A piece of the answer, as it is produced.
|
|
69
|
+
*
|
|
70
|
+
* Optional on the TYPE so a minimal adapter need not implement it — every
|
|
71
|
+
* shipped adapter does. Whether the caller SEES the pieces as they arrive is
|
|
72
|
+
* the whole difference between hosts, and that is what `'streaming'` in
|
|
73
|
+
* {@link AgentHost.capabilities} reports. A host without it buffers what it is
|
|
74
|
+
* handed and the authoritative `complete(output)` is what the caller
|
|
75
|
+
* receives; the buffer is settled by the completion, never sent alongside it,
|
|
76
|
+
* because a chunk is a preview of the same text and delivering both would
|
|
77
|
+
* hand the caller the answer twice.
|
|
78
|
+
*
|
|
79
|
+
* Handler code is identical either way: emit freely, complete once.
|
|
80
|
+
*/
|
|
81
|
+
emit?(chunk: string): void;
|
|
82
|
+
/** End the reply with a failure. */
|
|
83
|
+
fail(error: Error): void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* What you hand {@link AgentHost.serve}. Throwing is treated exactly like
|
|
87
|
+
* calling `reply.fail(err)` — a handler that throws is a failed request, never
|
|
88
|
+
* a hung one.
|
|
89
|
+
*/
|
|
90
|
+
export type HostHandler = (request: HostRequest, reply: HostReply) => void | Promise<void>;
|
|
91
|
+
/** A live host. */
|
|
92
|
+
export interface HostHandle {
|
|
93
|
+
/**
|
|
94
|
+
* Stop taking new requests, let the in-flight ones finish, then release the
|
|
95
|
+
* transport. Idempotent, so a shutdown hook and an explicit close can
|
|
96
|
+
* coexist. Requests arriving after it are refused with a
|
|
97
|
+
* {@link HostClosedError} naming the adapter.
|
|
98
|
+
*/
|
|
99
|
+
close(): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The port: something that can carry requests to one handler and carry its
|
|
103
|
+
* replies back.
|
|
104
|
+
*/
|
|
105
|
+
export interface AgentHost {
|
|
106
|
+
/**
|
|
107
|
+
* Which adapter this is. Every refusal names it, so an error tells you WHO
|
|
108
|
+
* refused rather than leaving you to guess which layer you are looking at.
|
|
109
|
+
*/
|
|
110
|
+
readonly name: string;
|
|
111
|
+
/** What this adapter can do beyond the baseline. Feature-detect; never assume. */
|
|
112
|
+
readonly capabilities: readonly HostCapability[];
|
|
113
|
+
/** Start serving. Resolves once the host is actually live. */
|
|
114
|
+
serve(handler: HostHandler): Promise<HostHandle>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A conversation packed for storage.
|
|
118
|
+
*
|
|
119
|
+
* `format` names WHAT is inside, so a reader that does not know the shape
|
|
120
|
+
* refuses BY NAME instead of restoring a conversation it cannot actually read.
|
|
121
|
+
* Formats are ADDED, never redefined: an old runtime meeting a new format says
|
|
122
|
+
* so and stops, which is the only safe thing it can do with a payload it cannot
|
|
123
|
+
* interpret.
|
|
124
|
+
*
|
|
125
|
+
* `'conversation-v1'` stores a conversation and only a conversation. A run that
|
|
126
|
+
* paused mid-flow is a conversation PLUS an engine checkpoint, and this format
|
|
127
|
+
* has nowhere to put the second half — which is why `standingAgent` refuses to
|
|
128
|
+
* store a paused run rather than storing half of it. Carrying a pause would be
|
|
129
|
+
* a NEW format name in this same envelope, read by a runtime that knows it and
|
|
130
|
+
* refused by name everywhere else. That is what the version field is for.
|
|
131
|
+
*/
|
|
132
|
+
export interface CheckpointEnvelope {
|
|
133
|
+
/** Names the shape of `data`. Unknown values are refused, never guessed at. */
|
|
134
|
+
readonly format: 'conversation-v1';
|
|
135
|
+
/** The conversation itself — an `AgentRunCheckpoint` for `'conversation-v1'`. */
|
|
136
|
+
readonly data: AgentRunCheckpoint;
|
|
137
|
+
/** Wall-clock when it was packed. Diagnostic. */
|
|
138
|
+
readonly savedAt: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Why a session is being woken.
|
|
142
|
+
*
|
|
143
|
+
* One member, because one thing in this release can actually fire it: a request
|
|
144
|
+
* arrived for that session. Naming reasons nothing can produce would be an
|
|
145
|
+
* interface describing a system that does not exist.
|
|
146
|
+
*/
|
|
147
|
+
export type WakeReason = 'invoke';
|
|
148
|
+
/**
|
|
149
|
+
* The port: where a conversation lives between requests.
|
|
150
|
+
*
|
|
151
|
+
* Deliberately two required methods. Anything a real store also wants — a TTL,
|
|
152
|
+
* a scan, a delete — is that store's own API, not a demand this port makes of
|
|
153
|
+
* every store that will ever implement it.
|
|
154
|
+
*/
|
|
155
|
+
export interface SessionLifecycle {
|
|
156
|
+
/** The stored conversation, or `undefined` for a session that has none yet. */
|
|
157
|
+
hydrate(sessionId: string): Promise<CheckpointEnvelope | undefined>;
|
|
158
|
+
/** Store the conversation for this session. Last write wins. */
|
|
159
|
+
persist(sessionId: string, envelope: CheckpointEnvelope): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Called once per served request, before `hydrate`, for stores that need to
|
|
162
|
+
* spin something up before they can answer. Errors from it fail the request —
|
|
163
|
+
* a store that could not wake cannot be read from either.
|
|
164
|
+
*/
|
|
165
|
+
onWake?(sessionId: string, reason: WakeReason): void | Promise<void>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* What to do when a request arrives for a session that already has a run in
|
|
169
|
+
* flight.
|
|
170
|
+
*
|
|
171
|
+
* - `'reject'` (default) — refuse it, naming the run that is already going.
|
|
172
|
+
* A user who double-submits gets one answer and one refusal, not two runs
|
|
173
|
+
* racing to write the same conversation.
|
|
174
|
+
* - `'enqueue'` — queue it. It starts after the active run has persisted, so
|
|
175
|
+
* the second turn sees the first turn's stored state rather than the state
|
|
176
|
+
* it was about to overwrite.
|
|
177
|
+
*
|
|
178
|
+
* This governs the SAME session only. A request for a different session is
|
|
179
|
+
* never refused — there is nothing wrong with it; it simply waits its turn.
|
|
180
|
+
*/
|
|
181
|
+
export type ConcurrentInvokePolicy = 'reject' | 'enqueue';
|
|
182
|
+
/**
|
|
183
|
+
* Options for {@link standingAgent}.
|
|
184
|
+
*
|
|
185
|
+
* Generic in the host's own handle type so composing does not cost you what
|
|
186
|
+
* the adapter told you. `nodeHost` hands back the URL it actually bound —
|
|
187
|
+
* which is the only way to find out when you asked for port `0` — and passing
|
|
188
|
+
* it through `standingAgent` keeps that, without the port having to know that
|
|
189
|
+
* "a URL" is a thing some adapters have.
|
|
190
|
+
*/
|
|
191
|
+
export interface StandingAgentOptions<TH extends HostHandle = HostHandle> {
|
|
192
|
+
/**
|
|
193
|
+
* The agent that answers. ONE instance, shared by every session — which is
|
|
194
|
+
* why the composer runs one request at a time (see
|
|
195
|
+
* {@link ConcurrentInvokePolicy}).
|
|
196
|
+
*/
|
|
197
|
+
readonly agent: Agent;
|
|
198
|
+
/** Where conversations live between requests. */
|
|
199
|
+
readonly sessions: SessionLifecycle;
|
|
200
|
+
/** What carries requests in. */
|
|
201
|
+
readonly host: AgentHost & {
|
|
202
|
+
serve(handler: HostHandler): Promise<TH>;
|
|
203
|
+
};
|
|
204
|
+
/** Default `'reject'`. See {@link ConcurrentInvokePolicy}. */
|
|
205
|
+
readonly onConcurrentInvoke?: ConcurrentInvokePolicy;
|
|
206
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosting/types — the two ports an agent needs to stand up and stay up.
|
|
3
|
+
*
|
|
4
|
+
* `AgentHost` is "something can call me". `SessionLifecycle` is "the
|
|
5
|
+
* conversation outlives the request". Both are deliberately written in the
|
|
6
|
+
* vocabulary every transport and every store already has — an input, a reply,
|
|
7
|
+
* a session id, a stored blob — and in nothing else.
|
|
8
|
+
*
|
|
9
|
+
* **The rule these types are written under:** no runtime, product or protocol
|
|
10
|
+
* gets a field, a name or an assumption here. A port shaped around one
|
|
11
|
+
* provider's request envelope stops being a port and becomes that provider's
|
|
12
|
+
* SDK with extra steps, and every later adapter pays for it. If a decision only
|
|
13
|
+
* makes sense for one place you might deploy, it belongs in the adapter for
|
|
14
|
+
* that place, not in this file. `nodeHost` is the first adapter and it does not
|
|
15
|
+
* get special treatment either: its paths, its status codes and its JSON body
|
|
16
|
+
* shape all live in `nodeHost.ts`, and nothing in this file knows they exist.
|
|
17
|
+
*
|
|
18
|
+
* Pattern: Ports & adapters (hexagonal). Role: the port side, exclusively.
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/hosting/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}
|
|
@@ -13,6 +13,7 @@ export { defineInstruction, type DefineInstructionOptions } from './factories/de
|
|
|
13
13
|
export { defineRelevanceHint, type RelevanceHintOptions } from './factories/defineRelevanceHint.js';
|
|
14
14
|
export { defineSkill, resolveSurfaceMode, type DefineSkillOptions, type SurfaceMode, type RefreshPolicy, type AutoActivateMode, } from './factories/defineSkill.js';
|
|
15
15
|
export { SkillRegistry, type SkillRegistryOptions } from './SkillRegistry.js';
|
|
16
|
+
export { skillsFromDir, type SkillsFromDirOptions } from './skillsFromDir.js';
|
|
16
17
|
export { buildListSkillsTool, buildReadSkillTool, type SkillToolPair } from './skillTools.js';
|
|
17
18
|
export { defineSteering, type DefineSteeringOptions } from './factories/defineSteering.js';
|
|
18
19
|
export { defineFact, type DefineFactOptions } from './factories/defineFact.js';
|
|
@@ -15,6 +15,10 @@ export { defineInstruction } from './factories/defineInstruction.js';
|
|
|
15
15
|
export { defineRelevanceHint } from './factories/defineRelevanceHint.js';
|
|
16
16
|
export { defineSkill, resolveSurfaceMode, } from './factories/defineSkill.js';
|
|
17
17
|
export { SkillRegistry } from './SkillRegistry.js';
|
|
18
|
+
// File-authored skills — a loader over `defineSkill`, not a second mechanism.
|
|
19
|
+
// Node-only (reads the filesystem); node:fs is imported lazily inside the call
|
|
20
|
+
// so this barrel stays safe to import from a browser bundle.
|
|
21
|
+
export { skillsFromDir } from './skillsFromDir.js';
|
|
18
22
|
// Skill-tool builders — used by SkillRegistry.toTools() and the Agent's
|
|
19
23
|
// auto-attach path. Exported so consumers building custom tool wiring
|
|
20
24
|
// (e.g., gatedTools chains) can compose the same `list_skills` /
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,+DAA+D;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,SAAS;AACT,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EACL,2BAA2B,GAE5B,MAAM,kCAAkC,CAAC;AAE1C,gFAAgF;AAChF,OAAO,EAAE,iBAAiB,EAAiC,MAAM,kCAAkC,CAAC;AACpG,OAAO,EAAE,mBAAmB,EAA6B,MAAM,oCAAoC,CAAC;AAEpG,OAAO,EACL,WAAW,EACX,kBAAkB,GAKnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,aAAa,EAA6B,MAAM,oBAAoB,CAAC;AAE9E,wEAAwE;AACxE,sEAAsE;AACtE,iEAAiE;AACjE,+BAA+B;AAC/B,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAsB,MAAM,iBAAiB,CAAC;AAE9F,OAAO,EAAE,cAAc,EAA8B,MAAM,+BAA+B,CAAC;AAE3F,OAAO,EAAE,UAAU,EAA0B,MAAM,2BAA2B,CAAC;AAE/E,6EAA6E;AAC7E,0EAA0E;AAC1E,qDAAqD;AACrD,OAAO,EACL,eAAe,GAGhB,MAAM,gCAAgC,CAAC;AAExC,4EAA4E;AAC5E,8EAA8E;AAC9E,OAAO,EACL,UAAU,EACV,WAAW,EACX,wBAAwB,GAoBzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,eAAe,EACf,WAAW,GAIZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,+DAA+D;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,SAAS;AACT,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EACL,2BAA2B,GAE5B,MAAM,kCAAkC,CAAC;AAE1C,gFAAgF;AAChF,OAAO,EAAE,iBAAiB,EAAiC,MAAM,kCAAkC,CAAC;AACpG,OAAO,EAAE,mBAAmB,EAA6B,MAAM,oCAAoC,CAAC;AAEpG,OAAO,EACL,WAAW,EACX,kBAAkB,GAKnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,aAAa,EAA6B,MAAM,oBAAoB,CAAC;AAE9E,8EAA8E;AAC9E,+EAA+E;AAC/E,6DAA6D;AAC7D,OAAO,EAAE,aAAa,EAA6B,MAAM,oBAAoB,CAAC;AAE9E,wEAAwE;AACxE,sEAAsE;AACtE,iEAAiE;AACjE,+BAA+B;AAC/B,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAsB,MAAM,iBAAiB,CAAC;AAE9F,OAAO,EAAE,cAAc,EAA8B,MAAM,+BAA+B,CAAC;AAE3F,OAAO,EAAE,UAAU,EAA0B,MAAM,2BAA2B,CAAC;AAE/E,6EAA6E;AAC7E,0EAA0E;AAC1E,qDAAqD;AACrD,OAAO,EACL,eAAe,GAGhB,MAAM,gCAAgC,CAAC;AAExC,4EAA4E;AAC5E,8EAA8E;AAC9E,OAAO,EACL,UAAU,EACV,WAAW,EACX,wBAAwB,GAoBzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,eAAe,EACf,WAAW,GAIZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skillsFromDir — load Skills that were AUTHORED AS FILES.
|
|
3
|
+
*
|
|
4
|
+
* The just-in-time half of skills already ships: `defineSkill` produces an
|
|
5
|
+
* Injection whose `description` is all the model sees until it calls
|
|
6
|
+
* `read_skill(<id>)`, at which point the body enters the context. What did not
|
|
7
|
+
* ship was the other half — a way to keep those bodies where prose belongs, in
|
|
8
|
+
* files, next to the code they describe, reviewable in a diff.
|
|
9
|
+
*
|
|
10
|
+
* This is that loader, and nothing more. It reads a directory of `SKILL.md`
|
|
11
|
+
* files and hands each one to `defineSkill`. The progressive disclosure, the
|
|
12
|
+
* activation tool, the slot routing — all unchanged, all upstream of here.
|
|
13
|
+
*
|
|
14
|
+
* skills/
|
|
15
|
+
* billing/SKILL.md
|
|
16
|
+
* refunds/SKILL.md
|
|
17
|
+
*
|
|
18
|
+
* ---
|
|
19
|
+
* name: billing
|
|
20
|
+
* description: Use for refunds, charges and billing questions.
|
|
21
|
+
* ---
|
|
22
|
+
* When handling billing: confirm identity first, then …
|
|
23
|
+
*
|
|
24
|
+
* const skills = await skillsFromDir('./skills');
|
|
25
|
+
* const agent = Agent.create({ provider, model }).skills({ list: () => skills }).build();
|
|
26
|
+
*
|
|
27
|
+
* The frontmatter is the disclosure stub (`name` + `description` — what the
|
|
28
|
+
* model reads when deciding), and everything after the closing fence is the
|
|
29
|
+
* body (what it reads after deciding). That is the same file convention Claude
|
|
30
|
+
* Code made familiar, so a skill folder is portable between the two.
|
|
31
|
+
*
|
|
32
|
+
* ── Why authorship is decided here, at load time ─────────────────────────────
|
|
33
|
+
* A Skill body is *instructions to a model*. Where it came from is therefore a
|
|
34
|
+
* security property, not a convenience: content fetched at run time from
|
|
35
|
+
* somewhere else is content someone else can change after you reviewed it.
|
|
36
|
+
* This loader accepts a local directory and nothing else — a URL is refused BY
|
|
37
|
+
* NAME rather than fetched — because "these files are mine" is a claim you can
|
|
38
|
+
* only make about a path on your own disk at build time. Each file is read
|
|
39
|
+
* ONCE, here; a later edit does not reach a run already in flight.
|
|
40
|
+
*
|
|
41
|
+
* Node-only. `node:fs/promises` and `node:path` are imported lazily inside the
|
|
42
|
+
* call, the same gating `lib/tool-lint/cli.ts` uses: this module is reachable
|
|
43
|
+
* from the `agentfootprint/injection-engine` barrel, and a TOP-LEVEL node:fs
|
|
44
|
+
* import detonates a browser bundle at module-eval even when nothing calls it.
|
|
45
|
+
*/
|
|
46
|
+
import type { Injection } from './types.js';
|
|
47
|
+
import { type SurfaceMode } from './factories/defineSkill.js';
|
|
48
|
+
export interface SkillsFromDirOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Override the activation tool name for every loaded skill. Defaults to
|
|
51
|
+
* `defineSkill`'s own default, `'read_skill'`.
|
|
52
|
+
*/
|
|
53
|
+
readonly viaToolName?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Where a loaded skill's body lands once activated. Defaults to
|
|
56
|
+
* `defineSkill`'s own default, `'auto'`. See {@link SurfaceMode}.
|
|
57
|
+
*/
|
|
58
|
+
readonly surfaceMode?: SurfaceMode;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Load every `SKILL.md` under `dir` as a Skill Injection.
|
|
62
|
+
*
|
|
63
|
+
* Two layouts are accepted, and they can be mixed:
|
|
64
|
+
* - `dir/<anything>/SKILL.md` — one folder per skill (the portable layout,
|
|
65
|
+
* and the one to prefer: the folder can hold the skill's other assets).
|
|
66
|
+
* - `dir/SKILL.md` — the directory IS one skill.
|
|
67
|
+
*
|
|
68
|
+
* The returned array is sorted by skill name, so a chart built from it is
|
|
69
|
+
* stable regardless of the order the filesystem happened to hand back.
|
|
70
|
+
*
|
|
71
|
+
* @param dir - A local filesystem path. A URL (or any `scheme://` string, or a
|
|
72
|
+
* UNC network path) is refused by name — see the module header for why.
|
|
73
|
+
* @param opts - Applied uniformly to every loaded skill.
|
|
74
|
+
*
|
|
75
|
+
* @throws when `dir` is not a local path, does not exist, is not a directory,
|
|
76
|
+
* or contains no `SKILL.md` at all; when a file's frontmatter is malformed
|
|
77
|
+
* (the message names the file); or when two files claim the same skill name
|
|
78
|
+
* (the message names both).
|
|
79
|
+
*/
|
|
80
|
+
export declare function skillsFromDir(dir: string, opts?: SkillsFromDirOptions): Promise<readonly Injection[]>;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skillsFromDir — load Skills that were AUTHORED AS FILES.
|
|
3
|
+
*
|
|
4
|
+
* The just-in-time half of skills already ships: `defineSkill` produces an
|
|
5
|
+
* Injection whose `description` is all the model sees until it calls
|
|
6
|
+
* `read_skill(<id>)`, at which point the body enters the context. What did not
|
|
7
|
+
* ship was the other half — a way to keep those bodies where prose belongs, in
|
|
8
|
+
* files, next to the code they describe, reviewable in a diff.
|
|
9
|
+
*
|
|
10
|
+
* This is that loader, and nothing more. It reads a directory of `SKILL.md`
|
|
11
|
+
* files and hands each one to `defineSkill`. The progressive disclosure, the
|
|
12
|
+
* activation tool, the slot routing — all unchanged, all upstream of here.
|
|
13
|
+
*
|
|
14
|
+
* skills/
|
|
15
|
+
* billing/SKILL.md
|
|
16
|
+
* refunds/SKILL.md
|
|
17
|
+
*
|
|
18
|
+
* ---
|
|
19
|
+
* name: billing
|
|
20
|
+
* description: Use for refunds, charges and billing questions.
|
|
21
|
+
* ---
|
|
22
|
+
* When handling billing: confirm identity first, then …
|
|
23
|
+
*
|
|
24
|
+
* const skills = await skillsFromDir('./skills');
|
|
25
|
+
* const agent = Agent.create({ provider, model }).skills({ list: () => skills }).build();
|
|
26
|
+
*
|
|
27
|
+
* The frontmatter is the disclosure stub (`name` + `description` — what the
|
|
28
|
+
* model reads when deciding), and everything after the closing fence is the
|
|
29
|
+
* body (what it reads after deciding). That is the same file convention Claude
|
|
30
|
+
* Code made familiar, so a skill folder is portable between the two.
|
|
31
|
+
*
|
|
32
|
+
* ── Why authorship is decided here, at load time ─────────────────────────────
|
|
33
|
+
* A Skill body is *instructions to a model*. Where it came from is therefore a
|
|
34
|
+
* security property, not a convenience: content fetched at run time from
|
|
35
|
+
* somewhere else is content someone else can change after you reviewed it.
|
|
36
|
+
* This loader accepts a local directory and nothing else — a URL is refused BY
|
|
37
|
+
* NAME rather than fetched — because "these files are mine" is a claim you can
|
|
38
|
+
* only make about a path on your own disk at build time. Each file is read
|
|
39
|
+
* ONCE, here; a later edit does not reach a run already in flight.
|
|
40
|
+
*
|
|
41
|
+
* Node-only. `node:fs/promises` and `node:path` are imported lazily inside the
|
|
42
|
+
* call, the same gating `lib/tool-lint/cli.ts` uses: this module is reachable
|
|
43
|
+
* from the `agentfootprint/injection-engine` barrel, and a TOP-LEVEL node:fs
|
|
44
|
+
* import detonates a browser bundle at module-eval even when nothing calls it.
|
|
45
|
+
*/
|
|
46
|
+
import { defineSkill } from './factories/defineSkill.js';
|
|
47
|
+
/** The file name every skill folder is expected to use. */
|
|
48
|
+
const SKILL_FILE = 'SKILL.md';
|
|
49
|
+
/**
|
|
50
|
+
* Skill ids ride into `read_skill`'s `enum` and into the skill-graph's node
|
|
51
|
+
* ids. The house tool-name charset keeps them quotable in a prompt, comparable
|
|
52
|
+
* as plain strings, and safe as an object key.
|
|
53
|
+
*/
|
|
54
|
+
const SKILL_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
55
|
+
/** Anything of the form `scheme://…` — http, https, s3, git+ssh, file, … */
|
|
56
|
+
const URL_LIKE_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//;
|
|
57
|
+
/**
|
|
58
|
+
* Load every `SKILL.md` under `dir` as a Skill Injection.
|
|
59
|
+
*
|
|
60
|
+
* Two layouts are accepted, and they can be mixed:
|
|
61
|
+
* - `dir/<anything>/SKILL.md` — one folder per skill (the portable layout,
|
|
62
|
+
* and the one to prefer: the folder can hold the skill's other assets).
|
|
63
|
+
* - `dir/SKILL.md` — the directory IS one skill.
|
|
64
|
+
*
|
|
65
|
+
* The returned array is sorted by skill name, so a chart built from it is
|
|
66
|
+
* stable regardless of the order the filesystem happened to hand back.
|
|
67
|
+
*
|
|
68
|
+
* @param dir - A local filesystem path. A URL (or any `scheme://` string, or a
|
|
69
|
+
* UNC network path) is refused by name — see the module header for why.
|
|
70
|
+
* @param opts - Applied uniformly to every loaded skill.
|
|
71
|
+
*
|
|
72
|
+
* @throws when `dir` is not a local path, does not exist, is not a directory,
|
|
73
|
+
* or contains no `SKILL.md` at all; when a file's frontmatter is malformed
|
|
74
|
+
* (the message names the file); or when two files claim the same skill name
|
|
75
|
+
* (the message names both).
|
|
76
|
+
*/
|
|
77
|
+
export async function skillsFromDir(dir, opts = {}) {
|
|
78
|
+
assertLocalDirectoryArgument(dir);
|
|
79
|
+
// Lazy node imports (browser-compat) — see module header.
|
|
80
|
+
const { readdir, readFile, stat } = await import('node:fs/promises');
|
|
81
|
+
const { join } = await import('node:path');
|
|
82
|
+
let isDirectory = false;
|
|
83
|
+
try {
|
|
84
|
+
isDirectory = (await stat(dir)).isDirectory();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
throw new Error(`skillsFromDir: '${dir}' does not exist. Pass the directory that holds your ` +
|
|
88
|
+
`${SKILL_FILE} folders.`);
|
|
89
|
+
}
|
|
90
|
+
if (!isDirectory) {
|
|
91
|
+
throw new Error(`skillsFromDir: '${dir}' is a file, not a directory. Pass the directory that holds ` +
|
|
92
|
+
`your ${SKILL_FILE} folders.`);
|
|
93
|
+
}
|
|
94
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
95
|
+
const files = [];
|
|
96
|
+
// `dir/SKILL.md` — the whole directory is one skill.
|
|
97
|
+
if (entries.some((e) => e.isFile() && e.name === SKILL_FILE)) {
|
|
98
|
+
files.push(join(dir, SKILL_FILE));
|
|
99
|
+
}
|
|
100
|
+
// `dir/<folder>/SKILL.md` — the portable layout.
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
if (!entry.isDirectory())
|
|
103
|
+
continue;
|
|
104
|
+
const candidate = join(dir, entry.name, SKILL_FILE);
|
|
105
|
+
try {
|
|
106
|
+
if ((await stat(candidate)).isFile())
|
|
107
|
+
files.push(candidate);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// A subdirectory without a SKILL.md is not an error — a skill folder can
|
|
111
|
+
// sit next to assets, fixtures, or anything else the author keeps here.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (files.length === 0) {
|
|
115
|
+
throw new Error(`skillsFromDir: no ${SKILL_FILE} found under '${dir}'. Expected '${dir}/<skill>/${SKILL_FILE}' ` +
|
|
116
|
+
`or '${dir}/${SKILL_FILE}'.`);
|
|
117
|
+
}
|
|
118
|
+
const parsed = [];
|
|
119
|
+
for (const file of files.sort()) {
|
|
120
|
+
parsed.push(parseSkillFile(await readFile(file, 'utf8'), file));
|
|
121
|
+
}
|
|
122
|
+
// Collisions are refused naming BOTH files: with only the id in the message
|
|
123
|
+
// you would know a duplicate exists and still have to go find the pair.
|
|
124
|
+
const byName = new Map();
|
|
125
|
+
for (const skill of parsed) {
|
|
126
|
+
const clash = byName.get(skill.name);
|
|
127
|
+
if (clash) {
|
|
128
|
+
throw new Error(`skillsFromDir: two files claim the skill name '${skill.name}' — '${clash.file}' and ` +
|
|
129
|
+
`'${skill.file}'. Skill ids must be unique (read_skill dispatches by id); rename one.`);
|
|
130
|
+
}
|
|
131
|
+
byName.set(skill.name, skill);
|
|
132
|
+
}
|
|
133
|
+
return parsed
|
|
134
|
+
.slice()
|
|
135
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
136
|
+
.map((skill) => defineSkill({
|
|
137
|
+
id: skill.name,
|
|
138
|
+
description: skill.description,
|
|
139
|
+
body: skill.body,
|
|
140
|
+
...(opts.viaToolName !== undefined && { viaToolName: opts.viaToolName }),
|
|
141
|
+
...(opts.surfaceMode !== undefined && { surfaceMode: opts.surfaceMode }),
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
// ─── Authorship guard ──────────────────────────────────────────────
|
|
145
|
+
/**
|
|
146
|
+
* Refuse anything that is not a local path, BY NAME. The message quotes what
|
|
147
|
+
* was passed, because the whole point is that the reader can see the thing
|
|
148
|
+
* that was rejected.
|
|
149
|
+
*/
|
|
150
|
+
function assertLocalDirectoryArgument(dir) {
|
|
151
|
+
if (typeof dir !== 'string' || dir.trim().length === 0) {
|
|
152
|
+
throw new Error(`skillsFromDir: expected a local directory path, got ${JSON.stringify(dir)}.`);
|
|
153
|
+
}
|
|
154
|
+
const scheme = URL_LIKE_RE.exec(dir);
|
|
155
|
+
if (scheme) {
|
|
156
|
+
throw new Error(`skillsFromDir: '${dir}' is a ${scheme[0].slice(0, -3)} URL, not a local directory. ` +
|
|
157
|
+
`Skill bodies are instructions to a model, so this loader only reads files you own ` +
|
|
158
|
+
`at build time — fetch remote content yourself, review it, and pass defineSkill(...) ` +
|
|
159
|
+
`the result.`);
|
|
160
|
+
}
|
|
161
|
+
if (dir.startsWith('\\\\')) {
|
|
162
|
+
throw new Error(`skillsFromDir: '${dir}' is a network (UNC) path, not a local directory. ` +
|
|
163
|
+
`Skill bodies are instructions to a model, so this loader only reads files you own ` +
|
|
164
|
+
`at build time.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ─── Frontmatter ───────────────────────────────────────────────────
|
|
168
|
+
/**
|
|
169
|
+
* Parse one `SKILL.md`. Every refusal names the file — a loader that says
|
|
170
|
+
* "malformed frontmatter" over a directory of forty files has told you nothing.
|
|
171
|
+
*
|
|
172
|
+
* The grammar is deliberately small: an opening `---` line, `key: value` lines
|
|
173
|
+
* (`#` comments and blank lines skipped, single/double quotes stripped), a
|
|
174
|
+
* closing `---` line, then the body. Keys other than `name` and `description`
|
|
175
|
+
* are IGNORED rather than rejected, so a file carrying extra frontmatter for
|
|
176
|
+
* another tool still loads here.
|
|
177
|
+
*/
|
|
178
|
+
function parseSkillFile(raw, file) {
|
|
179
|
+
// Strip a UTF-8 BOM — an editor artifact, not the author's intent.
|
|
180
|
+
const text = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
|
|
181
|
+
const lines = text.split(/\r?\n/);
|
|
182
|
+
if (lines[0]?.trim() !== '---') {
|
|
183
|
+
throw new Error(`skillsFromDir: '${file}' does not start with a '---' frontmatter block. ` +
|
|
184
|
+
`Expected:\n---\nname: my-skill\ndescription: When to use it.\n---\n<the body>`);
|
|
185
|
+
}
|
|
186
|
+
const closingIndex = lines.findIndex((line, i) => i > 0 && line.trim() === '---');
|
|
187
|
+
if (closingIndex === -1) {
|
|
188
|
+
throw new Error(`skillsFromDir: '${file}' opens a '---' frontmatter block that is never closed. ` +
|
|
189
|
+
`Add a '---' line after the last frontmatter key.`);
|
|
190
|
+
}
|
|
191
|
+
const fields = new Map();
|
|
192
|
+
for (let i = 1; i < closingIndex; i++) {
|
|
193
|
+
const line = lines[i] ?? '';
|
|
194
|
+
if (line.trim().length === 0 || line.trimStart().startsWith('#'))
|
|
195
|
+
continue;
|
|
196
|
+
const separator = line.indexOf(':');
|
|
197
|
+
if (separator === -1) {
|
|
198
|
+
throw new Error(`skillsFromDir: '${file}' frontmatter line ${i + 1} is not 'key: value' — got ` +
|
|
199
|
+
`${JSON.stringify(line)}.`);
|
|
200
|
+
}
|
|
201
|
+
const key = line.slice(0, separator).trim();
|
|
202
|
+
if (key.length === 0) {
|
|
203
|
+
throw new Error(`skillsFromDir: '${file}' frontmatter line ${i + 1} has an empty key — got ` +
|
|
204
|
+
`${JSON.stringify(line)}.`);
|
|
205
|
+
}
|
|
206
|
+
fields.set(key, unquote(line.slice(separator + 1).trim()));
|
|
207
|
+
}
|
|
208
|
+
const name = fields.get('name') ?? '';
|
|
209
|
+
const description = fields.get('description') ?? '';
|
|
210
|
+
const body = lines
|
|
211
|
+
.slice(closingIndex + 1)
|
|
212
|
+
.join('\n')
|
|
213
|
+
.trim();
|
|
214
|
+
if (name.length === 0) {
|
|
215
|
+
throw new Error(`skillsFromDir: '${file}' frontmatter is missing 'name'. It becomes the skill id the ` +
|
|
216
|
+
`model passes to read_skill.`);
|
|
217
|
+
}
|
|
218
|
+
if (!SKILL_NAME_RE.test(name)) {
|
|
219
|
+
throw new Error(`skillsFromDir: '${file}' frontmatter name '${name}' must match ` +
|
|
220
|
+
`/^[a-zA-Z0-9_-]{1,64}$/ — it is the id the model passes to read_skill.`);
|
|
221
|
+
}
|
|
222
|
+
if (description.length === 0) {
|
|
223
|
+
throw new Error(`skillsFromDir: '${file}' frontmatter is missing 'description'. It is the ONLY thing ` +
|
|
224
|
+
`the model reads when deciding whether to open this skill.`);
|
|
225
|
+
}
|
|
226
|
+
if (body.length === 0) {
|
|
227
|
+
throw new Error(`skillsFromDir: '${file}' has no body after the closing '---'. The body is what the ` +
|
|
228
|
+
`model reads once it activates the skill.`);
|
|
229
|
+
}
|
|
230
|
+
return { name, description, body, file };
|
|
231
|
+
}
|
|
232
|
+
/** Strip one layer of matching single or double quotes from a YAML-ish scalar. */
|
|
233
|
+
function unquote(value) {
|
|
234
|
+
if (value.length >= 2) {
|
|
235
|
+
const first = value[0];
|
|
236
|
+
const last = value[value.length - 1];
|
|
237
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
238
|
+
return value.slice(1, -1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=skillsFromDir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillsFromDir.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/skillsFromDir.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP — Model Context Protocol
|
|
3
|
-
* server
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* MCP — Model Context Protocol, both directions. `mcpClient` connects to
|
|
3
|
+
* someone else's MCP server and registers its tools on your Agent;
|
|
4
|
+
* `mcpServe` exposes your own tools AS an MCP server so any MCP client
|
|
5
|
+
* can call them.
|
|
6
6
|
*/
|
|
7
7
|
export { mcpClient } from './mcpClient.js';
|
|
8
|
+
export { mcpServe } from './mcpServe.js';
|
|
8
9
|
export { mockMcpClient, type MockMcpClientOptions, type MockMcpTool } from './mockMcpClient.js';
|
|
9
|
-
export type { McpClient, McpClientOptions, McpHttpTransport, McpStdioTransport, McpTransport, McpSdkClient, } from './types.js';
|
|
10
|
+
export type { McpClient, McpClientOptions, McpHttpTransport, McpStdioTransport, McpTransport, McpSdkClient, McpServeOptions, McpServeHandle, McpServeTransport, McpHttpServeTransport, McpStdioServeTransport, McpSdkServer, } from './types.js';
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP — Model Context Protocol
|
|
3
|
-
* server
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* MCP — Model Context Protocol, both directions. `mcpClient` connects to
|
|
3
|
+
* someone else's MCP server and registers its tools on your Agent;
|
|
4
|
+
* `mcpServe` exposes your own tools AS an MCP server so any MCP client
|
|
5
|
+
* can call them.
|
|
6
6
|
*/
|
|
7
7
|
export { mcpClient } from './mcpClient.js';
|
|
8
|
+
export { mcpServe } from './mcpServe.js';
|
|
8
9
|
export { mockMcpClient } from './mockMcpClient.js';
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAA+C,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAA+C,MAAM,oBAAoB,CAAC"}
|