fix: address ds-review-bot v8 findings
- llm-deepseek: the uncatalogued resolveModel fallback declares text-only modalities — the wire route is text-only regardless of catalog membership, so "unknown" must not let the host persist-then-fail images. - session.selectModel also consults the pending-inbox mirror: a queued image prompt enters the log only when claimed, after a switch would land. - attachment store: ensureDurableDirectory syncs every ancestor entry up to a caller-vouched boundary regardless of what mkdir reports — a raced "already existed" is not "already durable". - One image walker (imageBlockIn/imageInEvent) now serves both attachment authorization and the selection gate; referencedImage therefore also authorizes references inside wrapped message content. - InputHub: the scope disposer resolves the conversation service optionally (teardown/HMR must reach quiescence), and a send failing after its scope died releases the in-flight drafts instead of restoring them onto a disposed shell. - http-bridge destroys declared-oversize requests with connection: close instead of draining a body the client can trickle indefinitely. - LlmService validates AND detaches modality arrays identically on the advisory and exact routes; READMEs record the fourth INVALID_MODEL_INFO rejection reason. - CLI provider docs (JSDoc, README pair, Agent Note pair) describe the reuse behavior; llm-route.spec now parses the SHIPPED cordis.yml through the production extraction, pinning the row coupling. - image-display lane pins gallery/rail shape in inline snapshots and the object-URL scheme this environment must take; stale host.schema comment dropped.
This commit is contained in:
27 files changed
+337
-100
No files matched your search
@@ -116,6 +116,34 @@ export function resolveLlmRoute(input: LlmRouteInput): LlmRoute {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bypass parse of an include yml's top-level entry rows (id → row). Exported
|
||||
* so tests can pin the shipped tree's real row coupling instead of literals.
|
||||
* @param configPath - absolute path of the include cordis.yml.
|
||||
* @returns row map keyed by entry id.
|
||||
*/
|
||||
export function parseIncludeYmlRows(configPath: string): Map<string, { config?: unknown }> {
|
||||
const doc = yaml.load(readFileSync(configPath, 'utf8'), { schema: includeYamlSchema })
|
||||
if (!Array.isArray(doc)) throw new Error(`dsh: ${configPath} is not a top-level entry list`)
|
||||
const rows = new Map<string, { config?: unknown }>()
|
||||
for (const row of doc as { id?: string; config?: unknown }[]) {
|
||||
if (typeof row.id === 'string') rows.set(row.id, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
/**
|
||||
* Providers the yml's static pi-ai row routes — the roster {@link resolveLlmRoute} reuses.
|
||||
* @param rows - parsed include rows.
|
||||
* @returns provider ids in row order (empty when the row is absent).
|
||||
*/
|
||||
export function ymlPiAiProvidersOf(rows: ReadonlyMap<string, { config?: unknown }>): string[] {
|
||||
const config = rows.get('llm-pi-ai')?.config as { providers?: { provider?: unknown }[] } | undefined
|
||||
return (config?.providers ?? [])
|
||||
.map(entry => entry.provider)
|
||||
.filter((value): value is string => typeof value === 'string')
|
||||
}
|
||||
|
||||
/** One profile-json key mapped onto a yml row's config field. */
|
||||
interface ProfileMapping {
|
||||
jsonPath: string
|
||||
@@ -170,7 +198,11 @@ export interface AppCLIEntryOptions {
|
||||
port?: number
|
||||
/** Parent directory for name-created Workspaces; undefined uses the gateway's cwd fallback. */
|
||||
workspaceRoot?: string
|
||||
/** Host default provider override. Non-DeepSeek routes mount pi-ai with ambient credentials. */
|
||||
/**
|
||||
* Host default provider override. Providers the shipped yml pi-ai row
|
||||
* already routes are reused; only a provider absent from that row mounts
|
||||
* pi-ai dynamically.
|
||||
*/
|
||||
provider?: string
|
||||
/** Host default model override. */
|
||||
model?: string
|
||||
@@ -262,14 +294,11 @@ export class AppCLIEntry {
|
||||
if (this.options.model !== undefined) put('api-gateway', 'model', this.options.model)
|
||||
|
||||
const gatewayConfig = rows.get('api-gateway')?.config as Record<string, unknown> | undefined
|
||||
const piAiRow = rows.get('llm-pi-ai')?.config as { providers?: { provider?: unknown }[] } | undefined
|
||||
const route = resolveLlmRoute({
|
||||
cli: { provider: this.options.provider, model: this.options.model },
|
||||
profile: { provider: profile.provider, model: profile.model },
|
||||
gateway: { provider: gatewayConfig?.provider, model: gatewayConfig?.model },
|
||||
ymlPiAiProviders: (piAiRow?.providers ?? [])
|
||||
.map(p => p.provider)
|
||||
.filter((value): value is string => typeof value === 'string'),
|
||||
ymlPiAiProviders: ymlPiAiProvidersOf(rows),
|
||||
})
|
||||
this.piAiProvider = route.dynamicPiAiProvider
|
||||
|
||||
@@ -344,13 +373,7 @@ export class AppCLIEntry {
|
||||
|
||||
/** Bypass parse of the shipped yml (id → row) for patch-merge inputs; Loader still reads the file itself. */
|
||||
private parseYmlRows(): Map<string, { config?: unknown }> {
|
||||
const doc = yaml.load(readFileSync(this.options.configPath, 'utf8'), { schema: includeYamlSchema })
|
||||
if (!Array.isArray(doc)) throw new Error(`dsh: ${this.options.configPath} is not a top-level entry list`)
|
||||
const rows = new Map<string, { config?: unknown }>()
|
||||
for (const row of doc as { id?: string; config?: unknown }[]) {
|
||||
if (typeof row.id === 'string') rows.set(row.id, row)
|
||||
}
|
||||
return rows
|
||||
return parseIncludeYmlRows(this.options.configPath)
|
||||
}
|
||||
|
||||
/** Profile json under cwd; read-only — never created here, absent = no user config. */
|
||||
|
||||
Reference in New Issue
Block a user