@mohou/runtime-provider 1.0.20
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/README.md +10 -0
- package/package.json +31 -0
- package/src/codes.ts +31 -0
- package/src/echo.ts +74 -0
- package/src/index.ts +9 -0
- package/src/model-choice.ts +39 -0
- package/src/provider.ts +49 -0
- package/src/registry.ts +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: locked
|
|
3
|
+
updated: 2026-10-01
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @mohou/runtime-provider
|
|
7
|
+
|
|
8
|
+
Role: `provider`.
|
|
9
|
+
|
|
10
|
+
The brain interface and `echo` live here. Pi is `@mohou/runtime-pi`. Host consumes this package. It does not embed a vendor. Product: [provider injection](../../../docs/product/runtime/provider.md).
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mohou/runtime-provider",
|
|
3
|
+
"version": "1.0.20",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"lib/types",
|
|
15
|
+
"README.md",
|
|
16
|
+
"!**/*.tsbuildinfo"
|
|
17
|
+
],
|
|
18
|
+
"main": "./src/index.ts",
|
|
19
|
+
"types": "./src/index.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./src/index.ts",
|
|
23
|
+
"default": "./src/index.ts"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@mohou/contract": "^1.0.20"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/codes.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Codes this provider package emits. Callers match `code`. */
|
|
2
|
+
|
|
3
|
+
export const providerCodes = [
|
|
4
|
+
'provider-missing',
|
|
5
|
+
'provider-duplicate',
|
|
6
|
+
'provider-unhealthy',
|
|
7
|
+
'empty-completion',
|
|
8
|
+
'cancelled',
|
|
9
|
+
'unknown-model-provider',
|
|
10
|
+
'unknown-model',
|
|
11
|
+
'retry-exhausted',
|
|
12
|
+
] as const
|
|
13
|
+
|
|
14
|
+
/** A failure emitted by a runtime provider or its registry. */
|
|
15
|
+
export type ProviderCode = (typeof providerCodes)[number]
|
|
16
|
+
|
|
17
|
+
/** Failure a caller branches on. The message is for a person. */
|
|
18
|
+
export class ProviderError extends Error {
|
|
19
|
+
readonly code: ProviderCode
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param code - one of {@link providerCodes}
|
|
23
|
+
* @param message - human text; not the match key
|
|
24
|
+
* @param options - optional `cause`
|
|
25
|
+
*/
|
|
26
|
+
constructor(code: ProviderCode, message: string, options?: { cause?: unknown }) {
|
|
27
|
+
super(message, options)
|
|
28
|
+
this.name = 'ProviderError'
|
|
29
|
+
this.code = code
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/echo.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ProviderError } from './codes.ts'
|
|
2
|
+
import type { RuntimeAgentOptions, RuntimeLlmOptions, RuntimeProvider } from './provider.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The always-registered brain. `llm` returns the prompt. `agent` returns the goal.
|
|
6
|
+
* The tool set stays empty. A model name does not change it.
|
|
7
|
+
*/
|
|
8
|
+
export function createEchoProvider(): RuntimeProvider {
|
|
9
|
+
let running = false
|
|
10
|
+
return {
|
|
11
|
+
id: 'echo',
|
|
12
|
+
label: 'Echo',
|
|
13
|
+
configure() {},
|
|
14
|
+
start() {
|
|
15
|
+
running = true
|
|
16
|
+
return Promise.resolve()
|
|
17
|
+
},
|
|
18
|
+
stop() {
|
|
19
|
+
running = false
|
|
20
|
+
return Promise.resolve()
|
|
21
|
+
},
|
|
22
|
+
healthy() {
|
|
23
|
+
return running
|
|
24
|
+
},
|
|
25
|
+
describe() {
|
|
26
|
+
return [{ name: 'model', kind: 'string' }]
|
|
27
|
+
},
|
|
28
|
+
llm(prompt, options) {
|
|
29
|
+
const failed = rejection(running, options?.signal)
|
|
30
|
+
if (failed) return failed
|
|
31
|
+
if (prompt.length === 0) {
|
|
32
|
+
return Promise.reject(new ProviderError('empty-completion', 'llm returned an empty completion'))
|
|
33
|
+
}
|
|
34
|
+
if (options?.stream === true) {
|
|
35
|
+
observeLlm(options, { type: 'status', status: 'running' })
|
|
36
|
+
observeLlm(options, { type: 'text-delta', text: prompt })
|
|
37
|
+
observeLlm(options, { type: 'done', text: prompt })
|
|
38
|
+
}
|
|
39
|
+
return Promise.resolve(prompt)
|
|
40
|
+
},
|
|
41
|
+
agent(goal, options) {
|
|
42
|
+
const failed = rejection(running, options?.signal)
|
|
43
|
+
if (failed) return failed
|
|
44
|
+
if (goal.length === 0) {
|
|
45
|
+
return Promise.reject(new ProviderError('empty-completion', 'agent returned an empty completion'))
|
|
46
|
+
}
|
|
47
|
+
observe(options, { type: 'status', status: 'running' })
|
|
48
|
+
observe(options, { type: 'done', text: goal })
|
|
49
|
+
return Promise.resolve(goal)
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function rejection(running: boolean, signal: AbortSignal | undefined): Promise<never> | undefined {
|
|
55
|
+
if (signal?.aborted) return Promise.reject(new ProviderError('cancelled', 'cancelled'))
|
|
56
|
+
if (!running) return Promise.reject(new ProviderError('provider-unhealthy', 'runtime provider is not started'))
|
|
57
|
+
return undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function observeLlm(options: RuntimeLlmOptions, event: Parameters<NonNullable<RuntimeLlmOptions['onEvent']>>[0]): void {
|
|
61
|
+
try {
|
|
62
|
+
options.onEvent?.(event)
|
|
63
|
+
} catch {
|
|
64
|
+
// A throwing observer does not abort the completion.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function observe(options: RuntimeAgentOptions | undefined, event: Parameters<NonNullable<RuntimeAgentOptions['onEvent']>>[0]): void {
|
|
69
|
+
try {
|
|
70
|
+
options?.onEvent?.(event)
|
|
71
|
+
} catch {
|
|
72
|
+
// A throwing observer does not abort the run.
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Brain interface and the echo provider. @module @mohou/runtime-provider */
|
|
2
|
+
|
|
3
|
+
export const packageId = '@mohou/runtime-provider' as const
|
|
4
|
+
|
|
5
|
+
export { ProviderError, providerCodes, type ProviderCode } from './codes.ts'
|
|
6
|
+
export type { ModelListing, RuntimeAgentOptions, RuntimeLlmOptions, RuntimeProvider, RuntimeProviderConfig, SettingsField } from './provider.ts'
|
|
7
|
+
export { assertKnownModel, type ModelChoice } from './model-choice.ts'
|
|
8
|
+
export { createProviderRegistry, type ProviderRegistry } from './registry.ts'
|
|
9
|
+
export { createEchoProvider } from './echo.ts'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ProviderError } from './codes.ts'
|
|
2
|
+
import type { RuntimeProvider } from './provider.ts'
|
|
3
|
+
|
|
4
|
+
/** The vendor and model on one call. Neither is the brain id. */
|
|
5
|
+
export interface ModelChoice {
|
|
6
|
+
readonly provider?: string
|
|
7
|
+
readonly model?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Admit a call's vendor and model against the brain's catalog.
|
|
12
|
+
* A brain that does not implement `models` accepts any pair.
|
|
13
|
+
* This does not change the brain id or its tools.
|
|
14
|
+
* @param provider - the registered brain
|
|
15
|
+
* @param choice - call options, after host policy filled omissions
|
|
16
|
+
*/
|
|
17
|
+
export async function assertKnownModel(provider: RuntimeProvider, choice: ModelChoice): Promise<void> {
|
|
18
|
+
if (provider.models === undefined) return
|
|
19
|
+
const listings = await provider.models()
|
|
20
|
+
const vendor = choice.provider
|
|
21
|
+
const model = choice.model
|
|
22
|
+
if (vendor === undefined && model === undefined) return
|
|
23
|
+
|
|
24
|
+
if (vendor !== undefined) {
|
|
25
|
+
const listing = listings.find(item => item.provider === vendor)
|
|
26
|
+
if (listing === undefined) {
|
|
27
|
+
throw new ProviderError('unknown-model-provider', `unknown model provider: ${vendor}`)
|
|
28
|
+
}
|
|
29
|
+
if (model !== undefined && !listing.models.includes(model)) {
|
|
30
|
+
throw new ProviderError('unknown-model', `unknown model: ${vendor}/${model}`)
|
|
31
|
+
}
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const owners = listings.filter(item => item.models.includes(model ?? ''))
|
|
36
|
+
if (owners.length !== 1) {
|
|
37
|
+
throw new ProviderError('unknown-model', `unknown model: ${model ?? ''}`)
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/provider.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AppAgentEvent, AppAgentOptions, AppLlmEvent, AppLlmOptions } from '@mohou/contract'
|
|
2
|
+
|
|
3
|
+
/** Provider-only listener. Authors read a stream. They do not pass this. */
|
|
4
|
+
export type RuntimeLlmOptions = AppLlmOptions & {
|
|
5
|
+
onEvent?: (event: AppLlmEvent) => void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Provider-only listener. Authors read a stream. They do not pass this. */
|
|
9
|
+
export type RuntimeAgentOptions = AppAgentOptions & {
|
|
10
|
+
onEvent?: (event: AppAgentEvent) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** One settings field a panel can render. */
|
|
14
|
+
export interface SettingsField {
|
|
15
|
+
readonly name: string
|
|
16
|
+
readonly kind: 'string' | 'secret' | 'select'
|
|
17
|
+
readonly options?: readonly string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Models one vendor inside a brain exposes. The brain id is not this field. */
|
|
21
|
+
export interface ModelListing {
|
|
22
|
+
readonly provider: string
|
|
23
|
+
readonly models: readonly string[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Config Shell injects. Numeric limits are not this package's policy. */
|
|
27
|
+
export interface RuntimeProviderConfig {
|
|
28
|
+
readonly provider?: string
|
|
29
|
+
readonly model?: string
|
|
30
|
+
readonly options?: Record<string, string>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* One brain. Host calls `configure`, then `start`, then `llm` and `agent`.
|
|
35
|
+
* Switching model does not change the tools that provider already runs. That set is not a method on this interface.
|
|
36
|
+
*/
|
|
37
|
+
export interface RuntimeProvider {
|
|
38
|
+
readonly id: string
|
|
39
|
+
readonly label?: string
|
|
40
|
+
configure?(config: RuntimeProviderConfig): void
|
|
41
|
+
start(): Promise<void>
|
|
42
|
+
stop(): Promise<void>
|
|
43
|
+
healthy(): boolean
|
|
44
|
+
/** Vendor groups. Absent means this brain does not reject a model name. Each call may fetch. */
|
|
45
|
+
models?(): Promise<readonly ModelListing[]>
|
|
46
|
+
llm(prompt: string, options?: RuntimeLlmOptions): Promise<string>
|
|
47
|
+
agent(goal: string, options?: RuntimeAgentOptions): Promise<string>
|
|
48
|
+
describe?(): readonly SettingsField[]
|
|
49
|
+
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ProviderError } from './codes.ts'
|
|
2
|
+
import type { RuntimeProvider } from './provider.ts'
|
|
3
|
+
|
|
4
|
+
/** Registrations of brains Shell knows how to construct. */
|
|
5
|
+
export interface ProviderRegistry {
|
|
6
|
+
register(provider: RuntimeProvider): () => void
|
|
7
|
+
get(id: string): RuntimeProvider
|
|
8
|
+
ids(): readonly string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** A registry whose `register` returns the disposer. A second id throws. */
|
|
12
|
+
export function createProviderRegistry(): ProviderRegistry {
|
|
13
|
+
const providers = new Map<string, RuntimeProvider>()
|
|
14
|
+
return {
|
|
15
|
+
register(provider) {
|
|
16
|
+
if (providers.has(provider.id)) {
|
|
17
|
+
throw new ProviderError('provider-duplicate', `already registered: ${provider.id}`)
|
|
18
|
+
}
|
|
19
|
+
providers.set(provider.id, provider)
|
|
20
|
+
return () => {
|
|
21
|
+
providers.delete(provider.id)
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
get(id) {
|
|
25
|
+
const found = providers.get(id)
|
|
26
|
+
if (found === undefined) {
|
|
27
|
+
throw new ProviderError('provider-missing', `no runtime provider: ${id}`)
|
|
28
|
+
}
|
|
29
|
+
return found
|
|
30
|
+
},
|
|
31
|
+
ids() {
|
|
32
|
+
return [...providers.keys()]
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
}
|