@infinityi/engine-lib 1.0.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/LICENSE +21 -0
- package/README.md +488 -0
- package/dist/agent/agent-registry.d.ts +46 -0
- package/dist/agent/as-tool.d.ts +64 -0
- package/dist/agent/define.d.ts +35 -0
- package/dist/agent/handoff.d.ts +39 -0
- package/dist/agent/index.d.ts +20 -0
- package/dist/agent/index.js +38 -0
- package/dist/agent/registry.d.ts +27 -0
- package/dist/agent/types.d.ts +109 -0
- package/dist/context/index.d.ts +11 -0
- package/dist/context/index.js +21 -0
- package/dist/context/providers.d.ts +25 -0
- package/dist/context/types.d.ts +63 -0
- package/dist/context/window.d.ts +41 -0
- package/dist/errors.d.ts +93 -0
- package/dist/errors.js +24 -0
- package/dist/events/hub.d.ts +15 -0
- package/dist/events/index.d.ts +26 -0
- package/dist/events/index.js +24 -0
- package/dist/events/subscribers.d.ts +57 -0
- package/dist/events/telemetry.d.ts +61 -0
- package/dist/events/types.d.ts +39 -0
- package/dist/execution/index.d.ts +11 -0
- package/dist/execution/index.js +22 -0
- package/dist/execution/run.d.ts +35 -0
- package/dist/execution/types.d.ts +203 -0
- package/dist/execution/usage.d.ts +14 -0
- package/dist/index-02s1fjxr.js +226 -0
- package/dist/index-19pwq79t.js +0 -0
- package/dist/index-1p6mb2vz.js +32 -0
- package/dist/index-64tt9696.js +1796 -0
- package/dist/index-7690reng.js +96 -0
- package/dist/index-bqg01r42.js +354 -0
- package/dist/index-d4xz3abn.js +0 -0
- package/dist/index-dexgmwg6.js +148 -0
- package/dist/index-fkr3rcq9.js +97 -0
- package/dist/index-jg19te9v.js +0 -0
- package/dist/index-jp2b31xs.js +101 -0
- package/dist/index-jxgj4z08.js +68 -0
- package/dist/index-kte2h4k2.js +0 -0
- package/dist/index-pwr8179t.js +492 -0
- package/dist/index-rentvdpp.js +27 -0
- package/dist/index-vnby35rm.js +84 -0
- package/dist/index-w34cbktd.js +14 -0
- package/dist/index-xsv43c5j.js +39 -0
- package/dist/index-yrqrxwjt.js +148 -0
- package/dist/index-zfgr4xx3.js +90 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +117 -0
- package/dist/lifecycle/component.d.ts +74 -0
- package/dist/lifecycle/index.d.ts +12 -0
- package/dist/lifecycle/index.js +72 -0
- package/dist/messages/factory.d.ts +24 -0
- package/dist/messages/index.d.ts +8 -0
- package/dist/messages/index.js +17 -0
- package/dist/messages/types.d.ts +52 -0
- package/dist/providers/adapter.d.ts +42 -0
- package/dist/providers/anthropic/index.d.ts +31 -0
- package/dist/providers/anthropic/map.d.ts +12 -0
- package/dist/providers/anthropic/stream.d.ts +9 -0
- package/dist/providers/google/index.d.ts +29 -0
- package/dist/providers/google/map.d.ts +13 -0
- package/dist/providers/google/stream.d.ts +11 -0
- package/dist/providers/http.d.ts +61 -0
- package/dist/providers/index.d.ts +32 -0
- package/dist/providers/index.js +35 -0
- package/dist/providers/openai/index.d.ts +34 -0
- package/dist/providers/openai/map.d.ts +10 -0
- package/dist/providers/openai/stream.d.ts +9 -0
- package/dist/providers/openai-compatible/index.d.ts +37 -0
- package/dist/providers/openai-compatible/map.d.ts +13 -0
- package/dist/providers/openai-compatible/stream.d.ts +11 -0
- package/dist/providers/shared.d.ts +34 -0
- package/dist/providers/sse.d.ts +19 -0
- package/dist/providers/stream.d.ts +69 -0
- package/dist/providers/types.d.ts +137 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.js +11 -0
- package/dist/runtime/secret.d.ts +12 -0
- package/dist/runtime/types.d.ts +27 -0
- package/dist/schema/builder.d.ts +70 -0
- package/dist/schema/index.d.ts +13 -0
- package/dist/schema/index.js +15 -0
- package/dist/schema/json-schema.d.ts +19 -0
- package/dist/schema/types.d.ts +70 -0
- package/dist/schema/validate.d.ts +19 -0
- package/dist/session/index.d.ts +11 -0
- package/dist/session/index.js +8 -0
- package/dist/session/session.d.ts +31 -0
- package/dist/session/store.d.ts +20 -0
- package/dist/session/types.d.ts +55 -0
- package/dist/testing/conformance.d.ts +106 -0
- package/dist/testing/conformance.js +132 -0
- package/dist/testing/index.d.ts +84 -0
- package/dist/testing/index.js +31 -0
- package/dist/tools/define.d.ts +42 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/result.d.ts +36 -0
- package/dist/tools/types.d.ts +85 -0
- package/docs/README.md +36 -0
- package/examples/README.md +24 -0
- package/examples/incident-analysis.ts +100 -0
- package/examples/lifecycle.ts +53 -0
- package/examples/multi-agent.ts +93 -0
- package/examples/terminal-coder.ts +80 -0
- package/package.json +114 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider conformance suite (Phase 8).
|
|
3
|
+
*
|
|
4
|
+
* A shared, fixture-driven test battery that **every** {@link Provider} adapter
|
|
5
|
+
* must pass — the in-house OpenAI / Anthropic / Google / OpenAI-compatible
|
|
6
|
+
* adapters as well as any third-party adapter. It guarantees cross-provider
|
|
7
|
+
* parity on the parts of the contract that callers above the provider layer
|
|
8
|
+
* rely on: buffered completion, streaming, tool calling, usage reporting,
|
|
9
|
+
* capability honesty, and error mapping.
|
|
10
|
+
*
|
|
11
|
+
* The battery is provider-agnostic: each adapter supplies its **native wire
|
|
12
|
+
* fixtures** (the JSON / SSE bytes its vendor would return) plus the canonical
|
|
13
|
+
* normalized values those fixtures should decode to. The battery drives the
|
|
14
|
+
* public `Provider` seam through an injected fake `fetch` and asserts the
|
|
15
|
+
* normalized {@link CompletionResult} / {@link StreamEvent} shape — never the
|
|
16
|
+
* vendor wire format.
|
|
17
|
+
*
|
|
18
|
+
* Shipped from its own subpath (`@infinityi/engine-lib/testing/conformance`)
|
|
19
|
+
* rather than the main `@infinityi/engine-lib/testing` barrel. It registers
|
|
20
|
+
* tests against Bun's test-runner API when called, while remaining safe to
|
|
21
|
+
* import in non-Bun runtimes.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { describe, expect, it } from "bun:test";
|
|
26
|
+
* import { runProviderConformance } from "@infinityi/engine-lib/testing/conformance";
|
|
27
|
+
* import { createOpenAICompatible } from "@infinityi/engine-lib/providers";
|
|
28
|
+
*
|
|
29
|
+
* runProviderConformance("openai-compatible", {
|
|
30
|
+
* testApi: { describe, expect, it },
|
|
31
|
+
* makeProvider: ({ fetch }) => createOpenAICompatible({ baseUrl: "https://h/v1", model: "m", fetch }),
|
|
32
|
+
* expectPath: "/chat/completions",
|
|
33
|
+
* fixtures: {
|
|
34
|
+
* text: { body: { choices: [{ finish_reason: "stop", message: { content: "hi" } }] }, expectText: "hi" },
|
|
35
|
+
* // …toolCall, usage, stream
|
|
36
|
+
* },
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @module
|
|
41
|
+
*/
|
|
42
|
+
import type { describe as bunDescribe, expect as bunExpect, it as bunIt } from "bun:test";
|
|
43
|
+
import type { PipelineLike } from "@infinityi/forge/http/client";
|
|
44
|
+
import type { Provider, Usage } from "../providers/types";
|
|
45
|
+
/** A `fetch`-compatible function. */
|
|
46
|
+
type FetchFn = typeof globalThis.fetch;
|
|
47
|
+
export interface ConformanceTestApi {
|
|
48
|
+
readonly describe: typeof bunDescribe;
|
|
49
|
+
readonly expect: typeof bunExpect;
|
|
50
|
+
readonly it: typeof bunIt;
|
|
51
|
+
}
|
|
52
|
+
/** The transport seams the battery injects into the adapter under test. */
|
|
53
|
+
export interface ProviderIO {
|
|
54
|
+
/** Fake `fetch` returning the fixture bytes. */
|
|
55
|
+
readonly fetch: FetchFn;
|
|
56
|
+
/** Optional resilience override (the battery passes a no-retry pipeline
|
|
57
|
+
* for the error-mapping case so it doesn't pay the default retry backoff). */
|
|
58
|
+
readonly resilience?: PipelineLike;
|
|
59
|
+
}
|
|
60
|
+
/** Build a fresh adapter wired to the supplied transport seams. */
|
|
61
|
+
export type MakeProvider = (io: ProviderIO) => Provider;
|
|
62
|
+
/** Native wire fixtures plus the canonical normalized values they decode to. */
|
|
63
|
+
export interface ConformanceFixtures {
|
|
64
|
+
/** A buffered completion whose body decodes to assistant text. */
|
|
65
|
+
readonly text: {
|
|
66
|
+
readonly body: unknown;
|
|
67
|
+
readonly expectText: string;
|
|
68
|
+
};
|
|
69
|
+
/** A buffered completion whose body decodes to a single tool call. */
|
|
70
|
+
readonly toolCall: {
|
|
71
|
+
readonly body: unknown;
|
|
72
|
+
readonly expectName: string;
|
|
73
|
+
readonly expectArgs?: Record<string, unknown>;
|
|
74
|
+
};
|
|
75
|
+
/** A buffered completion whose body reports token usage. */
|
|
76
|
+
readonly usage: {
|
|
77
|
+
readonly body: unknown;
|
|
78
|
+
readonly expect: Usage;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* A streaming response (raw `text/event-stream` bytes) that yields text
|
|
82
|
+
* deltas and a final finish. Provide this iff the adapter declares
|
|
83
|
+
* `capabilities.streaming` — the battery cross-checks the two.
|
|
84
|
+
*/
|
|
85
|
+
readonly stream?: {
|
|
86
|
+
readonly sse: string;
|
|
87
|
+
readonly expectText: string;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Options for {@link runProviderConformance}. */
|
|
91
|
+
export interface ConformanceOptions {
|
|
92
|
+
/** Factory building the adapter under test from an injected fake `fetch`. */
|
|
93
|
+
readonly makeProvider: MakeProvider;
|
|
94
|
+
/** The native wire fixtures for this provider. */
|
|
95
|
+
readonly fixtures: ConformanceFixtures;
|
|
96
|
+
/** Substring asserted to appear in the request URL (e.g. `/messages`). */
|
|
97
|
+
readonly expectPath?: string;
|
|
98
|
+
/** Test-runner API. Pass Bun's `{ describe, expect, it }` from a test file. */
|
|
99
|
+
readonly testApi?: ConformanceTestApi;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Register a `describe()` block of shared assertions the `Provider` adapter
|
|
103
|
+
* `name` must satisfy. Call it from a `*.test.ts` file (it uses `bun:test`).
|
|
104
|
+
*/
|
|
105
|
+
export declare function runProviderConformance(name: string, opts: ConformanceOptions): void;
|
|
106
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
jsonFetch,
|
|
3
|
+
sseFetch
|
|
4
|
+
} from "../index-dexgmwg6.js";
|
|
5
|
+
import"../index-vnby35rm.js";
|
|
6
|
+
import"../index-19pwq79t.js";
|
|
7
|
+
import {
|
|
8
|
+
combine
|
|
9
|
+
} from "../index-bqg01r42.js";
|
|
10
|
+
import"../index-zfgr4xx3.js";
|
|
11
|
+
import {
|
|
12
|
+
ProviderError
|
|
13
|
+
} from "../index-7690reng.js";
|
|
14
|
+
import {
|
|
15
|
+
user
|
|
16
|
+
} from "../index-1p6mb2vz.js";
|
|
17
|
+
|
|
18
|
+
// src/testing/conformance.ts
|
|
19
|
+
function getTestApi() {
|
|
20
|
+
const globals = globalThis;
|
|
21
|
+
if (typeof globals.describe === "function" && typeof globals.expect === "function" && typeof globals.it === "function") {
|
|
22
|
+
return {
|
|
23
|
+
describe: globals.describe,
|
|
24
|
+
expect: globals.expect,
|
|
25
|
+
it: globals.it
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const requireFn = (0, eval)("require");
|
|
30
|
+
const api = typeof requireFn === "function" ? requireFn("bun:test") : undefined;
|
|
31
|
+
if (typeof api?.describe === "function" && typeof api.expect === "function" && typeof api.it === "function") {
|
|
32
|
+
return {
|
|
33
|
+
describe: api.describe,
|
|
34
|
+
expect: api.expect,
|
|
35
|
+
it: api.it
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
} catch {}
|
|
39
|
+
throw new Error("runProviderConformance requires Bun's test runner; call it from a Bun test file.");
|
|
40
|
+
}
|
|
41
|
+
var NO_RETRY = combine();
|
|
42
|
+
var REQUEST = { messages: [user("hi")] };
|
|
43
|
+
async function collect(events) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for await (const event of events)
|
|
46
|
+
out.push(event);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function runProviderConformance(name, opts) {
|
|
50
|
+
const { describe, expect, it } = opts.testApi ?? getTestApi();
|
|
51
|
+
const { makeProvider, fixtures, expectPath } = opts;
|
|
52
|
+
describe(`provider conformance — ${name}`, () => {
|
|
53
|
+
it("declares honest, well-formed capabilities and identity", () => {
|
|
54
|
+
const provider = makeProvider({
|
|
55
|
+
fetch: jsonFetch(fixtures.text.body).fetch
|
|
56
|
+
});
|
|
57
|
+
expect(typeof provider.name).toBe("string");
|
|
58
|
+
expect(provider.name.length).toBeGreaterThan(0);
|
|
59
|
+
expect(typeof provider.defaultModel).toBe("string");
|
|
60
|
+
expect(provider.defaultModel.length).toBeGreaterThan(0);
|
|
61
|
+
const caps = provider.capabilities;
|
|
62
|
+
for (const flag of [
|
|
63
|
+
caps.tools,
|
|
64
|
+
caps.streaming,
|
|
65
|
+
caps.multimodalInput,
|
|
66
|
+
caps.parallelToolCalls,
|
|
67
|
+
caps.structuredOutput
|
|
68
|
+
]) {
|
|
69
|
+
expect(typeof flag).toBe("boolean");
|
|
70
|
+
}
|
|
71
|
+
if (fixtures.stream !== undefined)
|
|
72
|
+
expect(caps.streaming).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it("completes a buffered turn into normalized text", async () => {
|
|
75
|
+
const { fetch, calls } = jsonFetch(fixtures.text.body);
|
|
76
|
+
const result = await makeProvider({ fetch }).complete(REQUEST);
|
|
77
|
+
expect(calls.length).toBeGreaterThan(0);
|
|
78
|
+
if (expectPath !== undefined)
|
|
79
|
+
expect(calls[0]?.url).toContain(expectPath);
|
|
80
|
+
expect(result.finishReason).toBe("stop");
|
|
81
|
+
expect(typeof result.model).toBe("string");
|
|
82
|
+
const text = result.message.content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
83
|
+
expect(text).toBe(fixtures.text.expectText);
|
|
84
|
+
});
|
|
85
|
+
it("surfaces a tool call with an id, name, and parsed arguments", async () => {
|
|
86
|
+
const result = await makeProvider({
|
|
87
|
+
fetch: jsonFetch(fixtures.toolCall.body).fetch
|
|
88
|
+
}).complete(REQUEST);
|
|
89
|
+
expect(result.finishReason).toBe("tool_calls");
|
|
90
|
+
expect(result.toolCalls.length).toBeGreaterThan(0);
|
|
91
|
+
const call = result.toolCalls[0];
|
|
92
|
+
expect(typeof call.id).toBe("string");
|
|
93
|
+
expect(call.id.length).toBeGreaterThan(0);
|
|
94
|
+
expect(call.name).toBe(fixtures.toolCall.expectName);
|
|
95
|
+
if (fixtures.toolCall.expectArgs !== undefined) {
|
|
96
|
+
expect(call.arguments).toEqual(fixtures.toolCall.expectArgs);
|
|
97
|
+
}
|
|
98
|
+
expect(result.message.content.some((p) => p.type === "tool_call")).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it("normalizes token usage", async () => {
|
|
101
|
+
const result = await makeProvider({
|
|
102
|
+
fetch: jsonFetch(fixtures.usage.body).fetch
|
|
103
|
+
}).complete(REQUEST);
|
|
104
|
+
expect(result.usage).toBeDefined();
|
|
105
|
+
expect(result.usage?.inputTokens).toBe(fixtures.usage.expect.inputTokens);
|
|
106
|
+
expect(result.usage?.outputTokens).toBe(fixtures.usage.expect.outputTokens);
|
|
107
|
+
expect(result.usage?.totalTokens).toBe(fixtures.usage.expect.totalTokens);
|
|
108
|
+
});
|
|
109
|
+
it("maps a non-2xx response to a ProviderError", async () => {
|
|
110
|
+
const provider = makeProvider({
|
|
111
|
+
fetch: jsonFetch({ error: "boom" }, { status: 500 }).fetch,
|
|
112
|
+
resilience: NO_RETRY
|
|
113
|
+
});
|
|
114
|
+
await expect(provider.complete(REQUEST)).rejects.toBeInstanceOf(ProviderError);
|
|
115
|
+
});
|
|
116
|
+
const streamFixture = fixtures.stream;
|
|
117
|
+
if (streamFixture !== undefined) {
|
|
118
|
+
it("streams text deltas bracketed by message_start and finish", async () => {
|
|
119
|
+
const events = await collect(makeProvider({ fetch: sseFetch(streamFixture.sse).fetch }).stream(REQUEST));
|
|
120
|
+
expect(events[0]?.type).toBe("message_start");
|
|
121
|
+
expect(events[events.length - 1]?.type).toBe("finish");
|
|
122
|
+
const text = events.filter((e) => e.type === "text_delta").map((e) => e.text).join("");
|
|
123
|
+
expect(text).toBe(streamFixture.expectText);
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
it.skip("streaming (no stream fixture provided)", () => {});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export {
|
|
131
|
+
runProviderConformance
|
|
132
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@infinityi/engine-lib/testing` — in-memory helpers and assertions for tests.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1 ships only the helpers relevant to the foundation layer;
|
|
5
|
+
* provider and session doubles arrive with Phases 2 and 5.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { Message } from "../messages/types";
|
|
10
|
+
import type { Schema } from "../schema/types";
|
|
11
|
+
import type { EngineContext } from "../runtime/types";
|
|
12
|
+
import type { CompletionRequest, CompletionResult, Provider, ProviderCapabilities, ToolCall, Usage } from "../providers/types";
|
|
13
|
+
import { InMemorySessionStore } from "../session/index";
|
|
14
|
+
import type { StreamEvent } from "../providers/stream";
|
|
15
|
+
/** Build a `Message[]` from arguments, for readable test fixtures. */
|
|
16
|
+
export declare function conversation(...messages: Message[]): Message[];
|
|
17
|
+
/** Options for {@link mockProvider}. */
|
|
18
|
+
export interface MockProviderOptions {
|
|
19
|
+
readonly name?: string;
|
|
20
|
+
readonly defaultModel?: string;
|
|
21
|
+
readonly capabilities?: Partial<ProviderCapabilities>;
|
|
22
|
+
/** Scripted buffered result (or a function of the request). */
|
|
23
|
+
readonly result?: CompletionResult | ((req: CompletionRequest) => CompletionResult);
|
|
24
|
+
/** Scripted stream events (or a function of the request). When omitted, a
|
|
25
|
+
* single text/finish stream is derived from {@link MockProviderOptions.result}. */
|
|
26
|
+
readonly events?: StreamEvent[] | ((req: CompletionRequest) => StreamEvent[]);
|
|
27
|
+
/** Called with every request, so tests can assert on what was sent. */
|
|
28
|
+
readonly onRequest?: (req: CompletionRequest, ctx?: EngineContext) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A network-free {@link Provider} for contract tests and Phase-4 run-loop
|
|
32
|
+
* tests. Returns scripted results/streams and records every request.
|
|
33
|
+
*/
|
|
34
|
+
export declare function mockProvider(opts?: MockProviderOptions): Provider;
|
|
35
|
+
/** Drain a {@link Provider.stream} into a {@link CompletionResult} for assertions. */
|
|
36
|
+
export declare function collectProviderStream(provider: Provider, req: CompletionRequest, ctx?: EngineContext): Promise<CompletionResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Parse `input` with `schema`, returning the typed value. Throws (failing
|
|
39
|
+
* the test) with a readable message listing all issues if invalid.
|
|
40
|
+
*/
|
|
41
|
+
export declare function expectValid<T>(schema: Schema<T>, input: unknown): T;
|
|
42
|
+
/** A recorded outbound call captured by a {@link RecordingFetch}. */
|
|
43
|
+
export interface RecordedCall {
|
|
44
|
+
readonly url: string;
|
|
45
|
+
readonly init?: RequestInit;
|
|
46
|
+
}
|
|
47
|
+
/** A fake `fetch` plus the list of calls it recorded. */
|
|
48
|
+
export interface RecordingFetch {
|
|
49
|
+
readonly fetch: typeof fetch;
|
|
50
|
+
readonly calls: RecordedCall[];
|
|
51
|
+
}
|
|
52
|
+
/** A `ReadableStream<Uint8Array>` that emits `chunks` (one enqueue each). */
|
|
53
|
+
export declare function byteStreamOf(...chunks: string[]): ReadableStream<Uint8Array>;
|
|
54
|
+
/**
|
|
55
|
+
* A `fetch` that returns a fixed JSON `body` / `status` and records every call,
|
|
56
|
+
* for driving a {@link Provider} adapter without a network.
|
|
57
|
+
*/
|
|
58
|
+
export declare function jsonFetch(body: unknown, init?: {
|
|
59
|
+
status?: number;
|
|
60
|
+
}): RecordingFetch;
|
|
61
|
+
/**
|
|
62
|
+
* A `fetch` that returns a fixed `text/event-stream` body and records every
|
|
63
|
+
* call, for driving a {@link Provider} adapter's `stream()` without a network.
|
|
64
|
+
*/
|
|
65
|
+
export declare function sseFetch(sse: string): RecordingFetch;
|
|
66
|
+
/**
|
|
67
|
+
* Build a buffered text {@link CompletionResult} (`finishReason: "stop"`) for
|
|
68
|
+
* scripting a {@link mockProvider} or {@link scriptedProvider} turn.
|
|
69
|
+
*/
|
|
70
|
+
export declare function textResult(text: string, usage?: Usage): CompletionResult;
|
|
71
|
+
/**
|
|
72
|
+
* Build a tool-call {@link CompletionResult} (`finishReason: "tool_calls"`),
|
|
73
|
+
* mirroring each {@link ToolCall} as a `tool_call` message part.
|
|
74
|
+
*/
|
|
75
|
+
export declare function toolCallResult(calls: ToolCall[], usage?: Usage): CompletionResult;
|
|
76
|
+
/**
|
|
77
|
+
* A {@link Provider} that returns each scripted result in turn; the last
|
|
78
|
+
* result repeats once the script is exhausted. Handy for multi-turn run-loop
|
|
79
|
+
* tests (e.g. a tool call followed by a final answer).
|
|
80
|
+
*/
|
|
81
|
+
export declare function scriptedProvider(results: readonly CompletionResult[], opts?: MockProviderOptions): Provider;
|
|
82
|
+
export { InMemorySessionStore } from "../session/index";
|
|
83
|
+
/** Construct a fresh in-memory {@link SessionStore} double. */
|
|
84
|
+
export declare function inMemorySessionStore(): InMemorySessionStore;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
byteStreamOf,
|
|
3
|
+
collectProviderStream,
|
|
4
|
+
conversation,
|
|
5
|
+
expectValid,
|
|
6
|
+
inMemorySessionStore,
|
|
7
|
+
jsonFetch,
|
|
8
|
+
mockProvider,
|
|
9
|
+
scriptedProvider,
|
|
10
|
+
sseFetch,
|
|
11
|
+
textResult,
|
|
12
|
+
toolCallResult
|
|
13
|
+
} from "../index-dexgmwg6.js";
|
|
14
|
+
import {
|
|
15
|
+
InMemorySessionStore
|
|
16
|
+
} from "../index-vnby35rm.js";
|
|
17
|
+
import"../index-zfgr4xx3.js";
|
|
18
|
+
export {
|
|
19
|
+
toolCallResult,
|
|
20
|
+
textResult,
|
|
21
|
+
sseFetch,
|
|
22
|
+
scriptedProvider,
|
|
23
|
+
mockProvider,
|
|
24
|
+
jsonFetch,
|
|
25
|
+
inMemorySessionStore,
|
|
26
|
+
expectValid,
|
|
27
|
+
conversation,
|
|
28
|
+
collectProviderStream,
|
|
29
|
+
byteStreamOf,
|
|
30
|
+
InMemorySessionStore
|
|
31
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defineTool` — the ergonomic constructor for {@link ToolDefinition}s.
|
|
3
|
+
*
|
|
4
|
+
* Application code should prefer this constructor over hand-writing
|
|
5
|
+
* `ToolDefinition` objects. The argument type `TArgs` is inferred from the
|
|
6
|
+
* parameter schema (`parameters: Schema<TArgs>`), so `execute` receives
|
|
7
|
+
* fully-typed, already-validated arguments with zero annotations.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { s } from "@infinityi/engine-lib/schema";
|
|
12
|
+
* import { defineTool } from "@infinityi/engine-lib/tools";
|
|
13
|
+
*
|
|
14
|
+
* const readFile = defineTool({
|
|
15
|
+
* name: "read_file",
|
|
16
|
+
* description: "Read a file from the workspace.",
|
|
17
|
+
* parameters: s.object({ path: s.string() }),
|
|
18
|
+
* execute: async ({ path }) => ({ ok: true, content: await workspace.read(path) }),
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import type { Schema } from "../schema/types";
|
|
25
|
+
import type { ToolContext, ToolDefinition, ToolResult } from "./types";
|
|
26
|
+
/** The shape passed to {@link defineTool}, with `execute` typed against the schema. */
|
|
27
|
+
export interface ToolSpec<TArgs> {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly description?: string;
|
|
30
|
+
readonly parameters: Schema<TArgs>;
|
|
31
|
+
execute(args: TArgs, ctx: ToolContext): ToolResult | Promise<ToolResult>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Define a tool with argument types inferred from its parameter schema.
|
|
35
|
+
*
|
|
36
|
+
* Pure: validates that `name` is non-empty and returns a frozen
|
|
37
|
+
* {@link ToolDefinition}. No execution or registration happens here. Return
|
|
38
|
+
* `{ ok: false, error }` for expected/domain failures; reserve thrown errors
|
|
39
|
+
* for unexpected implementation faults. `runAgent` isolates both forms as tool
|
|
40
|
+
* results so one bad tool call does not crash the whole run.
|
|
41
|
+
*/
|
|
42
|
+
export declare function defineTool<TArgs>(spec: ToolSpec<TArgs>): ToolDefinition<TArgs>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@infinityi/engine-lib/tools` — the tool contract: declarative tool definitions, the
|
|
3
|
+
* structured tool-result model, and the mappers that connect tools to the
|
|
4
|
+
* conversation model (Phase 1) and the provider toolset (Phase 2).
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
export { defineTool } from "./define";
|
|
9
|
+
export type { ToolSpec } from "./define";
|
|
10
|
+
export { renderToolContent, toProviderTool, toToolResultMessage } from "./result";
|
|
11
|
+
export type { ToolContext, ToolDefinition, ToolFailure, ToolResult, ToolSuccess, } from "./types";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineTool
|
|
3
|
+
} from "../index-w34cbktd.js";
|
|
4
|
+
import {
|
|
5
|
+
renderToolContent,
|
|
6
|
+
toProviderTool,
|
|
7
|
+
toToolResultMessage
|
|
8
|
+
} from "../index-rentvdpp.js";
|
|
9
|
+
import"../index-1p6mb2vz.js";
|
|
10
|
+
export {
|
|
11
|
+
toToolResultMessage,
|
|
12
|
+
toProviderTool,
|
|
13
|
+
renderToolContent,
|
|
14
|
+
defineTool
|
|
15
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalization between the Phase-3 tool layer and its neighbours.
|
|
3
|
+
*
|
|
4
|
+
* Two directions:
|
|
5
|
+
* - {@link renderToolContent} / {@link toToolResultMessage} map a
|
|
6
|
+
* {@link ToolResult} *down* into the Phase-1 conversation model (a
|
|
7
|
+
* `role: "tool"` {@link Message}), reusing the `toolResult` factory.
|
|
8
|
+
* - {@link toProviderTool} maps a {@link ToolDefinition} *across* into the
|
|
9
|
+
* Phase-2 {@link ProviderTool} the provider advertises to the model.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { Message, TextPart } from "../messages/types";
|
|
14
|
+
import type { ProviderTool } from "../providers/types";
|
|
15
|
+
import type { ToolDefinition, ToolResult } from "./types";
|
|
16
|
+
/**
|
|
17
|
+
* Render a {@link ToolResult}'s payload into `TextPart[]`.
|
|
18
|
+
*
|
|
19
|
+
* This rendering is part of the stable tool contract: strings pass through
|
|
20
|
+
* unchanged; `undefined`/`null` become empty text; any other value is
|
|
21
|
+
* JSON-encoded so structured results survive into the text-only Phase-1
|
|
22
|
+
* tool-result part.
|
|
23
|
+
*/
|
|
24
|
+
export declare function renderToolContent(result: ToolResult): TextPart[];
|
|
25
|
+
/**
|
|
26
|
+
* Map a {@link ToolResult} into a Phase-1 tool-result {@link Message}.
|
|
27
|
+
*
|
|
28
|
+
* A failure is marked `isError: true` so the model sees a recoverable tool
|
|
29
|
+
* error rather than a crashed run.
|
|
30
|
+
*/
|
|
31
|
+
export declare function toToolResultMessage(toolCallId: string, result: ToolResult): Message;
|
|
32
|
+
/**
|
|
33
|
+
* Project a {@link ToolDefinition} into the provider-facing {@link ProviderTool}
|
|
34
|
+
* (name + optional description + JSON-Schema parameters).
|
|
35
|
+
*/
|
|
36
|
+
export declare function toProviderTool(tool: ToolDefinition): ProviderTool;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool contract — a named capability the model can invoke.
|
|
3
|
+
*
|
|
4
|
+
* A {@link ToolDefinition} couples a Phase-1 parameter {@link Schema} (used to
|
|
5
|
+
* advertise the tool to a provider and to validate model-supplied arguments at
|
|
6
|
+
* the boundary in Phase 4) with a typed `execute` function. Execution returns a
|
|
7
|
+
* structured {@link ToolResult}: a *successful* outcome carries content fed back
|
|
8
|
+
* to the model, while a *failed* outcome is surfaced to the model as a tool
|
|
9
|
+
* error rather than thrown — bad arguments and tool failures become data the
|
|
10
|
+
* model can recover from, not unhandled exceptions. The stable application
|
|
11
|
+
* path is {@link defineTool}; hand-written {@link ToolDefinition} objects are
|
|
12
|
+
* mainly useful for adapters and tests.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import type { EngineContext } from "../runtime/types";
|
|
17
|
+
import type { Schema } from "../schema/types";
|
|
18
|
+
import type { RunBridge } from "../execution/types";
|
|
19
|
+
/**
|
|
20
|
+
* Per-invocation context handed to a tool's {@link ToolDefinition.execute}.
|
|
21
|
+
*
|
|
22
|
+
* Extends the shared {@link EngineContext} (telemetry / logger / cancellation
|
|
23
|
+
* signal) with call-correlation metadata. Only the owning agent's *name* is
|
|
24
|
+
* exposed — not the agent itself — so tools never carry a forward dependency on
|
|
25
|
+
* the agent module.
|
|
26
|
+
*/
|
|
27
|
+
export interface ToolContext extends EngineContext {
|
|
28
|
+
/** Correlates this invocation to the model's `ToolCall.id`. */
|
|
29
|
+
readonly toolCallId: string;
|
|
30
|
+
/** Name of the agent that owns this tool, when run inside one. */
|
|
31
|
+
readonly agentName?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Bridge to the surrounding run, present when the tool is dispatched by the
|
|
34
|
+
* Phase-4 loop. Lets a tool forward nested events and report token usage to
|
|
35
|
+
* the parent run (used by sub-agent-as-tool); absent when a tool is invoked
|
|
36
|
+
* outside `runAgent`.
|
|
37
|
+
*/
|
|
38
|
+
readonly run?: RunBridge;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A successful tool outcome.
|
|
42
|
+
*
|
|
43
|
+
* `content` is rendered to text for the model: strings pass through unchanged;
|
|
44
|
+
* any other value is JSON-encoded (see `renderToolContent`).
|
|
45
|
+
*/
|
|
46
|
+
export interface ToolSuccess {
|
|
47
|
+
readonly ok: true;
|
|
48
|
+
readonly content: unknown;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A failed tool outcome for expected/domain failures such as "file not found"
|
|
52
|
+
* or "permission denied". Surfaced to the model as a tool error (an `isError`
|
|
53
|
+
* tool-result message), never thrown out of the run loop.
|
|
54
|
+
*
|
|
55
|
+
* `error` is intentionally a string in the stable contract. If a tool needs to
|
|
56
|
+
* return structured diagnostics, encode them in the string or return a
|
|
57
|
+
* structured success payload with an application-level status.
|
|
58
|
+
*/
|
|
59
|
+
export interface ToolFailure {
|
|
60
|
+
readonly ok: false;
|
|
61
|
+
readonly error: string;
|
|
62
|
+
}
|
|
63
|
+
/** The structured result of a tool invocation, discriminated on `ok`. */
|
|
64
|
+
export type ToolResult = ToolSuccess | ToolFailure;
|
|
65
|
+
/**
|
|
66
|
+
* A named capability the model can invoke.
|
|
67
|
+
*
|
|
68
|
+
* `TArgs` is the validated argument type; with {@link defineTool} it is inferred
|
|
69
|
+
* from `parameters` so `execute` receives fully-typed arguments.
|
|
70
|
+
*/
|
|
71
|
+
export interface ToolDefinition<TArgs = unknown> {
|
|
72
|
+
readonly name: string;
|
|
73
|
+
readonly description?: string;
|
|
74
|
+
/** Parameter schema: `.jsonSchema` is advertised to the provider, `.parse` validates args. */
|
|
75
|
+
readonly parameters: Schema<TArgs>;
|
|
76
|
+
/**
|
|
77
|
+
* Run the tool with already-validated arguments.
|
|
78
|
+
*
|
|
79
|
+
* Return `{ ok: true, content }` for success and `{ ok: false, error }` for
|
|
80
|
+
* expected/domain failures. Throw only for unexpected implementation faults;
|
|
81
|
+
* `runAgent` catches thrown errors and feeds them back to the model as
|
|
82
|
+
* recoverable tool-result errors.
|
|
83
|
+
*/
|
|
84
|
+
execute(args: TArgs, ctx: ToolContext): ToolResult | Promise<ToolResult>;
|
|
85
|
+
}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# engine-lib documentation
|
|
2
|
+
|
|
3
|
+
- **Guide & concepts:** [`../README.md`](../README.md) - project goal, design principles, and annotated usage scenarios.
|
|
4
|
+
- **Runnable examples:** [`../examples/`](../examples/) - small, offline programs you can run with `bun`.
|
|
5
|
+
|
|
6
|
+
## API reference
|
|
7
|
+
|
|
8
|
+
The full API reference is generated from source doc-comments with
|
|
9
|
+
[TypeDoc](https://typedoc.org):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run docs # writes HTML to docs/api/ (git-ignored)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then open `docs/api/index.html`. Configuration lives in `typedoc.json` in the
|
|
16
|
+
source repository; the documented entry points are the public import surfaces:
|
|
17
|
+
|
|
18
|
+
| Import | Module |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `@infinityi/engine-lib` | stable root barrel: schemas, messages, errors, provider factories, tools, agents, execution, sessions, context helpers, event subscribers |
|
|
21
|
+
| `@infinityi/engine-lib/schema` | schema builder, JSON Schema export, and validation helpers |
|
|
22
|
+
| `@infinityi/engine-lib/messages` | provider-neutral message and content helpers |
|
|
23
|
+
| `@infinityi/engine-lib/errors` | public error taxonomy |
|
|
24
|
+
| `@infinityi/engine-lib/runtime` | Forge secret and telemetry integration helpers |
|
|
25
|
+
| `@infinityi/engine-lib/providers` | provider contracts, built-in provider factories, and advanced adapter/HTTP/SSE helpers |
|
|
26
|
+
| `@infinityi/engine-lib/tools` | tool definitions and tool-result mapping helpers |
|
|
27
|
+
| `@infinityi/engine-lib/agent` | agent definitions, registries, handoffs, and sub-agent-as-tool helpers |
|
|
28
|
+
| `@infinityi/engine-lib/execution` | `runAgent` and run result/event types |
|
|
29
|
+
| `@infinityi/engine-lib/session` | session handles and session store contract |
|
|
30
|
+
| `@infinityi/engine-lib/context` | context providers and context-window strategies |
|
|
31
|
+
| `@infinityi/engine-lib/events` | event hub, subscribers, event projection helpers, and telemetry bridge |
|
|
32
|
+
| `@infinityi/engine-lib/lifecycle` | Forge lifecycle adapter (`agentRuntimeComponent`) |
|
|
33
|
+
| `@infinityi/engine-lib/testing` | network-free test doubles (`mockProvider`, `scriptedProvider`, `textResult`, `toolCallResult`, `jsonFetch`/`sseFetch`, `inMemorySessionStore`) |
|
|
34
|
+
| `@infinityi/engine-lib/testing/conformance` | the provider conformance battery (`runProviderConformance`) |
|
|
35
|
+
|
|
36
|
+
The output is intentionally not committed; regenerate it locally or in CI.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Small, self-contained programs demonstrating the engine-lib API. Each one runs
|
|
4
|
+
**offline** with `bun` — they use the network-free test doubles from
|
|
5
|
+
`@infinityi/engine-lib/testing` (scripted providers) so no API key is required.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun examples/incident-analysis.ts # context injection + a read-only tool
|
|
9
|
+
bun examples/terminal-coder.ts # streaming tokens + a persisted session
|
|
10
|
+
bun examples/multi-agent.ts # handoff/delegation + sub-agent-as-tool
|
|
11
|
+
bun examples/lifecycle.ts # forge.boot with agentRuntimeComponent
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
| File | Demonstrates |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| [`incident-analysis.ts`](./incident-analysis.ts) | `defineAgent` + `defineTool`, `staticContext` injection, `onEvent` tool-call observation |
|
|
17
|
+
| [`terminal-coder.ts`](./terminal-coder.ts) | streaming (`stream: true`) `token` events, `createSession` history persistence |
|
|
18
|
+
| [`multi-agent.ts`](./multi-agent.ts) | Phase 7 — `handoffs` / `transfer_to_<name>` and `asTool(agent)` |
|
|
19
|
+
| [`lifecycle.ts`](./lifecycle.ts) | Phase 8 — `agentRuntimeComponent` start/healthcheck/stop under `forge.boot` |
|
|
20
|
+
|
|
21
|
+
> The examples import from `@infinityi/engine-lib`, so they work both inside this
|
|
22
|
+
> repository and from the published package. In your own application, pass a real
|
|
23
|
+
> provider, e.g. `createOpenAI({ apiKey, model })` or
|
|
24
|
+
> `createAnthropic({ apiKey, model })`.
|