@margay/ccl-core 1.3.5-rc.6
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/INTENT-GATEWAY-GUIDE.md +82 -0
- package/QUICKSTART.md +188 -0
- package/README.md +286 -0
- package/dist/cli.js +735032 -0
- package/dist/sdk/index.d.ts +13 -0
- package/dist/sdk/index.js +12 -0
- package/dist/sdk/mcpServer.d.ts +52 -0
- package/dist/sdk/mcpServer.js +134 -0
- package/dist/sdk/query.d.ts +91 -0
- package/dist/sdk/query.js +697 -0
- package/dist/sdk/queryHandle.d.ts +41 -0
- package/dist/sdk/queryHandle.js +414 -0
- package/dist/sdk/store.d.ts +16 -0
- package/dist/sdk/store.js +73 -0
- package/dist/sdk/transport.d.ts +20 -0
- package/dist/sdk/transport.js +100 -0
- package/dist/sdk/types.d.ts +423 -0
- package/dist/sdk/types.js +16 -0
- package/dist/vendor/ripgrep/VERSION +8 -0
- package/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
- package/package.json +149 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ccl-sdk — embeddable SDK form of CCL.
|
|
3
|
+
*
|
|
4
|
+
* import { query, resumeWithDecision } from '@margay/ccl-core/sdk'
|
|
5
|
+
*
|
|
6
|
+
* for await (const event of query('...', { canUseTool, sessionId })) { ... }
|
|
7
|
+
*/
|
|
8
|
+
export { buildCliArgs, buildCliEnv, decideFailClosed, getSessionUsage, prepareSkillsWrappers, queryWithTransport, resumeWithDecision, } from './query.js';
|
|
9
|
+
export { query, queryHandleWithTransport } from './queryHandle.js';
|
|
10
|
+
export { createSdkMcpServer, tool, SdkMcpRegistry, type SdkMcpToolDefinition, type McpSdkServerConfigWithInstance, } from './mcpServer.js';
|
|
11
|
+
export { defaultStoreDir, fileSessionStore } from './store.js';
|
|
12
|
+
export { spawnCliTransport } from './transport.js';
|
|
13
|
+
export type { AccumulatedUsage, CanUseToolContext, CanUseToolFn, CanUseToolResult, InitializationResult, LifecycleHooks, PendingApproval, PendingDecision, ProviderConfig, Query, QueryOptions, SDKEvent, SessionStore, Transport, Usage, WireMessage, } from './types.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ccl-sdk — embeddable SDK form of CCL.
|
|
3
|
+
*
|
|
4
|
+
* import { query, resumeWithDecision } from '@margay/ccl-core/sdk'
|
|
5
|
+
*
|
|
6
|
+
* for await (const event of query('...', { canUseTool, sessionId })) { ... }
|
|
7
|
+
*/
|
|
8
|
+
export { buildCliArgs, buildCliEnv, decideFailClosed, getSessionUsage, prepareSkillsWrappers, queryWithTransport, resumeWithDecision, } from './query.js';
|
|
9
|
+
export { query, queryHandleWithTransport } from './queryHandle.js';
|
|
10
|
+
export { createSdkMcpServer, tool, SdkMcpRegistry, } from './mcpServer.js';
|
|
11
|
+
export { defaultStoreDir, fileSessionStore } from './store.js';
|
|
12
|
+
export { spawnCliTransport } from './transport.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { type JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { type ZodRawShape } from 'zod/v4';
|
|
4
|
+
export type SdkMcpToolDefinition = {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputSchema: ZodRawShape;
|
|
8
|
+
handler: (args: Record<string, unknown>, extra: unknown) => Promise<unknown> | unknown;
|
|
9
|
+
annotations?: Record<string, unknown>;
|
|
10
|
+
searchHint?: string;
|
|
11
|
+
};
|
|
12
|
+
export type McpSdkServerConfigWithInstance = {
|
|
13
|
+
name: string;
|
|
14
|
+
version?: string;
|
|
15
|
+
tools?: SdkMcpToolDefinition[];
|
|
16
|
+
/** The live @modelcontextprotocol/sdk Server built from `tools`. */
|
|
17
|
+
instance?: Server;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
/** Define one in-process tool (Claude-Agent-SDK-compatible signature). */
|
|
21
|
+
export declare function tool(name: string, description: string, inputSchema: ZodRawShape, handler: SdkMcpToolDefinition['handler'], extras?: {
|
|
22
|
+
annotations?: Record<string, unknown>;
|
|
23
|
+
searchHint?: string;
|
|
24
|
+
}): SdkMcpToolDefinition;
|
|
25
|
+
/**
|
|
26
|
+
* Build an in-process MCP server from host-defined JS tools. Returns a config carrying the live
|
|
27
|
+
* `@modelcontextprotocol/sdk` Server; pass it to `query({ sdkMcpServers: [createSdkMcpServer(...)] })`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createSdkMcpServer(options: {
|
|
30
|
+
name: string;
|
|
31
|
+
version?: string;
|
|
32
|
+
tools?: SdkMcpToolDefinition[];
|
|
33
|
+
}): McpSdkServerConfigWithInstance;
|
|
34
|
+
/**
|
|
35
|
+
* Bridges host in-process MCP servers to the control protocol. For each inbound `mcp_message`
|
|
36
|
+
* control_request from the CLI subprocess, routes the JSONRPC message to the matching server and
|
|
37
|
+
* replies to that request_id with the server's JSONRPC response. Request/response are correlated by
|
|
38
|
+
* JSONRPC id; notifications (no id) are acked immediately.
|
|
39
|
+
*/
|
|
40
|
+
export declare class SdkMcpRegistry {
|
|
41
|
+
private readonly reply;
|
|
42
|
+
private readonly transports;
|
|
43
|
+
private readonly pending;
|
|
44
|
+
private constructor();
|
|
45
|
+
/** Connect every server's instance to a control transport. */
|
|
46
|
+
static create(configs: McpSdkServerConfigWithInstance[], reply: (requestId: string, mcpResponse: JSONRPCMessage | undefined) => void): Promise<SdkMcpRegistry>;
|
|
47
|
+
get names(): string[];
|
|
48
|
+
has(serverName: string): boolean;
|
|
49
|
+
/** Handle one inbound `mcp_message` control_request. Never throws (fail-soft). */
|
|
50
|
+
handle(serverName: string, message: JSONRPCMessage, requestId: string): void;
|
|
51
|
+
close(): Promise<void>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// slice sdk-in-process-mcp-server (G1/S1) — host defines JS tools IN-PROCESS; the agent running in
|
|
2
|
+
// the spawned CLI subprocess calls them over the control protocol (`mcp_message`). This file is
|
|
3
|
+
// SELF-CONTAINED: tsconfig.sdk.json compiles only `sdk/**` (rootDir:sdk), so it may import ONLY
|
|
4
|
+
// node_modules (never ../services or ../utils). The pass-through transport is therefore inlined.
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import { z } from 'zod/v4';
|
|
8
|
+
/** Define one in-process tool (Claude-Agent-SDK-compatible signature). */
|
|
9
|
+
export function tool(name, description, inputSchema, handler, extras) {
|
|
10
|
+
return { name, description, inputSchema, handler, ...(extras ?? {}) };
|
|
11
|
+
}
|
|
12
|
+
/** Normalize a handler return value into an MCP CallToolResult. */
|
|
13
|
+
function toCallToolResult(result) {
|
|
14
|
+
if (result &&
|
|
15
|
+
typeof result === 'object' &&
|
|
16
|
+
'content' in result) {
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
const text = typeof result === 'string' ? result : JSON.stringify(result ?? null);
|
|
20
|
+
return { content: [{ type: 'text', text }] };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build an in-process MCP server from host-defined JS tools. Returns a config carrying the live
|
|
24
|
+
* `@modelcontextprotocol/sdk` Server; pass it to `query({ sdkMcpServers: [createSdkMcpServer(...)] })`.
|
|
25
|
+
*/
|
|
26
|
+
export function createSdkMcpServer(options) {
|
|
27
|
+
const tools = options.tools ?? [];
|
|
28
|
+
const server = new Server({ name: options.name, version: options.version ?? '1.0.0' }, { capabilities: { tools: {} } });
|
|
29
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
30
|
+
tools: tools.map(t => ({
|
|
31
|
+
name: t.name,
|
|
32
|
+
description: t.description,
|
|
33
|
+
inputSchema: z.toJSONSchema(z.object(t.inputSchema)),
|
|
34
|
+
...(t.annotations ? { annotations: t.annotations } : {}),
|
|
35
|
+
})),
|
|
36
|
+
}));
|
|
37
|
+
server.setRequestHandler(CallToolRequestSchema, async ({ params }) => {
|
|
38
|
+
const t = tools.find(x => x.name === params.name);
|
|
39
|
+
if (!t) {
|
|
40
|
+
throw new Error(`SDK MCP tool '${params.name}' not found on server '${options.name}'`);
|
|
41
|
+
}
|
|
42
|
+
const out = await t.handler((params.arguments ?? {}), {});
|
|
43
|
+
return toCallToolResult(out);
|
|
44
|
+
});
|
|
45
|
+
return { name: options.name, version: options.version, tools, instance: server };
|
|
46
|
+
}
|
|
47
|
+
/** Minimal pass-through Transport for an in-process SDK MCP server (inlined; see file header). */
|
|
48
|
+
class SdkControlServerTransport {
|
|
49
|
+
onSend;
|
|
50
|
+
closed = false;
|
|
51
|
+
onclose;
|
|
52
|
+
onerror;
|
|
53
|
+
onmessage;
|
|
54
|
+
constructor(onSend) {
|
|
55
|
+
this.onSend = onSend;
|
|
56
|
+
}
|
|
57
|
+
async start() { }
|
|
58
|
+
async send(message) {
|
|
59
|
+
if (this.closed)
|
|
60
|
+
throw new Error('Transport is closed');
|
|
61
|
+
this.onSend(message);
|
|
62
|
+
}
|
|
63
|
+
async close() {
|
|
64
|
+
if (this.closed)
|
|
65
|
+
return;
|
|
66
|
+
this.closed = true;
|
|
67
|
+
this.onclose?.();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function messageId(message) {
|
|
71
|
+
return message.id;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Bridges host in-process MCP servers to the control protocol. For each inbound `mcp_message`
|
|
75
|
+
* control_request from the CLI subprocess, routes the JSONRPC message to the matching server and
|
|
76
|
+
* replies to that request_id with the server's JSONRPC response. Request/response are correlated by
|
|
77
|
+
* JSONRPC id; notifications (no id) are acked immediately.
|
|
78
|
+
*/
|
|
79
|
+
export class SdkMcpRegistry {
|
|
80
|
+
reply;
|
|
81
|
+
transports = new Map();
|
|
82
|
+
pending = new Map();
|
|
83
|
+
constructor(reply) {
|
|
84
|
+
this.reply = reply;
|
|
85
|
+
}
|
|
86
|
+
/** Connect every server's instance to a control transport. */
|
|
87
|
+
static async create(configs, reply) {
|
|
88
|
+
const reg = new SdkMcpRegistry(reply);
|
|
89
|
+
for (const cfg of configs) {
|
|
90
|
+
if (!cfg.instance)
|
|
91
|
+
continue;
|
|
92
|
+
const transport = new SdkControlServerTransport(message => {
|
|
93
|
+
const id = messageId(message);
|
|
94
|
+
if (id !== undefined && reg.pending.has(id)) {
|
|
95
|
+
const requestId = reg.pending.get(id);
|
|
96
|
+
reg.pending.delete(id);
|
|
97
|
+
reg.reply(requestId, message);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
await cfg.instance.connect(transport);
|
|
101
|
+
reg.transports.set(cfg.name, transport);
|
|
102
|
+
}
|
|
103
|
+
return reg;
|
|
104
|
+
}
|
|
105
|
+
get names() {
|
|
106
|
+
return [...this.transports.keys()];
|
|
107
|
+
}
|
|
108
|
+
has(serverName) {
|
|
109
|
+
return this.transports.has(serverName);
|
|
110
|
+
}
|
|
111
|
+
/** Handle one inbound `mcp_message` control_request. Never throws (fail-soft). */
|
|
112
|
+
handle(serverName, message, requestId) {
|
|
113
|
+
const transport = this.transports.get(serverName);
|
|
114
|
+
if (!transport) {
|
|
115
|
+
const id = messageId(message) ?? null;
|
|
116
|
+
this.reply(requestId, {
|
|
117
|
+
jsonrpc: '2.0',
|
|
118
|
+
id,
|
|
119
|
+
error: { code: -32601, message: `unknown SDK MCP server '${serverName}'` },
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const id = messageId(message);
|
|
124
|
+
if (id !== undefined)
|
|
125
|
+
this.pending.set(id, requestId);
|
|
126
|
+
transport.onmessage?.(message);
|
|
127
|
+
if (id === undefined)
|
|
128
|
+
this.reply(requestId, undefined);
|
|
129
|
+
}
|
|
130
|
+
async close() {
|
|
131
|
+
for (const t of this.transports.values())
|
|
132
|
+
await t.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ccl-sdk query() — the programmatic headless entry (issue P0-1) with the
|
|
3
|
+
* canUseTool permission callback (P0-2 including pending suspend/resume),
|
|
4
|
+
* session store integration (P0-3), provider mapping (P0-4), skills dirs
|
|
5
|
+
* (P1-5), lifecycle hooks (P1-6), and usage accumulation (P1-7).
|
|
6
|
+
*
|
|
7
|
+
* Architecture: the SDK is the HOST side of the stream-json control protocol
|
|
8
|
+
* that cli/print.ts already implements. We run the CLI as a managed
|
|
9
|
+
* subprocess (explicitly allowed by the issue's "进程内(或受管子进程)"),
|
|
10
|
+
* which keeps every user-visible CLI behavior — tools, skills, hooks, session
|
|
11
|
+
* persistence, gateway routing — byte-identical between CLI and SDK use.
|
|
12
|
+
*
|
|
13
|
+
* Pending suspend/resume mechanism (P0-2, zero CLI changes):
|
|
14
|
+
* - Suspend: on a `pending` callback result we persist the approval record,
|
|
15
|
+
* terminate the subprocess WITHOUT answering the can_use_tool request, and
|
|
16
|
+
* end the stream with a `suspended` event. The transcript on disk ends at
|
|
17
|
+
* the assistant tool_use whose tool never ran.
|
|
18
|
+
* - Resume: resumeWithDecision() relaunches with --resume <sessionId> and
|
|
19
|
+
* CLAUDE_CODE_RESUME_INTERRUPTED_TURN=1. The CLI's resume path drops the
|
|
20
|
+
* unresolved tool_use (filterUnresolvedToolUses) and auto-continues the
|
|
21
|
+
* interrupted turn (cli/print.ts:1195), so the model re-issues the tool
|
|
22
|
+
* call; the SDK intercepts the matching can_use_tool request and applies
|
|
23
|
+
* the recorded decision — on allow, with EXACTLY the approved input (the
|
|
24
|
+
* recorded pending input, or the host's updatedInput), regardless of what
|
|
25
|
+
* the model re-generated. Deny works symmetrically. One-shot: the record
|
|
26
|
+
* is deleted after it is applied.
|
|
27
|
+
*/
|
|
28
|
+
import type { CanUseToolFn, PendingDecision, QueryOptions, SDKEvent, SessionStore, Transport } from './types.js';
|
|
29
|
+
/** Builds the CLI argv for a query. Exported for unit tests. */
|
|
30
|
+
export declare function buildCliArgs(options: QueryOptions): string[];
|
|
31
|
+
/** Builds subprocess env additions. Exported for unit tests. */
|
|
32
|
+
export declare function buildCliEnv(options: QueryOptions): Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Wraps each skills dir as an ephemeral plugin (a temp dir whose `skills`
|
|
35
|
+
* entry symlinks to the user's dir) so the CLI loads it via --plugin-dir,
|
|
36
|
+
* its cwd-independent skill channel (P1-5). Exported for unit tests.
|
|
37
|
+
*/
|
|
38
|
+
export declare function prepareSkillsWrappers(skillsDirs: string[]): string[];
|
|
39
|
+
type PermissionDecisionWire = {
|
|
40
|
+
behavior: 'allow';
|
|
41
|
+
updatedInput: Record<string, unknown>;
|
|
42
|
+
toolUseID: string;
|
|
43
|
+
} | {
|
|
44
|
+
behavior: 'deny';
|
|
45
|
+
message: string;
|
|
46
|
+
interrupt?: boolean;
|
|
47
|
+
toolUseID: string;
|
|
48
|
+
};
|
|
49
|
+
type CallbackOutcome = {
|
|
50
|
+
kind: 'decision';
|
|
51
|
+
decision: PermissionDecisionWire;
|
|
52
|
+
failClosedReason?: string;
|
|
53
|
+
} | {
|
|
54
|
+
kind: 'pending';
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Fail-closed wrapper around the host's canUseTool callback (issue P0-2):
|
|
58
|
+
* throw, timeout, or an unrecognized return value all become deny, and the
|
|
59
|
+
* tool does not execute. `pending` is surfaced to the caller for suspend.
|
|
60
|
+
*/
|
|
61
|
+
export declare function decideFailClosed(callback: CanUseToolFn, toolName: string, input: Record<string, unknown>, context: {
|
|
62
|
+
toolUseId: string;
|
|
63
|
+
agentId?: string;
|
|
64
|
+
}, timeoutMs: number): Promise<CallbackOutcome>;
|
|
65
|
+
/**
|
|
66
|
+
* Core loop, transport-injectable for tests. Production callers use query()
|
|
67
|
+
* or resumeWithDecision().
|
|
68
|
+
*/
|
|
69
|
+
export declare function queryWithTransport(prompt: string | null, options: QueryOptions, transport: Transport): AsyncGenerator<SDKEvent>;
|
|
70
|
+
/** Spawns the managed CLI subprocess for a set of options. Shared with the P1 Query handle. */
|
|
71
|
+
export declare function spawnForOptions(options: QueryOptions): Transport;
|
|
72
|
+
/**
|
|
73
|
+
* Run one prompt against a CCL agent session and stream JSON-serializable
|
|
74
|
+
* events. See QueryOptions for session resume, permission callback, model,
|
|
75
|
+
* provider, and tool configuration.
|
|
76
|
+
*/
|
|
77
|
+
export declare function query(prompt: string, options?: QueryOptions): AsyncGenerator<SDKEvent>;
|
|
78
|
+
/**
|
|
79
|
+
* Resume a session suspended by a `pending` permission result and apply the
|
|
80
|
+
* owner's decision (issue P0-2 acceptance path). Cross-process: the pending
|
|
81
|
+
* record lives in the session store, the conversation in the CLI's
|
|
82
|
+
* transcript store — nothing is held in memory between suspend and resume.
|
|
83
|
+
*
|
|
84
|
+
* On allow, the suspended tool executes with exactly the recorded input (or
|
|
85
|
+
* decision.updatedInput if the owner amended it); on deny the model receives
|
|
86
|
+
* the denial and the session continues.
|
|
87
|
+
*/
|
|
88
|
+
export declare function resumeWithDecision(sessionId: string, decision: PendingDecision, options?: QueryOptions): AsyncGenerator<SDKEvent>;
|
|
89
|
+
/** P1-7: accumulated usage for a session, across all queries that ran it. */
|
|
90
|
+
export declare function getSessionUsage(sessionId: string, store?: SessionStore): ReturnType<SessionStore['getUsage']>;
|
|
91
|
+
export {};
|