@openshain/agent 0.2.0 → 0.4.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/NOTICE +4 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.js +48 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/session.d.ts +84 -16
- package/dist/session.js +622 -339
- package/package.json +7 -4
- package/src/client.ts +73 -0
- package/src/index.ts +5 -12
- package/src/session.ts +761 -397
- package/dist/loop.d.ts +0 -37
- package/dist/loop.js +0 -382
- package/src/loop.ts +0 -479
package/NOTICE
ADDED
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import type { ToolContent, ToolDefinition } from "@openshain/core";
|
|
4
|
+
/** What a tool call returned, as the client sees it: MCP content, and the same as text. */
|
|
5
|
+
export interface ClientResult {
|
|
6
|
+
content: ToolContent[];
|
|
7
|
+
isError: boolean;
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The runtime as a client sees it: the tools it offers and a way to call them. The interactive
|
|
12
|
+
* CLI's loop talks to the runtime through this and nothing else, the way Claude Code does over MCP.
|
|
13
|
+
*/
|
|
14
|
+
export interface RuntimeClient {
|
|
15
|
+
listTools(): Promise<ToolDefinition[]>;
|
|
16
|
+
call(name: string, input: unknown, signal?: AbortSignal): Promise<ClientResult>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/** Connects an MCP client to a server in the same process, over the SDK's in-memory transport. */
|
|
20
|
+
export declare function connectInMemory(server: Server): Promise<RuntimeClient>;
|
|
21
|
+
/** Adapts any connected MCP client to the runtime client the loop uses. */
|
|
22
|
+
export declare function wrap(client: Client): RuntimeClient;
|
|
23
|
+
/** Parses a JSON result. Returns undefined when the text is not JSON. */
|
|
24
|
+
export declare function jsonOf(result: ClientResult): unknown;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
3
|
+
import pkg from "../package.json" with { type: "json" };
|
|
4
|
+
/** Connects an MCP client to a server in the same process, over the SDK's in-memory transport. */
|
|
5
|
+
export async function connectInMemory(server) {
|
|
6
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
7
|
+
await server.connect(serverTransport);
|
|
8
|
+
const client = new Client({ name: "openshain", version: pkg.version });
|
|
9
|
+
await client.connect(clientTransport);
|
|
10
|
+
return wrap(client);
|
|
11
|
+
}
|
|
12
|
+
/** Adapts any connected MCP client to the runtime client the loop uses. */
|
|
13
|
+
export function wrap(client) {
|
|
14
|
+
return {
|
|
15
|
+
async listTools() {
|
|
16
|
+
const { tools } = await client.listTools();
|
|
17
|
+
return tools.map((tool) => ({
|
|
18
|
+
name: tool.name,
|
|
19
|
+
description: tool.description ?? "",
|
|
20
|
+
inputSchema: tool.inputSchema,
|
|
21
|
+
effect: tool.annotations?.readOnlyHint === true ? "observe" : "mutate",
|
|
22
|
+
}));
|
|
23
|
+
},
|
|
24
|
+
async call(name, input, signal) {
|
|
25
|
+
const result = await client.callTool({ name, arguments: (input ?? {}) }, undefined, signal ? { signal } : undefined);
|
|
26
|
+
const parts = (result.content ?? []);
|
|
27
|
+
const content = parts.map((part) => ({
|
|
28
|
+
type: "text",
|
|
29
|
+
text: part.type === "text" ? (part.text ?? "") : JSON.stringify(part),
|
|
30
|
+
}));
|
|
31
|
+
return {
|
|
32
|
+
content,
|
|
33
|
+
isError: result.isError === true,
|
|
34
|
+
text: content.map((c) => (c.type === "text" ? c.text : "")).join(""),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
close: () => client.close(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Parses a JSON result. Returns undefined when the text is not JSON. */
|
|
41
|
+
export function jsonOf(result) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(result.text);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { type ClientResult, connectInMemory, jsonOf, type RuntimeClient, wrap } from "./client.ts";
|
|
2
2
|
export { AGENT_NAMES, pickAgentName } from "./names.ts";
|
|
3
3
|
export { ANTHROPIC_PROVIDER_ID, AnthropicProvider, type AnthropicProviderOptions, anthropicProvider, } from "./providers/anthropic.ts";
|
|
4
4
|
export { OPENAI_COMPATIBLE_PROVIDER_ID, OpenAICompatibleProvider, type OpenAICompatibleProviderOptions, openaiCompatibleProvider, } from "./providers/openai-compatible.ts";
|
|
5
|
-
export { createSession,
|
|
5
|
+
export { type ApprovalAnswer, type ApprovalChoice, createSession, type HeldApproval, type Session, type SessionOptions, TURN_LIMITS, type TurnResult, type TurnStop, } from "./session.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// @openshain/agent:
|
|
2
|
-
export {
|
|
1
|
+
// @openshain/agent: The conversation loop that drives the runtime as an MCP client, and the model providers (bring your own key)
|
|
2
|
+
export { connectInMemory, jsonOf, wrap } from "./client.js";
|
|
3
3
|
export { AGENT_NAMES, pickAgentName } from "./names.js";
|
|
4
4
|
export { ANTHROPIC_PROVIDER_ID, AnthropicProvider, anthropicProvider, } from "./providers/anthropic.js";
|
|
5
5
|
export { OPENAI_COMPATIBLE_PROVIDER_ID, OpenAICompatibleProvider, openaiCompatibleProvider, } from "./providers/openai-compatible.js";
|
|
6
|
-
export { createSession,
|
|
6
|
+
export { createSession, TURN_LIMITS, } from "./session.js";
|
package/dist/session.d.ts
CHANGED
|
@@ -1,30 +1,65 @@
|
|
|
1
|
-
import { type AnyEvent, type
|
|
2
|
-
|
|
1
|
+
import { type AnyEvent, type Config, type ModelProvider, type Work, type WorkId } from "@openshain/core";
|
|
2
|
+
import { type RuntimeClient } from "./client.ts";
|
|
3
|
+
/** How much one turn of the conversation may do before it stops and the person is told. */
|
|
3
4
|
export declare const TURN_LIMITS: {
|
|
4
|
-
readonly modelCalls:
|
|
5
|
-
readonly toolCalls:
|
|
5
|
+
readonly modelCalls: 25;
|
|
6
|
+
readonly toolCalls: 40;
|
|
6
7
|
};
|
|
7
|
-
/** What the session's model may do: hand work out and look work up. It never touches files itself. */
|
|
8
|
-
export declare const SESSION_TOOLS: readonly ToolDefinition[];
|
|
9
8
|
export interface SessionOptions {
|
|
10
|
-
/**
|
|
11
|
-
model
|
|
9
|
+
/** The model the conversation runs on. The client owns it; the runtime never calls one. */
|
|
10
|
+
model: ModelProvider;
|
|
11
|
+
/** The workspace's configuration, for the prompt, the limits and the provider options. */
|
|
12
|
+
config: Pick<Config, "company" | "principal" | "profession" | "limits" | "model" | "debug">;
|
|
12
13
|
/** The name the agent goes by. Picked from the list, avoiding open sessions' names, when omitted. */
|
|
13
14
|
agentName?: string;
|
|
14
|
-
/** Called
|
|
15
|
-
onEvent?: (event: AnyEvent) => void | Promise<void>;
|
|
16
|
-
/** Called after every event a work started from this session records. A returned promise is awaited. */
|
|
17
|
-
onWorkEvent?: (workId: WorkId, event: AnyEvent) => void | Promise<void>;
|
|
15
|
+
/** Called for every event the session records or sees: the session's own and the works'. A returned promise is awaited. */
|
|
16
|
+
onEvent?: (workId: WorkId, event: AnyEvent) => void | Promise<void>;
|
|
18
17
|
/** Answers a question a work asks the person. Without it, the work waits for input. */
|
|
19
18
|
onInput?: (workId: WorkId, question: string) => Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Asks the person about a call the policy held, while the turn waits. Without it, the turn
|
|
21
|
+
* ends and the call stays held for `/approve` or for another client.
|
|
22
|
+
*/
|
|
23
|
+
onApproval?: (held: HeldApproval) => Promise<ApprovalAnswer>;
|
|
24
|
+
}
|
|
25
|
+
export type TurnStop = "turn_limit" | "aborted" | "max_tokens" | "refusal" | "model_error" | "approval";
|
|
26
|
+
/** A tool call the policy holds until a person decides on it. */
|
|
27
|
+
export interface HeldApproval {
|
|
28
|
+
approvalId: string;
|
|
29
|
+
workId: WorkId;
|
|
30
|
+
name: string;
|
|
31
|
+
input: unknown;
|
|
32
|
+
/** The rule that held it: the unit a person can say yes to for the rest of the conversation. */
|
|
33
|
+
ruleId: string;
|
|
34
|
+
/** review when a qualified reviewer has to decide; a person cannot stand in for one. */
|
|
35
|
+
kind: "approval" | "review";
|
|
36
|
+
reviewer?: {
|
|
37
|
+
role: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* What the person answered about a held call. `always` approves this one and every later call
|
|
43
|
+
* the same rule holds, for this conversation only: nothing is written to authority/, and every
|
|
44
|
+
* call is still recorded as requested and decided.
|
|
45
|
+
*/
|
|
46
|
+
export type ApprovalChoice = "approve" | "always" | "reject";
|
|
47
|
+
/** The person's answer: what they chose, and what they want the agent to know. */
|
|
48
|
+
export interface ApprovalAnswer {
|
|
49
|
+
choice: ApprovalChoice;
|
|
50
|
+
/** Why, in the person's words. Recorded with the decision and handed to the model. */
|
|
51
|
+
comment?: string;
|
|
20
52
|
}
|
|
21
|
-
export type TurnStop = "turn_limit" | "aborted" | "max_tokens" | "refusal" | "model_error";
|
|
22
53
|
export interface TurnResult {
|
|
23
54
|
/** What the model said to the person, possibly empty when the turn stopped early. */
|
|
24
55
|
reply: string;
|
|
25
56
|
/** Why the turn ended before the model replied, if it did. */
|
|
26
57
|
stopped?: TurnStop;
|
|
27
58
|
detail?: string;
|
|
59
|
+
/** The work the turn left open, when it stopped inside one. It can be continued with select. */
|
|
60
|
+
work?: WorkId;
|
|
61
|
+
/** The call held for approval, when the turn stopped for one. */
|
|
62
|
+
approval?: HeldApproval;
|
|
28
63
|
}
|
|
29
64
|
export interface Session {
|
|
30
65
|
readonly id: WorkId;
|
|
@@ -34,8 +69,41 @@ export interface Session {
|
|
|
34
69
|
turn(text: string, options?: {
|
|
35
70
|
signal?: AbortSignal;
|
|
36
71
|
}): Promise<TurnResult>;
|
|
37
|
-
/**
|
|
72
|
+
/** Names a stopped work as the candidate for the next request. The model decides whether to continue it. */
|
|
73
|
+
select(workId: WorkId): Promise<Work>;
|
|
74
|
+
/** Decides a held call as the person. approve runs it; either way the work becomes the candidate. */
|
|
75
|
+
decide(approvalId: string, decision: "approve" | "reject", comment?: string): Promise<{
|
|
76
|
+
workId: WorkId;
|
|
77
|
+
text: string;
|
|
78
|
+
}>;
|
|
79
|
+
/** Records what a qualified reviewer decided about a call held for review. */
|
|
80
|
+
review(input: {
|
|
81
|
+
approvalId: string;
|
|
82
|
+
decision: "approve" | "reject";
|
|
83
|
+
reviewer: {
|
|
84
|
+
name: string;
|
|
85
|
+
role: string;
|
|
86
|
+
qualification?: string;
|
|
87
|
+
};
|
|
88
|
+
interpretation: string;
|
|
89
|
+
appliesTo?: {
|
|
90
|
+
action?: string;
|
|
91
|
+
path?: string;
|
|
92
|
+
};
|
|
93
|
+
}): Promise<{
|
|
94
|
+
workId: WorkId;
|
|
95
|
+
text: string;
|
|
96
|
+
}>;
|
|
97
|
+
/** The calls held for approval across the workspace. */
|
|
98
|
+
approvals(): Promise<HeldApproval[]>;
|
|
99
|
+
/** The work the model is on right now, if any. */
|
|
100
|
+
currentWork(): WorkId | undefined;
|
|
101
|
+
/** Ends the conversation. The record stays; a work left in progress stays in progress. */
|
|
38
102
|
close(): Promise<Work>;
|
|
39
103
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Opens a conversation, recorded as a work of type "session", between the person and the model.
|
|
106
|
+
* The loop is a client of the runtime: it creates works, calls tools and closes works through
|
|
107
|
+
* the same MCP tools any other agent uses, and records its own model calls with work_record.
|
|
108
|
+
*/
|
|
109
|
+
export declare function createSession(client: RuntimeClient, options: SessionOptions): Promise<Session>;
|