@nathapp/nax-agent 0.84.0 → 0.85.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/CHANGELOG.md +6 -0
- package/README.md +27 -0
- package/dist/config/catalog-overrides.d.ts +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/mcp/connect.d.ts +3 -0
- package/dist/mcp/connect.js +144 -0
- package/dist/mcp/errors.d.ts +9 -0
- package/dist/mcp/errors.js +16 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/result-text.d.ts +8 -0
- package/dist/mcp/result-text.js +38 -0
- package/dist/mcp/stdio-close.d.ts +1 -0
- package/dist/mcp/stdio-close.js +31 -0
- package/dist/mcp/transport.d.ts +14 -0
- package/dist/mcp/transport.js +41 -0
- package/dist/mcp/types.d.ts +48 -0
- package/dist/mcp/types.js +1 -0
- package/dist/native/index.d.ts +1 -0
- package/dist/native/index.js +1 -0
- package/dist/native/provider-models.d.ts +13 -0
- package/dist/native/provider-models.js +22 -0
- package/dist/tools/denial-redirect.js +1 -1
- package/dist/tools/git.js +21 -5
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@nathapp/nax-agent` are recorded here. The format follow
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). While the version is `0.x`, a minor
|
|
5
5
|
release may change the public API.
|
|
6
6
|
|
|
7
|
+
## [0.85.0] - 2026-10-09
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `listProviderModels(providerId, catalogOverrides?)` and `ProviderModel`: the tool-capable models the catalog offers for one provider, as plain data (#2414).
|
|
12
|
+
|
|
7
13
|
## [0.84.0] - 2026-10-09
|
|
8
14
|
|
|
9
15
|
- Versioning: released in lockstep with `@nathapp/nax`, `@nathapp/nax-ai` and the other agent package at one shared version, starting at 0.84.0 (previously 0.3.x).
|
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ Requires **Node.js >= 22.19.0**. The package is ESM only and ships no Bun code:
|
|
|
16
16
|
|
|
17
17
|
- **`@nathapp/nax-agent`** is the supported entry. Every export is named, and none starts with `_`. Its exact names are pinned in [`api/nax-agent.api.txt`](api/nax-agent.api.txt), which CI compares with the built declarations.
|
|
18
18
|
- **`@nathapp/nax-agent/internal`** is **nax-only and outside semver.** nax bundles this package and reaches below the public entry for shared helpers, `NaxError`, deep modules and the `_*Deps` test seams. Names, shapes and behaviour there can change in any release, patch included, and a change there is not a breaking change. Do not import it from another project.
|
|
19
|
+
- **`@nathapp/nax-agent/mcp`** — the shared MCP connection layer; see below.
|
|
19
20
|
|
|
20
21
|
## Process-wide slots
|
|
21
22
|
|
|
@@ -97,6 +98,32 @@ Profiles set what a session may do:
|
|
|
97
98
|
- **History and restarts.** The store holds one document per session, saved at the end of every turn. `close()` keeps it. After a restart, `resumeAgentSession(sessionId, options)` reopens it with the same options you created it with; pass `instructions` and `tools` again, since they are not stored. If the process died mid-turn, `session.lastTurn` is `{ turnId, status: "interrupted" }`, and that turn's message is not in history. A resume with a different model throws `AGENT_SESSION_MODEL_MISMATCH`; a different reasoning effort (`[high]`) is the same model. Resuming with a different backend kind throws `AGENT_SESSION_BACKEND_MISMATCH`; a stored document with no `backend` field is native. Do not open one session id twice at once: the store has no lock.
|
|
98
99
|
- **Errors.** Every error is an `AgentSessionError` with a code `AGENT_SESSION_*`: `INVALID_OPTIONS`, `EXISTS`, `BUSY`, `CLOSED`, `INVALID_ANSWER`, `NOT_FOUND`, `SCHEMA_UNSUPPORTED`, `MODEL_MISMATCH`, `SANDBOX_UNAVAILABLE`, `TOOL_NAME_RESERVED`, `BACKEND_UNAVAILABLE`, `AUTH_REQUIRED`, `CAPABILITY_UNSUPPORTED` or `BACKEND_MISMATCH`. A stored document that cannot be read is a `NaxError` with `TRANSCRIPT_CORRUPT`.
|
|
99
100
|
|
|
101
|
+
## MCP connections (`@nathapp/nax-agent/mcp`)
|
|
102
|
+
|
|
103
|
+
A small MCP client for embedders that bridge MCP servers into their own tools.
|
|
104
|
+
The root entry never loads it.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { connectMcp } from "@nathapp/nax-agent/mcp";
|
|
108
|
+
|
|
109
|
+
const connection = await connectMcp(
|
|
110
|
+
{ kind: "stdio", command: "my-mcp-server", args: [], env: { API_KEY: "..." }, cwd: "/repo" },
|
|
111
|
+
{ signal, timeoutMs: 30_000, clientInfo: { name: "my-app", version: "1.0.0" } },
|
|
112
|
+
);
|
|
113
|
+
connection.tools; // listed once, every page
|
|
114
|
+
await connection.call("search", { q: "x" }, { signal, timeoutMs: 600_000, maxBytes: 1_048_576 });
|
|
115
|
+
await connection.close(); // waits for the process; SIGKILL after 3 s
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- Transports: `stdio` and streamable `http` (with `headers`). SSE is not supported.
|
|
119
|
+
- `env` is laid over the default environment, so `PATH` is kept. Server stderr is
|
|
120
|
+
never inherited; its last 512 bytes are attached to a connect failure.
|
|
121
|
+
- A server-side tool error is a result with `isError: true`; protocol errors,
|
|
122
|
+
timeouts, aborts and closed connections throw `McpCallError`.
|
|
123
|
+
- `onClose` reports a stdio server that exits on its own; HTTP has no such signal.
|
|
124
|
+
- The facade (`createAgentSession`) has no `mcpServers` option: wrap MCP tools as
|
|
125
|
+
`EmbedderTool`s yourself.
|
|
126
|
+
|
|
100
127
|
## Status and roadmap
|
|
101
128
|
|
|
102
129
|
`0.x` may reshape `.` with a minor bump. `@nathapp/nax-agent-acp` provides an ACP backend for the same API (0.3.0). See [`CHANGELOG.md`](CHANGELOG.md).
|
|
@@ -52,6 +52,18 @@ export interface OpenRouterRouting {
|
|
|
52
52
|
ignore?: readonly string[];
|
|
53
53
|
quantizations?: readonly string[];
|
|
54
54
|
sort?: "price" | "throughput" | "latency";
|
|
55
|
+
preferred_min_throughput?: number | {
|
|
56
|
+
p50?: number;
|
|
57
|
+
p75?: number;
|
|
58
|
+
p90?: number;
|
|
59
|
+
p99?: number;
|
|
60
|
+
};
|
|
61
|
+
preferred_max_latency?: number | {
|
|
62
|
+
p50?: number;
|
|
63
|
+
p75?: number;
|
|
64
|
+
p90?: number;
|
|
65
|
+
p99?: number;
|
|
66
|
+
};
|
|
55
67
|
}
|
|
56
68
|
/**
|
|
57
69
|
* One complete catalog entry for a model the bundled pi-ai snapshot does not
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export { capStrings, redactSecrets } from "#src/internal/redact";
|
|
|
20
20
|
export { NaxError } from "#src/infra/nax-error";
|
|
21
21
|
export { createTerminalAuthInteraction, PLAIN_STYLE, PromptCancelledError, type TerminalAuthOptions, type TerminalStyle, } from "#src/terminal-auth/index";
|
|
22
22
|
export type { CredentialSource } from "#src/native/credentials/session-source";
|
|
23
|
-
export { AuthCancelledError, type AuthEvent, type AuthInteraction, type AuthLink, type AuthMethod, type AuthOption, type AuthPrompt, type AuthResult, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, createFileTranscriptStore, createMemoryTranscriptStore, credentialFilePath, DEFAULT_PI_AUTH_PATH, type ImportOutcome, importPiCredentials, listStoredProviders, loginProviderIds, MAX_RETAINED_TRANSCRIPTS, type MemoryTranscriptStore, NATIVE_AGENT, type NativeCatalogOverrides, type NativeCompleteContext, type NativeCompleteOptions, type NativeCompleteResult, NativeSessionAdapter, NativeSessionUnsupportedError, type NativeTierConfig, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, providersWithoutCredentials, pruneRetainedTranscripts, type ResolveResult, type ResolveStatus, removeStoredProvider, resolveNativeId, runLogin, type StoredEntry, servedAuth, type TranscriptAcpRecord, type TranscriptDoc, type TranscriptStore, type TurnMarker, } from "#src/native/index";
|
|
23
|
+
export { AuthCancelledError, type AuthEvent, type AuthInteraction, type AuthLink, type AuthMethod, type AuthOption, type AuthPrompt, type AuthResult, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, createFileTranscriptStore, createMemoryTranscriptStore, credentialFilePath, DEFAULT_PI_AUTH_PATH, type ImportOutcome, importPiCredentials, listProviderModels, listStoredProviders, loginProviderIds, MAX_RETAINED_TRANSCRIPTS, type MemoryTranscriptStore, NATIVE_AGENT, type NativeCatalogOverrides, type NativeCompleteContext, type NativeCompleteOptions, type NativeCompleteResult, NativeSessionAdapter, NativeSessionUnsupportedError, type NativeTierConfig, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, type ProviderModel, providersWithoutCredentials, pruneRetainedTranscripts, type ResolveResult, type ResolveStatus, removeStoredProvider, resolveNativeId, runLogin, type StoredEntry, servedAuth, type TranscriptAcpRecord, type TranscriptDoc, type TranscriptStore, type TurnMarker, } from "#src/native/index";
|
|
24
24
|
export type { NativeSessionAdapterOptions } from "#src/native/session-adapter";
|
|
25
25
|
export type { AfterResponsePatch, AfterResponsePayload, AfterToolPatch, AfterToolPayload, BeforeCompactionPatch, BeforeCompactionPayload, BeforeRequestPatch, BeforeRequestPayload, BeforeToolOutcome, BeforeToolPayload, BeforeTurnEndPatch, BeforeTurnEndPayload, BeforeTurnPatch, BeforeTurnPayload, CompleteCallOptions, ExternalHandlerOf, LoopEvent, LoopEventMap, LoopHandlerContext, LoopHandlerEntry, LoopHandlerSet, PatchOf, PayloadOf, TransformContextPatch, TransformContextPayload, } from "#src/native/session/loop-events/types";
|
|
26
26
|
export { displayToolInput, TOOL_CALL_INPUT_BYTES, TOOL_RESULT_PREVIEW_BYTES, toolResultPreview, } from "#src/native/session/turn-event-emitter";
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export { killProcessGroup } from "#src/internal/process-kill";
|
|
|
17
17
|
export { capStrings, redactSecrets } from "#src/internal/redact";
|
|
18
18
|
export { NaxError } from "#src/infra/nax-error";
|
|
19
19
|
export { createTerminalAuthInteraction, PLAIN_STYLE, PromptCancelledError, } from "#src/terminal-auth/index";
|
|
20
|
-
export { AuthCancelledError, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, createFileTranscriptStore, createMemoryTranscriptStore, credentialFilePath, DEFAULT_PI_AUTH_PATH, importPiCredentials, listStoredProviders, loginProviderIds, MAX_RETAINED_TRANSCRIPTS, NATIVE_AGENT, NativeSessionAdapter, NativeSessionUnsupportedError, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, providersWithoutCredentials, pruneRetainedTranscripts, removeStoredProvider, resolveNativeId, runLogin, servedAuth, } from "#src/native/index";
|
|
20
|
+
export { AuthCancelledError, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, createFileTranscriptStore, createMemoryTranscriptStore, credentialFilePath, DEFAULT_PI_AUTH_PATH, importPiCredentials, listProviderModels, listStoredProviders, loginProviderIds, MAX_RETAINED_TRANSCRIPTS, NATIVE_AGENT, NativeSessionAdapter, NativeSessionUnsupportedError, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, providersWithoutCredentials, pruneRetainedTranscripts, removeStoredProvider, resolveNativeId, runLogin, servedAuth, } from "#src/native/index";
|
|
21
21
|
export { displayToolInput, TOOL_CALL_INPUT_BYTES, TOOL_RESULT_PREVIEW_BYTES, toolResultPreview, } from "#src/native/session/turn-event-emitter";
|
|
22
22
|
export { ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_PROFILE_REASON, ASK_TIMEOUT_REASON, ASK_UNAVAILABLE_REASON, ASK_UNSHOWABLE_REASON, appendApproval, appendApprovalAudit, approvalId, approvalsPath, chainAskLinks, clearApprovalsTaint, createApprovalsLink, findApproval, findSecretSpans, headlessAskResolver, isForgeCapable, lexBashCommand, maskForPrompt, parseRuleList, parseToolExpression, prepareApprovalsStore, readApprovals, readApprovalsFile, readApprovalsFileDetailed, redactForRow, redactRowStrings, removeApprovals, taintApprovals, writeApprovalsFile, } from "#src/permissions/index";
|
|
23
23
|
export { getAgentRuntime, nodeRuntime, setAgentRuntime, } from "#src/runtime/index";
|
package/dist/internal.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export * from "#src/native/credentials/index";
|
|
|
57
57
|
export * from "#src/native/errors";
|
|
58
58
|
export * from "#src/native/model-resolver";
|
|
59
59
|
export * from "#src/native/models";
|
|
60
|
+
export * from "#src/native/provider-models";
|
|
60
61
|
export * from "#src/native/session/compaction";
|
|
61
62
|
export { DEFAULT_INSTRUCTION_FILE_NAME, isInstructionFileName } from "#src/native/session/instruction-file-name";
|
|
62
63
|
export * from "#src/native/session/loop-events/external-handler";
|
package/dist/internal.js
CHANGED
|
@@ -57,6 +57,7 @@ export * from "#src/native/credentials/index";
|
|
|
57
57
|
export * from "#src/native/errors";
|
|
58
58
|
export * from "#src/native/model-resolver";
|
|
59
59
|
export * from "#src/native/models";
|
|
60
|
+
export * from "#src/native/provider-models";
|
|
60
61
|
export * from "#src/native/session/compaction";
|
|
61
62
|
export { DEFAULT_INSTRUCTION_FILE_NAME, isInstructionFileName } from "#src/native/session/instruction-file-name";
|
|
62
63
|
export * from "#src/native/session/loop-events/external-handler";
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connectMcp (S5-5 spec §3.1): one MCP server over stdio or streamable HTTP.
|
|
3
|
+
* Connect (initialize + every tools/list page) is bounded by timeoutMs and the
|
|
4
|
+
* caller's signal; a call honours its signal and timeout (reset on progress);
|
|
5
|
+
* close is idempotent, bounded by closeGraceMs, and for stdio resolves only
|
|
6
|
+
* once the process is gone. A failed connect is closed the same way.
|
|
7
|
+
*/
|
|
8
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
9
|
+
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { McpCallError, McpConnectError } from "#src/mcp/errors";
|
|
11
|
+
import { resultText } from "#src/mcp/result-text";
|
|
12
|
+
import { waitForStdioExit } from "#src/mcp/stdio-close";
|
|
13
|
+
import { _mcpTransportDeps } from "#src/mcp/transport";
|
|
14
|
+
export const DEFAULT_CLOSE_GRACE_MS = 3000;
|
|
15
|
+
const EXIT_REASON = "the server process exited";
|
|
16
|
+
const isRaw = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17
|
+
const messageOf = (error) => (error instanceof Error ? error.message : String(error));
|
|
18
|
+
/** Waits for `promise` at most `ms`; the timer is cleared, so nothing keeps the process alive. */
|
|
19
|
+
async function within(promise, ms) {
|
|
20
|
+
let timer;
|
|
21
|
+
const limit = new Promise((resolve) => {
|
|
22
|
+
timer = setTimeout(resolve, ms);
|
|
23
|
+
});
|
|
24
|
+
await Promise.race([promise.catch(() => undefined), limit]);
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
}
|
|
27
|
+
function trackLife(client, kind) {
|
|
28
|
+
const life = { exited: false, closing: false, exitReason: undefined, listeners: [] };
|
|
29
|
+
client.onclose = () => {
|
|
30
|
+
life.exited = true;
|
|
31
|
+
if (life.closing || kind !== "stdio" || life.exitReason !== undefined)
|
|
32
|
+
return;
|
|
33
|
+
life.exitReason = EXIT_REASON;
|
|
34
|
+
for (const listener of life.listeners)
|
|
35
|
+
listener(EXIT_REASON);
|
|
36
|
+
};
|
|
37
|
+
return life;
|
|
38
|
+
}
|
|
39
|
+
async function closeTransport(client, handle, graceMs, life) {
|
|
40
|
+
life.closing = true;
|
|
41
|
+
const pid = handle.pid();
|
|
42
|
+
if (handle.terminate !== undefined)
|
|
43
|
+
await within(handle.terminate(), graceMs);
|
|
44
|
+
const closed = client.close();
|
|
45
|
+
// The stdio path below waits on the process, not on this promise: attach a
|
|
46
|
+
// handler now so a transport close that later rejects is never unhandled.
|
|
47
|
+
void closed.catch(() => undefined);
|
|
48
|
+
if (pid === null)
|
|
49
|
+
return within(closed, graceMs);
|
|
50
|
+
await waitForStdioExit(pid, () => life.exited, graceMs);
|
|
51
|
+
}
|
|
52
|
+
function toolInfo(raw) {
|
|
53
|
+
if (!isRaw(raw) || typeof raw.name !== "string" || raw.name.length === 0)
|
|
54
|
+
return undefined;
|
|
55
|
+
const inputSchema = isRaw(raw.inputSchema) ? { ...raw.inputSchema } : {};
|
|
56
|
+
return { name: raw.name, description: typeof raw.description === "string" ? raw.description : "", inputSchema };
|
|
57
|
+
}
|
|
58
|
+
async function listAll(client, signal, timeout) {
|
|
59
|
+
const tools = [];
|
|
60
|
+
let cursor;
|
|
61
|
+
do {
|
|
62
|
+
const page = await client.listTools(cursor !== undefined ? { cursor } : undefined, { signal, timeout });
|
|
63
|
+
for (const raw of page.tools ?? []) {
|
|
64
|
+
const info = toolInfo(raw);
|
|
65
|
+
if (info !== undefined)
|
|
66
|
+
tools.push(info);
|
|
67
|
+
}
|
|
68
|
+
cursor = typeof page.nextCursor === "string" && page.nextCursor !== "" ? page.nextCursor : undefined;
|
|
69
|
+
} while (cursor !== undefined);
|
|
70
|
+
return tools;
|
|
71
|
+
}
|
|
72
|
+
function connectFailure(error, deadline, opts, handle) {
|
|
73
|
+
const timedOut = deadline.aborted || (error instanceof McpError && error.code === ErrorCode.RequestTimeout);
|
|
74
|
+
const reason = timedOut ? `timed out after ${opts.timeoutMs} ms` : messageOf(error);
|
|
75
|
+
const tail = handle.stderrTail().trim();
|
|
76
|
+
return new McpConnectError(`MCP connect failed: ${reason}`, tail === "" ? undefined : tail);
|
|
77
|
+
}
|
|
78
|
+
function createHandle(config) {
|
|
79
|
+
try {
|
|
80
|
+
return _mcpTransportDeps.create(config);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new McpConnectError(`MCP connect failed: ${messageOf(error)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export async function connectMcp(config, opts) {
|
|
87
|
+
const handle = createHandle(config);
|
|
88
|
+
const client = new Client({ name: opts.clientInfo.name, version: opts.clientInfo.version });
|
|
89
|
+
const graceMs = opts.closeGraceMs ?? DEFAULT_CLOSE_GRACE_MS;
|
|
90
|
+
const life = trackLife(client, config.kind);
|
|
91
|
+
const deadline = AbortSignal.timeout(opts.timeoutMs);
|
|
92
|
+
const signal = AbortSignal.any([opts.signal, deadline]);
|
|
93
|
+
try {
|
|
94
|
+
if (signal.aborted)
|
|
95
|
+
throw new McpConnectError("MCP connect failed: aborted");
|
|
96
|
+
await client.connect(handle.transport, { signal, timeout: opts.timeoutMs });
|
|
97
|
+
const tools = await listAll(client, signal, opts.timeoutMs);
|
|
98
|
+
return liveConnection(config, client, handle, tools, { graceMs, life });
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
await closeTransport(client, handle, graceMs, life);
|
|
102
|
+
throw error instanceof McpConnectError ? error : connectFailure(error, deadline, opts, handle);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function callTool(client, name, input, opts) {
|
|
106
|
+
try {
|
|
107
|
+
const raw = await client.callTool({ name, arguments: isRaw(input) ? { ...input } : {} }, undefined, {
|
|
108
|
+
signal: opts.signal,
|
|
109
|
+
timeout: opts.timeoutMs,
|
|
110
|
+
resetTimeoutOnProgress: true,
|
|
111
|
+
// The SDK sends a progressToken (so servers can report progress and reset
|
|
112
|
+
// the timeout) only when onprogress is set (shared/protocol.js:643-650).
|
|
113
|
+
onprogress: () => undefined,
|
|
114
|
+
});
|
|
115
|
+
return resultText(raw, opts.maxBytes);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
throw new McpCallError(`MCP call ${name} failed: ${messageOf(error)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function liveConnection(config, client, handle, tools, state) {
|
|
122
|
+
let closing;
|
|
123
|
+
const { life } = state;
|
|
124
|
+
return {
|
|
125
|
+
kind: config.kind,
|
|
126
|
+
tools,
|
|
127
|
+
call(name, input, opts) {
|
|
128
|
+
if (closing !== undefined || life.exited)
|
|
129
|
+
return Promise.reject(new McpCallError(`MCP call ${name} failed: the connection is closed`));
|
|
130
|
+
return callTool(client, name, input, opts);
|
|
131
|
+
},
|
|
132
|
+
onClose(listener) {
|
|
133
|
+
// An exit before anyone listened (spec §4.5, plan review I3) is still reported, once.
|
|
134
|
+
if (life.exitReason !== undefined)
|
|
135
|
+
listener(life.exitReason);
|
|
136
|
+
else
|
|
137
|
+
life.listeners.push(listener);
|
|
138
|
+
},
|
|
139
|
+
close() {
|
|
140
|
+
closing ??= closeTransport(client, handle, state.graceMs, life);
|
|
141
|
+
return closing;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Errors of the shared MCP layer (S5-5 spec §3.1, plan M-42). */
|
|
2
|
+
import { NaxError } from "#src/infra/nax-error";
|
|
3
|
+
export declare class McpConnectError extends NaxError {
|
|
4
|
+
readonly stderrTail?: string | undefined;
|
|
5
|
+
constructor(message: string, stderrTail?: string | undefined, context?: Record<string, unknown>);
|
|
6
|
+
}
|
|
7
|
+
export declare class McpCallError extends NaxError {
|
|
8
|
+
constructor(message: string, context?: Record<string, unknown>);
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Errors of the shared MCP layer (S5-5 spec §3.1, plan M-42). */
|
|
2
|
+
import { NaxError } from "#src/infra/nax-error";
|
|
3
|
+
export class McpConnectError extends NaxError {
|
|
4
|
+
stderrTail;
|
|
5
|
+
constructor(message, stderrTail, context = {}) {
|
|
6
|
+
super(message, "MCP_CONNECT_FAILED", context);
|
|
7
|
+
this.stderrTail = stderrTail;
|
|
8
|
+
this.name = "McpConnectError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class McpCallError extends NaxError {
|
|
12
|
+
constructor(message, context = {}) {
|
|
13
|
+
super(message, "MCP_CALL_FAILED", context);
|
|
14
|
+
this.name = "McpCallError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nathapp/nax-agent/mcp`: the shared MCP connection layer (S5-5 spec §3.1).
|
|
3
|
+
* Never imported from the root entry, so `@nathapp/nax-agent` alone never loads
|
|
4
|
+
* the MCP SDK.
|
|
5
|
+
*/
|
|
6
|
+
export { connectMcp, DEFAULT_CLOSE_GRACE_MS } from "#src/mcp/connect";
|
|
7
|
+
export { McpCallError, McpConnectError } from "#src/mcp/errors";
|
|
8
|
+
export { resultText } from "#src/mcp/result-text";
|
|
9
|
+
export type { ConnectMcpOptions, McpCallOptions, McpCallResult, McpConnection, McpToolInfo, McpTransportConfig, } from "#src/mcp/types";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nathapp/nax-agent/mcp`: the shared MCP connection layer (S5-5 spec §3.1).
|
|
3
|
+
* Never imported from the root entry, so `@nathapp/nax-agent` alone never loads
|
|
4
|
+
* the MCP SDK.
|
|
5
|
+
*/
|
|
6
|
+
export { connectMcp, DEFAULT_CLOSE_GRACE_MS } from "#src/mcp/connect";
|
|
7
|
+
export { McpCallError, McpConnectError } from "#src/mcp/errors";
|
|
8
|
+
export { resultText } from "#src/mcp/result-text";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `tools/call` result as text (S5-5 spec §5.4). Text items are kept; embedded
|
|
3
|
+
* text resources keep their uri; binary content becomes a one-line placeholder;
|
|
4
|
+
* structuredContent is printed only when no text item exists. Capped at
|
|
5
|
+
* maxBytes on a code point boundary.
|
|
6
|
+
*/
|
|
7
|
+
import type { McpCallResult } from "#src/mcp/types";
|
|
8
|
+
export declare function resultText(raw: unknown, maxBytes: number): McpCallResult;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cutToByteCap } from "#src/tools/truncate";
|
|
2
|
+
const isItem = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
const str = (value) => (typeof value === "string" ? value : undefined);
|
|
4
|
+
function resourceText(resource) {
|
|
5
|
+
if (!isItem(resource))
|
|
6
|
+
return "[resource omitted]";
|
|
7
|
+
const text = str(resource.text);
|
|
8
|
+
if (text !== undefined)
|
|
9
|
+
return `${str(resource.uri) ?? ""}\n${text}`;
|
|
10
|
+
return `[resource omitted: ${str(resource.mimeType) ?? "binary"}]`;
|
|
11
|
+
}
|
|
12
|
+
function itemText(item) {
|
|
13
|
+
switch (item.type) {
|
|
14
|
+
case "text":
|
|
15
|
+
return str(item.text) ?? "";
|
|
16
|
+
case "resource":
|
|
17
|
+
return resourceText(item.resource);
|
|
18
|
+
case "image":
|
|
19
|
+
case "audio":
|
|
20
|
+
return `[${item.type} omitted: ${str(item.mimeType) ?? "unknown"}]`;
|
|
21
|
+
case "resource_link":
|
|
22
|
+
return `[resource link: ${str(item.uri) ?? ""}]`;
|
|
23
|
+
default:
|
|
24
|
+
return `[${String(item.type ?? "unknown")} content omitted]`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function resultText(raw, maxBytes) {
|
|
28
|
+
const body = isItem(raw) ? raw : {};
|
|
29
|
+
const items = Array.isArray(body.content) ? body.content.filter(isItem) : [];
|
|
30
|
+
const hasText = items.some((item) => item.type === "text");
|
|
31
|
+
const parts = items.map(itemText);
|
|
32
|
+
if (!hasText && body.structuredContent !== undefined)
|
|
33
|
+
parts.push(JSON.stringify(body.structuredContent));
|
|
34
|
+
const full = parts.join("\n\n");
|
|
35
|
+
const bytes = Buffer.byteLength(full, "utf8");
|
|
36
|
+
const text = bytes <= maxBytes ? full : `${cutToByteCap(full, maxBytes)}\n[truncated: ${bytes} bytes in total]`;
|
|
37
|
+
return { text, isError: body.isError === true, bytesBeforeCap: bytes };
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function waitForStdioExit(pid: number, exited: () => boolean, graceMs: number): Promise<void>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* After close(), wait for an MCP stdio child to exit (S5-5 spec §3.1, I1). The
|
|
3
|
+
* SDK ends stdin, then SIGTERM after 2 s, then SIGKILL after 2 s more, on
|
|
4
|
+
* unref()'d timers: a server process exiting in between could orphan the child.
|
|
5
|
+
* These timers are ref'd, and after graceMs the child is SIGKILLed here.
|
|
6
|
+
*/
|
|
7
|
+
import { isProcessAlive } from "#src/internal/process-alive";
|
|
8
|
+
const POLL_MS = 25;
|
|
9
|
+
const AFTER_KILL_MS = 1000;
|
|
10
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
async function waitUntil(gone, ms) {
|
|
12
|
+
const until = Date.now() + ms;
|
|
13
|
+
while (Date.now() < until) {
|
|
14
|
+
if (gone())
|
|
15
|
+
return true;
|
|
16
|
+
await sleep(POLL_MS);
|
|
17
|
+
}
|
|
18
|
+
return gone();
|
|
19
|
+
}
|
|
20
|
+
export async function waitForStdioExit(pid, exited, graceMs) {
|
|
21
|
+
const gone = () => exited() || !isProcessAlive(pid);
|
|
22
|
+
if (await waitUntil(gone, graceMs))
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
process.kill(pid, "SIGKILL");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return; // ESRCH: it exited between the probe and the kill
|
|
29
|
+
}
|
|
30
|
+
await waitUntil(gone, AFTER_KILL_MS);
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
+
import type { McpTransportConfig } from "#src/mcp/types";
|
|
3
|
+
export declare const STDERR_TAIL_BYTES = 512;
|
|
4
|
+
export interface TransportHandle {
|
|
5
|
+
readonly transport: Transport;
|
|
6
|
+
readonly pid: () => number | null;
|
|
7
|
+
readonly stderrTail: () => string;
|
|
8
|
+
/** http: DELETE the MCP session, best effort. */
|
|
9
|
+
readonly terminate?: () => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/** Injectable seam: unit tests return an in-memory transport. */
|
|
12
|
+
export declare const _mcpTransportDeps: {
|
|
13
|
+
create: (config: McpTransportConfig) => TransportHandle;
|
|
14
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SDK transport for one McpTransportConfig (S5-5 spec §3.1). The only file
|
|
3
|
+
* besides connect.ts that imports the SDK. stdio: the env is an overlay on the
|
|
4
|
+
* SDK's default environment (SDK 1.30.0 already merges; passing it explicitly
|
|
5
|
+
* keeps PATH if a later SDK stops), and stderr is piped with a bounded tail
|
|
6
|
+
* kept for connect-failure messages, never inherited (stdout may carry ACP
|
|
7
|
+
* frames). http: streamable HTTP with the configured headers.
|
|
8
|
+
*/
|
|
9
|
+
import { getDefaultEnvironment, StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
10
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
11
|
+
export const STDERR_TAIL_BYTES = 512;
|
|
12
|
+
function stdioHandle(config) {
|
|
13
|
+
const transport = new StdioClientTransport({
|
|
14
|
+
command: config.command,
|
|
15
|
+
args: [...config.args],
|
|
16
|
+
env: { ...getDefaultEnvironment(), ...config.env },
|
|
17
|
+
cwd: config.cwd,
|
|
18
|
+
stderr: "pipe",
|
|
19
|
+
});
|
|
20
|
+
let tail = Buffer.alloc(0);
|
|
21
|
+
transport.stderr?.on("data", (chunk) => {
|
|
22
|
+
const joined = Buffer.concat([tail, chunk]);
|
|
23
|
+
tail = joined.subarray(Math.max(0, joined.length - STDERR_TAIL_BYTES));
|
|
24
|
+
});
|
|
25
|
+
return { transport, pid: () => transport.pid, stderrTail: () => tail.toString("utf8") };
|
|
26
|
+
}
|
|
27
|
+
function httpHandle(config) {
|
|
28
|
+
const transport = new StreamableHTTPClientTransport(new URL(config.url), {
|
|
29
|
+
requestInit: { headers: { ...config.headers } },
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
transport,
|
|
33
|
+
pid: () => null,
|
|
34
|
+
stderrTail: () => "",
|
|
35
|
+
terminate: () => transport.terminateSession().catch(() => undefined),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Injectable seam: unit tests return an in-memory transport. */
|
|
39
|
+
export const _mcpTransportDeps = {
|
|
40
|
+
create: (config) => config.kind === "stdio" ? stdioHandle(config) : httpHandle(config),
|
|
41
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Vocabulary of the shared MCP connection layer (S5-5 spec §3.1). Free of SDK types. */
|
|
2
|
+
import type { JSONSchema } from "#src/session/tool-descriptor";
|
|
3
|
+
export type McpTransportConfig = {
|
|
4
|
+
readonly kind: "stdio";
|
|
5
|
+
readonly command: string;
|
|
6
|
+
readonly args: readonly string[];
|
|
7
|
+
readonly env: Readonly<Record<string, string>>;
|
|
8
|
+
readonly cwd: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: "http";
|
|
11
|
+
readonly url: string;
|
|
12
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
13
|
+
};
|
|
14
|
+
export interface McpToolInfo {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly description: string;
|
|
17
|
+
readonly inputSchema: JSONSchema;
|
|
18
|
+
}
|
|
19
|
+
export interface McpCallResult {
|
|
20
|
+
readonly text: string;
|
|
21
|
+
readonly isError: boolean;
|
|
22
|
+
readonly bytesBeforeCap: number;
|
|
23
|
+
}
|
|
24
|
+
export interface McpCallOptions {
|
|
25
|
+
readonly signal: AbortSignal;
|
|
26
|
+
readonly timeoutMs: number;
|
|
27
|
+
readonly maxBytes: number;
|
|
28
|
+
}
|
|
29
|
+
export interface McpConnection {
|
|
30
|
+
readonly kind: "stdio" | "http";
|
|
31
|
+
readonly tools: readonly McpToolInfo[];
|
|
32
|
+
/** Server-side tool errors are `isError` results; transport failures throw McpCallError. */
|
|
33
|
+
call(name: string, input: unknown, opts: McpCallOptions): Promise<McpCallResult>;
|
|
34
|
+
/** stdio only: called once when the process exits without close() having been called. */
|
|
35
|
+
onClose(listener: (reason: string) => void): void;
|
|
36
|
+
/** Idempotent. Resolves when the transport, and for stdio the process, is gone. */
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export interface ConnectMcpOptions {
|
|
40
|
+
readonly signal: AbortSignal;
|
|
41
|
+
readonly timeoutMs: number;
|
|
42
|
+
readonly clientInfo: {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly version: string;
|
|
45
|
+
};
|
|
46
|
+
/** stdio: how long close() waits for the process before SIGKILL. Default 3000. */
|
|
47
|
+
readonly closeGraceMs?: number;
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/native/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { credentialFilePath, naxCredentialStore, type StoredEntry, servedAuth }
|
|
|
19
19
|
export { NativeSessionUnsupportedError } from "./errors.ts";
|
|
20
20
|
export { type ResolveResult, type ResolveStatus, resolveNativeId, } from "./model-resolver.ts";
|
|
21
21
|
export { NATIVE_AGENT } from "./models.ts";
|
|
22
|
+
export { listProviderModels, type ProviderModel } from "./provider-models.ts";
|
|
22
23
|
export { createMemoryTranscriptStore, type MemoryTranscriptStore } from "./session/memory-transcript-store.ts";
|
|
23
24
|
export { createFileTranscriptStore, MAX_RETAINED_TRANSCRIPTS, pruneRetainedTranscripts, } from "./session/transcript-store.ts";
|
|
24
25
|
export type { TranscriptAcpRecord, TranscriptDoc, TranscriptStore, TurnMarker } from "./session/transcript-types.ts";
|
package/dist/native/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { credentialFilePath, naxCredentialStore, servedAuth } from "./credential
|
|
|
17
17
|
export { NativeSessionUnsupportedError } from "./errors.js";
|
|
18
18
|
export { resolveNativeId, } from "./model-resolver.js";
|
|
19
19
|
export { NATIVE_AGENT } from "./models.js";
|
|
20
|
+
export { listProviderModels } from "./provider-models.js";
|
|
20
21
|
export { createMemoryTranscriptStore } from "./session/memory-transcript-store.js";
|
|
21
22
|
export { createFileTranscriptStore, MAX_RETAINED_TRANSCRIPTS, pruneRetainedTranscripts, } from "./session/transcript-store.js";
|
|
22
23
|
export { NativeSessionAdapter } from "./session-adapter.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type NativeCatalogOverrides } from "./client.ts";
|
|
2
|
+
export interface ProviderModel {
|
|
3
|
+
/** The model id without its provider prefix; the config string is `<provider>/<id>`. */
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly contextWindow: number;
|
|
6
|
+
/** The output ceiling, when the catalog states one. */
|
|
7
|
+
readonly maxTokens?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Tool-capable models for `providerId`, sorted by id. An unknown provider gives
|
|
11
|
+
* an empty list. A catalog that cannot load rejects.
|
|
12
|
+
*/
|
|
13
|
+
export declare function listProviderModels(providerId: string, catalogOverrides?: NativeCatalogOverrides): Promise<readonly ProviderModel[]>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The models the catalog offers for one provider, as plain data. The catalog is
|
|
3
|
+
* nax-ai's; nothing here carries a nax-ai type (see check-nax-ai-imports).
|
|
4
|
+
*/
|
|
5
|
+
import { byCodePoint } from "#src/internal/sort";
|
|
6
|
+
import { getNativeClient } from "./client.js";
|
|
7
|
+
/**
|
|
8
|
+
* Tool-capable models for `providerId`, sorted by id. An unknown provider gives
|
|
9
|
+
* an empty list. A catalog that cannot load rejects.
|
|
10
|
+
*/
|
|
11
|
+
export async function listProviderModels(providerId, catalogOverrides = []) {
|
|
12
|
+
const client = await getNativeClient(catalogOverrides);
|
|
13
|
+
const models = await client.listModels(providerId);
|
|
14
|
+
return models
|
|
15
|
+
.filter((model) => model.supportsTools)
|
|
16
|
+
.map((model) => ({
|
|
17
|
+
id: model.id,
|
|
18
|
+
contextWindow: model.contextWindow,
|
|
19
|
+
...(model.maxTokens !== undefined ? { maxTokens: model.maxTokens } : {}),
|
|
20
|
+
}))
|
|
21
|
+
.sort((a, b) => byCodePoint(a.id, b.id));
|
|
22
|
+
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `grep -n x y` did not. 20 of 34 denials in the two runs after #1971 carried
|
|
20
20
|
* no redirect (nax#1999). A single table cannot drift against itself.
|
|
21
21
|
*/
|
|
22
|
-
const GIT_READ_VERBS = new Set(["diff", "log", "show", "status", "blame"]);
|
|
22
|
+
const GIT_READ_VERBS = new Set(["diff", "log", "show", "status", "blame", "rev-parse"]);
|
|
23
23
|
/**
|
|
24
24
|
* Git verbs that stage or record a commit. Deliberately NOT every write verb:
|
|
25
25
|
* `git restore` reverts, which `GitCommit` does not do, and naming it would be
|
package/dist/tools/git.js
CHANGED
|
@@ -24,7 +24,7 @@ import { cutToByteCap, READ_CEILING } from "./truncate.js";
|
|
|
24
24
|
* Note what bounds these: the argv shape below, not the verb list. A read-only
|
|
25
25
|
* verb still spans the whole repository unless its scope is stated.
|
|
26
26
|
*/
|
|
27
|
-
export const GIT_READ_VERBS = ["diff", "log", "show", "status", "blame"];
|
|
27
|
+
export const GIT_READ_VERBS = ["diff", "log", "show", "status", "blame", "rev-parse"];
|
|
28
28
|
/**
|
|
29
29
|
* Typed flag fields, and the verbs each one is valid on.
|
|
30
30
|
*
|
|
@@ -164,6 +164,16 @@ export { GIT_ESCAPE_FLAGS } from "#src/tools/git-flags/index";
|
|
|
164
164
|
function looksLikeFlag(value) {
|
|
165
165
|
return value.startsWith("-");
|
|
166
166
|
}
|
|
167
|
+
function buildGitRootArgv(input) {
|
|
168
|
+
const extraFields = Object.keys(input).filter((field) => field !== "subcommand");
|
|
169
|
+
if (extraFields.length > 0) {
|
|
170
|
+
return { error: `rev-parse accepts no additional fields (received: ${extraFields.join(", ")})` };
|
|
171
|
+
}
|
|
172
|
+
return ["rev-parse", "--show-toplevel"];
|
|
173
|
+
}
|
|
174
|
+
function defaultGitExcludes(subcommand, excludes) {
|
|
175
|
+
return subcommand === "blame" ? [] : excludes;
|
|
176
|
+
}
|
|
167
177
|
/**
|
|
168
178
|
* `defaultExcludes`: pathspecs appended to an unscoped call (the host's own
|
|
169
179
|
* state, nax#2007); none by default.
|
|
@@ -173,6 +183,9 @@ export function buildGitArgv(input, defaultExcludes = []) {
|
|
|
173
183
|
if (typeof subcommand !== "string" || !GIT_READ_VERBS.includes(subcommand)) {
|
|
174
184
|
return { error: `subcommand must be one of: ${GIT_READ_VERBS.join(", ")}` };
|
|
175
185
|
}
|
|
186
|
+
if (subcommand === "rev-parse") {
|
|
187
|
+
return buildGitRootArgv(input);
|
|
188
|
+
}
|
|
176
189
|
const refs = Array.isArray(input.refs) ? input.refs : [];
|
|
177
190
|
const paths = Array.isArray(input.paths) ? input.paths : [];
|
|
178
191
|
// No `--relative` is injected. Since the single-frame redesign the permitted
|
|
@@ -281,8 +294,7 @@ export function buildGitArgv(input, defaultExcludes = []) {
|
|
|
281
294
|
// requested path would be a worse failure than the one being fixed.
|
|
282
295
|
// `blame` is exempt because git rejects exclude pathspecs on it and exits
|
|
283
296
|
// 128; do not "unify" it back in.
|
|
284
|
-
|
|
285
|
-
argv.push(...defaultExcludes);
|
|
297
|
+
argv.push(...defaultGitExcludes(subcommand, defaultExcludes));
|
|
286
298
|
return argv;
|
|
287
299
|
}
|
|
288
300
|
for (const path of paths) {
|
|
@@ -296,11 +308,15 @@ export function buildGitArgv(input, defaultExcludes = []) {
|
|
|
296
308
|
}
|
|
297
309
|
export const gitTool = {
|
|
298
310
|
name: "Git",
|
|
299
|
-
description: "Run a read-only git command (diff, log, show, status, blame) in the repository. Supply refs and pathspecs as arrays, not as a command line. Command-line flags are not accepted in any field; use the nameOnly, diffFilter, oneline, fullMessage and maxCount fields instead.",
|
|
311
|
+
description: "Run a read-only git command (diff, log, show, status, blame, rev-parse) in the repository. rev-parse is fixed to `git rev-parse --show-toplevel` and accepts no other fields. Supply refs and pathspecs as arrays, not as a command line. Command-line flags are not accepted in any field; use the nameOnly, diffFilter, oneline, fullMessage and maxCount fields instead.",
|
|
300
312
|
inputSchema: {
|
|
301
313
|
type: "object",
|
|
302
314
|
properties: {
|
|
303
|
-
subcommand: {
|
|
315
|
+
subcommand: {
|
|
316
|
+
type: "string",
|
|
317
|
+
enum: [...GIT_READ_VERBS],
|
|
318
|
+
description: "Read-only git subcommand; rev-parse is fixed to --show-toplevel",
|
|
319
|
+
},
|
|
304
320
|
refs: { type: "array", items: { type: "string" }, description: "Refs, e.g. ['HEAD~1','HEAD']" },
|
|
305
321
|
paths: { type: "array", items: { type: "string" }, description: "Pathspecs, relative to the permitted root" },
|
|
306
322
|
nameOnly: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nax-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"description": "nax's native coding agent: session contract, native loop, tools, permissions, sandbox, command-safety.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "William Khoo",
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"./internal": {
|
|
30
30
|
"types": "./dist/internal.d.ts",
|
|
31
31
|
"import": "./dist/internal.js"
|
|
32
|
+
},
|
|
33
|
+
"./mcp": {
|
|
34
|
+
"types": "./dist/mcp/index.d.ts",
|
|
35
|
+
"import": "./dist/mcp/index.js"
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
"imports": {
|
|
@@ -42,7 +46,8 @@
|
|
|
42
46
|
},
|
|
43
47
|
"dependencies": {
|
|
44
48
|
"@anthropic-ai/sandbox-runtime": "0.0.77",
|
|
45
|
-
"@
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
50
|
+
"@nathapp/nax-ai": "0.85.0",
|
|
46
51
|
"zod": "^4.3.6"
|
|
47
52
|
},
|
|
48
53
|
"publishConfig": {
|