@mearl/agent-runtime 0.0.0-stage → 2.22.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.
@@ -0,0 +1,64 @@
1
+ export type PluginModelMode = 'generate' | 'agent';
2
+ export interface PluginModelRequest {
3
+ provider: string;
4
+ mode: PluginModelMode;
5
+ prompt: string;
6
+ instructions?: string;
7
+ model?: string;
8
+ reasoningEffort?: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
9
+ outputSchema?: Record<string, unknown>;
10
+ /** Opt-in conversation scoped to the bound document or calling command execution. */
11
+ conversation?: string;
12
+ /** Required for an agent task; never inferred from a pure-generation request. */
13
+ permissions?: 'read-only';
14
+ }
15
+ export interface PluginModelCapability {
16
+ provider: string;
17
+ modes: PluginModelMode[];
18
+ permissions: Array<'read-only'>;
19
+ structuredOutput: boolean;
20
+ streaming: boolean;
21
+ cancellation: boolean;
22
+ }
23
+ export interface PluginModelOption {
24
+ id: string;
25
+ displayName: string;
26
+ description?: string;
27
+ isDefault?: boolean;
28
+ reasoningEfforts?: NonNullable<PluginModelRequest['reasoningEffort']>[];
29
+ defaultReasoningEffort?: PluginModelRequest['reasoningEffort'];
30
+ }
31
+ export interface PluginModelList {
32
+ provider: string;
33
+ models: PluginModelOption[];
34
+ }
35
+ export interface PluginModelResult {
36
+ text: string;
37
+ output?: unknown;
38
+ provider: string;
39
+ mode: PluginModelMode;
40
+ model?: string;
41
+ }
42
+ /** Execution observations contain no prompts, account identities or credential values. */
43
+ export interface PluginModelDiagnostic {
44
+ requestedModel?: string;
45
+ selectedModel?: string;
46
+ activeModel?: string;
47
+ runtime?: {
48
+ executable?: string;
49
+ version?: string;
50
+ selection: 'explicit' | 'automatic';
51
+ started?: boolean;
52
+ };
53
+ configuration?: {
54
+ source: 'default' | 'environment';
55
+ home?: string;
56
+ filePresent: boolean;
57
+ baseProvider?: string;
58
+ credentialStore?: 'file' | 'keyring' | 'auto' | 'ephemeral' | 'unknown';
59
+ };
60
+ authentication?: 'apikey' | 'chatgpt' | 'chatgptAuthTokens' | 'unknown';
61
+ threadRestrictionsAccepted?: boolean;
62
+ turnRestrictionsAccepted?: boolean;
63
+ retryCount?: number;
64
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ export * from './provider.js';
2
+ export * from './codex-provider.js';
3
+ export * from './qoder-provider.js';
4
+ export * from './model.js';
5
+ export type * from './generated/plugin-models.js';
6
+ export type * from './generated/agent-chat.js';
7
+ export type * from './generated/agent-skills.js';
8
+ export * from './model-catalog.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './provider.js';
2
+ export * from './codex-provider.js';
3
+ export * from './qoder-provider.js';
4
+ export * from './model.js';
5
+ export * from './model-catalog.js';
@@ -0,0 +1,4 @@
1
+ import { CodexAgentProvider } from './codex-provider.js';
2
+ import type { PluginModelOption } from './generated/plugin-models.js';
3
+ /** Discovery starts no thread and always releases its short-lived app-server. */
4
+ export declare function listCodexModels(signal: AbortSignal, provider?: CodexAgentProvider): Promise<PluginModelOption[]>;
@@ -0,0 +1,21 @@
1
+ import { CodexAgentProvider } from './codex-provider.js';
2
+ import { readFileSync } from 'node:fs';
3
+ /** Discovery starts no thread and always releases its short-lived app-server. */
4
+ export async function listCodexModels(signal, provider = new CodexAgentProvider(() => { }, JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version)) {
5
+ signal.throwIfAborted();
6
+ let abort;
7
+ const cancelled = new Promise((_resolve, reject) => {
8
+ abort = () => {
9
+ provider.dispose();
10
+ reject(signal.reason ?? new Error('Model discovery cancelled'));
11
+ };
12
+ signal.addEventListener('abort', abort, { once: true });
13
+ });
14
+ try {
15
+ return await Promise.race([provider.listModels(), cancelled]);
16
+ }
17
+ finally {
18
+ signal.removeEventListener('abort', abort);
19
+ provider.dispose();
20
+ }
21
+ }
@@ -0,0 +1,18 @@
1
+ import type { PluginModelDiagnostic, PluginModelRequest, PluginModelResult } from './generated/plugin-models.js';
2
+ import { CodexAgentProvider } from './codex-provider.js';
3
+ export { CODEX_MODEL_CAPABILITY } from './capabilities.js';
4
+ export interface ModelExecutionContext {
5
+ signal: AbortSignal;
6
+ cwd: string;
7
+ /** The supervised conversation worker owns this in-memory thread and provider. */
8
+ conversation?: {
9
+ threadId?: string;
10
+ };
11
+ onDiagnostic?(metadata: PluginModelDiagnostic): void;
12
+ onDelta(delta: string, index: number, metadata?: {
13
+ itemId?: string;
14
+ phase?: string;
15
+ }): void;
16
+ }
17
+ /** A supervised worker owns the provider; explicit conversations retain its in-memory thread. */
18
+ export declare function runCodexModel(request: PluginModelRequest, context: ModelExecutionContext, provider?: CodexAgentProvider): Promise<PluginModelResult>;
package/dist/model.js ADDED
@@ -0,0 +1,194 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { CodexAgentProvider } from './codex-provider.js';
3
+ export { CODEX_MODEL_CAPABILITY } from './capabilities.js';
4
+ /** A supervised worker owns the provider; explicit conversations retain its in-memory thread. */
5
+ export async function runCodexModel(request, context, provider = new CodexAgentProvider(() => { }, JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version)) {
6
+ if (request.provider !== 'codex' ||
7
+ request.mode !== 'agent' ||
8
+ request.permissions !== 'read-only')
9
+ throw Object.assign(new Error('Codex supports explicit read-only agent execution; strict tool-free generation is unavailable'), { code: 'PLUGIN_MODEL_CAPABILITY_UNSUPPORTED' });
10
+ context.signal.throwIfAborted();
11
+ let session, turn, text;
12
+ let bytes = 0, index = 0, retries = 0;
13
+ let buffered = '';
14
+ let streamItem, streamPhase;
15
+ const phases = new Map();
16
+ let flushTimer;
17
+ const flush = () => {
18
+ clearTimeout(flushTimer);
19
+ flushTimer = undefined;
20
+ try {
21
+ for (let offset = 0; offset < buffered.length; offset += 4000)
22
+ if (streamItem || streamPhase)
23
+ context.onDelta(buffered.slice(offset, offset + 4000), ++index, {
24
+ ...(streamItem ? { itemId: streamItem } : {}),
25
+ ...(streamPhase ? { phase: streamPhase } : {}),
26
+ });
27
+ else
28
+ context.onDelta(buffered.slice(offset, offset + 4000), ++index);
29
+ }
30
+ catch (error) {
31
+ fail(error);
32
+ }
33
+ buffered = '';
34
+ };
35
+ let finish, fail;
36
+ const completed = new Promise((resolve, reject) => {
37
+ finish = resolve;
38
+ fail = reject;
39
+ });
40
+ void completed.catch(() => { });
41
+ const listener = (event) => {
42
+ const params = event.params;
43
+ if (event.method === 'provider/model/diagnostic')
44
+ context.onDiagnostic?.(params);
45
+ if (event.method === 'account/updated')
46
+ context.onDiagnostic?.({
47
+ authentication: ['apikey', 'chatgpt', 'chatgptAuthTokens'].includes(params?.authMode)
48
+ ? params.authMode
49
+ : 'unknown',
50
+ });
51
+ if (event.method === 'session/model/updated' && typeof params?.model?.id === 'string')
52
+ context.onDiagnostic?.({ selectedModel: params.model.id });
53
+ if (event.method === 'session/activeModel/updated' && typeof params?.model?.id === 'string')
54
+ context.onDiagnostic?.({ activeModel: params.model.id });
55
+ const relevant = event.providerSessionId === undefined || event.providerSessionId === session;
56
+ if (event.kind === 'request' && relevant) {
57
+ try {
58
+ provider.respondToRequest(event.requestId, {
59
+ error: 'Interactive requests are disabled for background model tasks',
60
+ });
61
+ }
62
+ catch {
63
+ /* Disposal also closes unanswered provider requests. */
64
+ }
65
+ fail(Object.assign(new Error('Model requested interactive input'), {
66
+ code: 'PLUGIN_MODEL_INTERACTION_REQUIRED',
67
+ }));
68
+ provider.dispose();
69
+ return;
70
+ }
71
+ if ((event.kind === 'error' || event.method === 'provider/exited') && relevant) {
72
+ if (event.method !== 'provider/exited' && params?.willRetry === true) {
73
+ context.onDiagnostic?.({ retryCount: ++retries });
74
+ return;
75
+ }
76
+ fail(Object.assign(new Error(String(params?.error?.message ?? params?.message ?? 'Model provider failed')), { code: 'PLUGIN_MODEL_PROVIDER_ERROR' }));
77
+ provider.dispose();
78
+ return;
79
+ }
80
+ if (!session || event.providerSessionId !== session)
81
+ return;
82
+ if (event.method === 'item/started' && params?.item?.type === 'agentMessage') {
83
+ flush();
84
+ streamItem = params.item.id;
85
+ streamPhase = params.item.phase;
86
+ if (streamItem && streamPhase)
87
+ phases.set(streamItem, streamPhase);
88
+ }
89
+ else if (event.method === 'item/agentMessage/delta' && typeof params?.delta === 'string') {
90
+ const itemId = params.itemId ?? streamItem;
91
+ const phase = params.phase ?? (itemId ? phases.get(itemId) : streamPhase);
92
+ if (itemId !== streamItem || phase !== streamPhase)
93
+ flush();
94
+ streamItem = itemId;
95
+ streamPhase = phase;
96
+ bytes += Buffer.byteLength(params.delta);
97
+ if (bytes > 32 * 1024 * 1024) {
98
+ fail(new Error('Model output exceeds 32 MiB'));
99
+ return;
100
+ }
101
+ buffered += params.delta;
102
+ if (buffered.length >= 16000)
103
+ flush();
104
+ else
105
+ flushTimer ??= setTimeout(flush, 50);
106
+ }
107
+ else if (event.method === 'item/completed' &&
108
+ params?.item?.type === 'agentMessage' &&
109
+ typeof params.item.text === 'string') {
110
+ flush();
111
+ if (Buffer.byteLength(params.item.text) > 32 * 1024 * 1024) {
112
+ fail(new Error('Model output exceeds 32 MiB'));
113
+ return;
114
+ }
115
+ // Only final messages supply the business result; progress deltas may contain commentary.
116
+ if (!params.item.phase || params.item.phase === 'final_answer')
117
+ text = params.item.text;
118
+ }
119
+ else if (event.method === 'turn/completed') {
120
+ if (params?.turn?.status !== 'completed')
121
+ fail(Object.assign(new Error(`Model turn ended with ${String(params?.turn?.status)}${params?.turn?.error?.message ? `: ${params.turn.error.message}` : ''}`), { code: 'PLUGIN_MODEL_TURN_FAILED' }));
122
+ else if (text === undefined)
123
+ fail(new Error('Model returned no final text'));
124
+ else {
125
+ flush();
126
+ finish(text);
127
+ }
128
+ }
129
+ };
130
+ const unsubscribe = provider.onEvent(listener);
131
+ const abort = () => {
132
+ fail(Object.assign(new Error('Model execution cancelled'), { code: 'PLUGIN_EXECUTION_CANCELLED' }));
133
+ if (session && turn)
134
+ void provider.interrupt(session, turn).catch(() => { });
135
+ provider.dispose();
136
+ };
137
+ context.signal.addEventListener('abort', abort, { once: true });
138
+ try {
139
+ session =
140
+ context.conversation?.threadId ??
141
+ (await provider.startSession({
142
+ cwd: context.cwd,
143
+ ephemeral: true,
144
+ readOnly: true,
145
+ modelExecution: true,
146
+ ...(request.model ? { modelId: request.model } : {}),
147
+ ...(request.instructions ? { developerInstructions: request.instructions } : {}),
148
+ }));
149
+ if (context.conversation)
150
+ context.conversation.threadId = session;
151
+ context.onDiagnostic?.({
152
+ ...(provider.getSessionModel(session)?.id
153
+ ? { selectedModel: provider.getSessionModel(session).id }
154
+ : {}),
155
+ });
156
+ context.signal.throwIfAborted();
157
+ const [, result] = await Promise.all([
158
+ provider
159
+ .sendMessage(session, request.prompt, [], {
160
+ reasoningSummary: 'none',
161
+ reasoningEffort: request.reasoningEffort,
162
+ outputSchema: request.outputSchema,
163
+ modelExecution: true,
164
+ })
165
+ .then(value => {
166
+ turn = value.turnId;
167
+ }),
168
+ completed,
169
+ ]);
170
+ context.signal.throwIfAborted();
171
+ return {
172
+ text: result,
173
+ provider: request.provider,
174
+ mode: request.mode,
175
+ ...(provider.getSessionModel(session)?.id
176
+ ? { model: provider.getSessionModel(session).id }
177
+ : {}),
178
+ };
179
+ }
180
+ catch (error) {
181
+ if (context.conversation) {
182
+ delete context.conversation.threadId;
183
+ provider.dispose();
184
+ }
185
+ throw error;
186
+ }
187
+ finally {
188
+ clearTimeout(flushTimer);
189
+ context.signal.removeEventListener('abort', abort);
190
+ unsubscribe();
191
+ if (!context.conversation)
192
+ provider.dispose();
193
+ }
194
+ }
@@ -0,0 +1,2 @@
1
+ export { prependNodePath as createAgentChildEnv, resolveSpawnCommand as agentSpawnCommand, versionManagerExecutableCandidates, } from '@mearl/daemon-core';
2
+ export type { SpawnCommand as AgentSpawnCommand } from '@mearl/daemon-core';
@@ -0,0 +1 @@
1
+ export { prependNodePath as createAgentChildEnv, resolveSpawnCommand as agentSpawnCommand, versionManagerExecutableCandidates, } from '@mearl/daemon-core';
@@ -0,0 +1,58 @@
1
+ import type { AgentInstalledSkill } from './generated/agent-skills.js';
2
+ import type { AgentChatEventKind, AgentChatImageInput, AgentChatListSessionsOptions, AgentChatModelInfo, AgentChatProviderDescriptor, AgentChatRunOptions, AgentChatSessionSummary } from './generated/agent-chat.js';
3
+ export interface AgentProviderEvent {
4
+ providerSessionId?: string;
5
+ kind: AgentChatEventKind;
6
+ method: string;
7
+ params: unknown;
8
+ timestamp?: number;
9
+ requestId?: string | number;
10
+ }
11
+ export interface AgentProviderSessionOptions {
12
+ cwd?: string;
13
+ developerInstructions?: string;
14
+ ephemeral?: boolean;
15
+ readOnly?: boolean;
16
+ modelExecution?: boolean;
17
+ modelId?: string;
18
+ }
19
+ export interface AgentProviderTurnOptions {
20
+ reasoningEffort?: AgentChatRunOptions['reasoningEffort'];
21
+ reasoningSummary?: 'none' | 'detailed';
22
+ outputSchema?: Record<string, unknown>;
23
+ modelExecution?: boolean;
24
+ }
25
+ export type AgentProviderSessionSummary = Omit<AgentChatSessionSummary, 'providerId'>;
26
+ export interface AgentProviderListSessionsOptions extends Omit<AgentChatListSessionsOptions, 'providerId' | 'limit'> {
27
+ limit: number;
28
+ }
29
+ export interface AgentProviderListSessionsResult {
30
+ sessions: AgentProviderSessionSummary[];
31
+ nextCursor?: string;
32
+ }
33
+ export interface AgentProviderResumeResult {
34
+ providerSessionId: string;
35
+ history?: AgentProviderEvent[];
36
+ historyComplete?: boolean;
37
+ }
38
+ export interface AgentProvider {
39
+ readonly id: string;
40
+ getDescriptor(): Promise<AgentChatProviderDescriptor>;
41
+ prepare(): Promise<void>;
42
+ onEvent(listener: (event: AgentProviderEvent) => void): () => void;
43
+ listSessions?(options: AgentProviderListSessionsOptions): Promise<AgentProviderListSessionsResult>;
44
+ listSkills?(cwd?: string): Promise<AgentInstalledSkill[]>;
45
+ startSession(options: AgentProviderSessionOptions): Promise<string>;
46
+ resumeSession(providerSessionId: string, options: AgentProviderSessionOptions): Promise<AgentProviderResumeResult>;
47
+ getSessionModel?(providerSessionId: string): AgentChatModelInfo | undefined;
48
+ validateMessageInput?(images: AgentChatImageInput[]): Promise<void>;
49
+ sendMessage(providerSessionId: string, message: string, images?: AgentChatImageInput[], options?: AgentProviderTurnOptions): Promise<{
50
+ turnId: string;
51
+ }>;
52
+ interrupt(providerSessionId: string, turnId: string): Promise<void>;
53
+ closeSession(providerSessionId: string): Promise<void>;
54
+ openSession?(providerSessionId: string): Promise<void>;
55
+ openWorkspace?(cwd: string): Promise<void>;
56
+ respondToRequest(requestId: string | number, result: unknown): void;
57
+ dispose(): void;
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,68 @@
1
+ import type { AgentInstalledSkill } from './generated/agent-skills.js';
2
+ import fs from 'node:fs';
3
+ import type * as acp from '@agentclientprotocol/sdk';
4
+ import type { AgentChatImageInput, AgentChatModelInfo, AgentChatProviderDescriptor } from './generated/agent-chat.js';
5
+ import type { AgentProvider, AgentProviderEvent, AgentProviderListSessionsOptions, AgentProviderListSessionsResult, AgentProviderResumeResult, AgentProviderSessionOptions } from './provider.js';
6
+ export declare function qoderModelInfo(configOptions: acp.SessionConfigOption[] | null | undefined): AgentChatModelInfo | undefined;
7
+ export declare function parseQoderSkillsList(output: string, cwd: string, home?: string): AgentInstalledSkill[];
8
+ export declare function qoderInstallCommand(platform?: NodeJS.Platform): string;
9
+ interface QoderExecutableCandidateOptions {
10
+ env?: NodeJS.ProcessEnv;
11
+ homedir?: () => string;
12
+ platform?: NodeJS.Platform;
13
+ processExecPath?: string;
14
+ readdirSync?: typeof fs.readdirSync;
15
+ }
16
+ export declare function qoderExecutableCandidatePaths(options?: QoderExecutableCandidateOptions): string[];
17
+ export declare class QoderAgentProvider implements AgentProvider {
18
+ private readonly log;
19
+ private readonly clientVersion;
20
+ readonly id = "qoder";
21
+ private process;
22
+ private sdk;
23
+ private connection;
24
+ private context;
25
+ private initializeResponse;
26
+ private startPromise;
27
+ private lifecycle;
28
+ private listeners;
29
+ private sessions;
30
+ private pendingPermissions;
31
+ private historyCaptures;
32
+ private nextTurnId;
33
+ private readonly executableResolver;
34
+ constructor(log: (...args: any[]) => void, clientVersion: string);
35
+ getDescriptor(): Promise<AgentChatProviderDescriptor>;
36
+ prepare(): Promise<void>;
37
+ onEvent(listener: (event: AgentProviderEvent) => void): () => void;
38
+ listSessions(options: AgentProviderListSessionsOptions): Promise<AgentProviderListSessionsResult>;
39
+ listSkills(cwd?: string): Promise<AgentInstalledSkill[]>;
40
+ startSession(options: AgentProviderSessionOptions): Promise<string>;
41
+ resumeSession(providerSessionId: string, options: AgentProviderSessionOptions): Promise<AgentProviderResumeResult>;
42
+ getSessionModel(providerSessionId: string): AgentChatModelInfo | undefined;
43
+ validateMessageInput(images: AgentChatImageInput[]): Promise<void>;
44
+ sendMessage(providerSessionId: string, message: string, images?: AgentChatImageInput[]): Promise<{
45
+ turnId: string;
46
+ }>;
47
+ interrupt(providerSessionId: string, _turnId: string): Promise<void>;
48
+ closeSession(providerSessionId: string): Promise<void>;
49
+ respondToRequest(requestId: string | number, result: unknown): void;
50
+ dispose(): void;
51
+ private ensureStarted;
52
+ private start;
53
+ private runPrompt;
54
+ private handleSessionUpdate;
55
+ private emitToolUpdate;
56
+ private handlePermissionRequest;
57
+ private cancelPermissions;
58
+ private handleExit;
59
+ private getContext;
60
+ private assertImageInputSupported;
61
+ private withTimeout;
62
+ private probeExecutable;
63
+ private runSkillsList;
64
+ private resolveExecutable;
65
+ private resolveExecutableCandidates;
66
+ private emit;
67
+ }
68
+ export {};