@zenera/neo 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/agent.d.ts +66 -0
  4. package/dist/agent.js +97 -0
  5. package/dist/architecture.d.ts +74 -0
  6. package/dist/architecture.js +105 -0
  7. package/dist/embedding.d.ts +60 -0
  8. package/dist/embedding.js +21 -0
  9. package/dist/embeddings/gemini.d.ts +24 -0
  10. package/dist/embeddings/gemini.js +71 -0
  11. package/dist/embeddings/index.d.ts +4 -0
  12. package/dist/embeddings/index.js +4 -0
  13. package/dist/embeddings/openai.d.ts +18 -0
  14. package/dist/embeddings/openai.js +27 -0
  15. package/dist/embeddings/openrouter.d.ts +21 -0
  16. package/dist/embeddings/openrouter.js +54 -0
  17. package/dist/events.d.ts +144 -0
  18. package/dist/events.js +110 -0
  19. package/dist/ids.d.ts +14 -0
  20. package/dist/ids.js +33 -0
  21. package/dist/index.d.ts +25 -0
  22. package/dist/index.js +25 -0
  23. package/dist/inspect/client.d.ts +2 -0
  24. package/dist/inspect/client.js +1326 -0
  25. package/dist/inspect/css.d.ts +2 -0
  26. package/dist/inspect/css.js +164 -0
  27. package/dist/inspect/index.d.ts +9 -0
  28. package/dist/inspect/index.js +12 -0
  29. package/dist/inspect/page.d.ts +3 -0
  30. package/dist/inspect/page.js +93 -0
  31. package/dist/inspect/report.d.ts +54 -0
  32. package/dist/inspect/report.js +62 -0
  33. package/dist/kernel.d.ts +178 -0
  34. package/dist/kernel.js +929 -0
  35. package/dist/memory-stores/factory.d.ts +15 -0
  36. package/dist/memory-stores/factory.js +29 -0
  37. package/dist/memory-stores/file.d.ts +37 -0
  38. package/dist/memory-stores/file.js +174 -0
  39. package/dist/memory-stores/in-memory.d.ts +17 -0
  40. package/dist/memory-stores/in-memory.js +80 -0
  41. package/dist/memory-stores/index.d.ts +4 -0
  42. package/dist/memory-stores/index.js +5 -0
  43. package/dist/memory.d.ts +115 -0
  44. package/dist/memory.js +213 -0
  45. package/dist/model.d.ts +25 -0
  46. package/dist/model.js +2 -0
  47. package/dist/models/anthropic.d.ts +36 -0
  48. package/dist/models/anthropic.js +293 -0
  49. package/dist/models/factory.d.ts +202 -0
  50. package/dist/models/factory.js +533 -0
  51. package/dist/models/gemini.d.ts +45 -0
  52. package/dist/models/gemini.js +368 -0
  53. package/dist/models/index.d.ts +7 -0
  54. package/dist/models/index.js +7 -0
  55. package/dist/models/openai-chat.d.ts +19 -0
  56. package/dist/models/openai-chat.js +203 -0
  57. package/dist/models/openai-responses.d.ts +31 -0
  58. package/dist/models/openai-responses.js +258 -0
  59. package/dist/models/openrouter.d.ts +44 -0
  60. package/dist/models/openrouter.js +230 -0
  61. package/dist/payload-stores/factory.d.ts +15 -0
  62. package/dist/payload-stores/factory.js +30 -0
  63. package/dist/payload-stores/file.d.ts +29 -0
  64. package/dist/payload-stores/file.js +112 -0
  65. package/dist/payload-stores/in-memory.d.ts +17 -0
  66. package/dist/payload-stores/in-memory.js +40 -0
  67. package/dist/payload-stores/index.d.ts +4 -0
  68. package/dist/payload-stores/index.js +5 -0
  69. package/dist/payload.d.ts +89 -0
  70. package/dist/payload.js +181 -0
  71. package/dist/project/config.d.ts +302 -0
  72. package/dist/project/config.js +290 -0
  73. package/dist/project/index.d.ts +4 -0
  74. package/dist/project/index.js +4 -0
  75. package/dist/project/load.d.ts +142 -0
  76. package/dist/project/load.js +430 -0
  77. package/dist/project/refs.d.ts +23 -0
  78. package/dist/project/refs.js +70 -0
  79. package/dist/prompt.d.ts +66 -0
  80. package/dist/prompt.js +62 -0
  81. package/dist/runner.d.ts +103 -0
  82. package/dist/runner.js +537 -0
  83. package/dist/services.d.ts +23 -0
  84. package/dist/services.js +44 -0
  85. package/dist/skill-providers/factory.d.ts +16 -0
  86. package/dist/skill-providers/factory.js +26 -0
  87. package/dist/skill-providers/file.d.ts +44 -0
  88. package/dist/skill-providers/file.js +209 -0
  89. package/dist/skill-providers/index.d.ts +4 -0
  90. package/dist/skill-providers/index.js +5 -0
  91. package/dist/skill-providers/static.d.ts +13 -0
  92. package/dist/skill-providers/static.js +47 -0
  93. package/dist/skills.d.ts +102 -0
  94. package/dist/skills.js +232 -0
  95. package/dist/state.d.ts +86 -0
  96. package/dist/state.js +49 -0
  97. package/dist/tools/exa.d.ts +23 -0
  98. package/dist/tools/exa.js +389 -0
  99. package/dist/tools/index.d.ts +4 -0
  100. package/dist/tools/index.js +6 -0
  101. package/dist/tools/sandbox.d.ts +172 -0
  102. package/dist/tools/sandbox.js +602 -0
  103. package/dist/tools/workspace.d.ts +53 -0
  104. package/dist/tools/workspace.js +1034 -0
  105. package/dist/trajectory.d.ts +213 -0
  106. package/dist/trajectory.js +232 -0
  107. package/dist/types.d.ts +195 -0
  108. package/dist/types.js +134 -0
  109. package/package.json +98 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrey Ryabov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @zenera/neo
2
+
3
+ **A multi-agent runtime for Node.js: agents, models, tools, skills, memory and
4
+ an append-only trajectory.**
5
+
6
+ [![npm](https://img.shields.io/npm/v/@zenera/neo.svg)](https://www.npmjs.com/package/@zenera/neo)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
+ [![Node](https://img.shields.io/badge/node-%E2%89%A524-brightgreen.svg)](https://nodejs.org)
9
+
10
+ > Part of [ZeneraNeo](https://github.com/andreyryabov/ZeneraNeo). This is an
11
+ > open-source side project for experimentation and chore work — **not** the
12
+ > official Zenera AI Platform. It carries no support or stability promises.
13
+
14
+ ## Install
15
+
16
+ Node.js 24+, ESM only. Runtime dependencies are `yaml` and `zod` and nothing
17
+ else; the four vendor SDKs are **optional peer dependencies**, not loaded until
18
+ a client for that vendor is first built, so an OpenAI-only application never
19
+ pays for the others.
20
+
21
+ ```sh
22
+ npm i @zenera/neo openai
23
+ npm i @zenera/neo @anthropic-ai/sdk
24
+ npm i @zenera/neo @google/genai
25
+ npm i @zenera/neo @openrouter/sdk
26
+ ```
27
+
28
+ ## Use
29
+
30
+ There are two ways in, and they meet in the middle: declare the agents in code,
31
+ or load them from a folder.
32
+
33
+ ### In code
34
+
35
+ An agent is an instruction, a model, some tools, and who it may hand work to.
36
+ That is the whole list — there is no orchestration layer underneath.
37
+
38
+ ```ts
39
+ import { AgentRunner, createModel, tool } from '@zenera/neo';
40
+
41
+ const getWeather = tool<{ city: string }>({
42
+ name: 'get_weather',
43
+ description: 'Current weather for a city.',
44
+ parameters: {
45
+ type: 'object',
46
+ properties: { city: { type: 'string' } },
47
+ required: ['city'],
48
+ additionalProperties: false,
49
+ },
50
+ execute: ({ city }) => ({ city, tempC: 21, sky: 'clear' }),
51
+ });
52
+
53
+ const runner = new AgentRunner({ model: createModel('openai:gpt-5.4-mini') });
54
+
55
+ const planner = runner.agent({
56
+ name: 'planner',
57
+ description: 'Turns a request into a concrete day-by-day plan.',
58
+ instructions: 'Answer with a plan. Check the weather before you commit to a day.',
59
+ tools: [getWeather],
60
+ });
61
+
62
+ const result = await runner.run(planner, 'Two days in Rome, outdoors where possible.');
63
+ console.log(result.output);
64
+ ```
65
+
66
+ `run()` returns a handle: `await` it for the answer, or iterate it for the
67
+ stream — token deltas plus a checkpoint for every model call, tool call,
68
+ handoff, fork and join.
69
+
70
+ ```ts
71
+ for await (const ev of runner.run(planner, 'Two days in Rome.')) {
72
+ if (ev.type === 'text_delta') process.stdout.write(ev.delta);
73
+ }
74
+ ```
75
+
76
+ A second agent and a handoff is one more `runner.agent({ … })` plus
77
+ `handoffs: [planner]` on the first. Skills, memory scopes and payload stores are
78
+ declared once on the runner and referenced by id from each agent allowed to use
79
+ them.
80
+
81
+ ### From a folder
82
+
83
+ An agentic project is a folder — prompts, agent wiring, skills and tool
84
+ selections are Markdown and YAML files, not code buried inside an application.
85
+ Load one and run it:
86
+
87
+ ```ts
88
+ import { loadProject } from '@zenera/neo';
89
+
90
+ const project = await loadProject('./my-project', { tools: [lookupPolicy] });
91
+
92
+ for await (const ev of project.run('Water damage, policy NM-448127.')) {
93
+ // stream events: thinking, text, tool calls, handoffs, usage
94
+ }
95
+ ```
96
+
97
+ The whole system is an `agents.yaml`:
98
+
99
+ ```yaml
100
+ default: intake
101
+ model: anthropic:claude-sonnet-4-5
102
+ skills: agents/skills
103
+
104
+ agents:
105
+ - name: intake
106
+ description: Takes the first message and routes the case.
107
+ system: agents/prompts/intake.md
108
+ tools: [policy_lookup]
109
+ handoffs: [adjuster]
110
+
111
+ - name: adjuster
112
+ description: Weighs the written policy against the case and explains the outcome.
113
+ system: agents/prompts/adjuster.md
114
+ model: openai:gpt-5.4
115
+ tools: [workspace:*, sandbox:*]
116
+ skills:
117
+ discovery: index # the model loads what the case needs
118
+ preload: [house_style] # always on, from turn one
119
+ ```
120
+
121
+ It is validated strictly at load: an unknown tool, a handoff to nobody, a
122
+ missing prompt file — each fails immediately, naming the offending key, instead
123
+ of surfacing three turns into a run as a confused model.
124
+
125
+ ## What it gives you
126
+
127
+ - **Agents** — an instruction, a model, tools, skills, plus handoffs and fork.
128
+ There is no hidden orchestration layer.
129
+ - **Models** — OpenAI, Anthropic, Google/Vertex and OpenRouter behind one
130
+ interface, each through its own SDK, plus any OpenAI-compatible endpoint.
131
+ Named providers and model aliases resolve from config or from the host.
132
+ - **Tools** — plain typed functions, selectable per agent by name, group or
133
+ wildcard. Workspace file tools and a Podman sandbox ship with the library.
134
+ - **Skills** — instruction bundles discovered and loaded on demand instead of
135
+ permanently occupying the prompt, optionally owning their own tools.
136
+ - **Memory** — pluggable `MemoryStore`s, with scopes deciding what is private to
137
+ an agent and what is shared.
138
+ - **Trajectory** — an append-only log of everything that happened. Provider
139
+ messages are a projection of it, and compaction _covers_ nodes rather than
140
+ deleting them, so the audit trail survives context pressure.
141
+ - **Inspection** — `renderReportHtml` turns any run into one self-contained HTML
142
+ file: the graph, every request, every token.
143
+
144
+ ## Entry points
145
+
146
+ | Specifier | Contents |
147
+ | ----------------------------- | ----------------------------------------- |
148
+ | `@zenera/neo` | agents, models, tools, kernel, inspection |
149
+ | `@zenera/neo/project` | loading a project folder |
150
+ | `@zenera/neo/skill-providers` | skill discovery and loading |
151
+ | `@zenera/neo/memory-stores` | `MemoryStore` implementations |
152
+ | `@zenera/neo/payload-stores` | payload persistence |
153
+
154
+ ## Documentation
155
+
156
+ [DESIGN.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/DESIGN.md) ·
157
+ [docs/projects.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/projects.md) ·
158
+ [docs/agents-yaml.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/agents-yaml.md)
159
+ · and
160
+ [`examples/`](https://github.com/andreyryabov/ZeneraNeo/tree/main/examples) for
161
+ eight worked demos, from a single agent to a project loaded entirely from a
162
+ folder.
163
+
164
+ ## The rest of the family
165
+
166
+ | Package | What it is |
167
+ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
168
+ | [`@zenera/cli`](https://github.com/andreyryabov/ZeneraNeo/blob/main/packages/cli/README.md) | `zen` — this runtime on the command line, with a TUI |
169
+ | [`@zenera/faker`](https://github.com/andreyryabov/ZeneraNeo/blob/main/packages/faker/README.md) | `zen faker` — a mock API from an openapi/swagger document |
170
+ | [`@zenera/rag`](https://github.com/andreyryabov/ZeneraNeo/blob/main/packages/rag/README.md) | `zen rag` — an API description as a searchable graph, + tools |
171
+
172
+ `@zenera/rag` also exports tools you can hand straight to `loadProject`, so an
173
+ agent can search an API it has never read.
174
+
175
+ ## License
176
+
177
+ Early days and moving fast — issues, questions and pull requests are welcome.
178
+ [MIT](LICENSE).
@@ -0,0 +1,66 @@
1
+ import type { MemoryBinding, ResolvedBinding } from './memory.ts';
2
+ import type { Model } from './model.ts';
3
+ import type { Instructions } from './prompt.ts';
4
+ import type { SkillBinding } from './skills.ts';
5
+ import { type AnyTool } from './types.ts';
6
+ export interface ForkOptions {
7
+ /** agents a branch may run; defaults to any registered agent */
8
+ agents?: string[];
9
+ maxBranches?: number;
10
+ }
11
+ export interface AgentOptions<TCtx = unknown> {
12
+ name: string;
13
+ description?: string;
14
+ /**
15
+ * A string, a function of the run's immutable config, or an ordered list of
16
+ * both plus files. Composed into one prompt — and the files it names are
17
+ * recorded on the `system_prompt` node, so the trajectory says which
18
+ * document to edit.
19
+ */
20
+ instructions?: Instructions<TCtx>;
21
+ model?: Model;
22
+ tools?: AnyTool<TCtx>[];
23
+ /** agents this one may hand the conversation over to (by name or instance) */
24
+ handoffs?: (string | Agent<TCtx>)[];
25
+ /** long-lived memory spaces this agent may read and/or write */
26
+ memory?: MemoryBinding<TCtx>[];
27
+ /** on-demand instruction bundles */
28
+ skills?: SkillBinding;
29
+ /** opt-in to parallel sub-agents */
30
+ fork?: ForkOptions;
31
+ }
32
+ /**
33
+ * Note what is *not* here any more: `maxTurns`. A run ends on a final answer,
34
+ * an abort or an unrecoverable error — never because a counter ran out.
35
+ */
36
+ export declare class Agent<TCtx = unknown> {
37
+ readonly name: string;
38
+ readonly description?: string;
39
+ readonly instructions?: Instructions<TCtx>;
40
+ readonly model?: Model;
41
+ readonly tools: AnyTool<TCtx>[];
42
+ readonly handoffs: string[];
43
+ readonly memory: MemoryBinding<TCtx>[];
44
+ readonly skills?: SkillBinding;
45
+ readonly fork?: ForkOptions;
46
+ constructor(opts: AgentOptions<TCtx>);
47
+ /**
48
+ * Memory bindings with their scope resolved against the run context. The
49
+ * resolved value is what gets recorded in the trajectory, so a resumed run
50
+ * cannot drift into another user's space.
51
+ */
52
+ memoryBindings(ctx: TCtx): ResolvedBinding[];
53
+ }
54
+ export declare class AgentRegistry<TCtx = unknown> {
55
+ readonly agents: Map<string, Agent<TCtx>>;
56
+ /** Registers agents; chainable. */
57
+ add(...agents: Agent<TCtx>[]): this;
58
+ /** Creates + registers + returns an agent in one call. */
59
+ agent(opts: AgentOptions<TCtx>): Agent<TCtx>;
60
+ find(name: string): Agent<TCtx> | undefined;
61
+ get(name: string): Agent<TCtx>;
62
+ names(): string[];
63
+ }
64
+ export declare function handoffTool<TCtx>(target: string, description?: string): AnyTool<TCtx>;
65
+ export declare function handoffTarget(toolName: string): string | undefined;
66
+ //# sourceMappingURL=agent.d.ts.map
package/dist/agent.js ADDED
@@ -0,0 +1,97 @@
1
+ import { HANDOFF_PREFIX } from "./types.js";
2
+ /**
3
+ * Note what is *not* here any more: `maxTurns`. A run ends on a final answer,
4
+ * an abort or an unrecoverable error — never because a counter ran out.
5
+ */
6
+ export class Agent {
7
+ name;
8
+ description;
9
+ instructions;
10
+ model;
11
+ tools;
12
+ handoffs;
13
+ memory;
14
+ skills;
15
+ fork;
16
+ constructor(opts) {
17
+ this.name = opts.name;
18
+ this.description = opts.description;
19
+ this.instructions = opts.instructions;
20
+ this.model = opts.model;
21
+ this.tools = opts.tools ?? [];
22
+ this.handoffs = (opts.handoffs ?? []).map((h) => (typeof h === 'string' ? h : h.name));
23
+ this.memory = opts.memory ?? [];
24
+ this.skills = opts.skills;
25
+ this.fork = opts.fork;
26
+ }
27
+ /**
28
+ * Memory bindings with their scope resolved against the run context. The
29
+ * resolved value is what gets recorded in the trajectory, so a resumed run
30
+ * cannot drift into another user's space.
31
+ */
32
+ memoryBindings(ctx) {
33
+ return this.memory.map((b) => ({
34
+ store: b.store,
35
+ scope: typeof b.scope === 'function' ? b.scope(ctx) : (b.scope ?? `agent:${this.name}`),
36
+ access: b.access,
37
+ autoRecall: b.autoRecall,
38
+ }));
39
+ }
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // Registry
43
+ // ---------------------------------------------------------------------------
44
+ export class AgentRegistry {
45
+ agents = new Map();
46
+ /** Registers agents; chainable. */
47
+ add(...agents) {
48
+ for (const a of agents) {
49
+ if (this.agents.has(a.name)) {
50
+ throw new Error(`duplicate agent name: ${a.name}`);
51
+ }
52
+ this.agents.set(a.name, a);
53
+ }
54
+ return this;
55
+ }
56
+ /** Creates + registers + returns an agent in one call. */
57
+ agent(opts) {
58
+ const a = new Agent(opts);
59
+ this.add(a);
60
+ return a;
61
+ }
62
+ find(name) {
63
+ return this.agents.get(name);
64
+ }
65
+ get(name) {
66
+ const a = this.agents.get(name);
67
+ if (!a) {
68
+ throw new Error(`unknown agent: ${name} (known: ${[...this.agents.keys()].join(', ')})`);
69
+ }
70
+ return a;
71
+ }
72
+ names() {
73
+ return [...this.agents.keys()];
74
+ }
75
+ }
76
+ // Hand-offs are modelled as ordinary tools named `transfer_to_<agent>`, so the
77
+ // model picks the next agent with the same mechanism it uses for everything
78
+ // else. The kernel recognises them purely by this prefix.
79
+ export function handoffTool(target, description) {
80
+ return {
81
+ name: `${HANDOFF_PREFIX}${target}`,
82
+ description: description ?? `Hand the conversation over to the "${target}" agent.`,
83
+ parameters: {
84
+ type: 'object',
85
+ properties: { reason: { type: 'string', description: 'Why the hand-off is needed.' } },
86
+ required: [],
87
+ additionalProperties: false,
88
+ },
89
+ // The switch itself happens in the kernel; this body only produces the
90
+ // tool message that acknowledges the call to the model.
91
+ execute: () => `transferred to ${target}`,
92
+ };
93
+ }
94
+ export function handoffTarget(toolName) {
95
+ return toolName.startsWith(HANDOFF_PREFIX) ? toolName.slice(HANDOFF_PREFIX.length) : undefined;
96
+ }
97
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1,74 @@
1
+ import type { AgentRegistry } from './agent.ts';
2
+ import type { Model } from './model.ts';
3
+ import type { Services } from './services.ts';
4
+ import type { SkillBinding } from './skills.ts';
5
+ export interface ArchTool {
6
+ name: string;
7
+ description?: string;
8
+ /** the skill that unlocks it; absent for tools the agent always carries */
9
+ skill?: string;
10
+ }
11
+ export interface ArchSkill {
12
+ name: string;
13
+ description?: string;
14
+ version?: string;
15
+ /** loaded before the first model call, so it is never in the rendered index */
16
+ preload?: boolean;
17
+ /** tools this skill unlocks, by name */
18
+ toolNames?: string[];
19
+ }
20
+ export interface ArchMemory {
21
+ store: string;
22
+ /** `(per run)` when the binding resolves its scope from the run context */
23
+ scope: string;
24
+ access: 'read' | 'read-write';
25
+ autoRecall?: boolean;
26
+ }
27
+ export interface ArchAgent {
28
+ name: string;
29
+ description?: string;
30
+ /** model id, from the agent or inherited from the runner */
31
+ model?: string;
32
+ /** true when the id above came from the runner rather than the agent */
33
+ inheritedModel?: boolean;
34
+ tools: ArchTool[];
35
+ /** agent names this one may transfer to */
36
+ handoffs: string[];
37
+ skills?: {
38
+ provider: string;
39
+ discovery: SkillBinding['discovery'];
40
+ /** empty when the provider is not registered with the services */
41
+ catalog: ArchSkill[];
42
+ /** the binding names a provider the services do not know */
43
+ unresolved?: boolean;
44
+ };
45
+ memory: ArchMemory[];
46
+ fork?: {
47
+ agents?: string[];
48
+ maxBranches?: number;
49
+ };
50
+ }
51
+ export interface Architecture {
52
+ /**
53
+ * `declared` comes from the registry and is complete. `observed` is
54
+ * reconstructed from a trajectory and only contains what a run touched —
55
+ * the fallback when nobody handed the report a runner.
56
+ */
57
+ source: 'declared' | 'observed';
58
+ agents: ArchAgent[];
59
+ }
60
+ export interface DescribeOptions<TCtx = unknown> {
61
+ /** needed to enumerate skill catalogs */
62
+ services?: Services;
63
+ /** the runner's fallback model, for agents that do not pin one */
64
+ defaultModel?: Model;
65
+ /** resolves context-dependent memory scopes; omitted leaves them symbolic */
66
+ context?: TCtx;
67
+ }
68
+ /**
69
+ * Projects a registry into a snapshot. Async only because a skill catalog is
70
+ * an index a provider has to be asked for — everything else is already in
71
+ * memory.
72
+ */
73
+ export declare function describeArchitecture<TCtx = unknown>(registry: AgentRegistry<TCtx>, opts?: DescribeOptions<TCtx>): Promise<Architecture>;
74
+ //# sourceMappingURL=architecture.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { HANDOFF_PREFIX } from "./types.js";
2
+ /**
3
+ * Projects a registry into a snapshot. Async only because a skill catalog is
4
+ * an index a provider has to be asked for — everything else is already in
5
+ * memory.
6
+ */
7
+ export async function describeArchitecture(registry, opts = {}) {
8
+ const agents = [];
9
+ for (const agent of registry.agents.values()) {
10
+ agents.push(await describeAgent(agent, opts));
11
+ }
12
+ return { source: 'declared', agents };
13
+ }
14
+ async function describeAgent(agent, opts) {
15
+ const skills = await describeSkills(agent, opts.services);
16
+ const own = new Set(agent.tools.map((t) => t.name));
17
+ const tools = agent.tools
18
+ // A hand-off is already an edge between two agents; listing its
19
+ // synthetic `transfer_to_*` tool as well would draw it twice.
20
+ .filter((t) => !t.name.startsWith(HANDOFF_PREFIX))
21
+ .map((t) => ({ name: t.name, description: t.description }));
22
+ for (const skill of skills?.catalog ?? []) {
23
+ for (const name of skill.toolNames ?? []) {
24
+ if (own.has(name)) {
25
+ continue;
26
+ }
27
+ own.add(name);
28
+ tools.push({
29
+ name,
30
+ description: opts.services?.skillProvider(skills.provider).tool(name)?.description,
31
+ skill: skill.name,
32
+ });
33
+ }
34
+ }
35
+ const model = agent.model?.id ?? opts.defaultModel?.id;
36
+ return {
37
+ name: agent.name,
38
+ description: agent.description,
39
+ model,
40
+ inheritedModel: model !== undefined && !agent.model ? true : undefined,
41
+ tools,
42
+ handoffs: [...agent.handoffs],
43
+ skills,
44
+ memory: describeMemory(agent, opts.context),
45
+ fork: agent.fork
46
+ ? { agents: agent.fork.agents, maxBranches: agent.fork.maxBranches }
47
+ : undefined,
48
+ };
49
+ }
50
+ async function describeSkills(agent, services) {
51
+ const binding = agent.skills;
52
+ if (!binding) {
53
+ return undefined;
54
+ }
55
+ const head = { provider: binding.provider, discovery: binding.discovery };
56
+ if (!services?.hasSkills(binding.provider)) {
57
+ return { ...head, catalog: [], unresolved: true };
58
+ }
59
+ const preload = new Set(binding.preload ?? []);
60
+ const keep = allowFilter(binding);
61
+ const summaries = (await services.skillProvider(binding.provider).list()).filter(keep);
62
+ return {
63
+ ...head,
64
+ catalog: summaries.map((s) => ({
65
+ name: s.name,
66
+ description: s.description,
67
+ version: s.version,
68
+ preload: preload.has(s.name) ? true : undefined,
69
+ toolNames: s.toolNames,
70
+ })),
71
+ };
72
+ }
73
+ function allowFilter(binding) {
74
+ const allow = binding.allow;
75
+ if (!allow) {
76
+ return () => true;
77
+ }
78
+ if (typeof allow === 'function') {
79
+ return allow;
80
+ }
81
+ const set = new Set(allow);
82
+ return (s) => set.has(s.name);
83
+ }
84
+ /**
85
+ * With a context in hand this is exactly what the run will record. Without
86
+ * one, a scope function is left symbolic rather than called: it would be
87
+ * handed `undefined` and either throw or invent a space no run ever uses.
88
+ */
89
+ function describeMemory(agent, context) {
90
+ if (context !== undefined) {
91
+ return agent.memoryBindings(context).map((b) => ({
92
+ store: b.store,
93
+ scope: b.scope,
94
+ access: b.access,
95
+ autoRecall: b.autoRecall ? true : undefined,
96
+ }));
97
+ }
98
+ return agent.memory.map((b) => ({
99
+ store: b.store,
100
+ scope: typeof b.scope === 'function' ? '(per run)' : (b.scope ?? `agent:${agent.name}`),
101
+ access: b.access,
102
+ autoRecall: b.autoRecall ? true : undefined,
103
+ }));
104
+ }
105
+ //# sourceMappingURL=architecture.js.map
@@ -0,0 +1,60 @@
1
+ import type { TokenUsage } from './types.ts';
2
+ /**
3
+ * Which side of a retrieval this text is.
4
+ *
5
+ * The only asymmetry worth a place in the contract: the current generation of
6
+ * embedding models is trained so that a question and the passage answering it
7
+ * are encoded *differently*, and mixing the two up costs recall silently rather
8
+ * than loudly. Google spells it `taskType`, OpenRouter `inputType`, and OpenAI
9
+ * has no such knob at all — which is exactly why it belongs on the request
10
+ * rather than on any one adapter.
11
+ */
12
+ export type EmbeddingTaskType = 'query' | 'document';
13
+ export interface EmbeddingRequest {
14
+ /** the texts to encode, in the order the vectors come back */
15
+ input: string[];
16
+ taskType?: EmbeddingTaskType;
17
+ /** truncate to this width, where the model supports it */
18
+ dimensions?: number;
19
+ /**
20
+ * Scale each vector to unit length. On by default — see `vectors` below.
21
+ * Turn it off to see what the model actually returned.
22
+ */
23
+ normalize?: boolean;
24
+ signal?: AbortSignal;
25
+ }
26
+ export interface EmbeddingResponse {
27
+ /**
28
+ * One vector per input, in the same order, and of unit length unless the
29
+ * request asked otherwise.
30
+ *
31
+ * Normalising here rather than leaving it to the caller is the one place
32
+ * this contract does more than translate. Whether a model returns unit
33
+ * vectors is a property of the *model*, not the vendor, and it changes
34
+ * between generations: `gemini-embedding-2` is unit at every width,
35
+ * `gemini-embedding-001` comes back at |v| ≈ 0.58 when truncated to 768 of
36
+ * its 3072, and `text-embedding-005` at ≈ 0.67 for 256 of its 768 — because
37
+ * truncation is a raw slice and only some models rescale afterwards.
38
+ *
39
+ * Cosine similarity divides the magnitude out and so cannot see any of
40
+ * that; a dot-product index, which is what pgvector and most vector stores
41
+ * default to, can see nothing else. So a caller that did not normalise
42
+ * would not get an error, it would get quietly worse results — and it
43
+ * cannot know which model it was handed.
44
+ */
45
+ vectors: number[][];
46
+ /** the width actually returned, which a truncating request may have chosen */
47
+ dimensions: number;
48
+ /** input tokens only; some providers report nothing at all */
49
+ usage?: TokenUsage;
50
+ }
51
+ export interface Embedder {
52
+ readonly id: string;
53
+ embed(req: EmbeddingRequest): Promise<EmbeddingResponse>;
54
+ }
55
+ /**
56
+ * The one way an adapter finishes, so the unit-length guarantee holds for every
57
+ * provider without three copies of the same loop.
58
+ */
59
+ export declare function embeddingResponse(vectors: number[][], req: EmbeddingRequest, usage?: TokenUsage): EmbeddingResponse;
60
+ //# sourceMappingURL=embedding.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The one way an adapter finishes, so the unit-length guarantee holds for every
3
+ * provider without three copies of the same loop.
4
+ */
5
+ export function embeddingResponse(vectors, req, usage) {
6
+ return {
7
+ vectors: req.normalize === false ? vectors : vectors.map(unit),
8
+ dimensions: vectors[0]?.length ?? 0,
9
+ usage,
10
+ };
11
+ }
12
+ function unit(vector) {
13
+ let sum = 0;
14
+ for (const x of vector) {
15
+ sum += x * x;
16
+ }
17
+ const length = Math.sqrt(sum);
18
+ // A zero vector has no direction to preserve, and dividing would make it NaN.
19
+ return length === 0 ? vector : vector.map((x) => x / length);
20
+ }
21
+ //# sourceMappingURL=embedding.js.map
@@ -0,0 +1,24 @@
1
+ import type { GoogleGenAI } from '@google/genai';
2
+ import { type Embedder, type EmbeddingRequest, type EmbeddingResponse } from '../embedding.ts';
3
+ /** Provider-specific knobs. */
4
+ export interface GeminiEmbedderOptions {
5
+ /** default width; a request may override it */
6
+ dimensions?: number;
7
+ /** a document's title, which the retrieval task type takes into account */
8
+ title?: string;
9
+ /**
10
+ * Texts this model accepts per request. One is the default because that is
11
+ * what every `gemini-embedding-*` model takes — `embedContent` is a
12
+ * single-document endpoint there, and the batch path is a separate,
13
+ * job-based API. The older `text-embedding-*` models accept far more and
14
+ * can say so.
15
+ */
16
+ maxBatch?: number;
17
+ }
18
+ export declare class GeminiEmbedder implements Embedder {
19
+ #private;
20
+ readonly id: string;
21
+ constructor(id: string, client: GoogleGenAI, options?: GeminiEmbedderOptions);
22
+ embed(req: EmbeddingRequest): Promise<EmbeddingResponse>;
23
+ }
24
+ //# sourceMappingURL=gemini.d.ts.map