refactor(consumers): remove cross-package runtime relays

This commit is contained in:
imccyu 2026-08-30 02:29:52 +08:00
parent f4e49ccf8f
commit 9135a13a8b
167 changed files with 561 additions and 493 deletions

View file

@ -10,7 +10,6 @@ import { SessionId } from '@deepseek-ai/dsh-session'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
import { SETTINGS_NAMESPACE, SHIPPED_PRESET_ROOT } from '@deepseek-ai/dsh-agent-presets'
import { applyChildComposition, childSessionMeta } from '@deepseek-ai/dsh-subagent'
@ -864,7 +863,7 @@ describe('the default preset as a user setting', () => {
it('composes an unnamed session from the stored default, not the composed one', async () => {
expect(ctx.agentPresets.defaultId).toBe('standard')
await ctx.settings.update(settingsNamespace(SETTINGS_NAMESPACE), { default: 'minimal' })
await ctx.settings.update(SETTINGS_NAMESPACE, { default: 'minimal' })
try {
expect(ctx.agentPresets.defaultId).toBe('minimal')
@ -883,7 +882,7 @@ describe('the default preset as a user setting', () => {
// The context is shared with the rest of the file. `replace({})` drops
// the user section wholesale so the field re-inherits the composition
// base; `update` merges, and would leave the override standing.
await ctx.settings.replace(settingsNamespace(SETTINGS_NAMESPACE), {})
await ctx.settings.replace(SETTINGS_NAMESPACE, {})
}
expect(ctx.agentPresets.defaultId).toBe('standard')

View file

@ -9,7 +9,6 @@ import { join } from 'node:path'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold,
@ -34,7 +33,7 @@ describe.skipIf(MODE === 'record')('web e2e: declared reasoning efforts reach th
// = the wire spelling dispatch would send (`max: ultra` renames; the
// valueless `off` means "supported, send nothing"). The route sets no
// deployment default, so the pane leads with the provider-default entry.
await scaffold.ctx.settings.update(settingsNamespace('llm-pi-ai'), {
await scaffold.ctx.settings.update('llm-pi-ai', {
providers: {
'acme-gateway': {
displayName: 'Acme Gateway',

View file

@ -17,7 +17,6 @@ import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { SessionId } from '@deepseek-ai/dsh-session'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { launchWebScaffold, watchConsole, type WebScaffold } from './scaffold.ts'
import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './support.ts'
@ -62,7 +61,7 @@ describe('web e2e: the composer model switch is the default for later sessions',
// Declared through the settings seam rather than the Models page: this
// scenario is about the composer, and the declaring flow is covered by
// models-settings.e2e.
await scaffold.ctx.settings.update(settingsNamespace('llm-pi-ai'), {
await scaffold.ctx.settings.update('llm-pi-ai', {
providers: {
[START_ROUTE]: {
displayName: 'Origin Gateway',
@ -136,7 +135,7 @@ describe('web e2e: the composer model switch is the default for later sessions',
// default still names the route, and nothing serves it any more.
// `replace`, not `update`: a merge patch of `{providers: {}}` leaves every
// stored profile in place.
await scaffold.ctx.settings.replace(settingsNamespace('llm-pi-ai'), { providers: {} })
await scaffold.ctx.settings.replace('llm-pi-ai', { providers: {} })
await expect.poll(async () => box.isEnabled(), { timeout: 15_000 }).toBe(false)
expect(await box.getAttribute('data-placeholder')).toBe('当前模型不可用,请先选择模型')

View file

@ -9,7 +9,6 @@ import { join } from 'node:path'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import {
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold,
@ -118,7 +117,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
// An old acknowledgement means materially revised copy: welcome returns,
// while the already-configured provider step remains complete.
await scaffold.ctx.settings.mutate(settingsNamespace(WELCOME_NOTICE_SETTINGS_NAMESPACE), [{
await scaffold.ctx.settings.mutate(WELCOME_NOTICE_SETTINGS_NAMESPACE, [{
op: 'set', path: [WELCOME_NOTICE_ACK_FIELD], value: 'previous-copy-version',
}])
const thirdReloadWarnings = tripwire.warnings.length

View file

@ -57,7 +57,6 @@ import {
type Profile,
} from '@deepseek-ai/dsh-app-boot'
import { dshHomePath } from '@deepseek-ai/dsh-home-paths'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { LlmAdapter } from '@deepseek-ai/dsh-llm'
import type {
LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, RetryPolicyConfig, StreamChunk,
@ -636,7 +635,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
await ctx.loader.await()
assertEntriesLoaded(ctx, 'web e2e scaffold')
if (options.welcomeNoticePending !== true) {
await ctx.settings.mutate(settingsNamespace(WELCOME_NOTICE_SETTINGS_NAMESPACE), [{
await ctx.settings.mutate(WELCOME_NOTICE_SETTINGS_NAMESPACE, [{
op: 'set', path: [WELCOME_NOTICE_ACK_FIELD], value: WELCOME_NOTICE_VERSION,
}])
}

View file

@ -10,7 +10,6 @@ import { afterEach, expect, it } from 'vitest'
import { ToolCallId } from '@deepseek-ai/dsh-llm'
import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox'
import { SessionId } from '@deepseek-ai/dsh-session'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
// Empty type imports carry the tools/sandboxPolicy/approval Context merges.
import type {} from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-sandbox-policy'
@ -101,7 +100,7 @@ it('assembles the shipped Web transport, catalog, guidance, and defaults', async
],
}
`)
await ctx.settings.update(settingsNamespace('llm-deepseek'), {
await ctx.settings.update('llm-deepseek', {
retryPolicy: { mode: 'always', maxRetries: 5 },
})
expect(ctx.llm.providerRetryPolicy('deepseek-official')).toMatchInlineSnapshot(`
@ -112,7 +111,7 @@ it('assembles the shipped Web transport, catalog, guidance, and defaults', async
"mode": "always",
}
`)
await ctx.settings.update(settingsNamespace('llm-pi-ai'), {
await ctx.settings.update('llm-pi-ai', {
providers: {
openai: {},
anthropic: { retryPolicy: { mode: 'always' } },

View file

@ -16,6 +16,7 @@ import { realpath } from 'node:fs/promises'
import { isAbsolute, resolve } from 'node:path'
import { Readable, Writable } from 'node:stream'
import Schema from '@deepseek-ai/schemastery'
import { brandString } from '@deepseek-ai/dsh-brand'
import { errorChain } from '@deepseek-ai/dsh-llm'
import {
agent as createAcpAgentApp,
@ -45,7 +46,7 @@ import {
type Stream,
} from '@agentclientprotocol/sdk'
import type { ModelSelection } from '@deepseek-ai/dsh-agent'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-session-persistence'
// Side-effect type import: declaration-merges the approval waterfall answered below.
import type {} from '@deepseek-ai/dsh-user-approval'
@ -195,7 +196,7 @@ export function apply(ctx: Context, config: AcpConfig): void {
async newSession(params: NewSessionRequest, signal: AbortSignal): Promise<NewSessionResponse> {
assertOpen()
validateWorkspaceParams(params)
const sessionId = SessionId(randomUUID())
const sessionId = brandString<SessionId>(randomUUID())
// No preset composition: the ACP bundle keeps the model-facing rows in
// the host plane, so this agent reads them from the global layer. A
// deployment that configures a roster has to join one here first
@ -237,7 +238,7 @@ export function apply(ctx: Context, config: AcpConfig): void {
async resumeSession(params: ResumeSessionRequest, signal: AbortSignal): Promise<ResumeSessionResponse> {
assertOpen()
validateWorkspaceParams(params)
const sessionId = SessionId(params.sessionId)
const sessionId = brandString<SessionId>(params.sessionId)
if (sessions.has(sessionId) || activating.has(sessionId) || ctx.sessions.get(sessionId) !== undefined) {
throw invalidParams(`session is already active: ${sessionId}`)
}
@ -333,7 +334,7 @@ export function apply(ctx: Context, config: AcpConfig): void {
signal: AbortSignal,
): Promise<SetSessionConfigOptionResponse> {
assertOpen()
const record = requireSession(SessionId(params.sessionId))
const record = requireSession(brandString<SessionId>(params.sessionId))
try {
return { configOptions: await record.setConfig(params.configId, params.value, signal) }
} catch (error: unknown) {
@ -344,7 +345,7 @@ export function apply(ctx: Context, config: AcpConfig): void {
async closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
assertOpen()
const sessionId = SessionId(params.sessionId)
const sessionId = brandString<SessionId>(params.sessionId)
const record = requireSession(sessionId)
try {
await record.close('ACP session closed')
@ -358,12 +359,12 @@ export function apply(ctx: Context, config: AcpConfig): void {
async prompt(params: PromptRequest, requestSignal: AbortSignal): Promise<PromptResponse> {
assertOpen()
const record = requireSession(SessionId(params.sessionId))
const record = requireSession(brandString<SessionId>(params.sessionId))
return record.prompt(params, imagePromptEnabled, requestSignal)
},
cancel(params: CancelNotification): Promise<void> {
sessions.get(SessionId(params.sessionId))?.cancel()
sessions.get(brandString<SessionId>(params.sessionId))?.cancel()
return Promise.resolve()
},
}

View file

@ -18,7 +18,7 @@ import Group from '@deepseek-ai/cordis-plugin-group'
import { dshHomePath, resolveDshHome } from '@deepseek-ai/dsh-home-paths'
import { createLaunchEnvironmentSnapshot, type LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
import type {} from '@deepseek-ai/cordis-plugin-hmr'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-system-prompt'
declare module '@deepseek-ai/cordis' {
interface Context {
@ -840,7 +840,7 @@ export function addHarnessSourceSection(ctx: Context, sourceRoot: string): (() =
if (systemPrompt === undefined) return undefined
return systemPrompt.section({
name: HARNESS_SOURCE_SECTION,
order: FIRST_PARTY_SECTION_ORDER.HARNESS_SOURCE,
order: systemPrompt.getSectionOrder('HARNESS_SOURCE'),
text: `The DeepSeek Harness implementation checkout is at ${sourceRoot}. The checkout location and current working directory are separate values and may differ; never infer the working directory from this path. Use pwd to determine the current working directory. Use this checkout only to inspect or extend DSH itself.`,
})
}

View file

@ -11,12 +11,13 @@
import { randomUUID } from 'node:crypto'
import type { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { brandString } from '@deepseek-ai/dsh-brand'
import { installModelSelection } from '@deepseek-ai/dsh-agent'
import type { Agent, ModelSelectionRef } from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-agent-default-model'
import { assertNever, createUserMessage } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import { assertNever } from '@deepseek-ai/dsh-util-values'
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
// Empty type imports carry the loader Context merge for the settlement await
// and the cmdline Context merge for the appExit host value.
import type {} from '@deepseek-ai/cordis-plugin-loader'
@ -175,7 +176,7 @@ async function run(ctx: Context, task: string, io: HeadlessIo): Promise<void> {
// that DOES configure one has to join it here first
// (@deepseek-ai/dsh-agent-presets README, "Composing a child agent").
const { agent } = await agents.create({
sessionId: SessionId(`session-${randomUUID()}`),
sessionId: brandString<SessionId>(`session-${randomUUID()}`),
meta: { cwd: process.cwd() },
agentOptions: { provider: selection.provider, model: selection.model },
setup: (agentCtx) => {

View file

@ -25,7 +25,6 @@ import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
import { scrubbedParentEnv } from '@deepseek-ai/dsh-subprocess'
import type {} from '@deepseek-ai/cordis-plugin-loader'
import type {} from '@deepseek-ai/dsh-host-webserver'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-shell-env'
/** Stable Cordis plugin name. */
@ -245,7 +244,7 @@ export function apply(ctx: Context, config: Config): void {
addHarnessSourceSection(promptCtx, SOURCE_ROOT)
promptCtx.systemPrompt.section({
name: 'app:web-surface',
order: FIRST_PARTY_SECTION_ORDER.WEB_SURFACE,
order: promptCtx.systemPrompt.getSectionOrder('WEB_SURFACE'),
text: () => webSurfacePrompt(localWebUrl(promptCtx)),
})
})

View file

@ -5,7 +5,8 @@ import {
createToolResultMessage,
createUserMessage,
} from '@deepseek-ai/dsh-llm/message'
import { ToolCallId, type MessageId } from '@deepseek-ai/dsh-llm/brand'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { MessageId, ToolCallId } from '@deepseek-ai/dsh-llm/brand'
import type {
AssistantMessage,
ContentBlock,
@ -17,11 +18,11 @@ import type {
} from '@deepseek-ai/dsh-llm'
import type { AttachmentIdType, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
import type {
JsonValue,
SessionEvent,
SessionHeader,
SessionId,
} from '@deepseek-ai/dsh-session/types'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import { isChunkRow, packChunkRuns } from '@deepseek-ai/dsh-session/chunk-rows'
import type { ChunkRow } from '@deepseek-ai/dsh-session/chunk-rows'
import type { TodoItem } from '@deepseek-ai/dsh-tool-todo/client'
@ -355,7 +356,7 @@ function assistantMessage(content: ContentBlock[], model = 'fx-1'): AssistantMes
}
function toolResultMessage(callId: string, content: ContentBlock[], isError: boolean): ToolResultMessage {
return createToolResultMessage({ callId: ToolCallId(callId), content, isError })
return createToolResultMessage({ callId: brandString<ToolCallId>(callId), content, isError })
}
const MARKDOWN_FIXTURE = [

View file

@ -1,7 +1,7 @@
/** Host registration for the browser locale preference. */
import type { Context } from '@deepseek-ai/cordis'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
import { LOCALE_SETTINGS_NAMESPACE, LocaleSettingsSchema } from './locale-settings.ts'
export {
@ -16,7 +16,7 @@ export {
export function apply(ctx: Context): void {
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.register(
settingsNamespace(LOCALE_SETTINGS_NAMESPACE),
LOCALE_SETTINGS_NAMESPACE,
LocaleSettingsSchema,
)
})

View file

@ -1,6 +1,6 @@
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
import { SettingsProvider, settingsNamespace, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import { SettingsProvider, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import {
LOCALE_SETTINGS_NAMESPACE, apply,
} from '@deepseek-ai/dsh-client-locale'
@ -19,7 +19,7 @@ describe('locale host', () => {
await ctx.plugin(MemorySettings).await()
const fiber = ctx.plugin({ apply })
await fiber.await()
const ns = settingsNamespace(LOCALE_SETTINGS_NAMESPACE)
const ns = LOCALE_SETTINGS_NAMESPACE
expect(ctx.settings.get(ns)).toEqual({})
await ctx.settings.update(ns, { preference: 'en' })
expect(ctx.settings.get(ns)).toEqual({ preference: 'en' })

View file

@ -58,7 +58,7 @@ function styleInjectionModule(
* Everything else under @deepseek-ai/* is either a module-table entry
* (external) or a leak the purity gate rejects.
*/
export const INLINE_SAFE = /^(?:@deepseek-ai\/dsh-(?:file-reference|session|llm|tools|brand|deque|typert-protocol|util-crypto|util-workspace-path)(?:\/|$)|@deepseek-ai\/dsh-token-meter\/client$)/
export const INLINE_SAFE = /^(?:@deepseek-ai\/dsh-(?:file-reference|session|llm|tools|brand|deque|typert-protocol|util-crypto|util-values|util-workspace-path)(?:\/|$)|@deepseek-ai\/dsh-token-meter\/client$)/
/**
* Vendored framework libraries: rescoped into @deepseek-ai, so the gate below

View file

@ -1,7 +1,7 @@
/** Host registration for browser Chat preferences. */
import type { Context } from '@deepseek-ai/cordis'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
import { CHAT_SETTINGS_NAMESPACE, ChatSettingsSchema } from './chat-settings.ts'
export {
@ -13,7 +13,7 @@ export {
export function apply(ctx: Context): void {
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.register(
settingsNamespace(CHAT_SETTINGS_NAMESPACE),
CHAT_SETTINGS_NAMESPACE,
ChatSettingsSchema,
)
})

View file

@ -1,6 +1,6 @@
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
import { SettingsProvider, settingsNamespace, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import { SettingsProvider, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import {
CHAT_SETTINGS_NAMESPACE, DEFAULT_TRANSCRIPT_VIEW_MODE, apply,
} from '../src/index.ts'
@ -19,7 +19,7 @@ describe('ui-chat Host settings', () => {
await ctx.plugin(MemorySettings).await()
const fiber = ctx.plugin({ apply })
await fiber.await()
const ns = settingsNamespace(CHAT_SETTINGS_NAMESPACE)
const ns = CHAT_SETTINGS_NAMESPACE
expect(ctx.settings.get(ns)).toEqual({ transcriptView: DEFAULT_TRANSCRIPT_VIEW_MODE })
await ctx.settings.update(ns, { transcriptView: 'normal' })

View file

@ -1,7 +1,7 @@
/** Host registration for browser conversation preferences. */
import type { Context } from '@deepseek-ai/cordis'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
import { CONVERSATION_SETTINGS_NAMESPACE, ConversationSettingsSchema } from './submission-settings.ts'
export {
@ -16,7 +16,7 @@ export {
export function apply(ctx: Context): void {
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.register(
settingsNamespace(CONVERSATION_SETTINGS_NAMESPACE),
CONVERSATION_SETTINGS_NAMESPACE,
ConversationSettingsSchema,
)
})

View file

@ -1,6 +1,6 @@
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
import { SettingsProvider, settingsNamespace, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import { SettingsProvider, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import {
CONVERSATION_SETTINGS_NAMESPACE, DEFAULT_BUSY_ENTER_BEHAVIOR, apply,
} from '@deepseek-ai/dsh-client-ui-conversation'
@ -19,7 +19,7 @@ describe('ui-conversation host', () => {
await ctx.plugin(MemorySettings).await()
const fiber = ctx.plugin({ apply })
await fiber.await()
const ns = settingsNamespace(CONVERSATION_SETTINGS_NAMESPACE)
const ns = CONVERSATION_SETTINGS_NAMESPACE
expect(ctx.settings.get(ns)).toEqual({ busyEnter: DEFAULT_BUSY_ENTER_BEHAVIOR })
await ctx.settings.update(ns, { busyEnter: 'steer' })
expect(ctx.settings.get(ns)).toEqual({ busyEnter: 'steer' })

View file

@ -6,7 +6,7 @@
*/
import type { Context } from '@deepseek-ai/cordis'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-system-prompt'
/** Services required for the model guidance paired with the browser renderer. */
export const inject = ['systemPrompt']
@ -22,7 +22,7 @@ const FILE_REFERENCE_PROMPT = 'When you successfully create or modify files, men
export function apply(ctx: Context): void {
ctx.systemPrompt.section({
name: 'ui:deliverable-file-references',
order: FIRST_PARTY_SECTION_ORDER.DELIVERABLE_FILE_REFERENCES,
order: ctx.systemPrompt.getSectionOrder('DELIVERABLE_FILE_REFERENCES'),
text: FILE_REFERENCE_PROMPT,
})
}

View file

@ -2,7 +2,7 @@
import type { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
/** Durable settings namespace for product-wide GUI onboarding facts. */
const ONBOARDING_SETTINGS_NAMESPACE = 'ui-onboarding'
@ -20,7 +20,7 @@ const OnboardingSettingsSchema: z<OnboardingSettings> = z.object({
export function apply(ctx: Context): void {
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.register(
settingsNamespace(ONBOARDING_SETTINGS_NAMESPACE),
ONBOARDING_SETTINGS_NAMESPACE,
OnboardingSettingsSchema,
)
})

View file

@ -1,6 +1,6 @@
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
import { SettingsProvider, settingsNamespace, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import { SettingsProvider, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import { apply } from '../src/index.ts'
/** Mirrors the module-local namespace id in src/index.ts. */
@ -21,11 +21,11 @@ describe('ui-settings-general host', () => {
const fiber = ctx.plugin({ apply })
await fiber.await()
expect(ctx.settings.describe().map(row => row.ns)).toContain(
settingsNamespace(ONBOARDING_SETTINGS_NAMESPACE),
ONBOARDING_SETTINGS_NAMESPACE,
)
await fiber.dispose()
expect(ctx.settings.describe().map(row => row.ns)).not.toContain(
settingsNamespace(ONBOARDING_SETTINGS_NAMESPACE),
ONBOARDING_SETTINGS_NAMESPACE,
)
})
})

View file

@ -23,7 +23,7 @@
import { useState } from 'react'
import type { ReactNode } from 'react'
import type { JsonValue } from '@deepseek-ai/dsh-api-remotes/client'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import { apiKeyFailure } from './apiKey.ts'
import { EditorFooter } from './EditorFooter.tsx'
import { validateDeepSeekModels } from './DeepSeekModelsEditor.tsx'

View file

@ -24,8 +24,9 @@
import { useEffect, useMemo, useState } from 'react'
import type { ReactNode } from 'react'
import type {
CredentialInfo, JsonValue, SettingsNamespaceView, SettingsPathOpView,
CredentialInfo, SettingsNamespaceView, SettingsPathOpView,
} from '@deepseek-ai/dsh-api-remotes/client'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import {
DeepSeekModelsEditor, modelDrafts, validateDeepSeekModels,
} from './DeepSeekModelsEditor.tsx'

View file

@ -5,8 +5,9 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
import Schema from '@deepseek-ai/schemastery'
import { bindSnapshotSelector, RemoteError } from '@deepseek-ai/dsh-client-test-runtime'
import type {
CredentialInfo, JsonValue, RemoteResult, SettingsNamespaceView,
CredentialInfo, RemoteResult, SettingsNamespaceView,
} from '@deepseek-ai/dsh-api-remotes/client'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import {
ModelsSection, needsSetup, providerCopy, providerTargetLabel, removeProviderProfile,
} from '../src/client/ModelsSection.tsx'

View file

@ -3,7 +3,8 @@
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import Schema from '@deepseek-ai/schemastery'
import type { JsonValue, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
import type { SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import { bindSnapshotSelector, RemoteError } from '@deepseek-ai/dsh-client-test-runtime'
import { DeepSeekOnboardingDialog } from '../src/client/DeepSeekOnboardingDialog.tsx'
import type { DeepSeekOnboardingDialogProps } from '../src/client/DeepSeekOnboardingDialog.tsx'

View file

@ -4,7 +4,8 @@ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/re
import { afterEach, describe, expect, it, vi } from 'vitest'
import Schema from '@deepseek-ai/schemastery'
import { bindSnapshotSelector, RemoteError } from '@deepseek-ai/dsh-client-test-runtime'
import type { JsonValue, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
import type { SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import { ModelsSection, providerCopy } from '../src/client/ModelsSection.tsx'
import type { ModelsSectionInjected, ModelsSectionProps } from '../src/client/ModelsSection.tsx'
import { CustomProviderCard } from '../src/client/CustomProviderCard.tsx'

View file

@ -9,9 +9,10 @@
import { Service } from '@deepseek-ai/cordis'
import type { Context } from '@deepseek-ai/cordis'
import type {
JsonValue, SettingsNamespaceView, SettingsPathOpView,
SettingsNamespaceView, SettingsPathOpView,
} from '@deepseek-ai/dsh-api-remotes/client'
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
// Type-only, and deliberately NOT `@deepseek-ai/dsh-api-remotes/client`: this
// package is reachable from the Host build graph through its feature-package
// callers, and api-remotes' Client face imports a Host-tsdown-generated

View file

@ -2,9 +2,10 @@ import { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { describe, expect, it, vi } from 'vitest'
import type {
JsonValue, SettingsNamespaceView, SettingsPathOpView,
SettingsNamespaceView, SettingsPathOpView,
} from '@deepseek-ai/dsh-api-remotes/client'
import { RemoteError, TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
import { SettingsSchemaService } from '../src/client/schema.ts'
import { SettingsScopeController, SettingsScopeBinder } from '../src/client/settings-scope.ts'

View file

@ -2,7 +2,7 @@
import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-host-webserver'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
import { bootThemeInjection } from './boot-theme.ts'
import {
DEFAULT_FONT_SIZE, DEFAULT_PREFERENCE, THEME_SETTINGS_NAMESPACE, ThemeSettingsSchema,
@ -15,7 +15,7 @@ export {
type ThemePreference, type ThemeSettings,
} from './theme-settings.ts'
const THEME_NAMESPACE = settingsNamespace(THEME_SETTINGS_NAMESPACE)
const THEME_NAMESPACE = THEME_SETTINGS_NAMESPACE
/** Read the registered theme section or the schema defaults without a settings provider. */
function readSection(ctx: Context): { preference: ThemePreference; fontSize: number } {

View file

@ -1,7 +1,7 @@
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
import type { IndexInjection } from '@deepseek-ai/dsh-host-webserver'
import { SettingsProvider, settingsNamespace, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import { SettingsProvider, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
import {
DEFAULT_PREFERENCE, THEME_SETTINGS_NAMESPACE, apply,
} from '@deepseek-ai/dsh-client-ui-theme'
@ -33,7 +33,7 @@ describe('ui-theme host', () => {
await ctx.plugin(MemorySettings).await()
const fiber = ctx.plugin({ apply })
await fiber.await()
const ns = settingsNamespace(THEME_SETTINGS_NAMESPACE)
const ns = THEME_SETTINGS_NAMESPACE
expect(ctx.settings.get(ns)).toEqual({ preference: DEFAULT_PREFERENCE, fontSize: 14 })
await ctx.settings.update(ns, { preference: 'dark', fontSize: 16 })
expect(ctx.settings.get(ns)).toEqual({ preference: 'dark', fontSize: 16 })
@ -54,7 +54,7 @@ describe('ui-theme host', () => {
expect(rows[0]).toMatchObject({ kind: 'script', placement: 'body' })
expect(scriptText(rows[0])).toContain('const preference = "system"')
expect(scriptText(rows[0])).toContain('"14px"')
await ctx.settings.update(settingsNamespace(THEME_SETTINGS_NAMESPACE), { preference: 'dark', fontSize: 17 })
await ctx.settings.update(THEME_SETTINGS_NAMESPACE, { preference: 'dark', fontSize: 17 })
expect(scriptText(collect(ctx)[0])).toContain('const preference = "dark"')
expect(scriptText(collect(ctx)[0])).toContain('"17px"')
await fiber.dispose()

View file

@ -1,6 +1,6 @@
/** Test adapter for the production conversation.details.tool registration. */
import type { SessionLiveEventEntry } from '@deepseek-ai/dsh-api-session-controller/client'
import { isJsonValue, type JsonValue } from '@deepseek-ai/dsh-session'
import { isJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
import type {
ChatConversationViewNode, ChatSnapshot, ConversationNode, DetailsSlotProps,
DetailsToolOwnerProps, RunningToolCall, ToolResultNode,

View file

@ -15,7 +15,7 @@ import z from '@deepseek-ai/schemastery'
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import { CodeRuntime, DUNDER_MEMBER, PORTABLE_RESERVED_WORDS, RESERVED_BINDING_GLOBALS, RESERVED_ERROR_MEMBERS } from '@deepseek-ai/dsh-code-runtime'
import type { CodeBindingNamespace, CodeJsonValue, CodeRunFailure, CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import { snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
import type { ReplyMessage, WorkerBootData, WorkerToHost } from './protocol.ts'
import { jsonStringBytesUpTo, jsonValueBytesUpTo, truncateJsonStringBytes } from './output-json.ts'
import { decodeWorkerJson, encodeWorkerJson } from './worker-json.ts'

View file

@ -1,6 +1,6 @@
import { runInNewContext } from 'node:vm'
import { describe, expect, it } from 'vitest'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import { snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
import { decodeWorkerJson, encodeWorkerJson, snapshotCodeJsonValue } from '../src/worker-json.ts'
describe('snapshotCodeJsonValue', () => {

View file

@ -4,8 +4,8 @@
* @module @deepseek-ai/dsh-compaction-basic/config
*/
import { deepFreeze } from '@deepseek-ai/dsh-llm'
import type { LlmCallConfig } from '@deepseek-ai/dsh-llm'
import { deepFreeze } from '@deepseek-ai/dsh-util-values'
import type {
BasicCompactionConfig,
CompactionPolicyConfig,

View file

@ -10,8 +10,9 @@ import { CompactionEngine, ManualCompactionError } from '@deepseek-ai/dsh-compac
import type { CompactionResult, CompactionTrigger } from '@deepseek-ai/dsh-compaction'
import type { TokenMeter } from '@deepseek-ai/dsh-token-meter'
import type { Session } from '@deepseek-ai/dsh-session'
import { CONTEXT_WINDOW_EXCEEDED_CODE, assertNever } from '@deepseek-ai/dsh-llm'
import { CONTEXT_WINDOW_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm'
import type { LlmCallConfig } from '@deepseek-ai/dsh-llm'
import { assertNever } from '@deepseek-ai/dsh-util-values'
import type { Agent, PreStepDecision } from '@deepseek-ai/dsh-agent'
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
// Type-only: makes the optional sibling service available to `ctx.get()`.

View file

@ -1,6 +1,6 @@
/** Configuration resolution for deterministic tool-result pruning. */
import { deepFreeze } from '@deepseek-ai/dsh-llm'
import { deepFreeze } from '@deepseek-ai/dsh-util-values'
import type { ResolvedConfig, ToolResultPruneConfig } from './types.ts'
/** Fixed marker substituted for every removed middle span. */

View file

@ -8,8 +8,8 @@ import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
import type { FileSystem, FsInfo, FsTarget, FsVersion } from '@deepseek-ai/dsh-fs'
import { assertNever } from '@deepseek-ai/dsh-llm'
import { dshHomeDisplay } from '@deepseek-ai/dsh-home-paths'
import { assertNever } from '@deepseek-ai/dsh-util-values'
import { resolveConfig, resolveDiscoveryConfig, type ResolvedConfig } from './config.ts'
import { trimmedInstructionDigest } from './digest.ts'
import {

View file

@ -11,7 +11,6 @@ import FileReferenceService, {
FILE_REFERENCE_PROMPT,
type FileReferenceCandidate,
} from '@deepseek-ai/dsh-file-reference'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-tools'
import {
DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES,
@ -69,7 +68,7 @@ export class LocalFileReferenceService extends FileReferenceService {
const fiber = agent.ctx.inject(['systemPrompt', 'tools'], (scope) => {
scope.systemPrompt.section({
name: 'context:file-reference',
order: FIRST_PARTY_SECTION_ORDER.FILE_REFERENCE,
order: scope.systemPrompt.getSectionOrder('FILE_REFERENCE'),
text: () => agent.ctx.tools.get('read', agent) === undefined ? '' : FILE_REFERENCE_PROMPT,
})
})

View file

@ -2,8 +2,8 @@
import { isCompactCheckpointSource } from '@deepseek-ai/dsh-compaction'
import type { SessionSurfaceSnapshot } from '@deepseek-ai/dsh-session-query'
import { assertNever } from '@deepseek-ai/dsh-llm'
import { TextRetainer } from '@deepseek-ai/dsh-output-retention'
import { assertNever } from '@deepseek-ai/dsh-util-values'
import { stringifyTagSafeJson } from './serialization.ts'
import type { ReferencedConversationItem } from './types.ts'

View file

@ -1,6 +1,7 @@
/** Canonical session URI and inline mention encoding. */
import { SessionId, type SessionId as SessionIdType } from '@deepseek-ai/dsh-session'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { SessionId as SessionIdType } from '@deepseek-ai/dsh-session'
import { SessionReferenceError } from './config.ts'
import type { SessionReferenceInput } from './types.ts'
@ -31,7 +32,7 @@ export function decodeSessionReferenceUri(uri: string): SessionIdType {
try {
const parsed: unknown = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'))
if (typeof parsed !== 'string') throw new TypeError('decoded session id is not a string')
const sessionId = SessionId(parsed)
const sessionId = brandString<SessionIdType>(parsed)
if (encodeSessionReferenceUri(sessionId) !== uri) throw new TypeError('URI is not canonical')
return sessionId
} catch (error: unknown) {

View file

@ -1,7 +1,7 @@
/** Browser-zone derivation and model-facing policy text for one open request turn. */
import { assertNever } from '@deepseek-ai/dsh-llm'
import type { UserMessage } from '@deepseek-ai/dsh-llm'
import { assertNever } from '@deepseek-ai/dsh-util-values'
const IANA_TIME_ZONE = /^[A-Za-z][A-Za-z0-9_+.-]*(?:\/[A-Za-z0-9_+.-]+)+$/

View file

@ -8,7 +8,7 @@ import { Context, Service } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import type { ModelSelection } from '@deepseek-ai/dsh-agent'
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm'
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
declare module '@deepseek-ai/cordis' {
interface Context {
@ -18,7 +18,7 @@ declare module '@deepseek-ai/cordis' {
}
/** Settings namespace carrying the default model selection for future Agents. */
export const AGENT_DEFAULT_MODEL_SETTINGS_NAMESPACE = settingsNamespace('agent-default-model')
export const AGENT_DEFAULT_MODEL_SETTINGS_NAMESPACE = 'agent-default-model'
/** Stored and composed default model selection. */
export interface AgentDefaultModelSettings {
@ -73,11 +73,13 @@ export class AgentDefaultModelConfig extends Service {
super(ctx, 'agentDefaultModel')
const entry: AgentDefaultModelSettings = { provider: config.provider, model: config.model }
this.source = () => entry
installSettingsSection(ctx, AGENT_DEFAULT_MODEL_SETTINGS_NAMESPACE, AGENT_DEFAULT_MODEL_SETTINGS_SCHEMA, entry, {
setSource: (current) => { this.source = current },
// Every consumer reads through currentSelection(), so no registration-level fact
// needs rebuilding when the settings document changes.
onChange: () => {},
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.installSection(ctx, AGENT_DEFAULT_MODEL_SETTINGS_NAMESPACE, AGENT_DEFAULT_MODEL_SETTINGS_SCHEMA, entry, {
setSource: (current) => { this.source = current },
// Every consumer reads through currentSelection(), so no registration-level fact
// needs rebuilding when the settings document changes.
onChange: () => {},
})
})
}

View file

@ -21,10 +21,10 @@ import {
BlockAssembler,
LlmError,
createAssistantMessage,
deepFreeze,
errorChain,
markAgentLoopRequest,
} from '@deepseek-ai/dsh-llm'
import { deepFreeze } from '@deepseek-ai/dsh-util-values'
import type { Scope } from '@deepseek-ai/dsh-scope'
import { createScope } from '@deepseek-ai/dsh-scope'
import type { EpochHeader, RequestContext, Session, SessionId, TurnEndReason, UserMessage } from '@deepseek-ai/dsh-session'

View file

@ -9,6 +9,7 @@ import { Context, FiberState, Service } from '@deepseek-ai/cordis'
import { randomUUID } from 'node:crypto'
import z from '@deepseek-ai/schemastery'
import { z as zod } from 'zod'
import { brandString } from '@deepseek-ai/dsh-brand'
import { emitAgentEvent } from '@deepseek-ai/dsh-agent'
import type {
Agent,
@ -22,9 +23,9 @@ import type {
TurnBoundaryProjection,
} from '@deepseek-ai/dsh-agent'
import { errorChain, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
import { SessionId, SessionPreparation } from '@deepseek-ai/dsh-session'
import type { Session, SessionHeader } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-settings'
import { SessionPreparation } from '@deepseek-ai/dsh-session'
import type { Session, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-session-projection'
@ -289,7 +290,7 @@ function applyLauncherIdentities(
}
/** Settings namespace carrying the tool-call parallelism a user owns. */
export const AGENT_LOOP_SETTINGS_NAMESPACE = settingsNamespace('agent-loop')
export const AGENT_LOOP_SETTINGS_NAMESPACE = 'agent-loop'
/**
* The agent-loop fields a user owns. Deliberately a strict subset of
@ -389,16 +390,18 @@ export class AgentLoop extends Service implements AgentFactory {
return source().maxParallelToolCalls
},
}
installSettingsSection(ctx, AGENT_LOOP_SETTINGS_NAMESPACE, AGENT_LOOP_SETTINGS_SCHEMA, entry, {
// The schema admits any integer above zero; `resolveMaxParallelToolCalls`
// owns the whole rule, so refusing here keeps the running scheduler on
// its last good cap instead of failing at the next tool group.
validate: value => void resolveMaxParallelToolCalls(value.maxParallelToolCalls),
setSource: (current) => {
source = current
},
// Nothing is derived from the cap: the getter above is the only reader.
onChange: () => {},
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.installSection(ctx, AGENT_LOOP_SETTINGS_NAMESPACE, AGENT_LOOP_SETTINGS_SCHEMA, entry, {
// The schema admits any integer above zero; `resolveMaxParallelToolCalls`
// owns the whole rule, so refusing here keeps the running scheduler on
// its last good cap instead of failing at the next tool group.
validate: value => void resolveMaxParallelToolCalls(value.maxParallelToolCalls),
setSource: (current) => {
source = current
},
// Nothing is derived from the cap: the getter above is the only reader.
onChange: () => {},
})
})
validateConfiguredAgents(this.config.agents)
// Register only after every config validation above has passed, so a
@ -415,7 +418,7 @@ export class AgentLoop extends Service implements AgentFactory {
for (const { id, sessionId, cwd, resumeSessionId, ...options } of this.config.agents) {
const meta = cwd === undefined ? {} : { cwd }
if (resumeSessionId === undefined || resumeSessionId === '') {
const configuredId = sessionId ?? SessionId(`${id}-session-${randomUUID()}`)
const configuredId = sessionId ?? brandString<SessionId>(`${id}-session-${randomUUID()}`)
const persistence = sessionId === undefined ? undefined : ctx.get('sessionPersistence')
if (persistence === undefined) {
this.create(configuredId, options, meta)

View file

@ -12,9 +12,10 @@
*/
import type { Context } from '@deepseek-ai/cordis'
import { assertNever, createToolResultMessage, type ToolCallBlock } from '@deepseek-ai/dsh-llm'
import { createToolResultMessage, type ToolCallBlock } from '@deepseek-ai/dsh-llm'
import type { Session, UserMessage } from '@deepseek-ai/dsh-session'
import { TOOL_ABORTED_BEFORE_DISPATCH, TOOL_RUNTIME_SCHEDULER, type ToolExecutionInput, type ToolExecutionMode, type ToolExecutionResult, type ToolRunContext } from '@deepseek-ai/dsh-tools'
import { assertNever } from '@deepseek-ai/dsh-util-values'
/** One tool call after argument parsing, ready to schedule. */
interface PlannedCall {

View file

@ -1,6 +1,6 @@
import { runInNewContext } from 'node:vm'
import { describe, expect, it } from 'vitest'
import { isJsonValue, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-session'
import { isJsonValue, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
function objectWithForgedIntrinsicPrototype(revoked = false): Record<string, unknown> {
const prototype = Object.create(null) as Record<string, unknown>

View file

@ -9,11 +9,11 @@ import z from '@deepseek-ai/schemastery'
import { AnonymousEntries, NamedEntries, ScopedLayers, scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope'
import type { ScopeKey, ScopeLayer, Scoped } from '@deepseek-ai/dsh-scope'
import type { ToolCallId, ContentBlock, ToolSchema } from '@deepseek-ai/dsh-llm'
import { assertNever, deepFreeze, HarnessError } from '@deepseek-ai/dsh-llm'
import { HarnessError } from '@deepseek-ai/dsh-llm'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue, UserMessage } from '@deepseek-ai/dsh-session'
import { FIRST_PARTY_SECTION_ORDER, type ToolProviderResult } from '@deepseek-ai/dsh-system-prompt'
import type { UserMessage } from '@deepseek-ai/dsh-session'
import { assertNever, deepFreeze, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
import type { ToolProviderResult } from '@deepseek-ai/dsh-system-prompt'
import type { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
// Type-only: makes `ctx.get('approval')` resolve to the ApprovalService
// augmentation. The seam stays optional at runtime — see `serviceAsk`.
@ -21,7 +21,7 @@ import type {} from '@deepseek-ai/dsh-user-approval'
import type { ToolCallView, ToolResultView } from './presentation.ts'
import { assertSupportedJsonSchema, validateJsonSchemaValue } from './json-schema.ts'
import type { JsonSchemaNode } from './json-schema.ts'
import { createRunCodeTool, RUN_CODE_NAME, SDK_SECTION_ORDER } from './ptc.ts'
import { createRunCodeTool, RUN_CODE_NAME } from './ptc.ts'
import type { CodeSdkLanguage } from './ptc.ts'
import { renderToolsSdk } from './ts-types.ts'
import type { ToolSdkSchema } from './ts-types.ts'
@ -43,13 +43,6 @@ import { renderToolsSdkPy } from './py-types.ts'
* with its zh pair, plus this package's own README pair and the
* {@link Config.mode} JSDoc.
*/
/**
* Prompt order of the `ptc` collapse statement: after the persona and before
* per-tool guidance, so the model reads which tools it may call before it
* reads what each one is for.
*/
const COLLAPSE_SECTION_ORDER = FIRST_PARTY_SECTION_ORDER.PTC_ONLY
/**
* The model-facing statement of the `ptc` collapse. Names the consequence
* (the call fails) and the route (inside the program), because a rule the
@ -98,7 +91,6 @@ export {
type JsonSchemaScalar,
} from './json-schema.ts'
export type { JsonValue } from '@deepseek-ai/dsh-session'
export type { PtcDispatchEventData, PtcDispatchStartEventData } from './types.ts'
export { CodeRunFailedError, RUN_CODE_NAME } from './ptc.ts'
@ -846,8 +838,8 @@ export class ToolRuntime extends Service {
* Without this the model reads a catalog of tools it is told to use and no
* statement that only `run_code` may be called, so it emits a native call,
* receives `UNKNOWN_TOOL` for a tool the prompt just declared, and concludes
* the deployment is inconsistent. {@link COLLAPSE_SECTION_ORDER} places the rule
* before that guidance rather than after it.
* the deployment is inconsistent. Its order places the rule before that
* guidance rather than after it.
*
* `both` renders empty: native calls do execute there, so the rule is false.
* @returns the section registration.
@ -855,7 +847,7 @@ export class ToolRuntime extends Service {
private collapseSection(): { name: string; order: number; text: (context: { scope?: ScopeKey }) => string } {
return {
name: 'tools:ptc-only',
order: COLLAPSE_SECTION_ORDER,
order: this.ctx.systemPrompt.getSectionOrder('PTC_ONLY'),
// The SAME predicate the executor denies by, so the prompt cannot state
// a rule the registry does not enforce (see `collapses`).
text: context => this.modeFor(context.scope) === 'ptc' ? PTC_ONLY_INSTRUCTION : '',
@ -875,7 +867,7 @@ export class ToolRuntime extends Service {
private sdkSection(): { name: string; order: number; text: (context: { scope?: ScopeKey }) => string } {
return {
name: 'tools:sdk',
order: SDK_SECTION_ORDER,
order: this.ctx.systemPrompt.getSectionOrder('TOOLS_SDK'),
// Regenerate from the calling scope's visible tools in stable order.
text: (context) => {
const mode = this.modeFor(context.scope)

View file

@ -11,8 +11,8 @@
* @module dsh-tools/json-schema
*/
import { assertNever, HarnessError } from '@deepseek-ai/dsh-llm'
import { isJsonValue, type JsonValue } from '@deepseek-ai/dsh-session'
import { HarnessError } from '@deepseek-ai/dsh-llm'
import { assertNever, isJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
/** Scalar JSON values supported by `enum` and `const`. */
export type JsonSchemaScalar = string | number | boolean | null

View file

@ -6,12 +6,11 @@
* @module @deepseek-ai/dsh-tools/src/ptc
*/
import { ToolCallId, createUserMessage, HarnessError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import { brandString } from '@deepseek-ai/dsh-brand'
import { createUserMessage, HarnessError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, ToolCallId } from '@deepseek-ai/dsh-llm'
import type { CodeBindingFunction, CodeRunResult, CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import { snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
import { defineTool, parameterSchemaSpecToJsonSchema } from './schema.ts'
import { TOOL_RUNTIME_SCHEDULER } from './index.ts'
import type { PtcDispatchLog, ToolDefinition, ToolExecutionResult, ToolRuntime, ToolRunContext } from './index.ts'
@ -20,9 +19,6 @@ import type {} from './types.ts'
/** The model-facing name of the PTC mode tool. */
export const RUN_CODE_NAME = 'run_code'
/** The `tools:sdk` section order, after per-tool guidance sections. */
export const SDK_SECTION_ORDER = FIRST_PARTY_SECTION_ORDER.TOOLS_SDK
/**
* The language-specific `run_code` schema text: the tool `description` and its
* `code` parameter description, kept together so a language's two model-facing
@ -470,7 +466,7 @@ export function createRunCodeTool(registry: ToolRuntime, options: RunCodeBridgeO
}
const normalized = jsonNormalizeArgs(rawArgs)
const n = ++dispatches
const subCallId = ToolCallId(`${String(exec.callId)}:code:${n}`)
const subCallId = brandString<ToolCallId>(`${String(exec.callId)}:code:${n}`)
const input = {
callId: subCallId,
rootCallId: exec.rootCallId,

View file

@ -2,7 +2,7 @@
import { HarnessError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import type { ToolDefinition, ToolExecution, ToolExecutionResult, ToolRunContext, ToolResult } from './index.ts'
import { assertSupportedJsonSchema, isJsonSchemaRecord, isPlainJsonArray, JsonSchemaError, validateJsonSchemaValue } from './json-schema.ts'
import type { JsonSchemaNode, JsonSchemaScalar, ObjectJsonSchema } from './json-schema.ts'

View file

@ -1,7 +1,7 @@
/** Canonical tool-definition fixtures for repository tests. @module dsh-tools/testing */
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import { defineTool } from './schema.ts'
import type { DefineToolOptions, ParameterSchemaSpec } from './schema.ts'
import type { ToolDefinition, ToolRunContext } from './index.ts'

View file

@ -7,7 +7,7 @@
import { describe, expect, it } from 'vitest'
import fc from 'fast-check'
import { isJsonValue } from '@deepseek-ai/dsh-session'
import { isJsonValue } from '@deepseek-ai/dsh-util-values'
import { parameterSchemaSpecToJsonSchema, validateArgs } from '@deepseek-ai/dsh-tools'
import type { ParameterPropertySpec, ParameterSchemaSpec, ValueSchemaSpec } from '@deepseek-ai/dsh-tools'

View file

@ -3,14 +3,15 @@ import { Context } from '@deepseek-ai/cordis'
import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
import { createScope } from '@deepseek-ai/dsh-scope'
import type { Scope } from '@deepseek-ai/dsh-scope'
import SystemPrompt, { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
import ToolRuntime, { CodeRunFailedError, RUN_CODE_NAME, TOOL_ABORTED_BEFORE_DISPATCH, defineContentToolFixture, defineTool } from '@deepseek-ai/dsh-tools'
import type { Config, JsonSchemaNode, PostToolDecision, ToolExecutionResult } from '@deepseek-ai/dsh-tools'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { Session, SessionId } from '@deepseek-ai/dsh-session'
import type { JsonValue, SessionEventMap } from '@deepseek-ai/dsh-session'
import type { SessionEventMap } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
const testToolSignal = new AbortController().signal
@ -143,7 +144,7 @@ describe('mode-aware wire contribution', () => {
// saying how it is reached.
ctx.systemPrompt.section({
name: 'tool:echo',
order: FIRST_PARTY_SECTION_ORDER.TOOL_READ,
order: ctx.systemPrompt.getSectionOrder('TOOL_READ'),
text: 'Use the echo tool.',
})
@ -213,7 +214,7 @@ describe('mode-aware wire contribution', () => {
const { scope, agent } = await mintAgentScope(ctx)
scope.ctx.systemPrompt.section({
name: 'tools:sdk',
order: FIRST_PARTY_SECTION_ORDER.TOOLS_SDK,
order: scope.ctx.systemPrompt.getSectionOrder('TOOLS_SDK'),
text: 'SCOPED SDK',
})
@ -301,7 +302,7 @@ describe('mode-aware wire contribution', () => {
expect(() => scope.ctx.tools.restrict({ deny: [RUN_CODE_NAME] })).toThrow(/cannot name reserved PTC mode presentation transport/)
scope.ctx.systemPrompt.section({
name: 'scoped-note',
order: FIRST_PARTY_SECTION_ORDER.TOOLS_SDK - 10,
order: scope.ctx.systemPrompt.getSectionOrder('TOOLS_SDK') - 10,
text: 'safe note',
})
scope.ctx.tools.register(defineContentToolFixture({

View file

@ -5,10 +5,10 @@ import {
valueSchemaSpecToJsonSchema,
type InferArgs,
type InferValue,
type JsonValue,
type ParameterSchemaSpec,
type ValueSchemaSpec,
} from '../src/index.ts'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
describe('the unified author schema DSL', () => {
it('compiles every value root and the author-only json node', () => {

View file

@ -10,9 +10,10 @@ import ApprovalService, { type ApprovalOutcome, type ApprovalRequest } from '@de
import ToolRuntime, {
defineContentToolFixture, defineTool, JsonSchemaError, parameterSchemaSpecToJsonSchema, validateArgs, ToolArgsError, ToolNotFoundError,
TOOL_ABORTED, TOOL_ABORTED_BEFORE_DISPATCH,
type InferArgs, type JsonValue, type ParameterSchemaSpec, type PreToolDecision, type PostToolDecision,
type InferArgs, type ParameterSchemaSpec, type PreToolDecision, type PostToolDecision,
type JsonSchemaNode, type ToolDefinition, type ToolDispatchExecution, type ToolExecutionResult, type ToolExecutionToken,
} from '@deepseek-ai/dsh-tools'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
const testToolSignal = new AbortController().signal

View file

@ -9,6 +9,7 @@
*/
import { Context, Service } from '@deepseek-ai/cordis'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { CredentialInfo, CredentialKey, CredentialRecord, CredentialRef } from './types.ts'
export type {
@ -29,7 +30,7 @@ export function credentialRef(value: string): CredentialRef {
if (!isCredentialRefName(value)) {
throw new TypeError(`credential ref "${value}" must match ${String(REF_PATTERN)}`)
}
return value as CredentialRef
return brandString<CredentialRef>(value)
}
/**
@ -71,7 +72,7 @@ export function credentialKey(scope: string, id: string): CredentialKey {
throw new TypeError(`credential key segment "${segment}" must match ${String(KEY_SEGMENT_PATTERN)}`)
}
}
return `${scope}/${id}` as CredentialKey
return brandString<CredentialKey>(`${scope}/${id}`)
}
/**

View file

@ -2,11 +2,11 @@
import { randomUUID } from 'node:crypto'
import type { Context } from '@deepseek-ai/cordis'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
import type { Session, SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
import { errorMessage, TeamError } from './error.ts'
import type { TeamJournal } from './journal.ts'
import type { TeamRuntimeLifecycle } from './lifecycle.ts'
@ -68,7 +68,7 @@ export class TeamMailbox {
if (this.lifecycle.disposed || event.type !== 'user/message' || event.data.source.kind !== 'team-message') return
const source = event.data.source
const acknowledgement = Promise.resolve().then(async () => {
const root = this.ctx.agents.get(SessionId(source.teamId))
const root = this.ctx.agents.get(brandString<SessionId>(source.teamId))
if (root !== undefined) await this.checkpointDelivered(root, session, source.messageId)
}).catch((error: unknown) => {
this.ctx.logger.warn(`Team message "${source.messageId}" acknowledgement failed: ${errorMessage(error)}`)

View file

@ -1,9 +1,9 @@
/** Host-only Team state projected incrementally from committed Session events. */
import { z } from 'zod'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session'
import type { SessionEvent, SessionEventMap, SessionId } from '@deepseek-ai/dsh-session'
import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection'
import type {
TeamId,
@ -21,7 +21,7 @@ import { assertTaskGraphCandidate } from './task-graph.ts'
const nonNegativeSafeInteger = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)
const positiveSafeInteger = nonNegativeSafeInteger.min(1)
const sessionIdSchema = z.string().min(1).transform(value => SessionId(value))
const sessionIdSchema = z.string().min(1).transform(value => brandString<SessionId>(value))
const teamIdSchema = z.string().min(1).transform(value => toTeamId(value))
const numericTaskIdPattern = /^task-(\d+)$/u
const teamTaskIdSchema = z.string().min(1).refine((value) => {

View file

@ -2,9 +2,10 @@
import { randomUUID } from 'node:crypto'
import type { Context } from '@deepseek-ai/cordis'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { MessageId } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionId } from '@deepseek-ai/dsh-session'
import { foldSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
import type { ContinuableStart } from '@deepseek-ai/dsh-subagent'
import { errorMessage, TeamError } from './error.ts'
@ -254,7 +255,7 @@ export class TeamRoster {
const root = membership.root
const name = this.memberName(request.name)
const description = requiredText(request.description, 'description', 200)
const childId = SessionId(randomUUID())
const childId = brandString<SessionId>(randomUUID())
const member: TeamMemberSnapshot = {
id: childId,
name,

View file

@ -5,7 +5,6 @@ import z from '@deepseek-ai/schemastery'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { TeamTaskId } from '@deepseek-ai/dsh-experimental-agent-team'
import type { TeamMemberView } from '@deepseek-ai/dsh-experimental-agent-team'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { InferValue, ValueSchemaSpec } from '@deepseek-ai/dsh-tools'
@ -164,7 +163,7 @@ function install(agent: Agent, ctx: Context, config: Required<Config>): () => vo
try {
register(scoped.systemPrompt.section({
name: 'team:policy',
order: FIRST_PARTY_SECTION_ORDER.TEAM_POLICY,
order: scoped.systemPrompt.getSectionOrder('TEAM_POLICY'),
text: () => {
const membership = ctx.agentTeams.membership(agent)
return `${POLICY}\n\nYour Team role is ${membership.role}; your Team name is ${membership.name}; Team id is ${membership.id}.`

View file

@ -12,11 +12,12 @@
import type { Context } from '@deepseek-ai/cordis'
import type {
ApprovalRequestId, CordisDynamicPluginId, DynamicCordisInvokeResult, JsonValue,
ApprovalRequestId, CordisDynamicPluginId, DynamicCordisInvokeResult,
DynamicCordisInventoryRow,
} from '@deepseek-ai/dsh-api-remotes/client'
import type { ClientModuleSystem } from '@deepseek-ai/dsh-client-modules/client'
import type { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
// The Client Remote assembly is the one place the two planes meet: it mounts the
// `dynamicCordisRunner` namespace and re-exports its payload vocabulary, so this
// package names what it sends without importing a Host package.

View file

@ -3,8 +3,9 @@
import type { Context } from '@deepseek-ai/cordis'
import type {
CordisInspectProviderManifest, CordisInspectQueryRequest, CordisInspectQueryResolution,
CordisInspectRequestId, JsonValue, SessionId,
CordisInspectRequestId, SessionId,
} from '@deepseek-ai/dsh-api-remotes/client'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
/** Context supplied to a Client inspect provider query. */
export interface ClientCordisInspectQueryContext {

View file

@ -1,7 +1,7 @@
/** Built-in Client inspect providers over live Client-owned services. */
import type { Context } from '@deepseek-ai/cordis'
import type { JsonValue } from '@deepseek-ai/dsh-api-remotes/client'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import type { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client'
import type {} from '@deepseek-ai/dsh-client-ui-theme/client'
import { queryEventApi, queryServiceApi } from './api-catalog.ts'

View file

@ -19,7 +19,7 @@ import { scopeOf } from '@deepseek-ai/dsh-scope'
import { assertSupportedJsonSchema, defineTool } from '@deepseek-ai/dsh-tools'
import type { ToolDefinition } from '@deepseek-ai/dsh-tools'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
const DYNAMIC_TOOL = Symbol('cordis-host-runner.dynamic-tool')
const SCHEMA_TYPES = new Set<unknown>(['string', 'number', 'integer', 'boolean', 'null', 'object', 'array', 'json'])

View file

@ -9,8 +9,8 @@ import type { Fiber } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { JsonValue } from '@deepseek-ai/dsh-session/types'
import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import { isPlugin, normalizeHandler } from './guard.ts'
import { CordisInspectRegistryService } from './inspect-registry.ts'
import { missingServices, startHostHalf } from './lifecycle.ts'

View file

@ -3,8 +3,7 @@
import { Service } from '@deepseek-ai/cordis'
import type { Context } from '@deepseek-ai/cordis'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-session/types'
import { snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
import { assertSupportedJsonSchema, validateJsonSchemaValue } from '@deepseek-ai/dsh-tools'
import type { JsonSchemaNode } from '@deepseek-ai/dsh-tools'
import type {

View file

@ -4,7 +4,8 @@
*/
import type { Branded } from '@deepseek-ai/dsh-brand'
import type { JsonValue, SessionId } from '@deepseek-ai/dsh-session/types'
import type { SessionId } from '@deepseek-ai/dsh-session/types'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
/** Stable identity of one dynamic plugin instance. */
export type CordisDynamicPluginId = Branded<'CordisDynamicPluginId'>

View file

@ -1914,10 +1914,17 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
returns: 'the absolute local document path, or undefined for non-file storage.',
},
{
signature: 'register<T>(ns: SettingsNamespace, schema: z<T>, options?: SettingsRegisterOptions<T>): SettingsScope<T>',
signature: 'register<const Namespace extends string, T>( ns: Namespace & SettingsNamespaceInput<Namespace>, schema: z<T>, options?: SettingsRegisterOptions<T>, ): SettingsScope<T>',
description: 'Register a namespace schema and receive its owner scope. The registration is an effect on the calling plugin\'s fiber: disposing that fiber removes the namespace and its observers. An invalid stored section fails the registration itself — the earliest point where the schema can judge it.',
parameters: [{ name: 'ns', description: 'unique namespace; duplicate registration fails loud.' }, { name: 'schema', description: 'schemastery schema resolving this namespace\'s value.' }, { name: 'options', description: 'composition `base` layer and effect timing.' }],
returns: 'the owner scope for reads, observation, and updates.',
throws: ['{TypeError} when `ns` is not a lowercase hyphenated identifier.'],
},
{
signature: 'installSection<const Namespace extends string, T>( owner: Context, ns: Namespace & SettingsNamespaceInput<Namespace>, schema: z<T>, entry: T, hooks: SettingsSectionHooks<T>, ): void',
description: 'Attach one optional-settings consumer to this provider. The consumer registers its composition entry as the base layer while this provider is present, then falls back to that entry if the provider detaches.',
parameters: [{ name: 'owner', description: 'consumer context whose unload suppresses fallback work.' }, { name: 'ns', description: 'consumer-owned settings namespace.' }, { name: 'schema', description: 'schema resolving the namespace.' }, { name: 'entry', description: 'composition entry used as the base and fallback value.' }, { name: 'hooks', description: 'source sink, change notification, and optional validation.' }],
throws: ['{TypeError} when `ns` is not a lowercase hyphenated identifier.'],
},
{
signature: 'describe(options?: SettingsDescribeOptions): SettingsDescriptor[]',
@ -1926,25 +1933,29 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
returns: 'one descriptor per registered namespace, in registration order.',
},
{
signature: 'get(ns: SettingsNamespace): unknown',
signature: 'get<const Namespace extends string>(ns: Namespace & SettingsNamespaceInput<Namespace>): unknown',
description: 'Read one registered namespace\'s resolved value.',
parameters: [{ name: 'ns', description: 'the namespace to read.' }],
returns: 'the resolved value, or `undefined` while unregistered.',
throws: ['{TypeError} when `ns` is not a lowercase hyphenated identifier.'],
},
{
signature: 'async update(ns: SettingsNamespace, patch: object, expectedRevision?: number): Promise<void>',
signature: 'async update<const Namespace extends string>( ns: Namespace & SettingsNamespaceInput<Namespace>, patch: object, expectedRevision?: number, ): Promise<void>',
description: 'Merge a patch into one registered namespace\'s user layer, validate the resolved candidate, persist through the provider, then commit and emit. A validation failure rejects before anything is persisted. Writes to one namespace are serialized: concurrent updates apply in call order, each merging over the previous write\'s committed section.',
parameters: [{ name: 'ns', description: 'the registered namespace to update.' }, { name: 'patch', description: 'plain-object patch over the user section.' }, { name: 'expectedRevision', description: 'the descriptor `revision` the caller read; a namespace that moved past it rejects with {@link SettingsConflictError}.' }],
throws: ['{TypeError} when `ns` is not a lowercase hyphenated identifier.'],
},
{
signature: 'async replace(ns: SettingsNamespace, section: object, expectedRevision?: number): Promise<void>',
signature: 'async replace<const Namespace extends string>( ns: Namespace & SettingsNamespaceInput<Namespace>, section: object, expectedRevision?: number, ): Promise<void>',
description: 'Replace one registered namespace\'s user section wholesale, validate, persist, then commit and emit. Keys absent from `section` fall back to the composition `base` and schema defaults — this is the removal/reset path a merge-only patch cannot express (`replace({})` re-inherits everything).',
parameters: [{ name: 'ns', description: 'the registered namespace to replace.' }, { name: 'section', description: 'the complete next user section.' }, { name: 'expectedRevision', description: 'the descriptor `revision` the caller read; a namespace that moved past it rejects with {@link SettingsConflictError}.' }],
throws: ['{TypeError} when `ns` is not a lowercase hyphenated identifier.'],
},
{
signature: 'async mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[], expectedRevision?: number): Promise<void>',
signature: 'async mutate<const Namespace extends string>( ns: Namespace & SettingsNamespaceInput<Namespace>, ops: readonly SettingsPathOp[], expectedRevision?: number, ): Promise<void>',
description: 'Apply path-addressed edits to one registered namespace\'s user section, validate, persist, then commit and emit. The ops are applied to the section as it stands when the write reaches the front of the queue, so a caller never has to restate fields it did not touch — and, crucially, cannot delete fields it never saw. This is the write path for any caller holding a redacted view; `replace` remains the wholesale reset.',
parameters: [{ name: 'ns', description: 'the registered namespace to edit.' }, { name: 'ops', description: 'ordered path edits; later ops observe earlier ones.' }, { name: 'expectedRevision', description: 'the descriptor `revision` the caller read; a namespace that moved past it rejects with {@link SettingsConflictError}.' }],
throws: ['{TypeError} when `ns` is not a lowercase hyphenated identifier.'],
},
],
},
@ -2314,6 +2325,18 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
parameters: [{ name: 'section', description: 'the section to register.' }],
returns: 'the exact Cordis effect disposer.',
},
{
signature: 'getSectionOrder(name: PromptSectionOrderName): number',
description: 'Resolve the centrally owned placement of a repository prompt section.',
parameters: [{ name: 'name', description: 'stable section placement name.' }],
returns: 'the section\'s numeric sort order.',
},
{
signature: 'getContextOrder(name: PromptContextOrderName): number',
description: 'Resolve the centrally owned placement of a repository runtime context.',
parameters: [{ name: 'name', description: 'stable context placement name.' }],
returns: 'the context\'s numeric sort order.',
},
{
signature: 'context(context: PromptContext): () => void',
description: 'Register ordered dynamic context in the calling context\'s scope. Scoped entries shadow global entries with the same name.',
@ -4566,10 +4589,18 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'PromptContext',
declaration: 'export interface PromptContext {\n readonly name: string;\n readonly order: number;\n readonly text: string | ((context: AssembleContext) => string);\n}',
},
{
name: 'PromptContextOrderName',
declaration: 'export type PromptContextOrderName = keyof typeof CONTEXT_ORDERS;',
},
{
name: 'PromptSection',
declaration: 'export interface PromptSection {\n readonly name: string;\n readonly order: number;\n readonly text: string | ((context: AssembleContext) => string);\n readonly complete?: boolean;\n}',
},
{
name: 'PromptSectionOrderName',
declaration: 'export type PromptSectionOrderName = keyof typeof SECTION_ORDERS;',
},
{
name: 'ProviderRequestId',
declaration: 'export type ProviderRequestId = Branded<\'ProviderRequestId\'>;',
@ -5214,6 +5245,10 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'SettingsSecretView',
declaration: 'export interface SettingsSecretView {\n path: string[];\n set: boolean;\n}',
},
{
name: 'SettingsSectionHooks',
declaration: 'export interface SettingsSectionHooks<T> {\n setSource(current: () => T): void;\n onChange(): void;\n validate?: (value: T) => void;\n}',
},
{
name: 'SettingsUpdateSource',
declaration: 'export type SettingsUpdateSource = \'update\' | \'provider\';',
@ -5520,7 +5555,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'SystemPrompt',
declaration: 'export class SystemPrompt extends Service {\n static Config: z<Config>;\n constructor(ctx: Context, config: Config);\n section(section: PromptSection): () => void;\n context(context: PromptContext): () => void;\n suppressRuntimeContext(): () => void;\n tools(provider: (context: AssembleContext) => ToolProviderResult): () => void;\n variable(name: string, provider: (context: AssembleContext) => string | undefined): () => void;\n async assemble(context: AssembleContext = {}): Promise<PromptAssembly>;\n}',
declaration: 'export class SystemPrompt extends Service {\n static Config: z<Config>;\n constructor(ctx: Context, config: Config);\n section(section: PromptSection): () => void;\n getSectionOrder(name: PromptSectionOrderName): number;\n getContextOrder(name: PromptContextOrderName): number;\n context(context: PromptContext): () => void;\n suppressRuntimeContext(): () => void;\n tools(provider: (context: AssembleContext) => ToolProviderResult): () => void;\n variable(name: string, provider: (context: AssembleContext) => string | undefined): () => void;\n async assemble(context: AssembleContext = {}): Promise<PromptAssembly>;\n}',
},
{
name: 'TableKeyOf',

View file

@ -10,11 +10,10 @@ import {
} from '@deepseek-ai/dsh-cordis-host-runner'
import type { DynamicCordisReference } from '@deepseek-ai/dsh-cordis-host-runner'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import type { UserMessage } from '@deepseek-ai/dsh-session'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { ToolExecution } from '@deepseek-ai/dsh-tools'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import { missingServices, providedServices } from './inspect.ts'
import {
presentDefineCall, presentInspectListCall, presentInspectQueryCall, presentInspectSelfCall, presentRunCall,
@ -35,7 +34,7 @@ function requireAgent(exec: ToolExecution): Agent {
export function apply(ctx: Context): void {
ctx.systemPrompt.section({
name: 'tool:cordis',
order: FIRST_PARTY_SECTION_ORDER.TOOL_CORDIS,
order: ctx.systemPrompt.getSectionOrder('TOOL_CORDIS'),
text: CORDIS_SYSTEM_PROMPT,
})
for (const provider of hostInspectProviders(ctx)) {

View file

@ -3,7 +3,7 @@
import type { Context } from '@deepseek-ai/cordis'
import { HOST_BUILTIN_INSPECTION } from '@deepseek-ai/dsh-cordis-host-runner'
import type { HostCordisInspectProviderRegistration } from '@deepseek-ai/dsh-cordis-host-runner'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import { EVENT_API, queryEventApi, queryServiceApi } from './api-catalog.ts'
const EMPTY_INPUT = { type: 'object', properties: {}, additionalProperties: false } as const

View file

@ -1,7 +1,8 @@
/** Shared top-level-call post-policy selection for search result spill. @module dsh-tool-fs-search/direct-call */
import type { Context } from '@deepseek-ai/cordis'
import type { JsonValue, PostToolDecision, ToolDefinition, ToolExecution, ToolExecutionResult } from '@deepseek-ai/dsh-tools'
import type { PostToolDecision, ToolDefinition, ToolExecution, ToolExecutionResult } from '@deepseek-ai/dsh-tools'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
/**
* Return the accepted canonical value only when this tool still owns a direct

View file

@ -14,7 +14,6 @@ import { sep } from 'node:path'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { GenericCallView, SearchResultView, ToolResult } from '@deepseek-ai/dsh-tools'
import type { SpillRef } from '@deepseek-ai/dsh-spill'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import { runRipgrep, toWorkdirRelative, trySaveFormattedResult } from './search-core.ts'
import { globSearchMeta, searchViewFromMeta } from './presentation.ts'
import { acceptedDirectCallValue } from './direct-call.ts'
@ -300,7 +299,7 @@ export function applyGlobTool(ctx: Context, caps: GlobToolCaps): void {
: 'while a larger one keeps the modification-time-ordered head.'
ctx.systemPrompt.section({
name: 'tool:glob',
order: FIRST_PARTY_SECTION_ORDER.TOOL_GLOB,
order: ctx.systemPrompt.getSectionOrder('TOOL_GLOB'),
text: 'Use the glob tool — not shell find — to discover files by path pattern. A pattern with no "/" matches basenames at any depth, so "*" matches every file in the tree rather than its top level. '
+ `Results are files only, never directories, and include hidden and ignored files: a result that fits comes back in modification-time order, ${overCapGuidance}`,
})

View file

@ -16,7 +16,6 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
import type { GenericCallView, SearchResultView, ToolResult } from '@deepseek-ai/dsh-tools'
import type { RetainedItems } from '@deepseek-ai/dsh-output-retention'
import type { SpillRef } from '@deepseek-ai/dsh-spill'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import type { GrepMatch } from './search-core.ts'
import { SearchError, previewLine, retainGrepMatches, runRipgrep, toWorkdirRelative, trySaveFormattedResult } from './search-core.ts'
import { grepSearchMeta, searchViewFromMeta } from './presentation.ts'
@ -275,7 +274,7 @@ export function presentGrepResult(
export function applyGrepTool(ctx: Context, caps: GrepToolCaps): void {
ctx.systemPrompt.section({
name: 'tool:grep',
order: FIRST_PARTY_SECTION_ORDER.TOOL_GREP,
order: ctx.systemPrompt.getSectionOrder('TOOL_GREP'),
text: 'Use the grep tool — not shell grep or rg — to search file contents. Use read on a matched file when you need surrounding context.',
})

View file

@ -9,7 +9,7 @@
*/
import { describe, expect, it } from 'vitest'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import {
globSearchMeta,
grepSearchMeta,

View file

@ -9,7 +9,6 @@ import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { DiffCallView, DiffResultView, ToolResult } from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-fs'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import { computeHunkDiffs, diffsFromMeta } from './diff.ts'
import { remediateFsError } from './error.ts'
import { sessionResolveOptions } from './session-cwd.ts'
@ -76,7 +75,7 @@ export function formatEditOutput(displayPath: string, replaceAll: boolean): stri
export function applyEditTool(ctx: Context, sandbox: FsSandboxController): void {
ctx.systemPrompt.section({
name: 'tool:edit',
order: FIRST_PARTY_SECTION_ORDER.TOOL_EDIT,
order: ctx.systemPrompt.getSectionOrder('TOOL_EDIT'),
text: 'Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-observation-policy requires it), unless you just created or edited it in this session.',
})

View file

@ -8,7 +8,6 @@ import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { GenericCallView, ReadResultView, ToolResult } from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-fs'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import { buildWindow, formatReadOutput, langFromPath, readMetaFromMeta } from './read-render.ts'
import { resolveRegularReadTarget } from './read-target.ts'
@ -69,7 +68,7 @@ export function parseReadArgs(args: { file_path: string; offset?: number; limit?
export function applyReadTool(ctx: Context, caps: ReadToolCaps): void {
ctx.systemPrompt.section({
name: 'tool:read',
order: FIRST_PARTY_SECTION_ORDER.TOOL_READ,
order: ctx.systemPrompt.getSectionOrder('TOOL_READ'),
text: 'Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.',
})

View file

@ -10,7 +10,6 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
import type { DiffCallView, DiffResultView, ToolResult } from '@deepseek-ai/dsh-tools'
import type { FsWriteOutcome } from '@deepseek-ai/dsh-fs'
import type {} from '@deepseek-ai/dsh-fs'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import { computeHunkDiffs, diffsFromMeta } from './diff.ts'
import { remediateFsError } from './error.ts'
import { sessionResolveOptions } from './session-cwd.ts'
@ -62,7 +61,7 @@ interface WriteToolArgs {
export function applyWriteTool(ctx: Context, sandbox: FsSandboxController): void {
ctx.systemPrompt.section({
name: 'tool:write',
order: FIRST_PARTY_SECTION_ORDER.TOOL_WRITE,
order: ctx.systemPrompt.getSectionOrder('TOOL_WRITE'),
text: 'Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-observation-policy requires it) and prefer edit for targeted changes.',
})

View file

@ -7,7 +7,7 @@
import { describe, expect, it } from 'vitest'
import { computeHunkDiffs, diffsFromMeta, DIFF_CONTEXT } from '../src/diff.ts'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
const lines = (n: number): string => Array.from({ length: n }, (_, i) => `line${i + 1}`).join('\n') + '\n'

View file

@ -11,7 +11,6 @@ import type { GoalRef, GoalView } from '@deepseek-ai/dsh-goal'
import { boundContextSummary, createUserMessage, HarnessError } from '@deepseek-ai/dsh-llm'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { GenericCallView } from '@deepseek-ai/dsh-tools'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import {
completionAuthority,
goalToolExecution,
@ -188,7 +187,7 @@ export function apply(ctx: Context, config: Config): void {
const resolved = resolveConfig(config)
ctx.systemPrompt.section({
name: 'tool:goal',
order: FIRST_PARTY_SECTION_ORDER.TOOL_GOAL,
order: ctx.systemPrompt.getSectionOrder('TOOL_GOAL'),
text: guidance(resolved.blockedAfterConsecutiveRounds),
})

View file

@ -21,7 +21,7 @@ import { SANDBOX_MODES, setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy'
import type {} from '@deepseek-ai/dsh-shell'
import type { ApprovalPolicy } from '@deepseek-ai/dsh-user-approval'
import { APPROVAL_POLICIES, setApprovalPolicy } from '@deepseek-ai/dsh-user-approval'
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
// Type-only: resolves the optional projection and command children.
import type {} from '@deepseek-ai/dsh-session-projection'
import type {} from '@deepseek-ai/dsh-commands'
@ -73,7 +73,7 @@ export interface PresetSpec {
export const CUSTOM_PRESET = 'custom'
/** Settings namespace carrying the default for future sessions. */
export const PERMISSION_SETTINGS_NAMESPACE = settingsNamespace('permission')
export const PERMISSION_SETTINGS_NAMESPACE = 'permission'
/**
* The projection unit's knob state: the last seen value of each knob event,
@ -211,13 +211,15 @@ export class PermissionPresetService extends Service {
const settingsSchema: z<PermissionSettings> = z.object({
defaultPreset: z.union(presetChoices).required(),
})
installSettingsSection(ctx, PERMISSION_SETTINGS_NAMESPACE, settingsSchema, baseSettings, {
setSource: (current) => {
this.defaultSettings = current
},
// The source thunk reads the latest scope snapshot at session creation;
// no process-level registration needs replacement on change.
onChange: () => {},
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.installSection(ctx, PERMISSION_SETTINGS_NAMESPACE, settingsSchema, baseSettings, {
setSource: (current) => {
this.defaultSettings = current
},
// The source thunk reads the latest scope snapshot at session creation;
// no process-level registration needs replacement on change.
onChange: () => {},
})
})
// zod `.optional()` types the key `string | undefined` while the domain

View file

@ -169,7 +169,7 @@ export class ApprovalService extends Service {
ctx.inject(['systemPrompt'], (scope: Context) => {
scope.systemPrompt.context({
name: 'approval:policy',
order: 115,
order: scope.systemPrompt.getContextOrder('APPROVAL_POLICY'),
text: (context) => {
const agent = context.agent
// A bare assemble() (tests, diagnostics) has no session to state.

View file

@ -15,7 +15,6 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
import type { GenericCallView, ToolDefinition, ToolExecution } from '@deepseek-ai/dsh-tools'
import { JobId } from '@deepseek-ai/dsh-jobs'
import type { JobSnapshot } from '@deepseek-ai/dsh-jobs'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import type { Agent } from '@deepseek-ai/dsh-agent'
export const name = 'tool-jobs'
@ -262,7 +261,7 @@ export function apply(ctx: Context, config: Config): void {
// Cross-call guidance follows the filesystem sections and precedes product sections.
ctx.systemPrompt.section({
name: 'tool:jobs',
order: FIRST_PARTY_SECTION_ORDER.TOOL_JOBS,
order: ctx.systemPrompt.getSectionOrder('TOOL_JOBS'),
text: 'Track every background job id you start. You are notified in-session when a job finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running job\'s work. Before giving a final answer, collect every still-relevant job with job_output (set wait: true only when you are genuinely blocked on it), and job_kill jobs that stopped mattering.',
})

View file

@ -18,8 +18,9 @@ import type { ModelModality, RetryPolicyConfig } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/dsh-fs'
import { credentialRef } from '@deepseek-ai/dsh-credentials'
import { launchEnvironmentOf, type LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
import { deepEqualJson, installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import { deepEqualJson } from '@deepseek-ai/dsh-util-values'
import { getOrCreateAnonymousUserId, type AnonymousUserId } from '@deepseek-ai/dsh-anonymous-user-id'
import {
DEFAULT_CONTEXT_WINDOW,
@ -83,7 +84,7 @@ export type * from './types.ts'
export const name = 'llm-deepseek'
export const inject = ['llm']
const NS = settingsNamespace('llm-deepseek')
const NS = 'llm-deepseek'
const DEFAULT_API_KEY_ENV = 'DEEPSEEK_API_KEY'
/** The single provider route this plugin owns. */
const PROVIDER = 'deepseek-official'
@ -486,10 +487,12 @@ export function apply(ctx: Context, config: Config): void {
registeredPolicy = policy
}
installSettingsSection(ctx, NS, Config, config, {
setSource: (source) => {
current = source
},
onChange: ensureRegistrationFacts,
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.installSection(ctx, NS, Config, config, {
setSource: (source) => {
current = source
},
onChange: ensureRegistrationFacts,
})
})
}

View file

@ -8,8 +8,9 @@
* @module dsh-llm-deepseek/translate
*/
import { ToolCallId, EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, FinishReason, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm'
import { brandString } from '@deepseek-ai/dsh-brand'
import { EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, FinishReason, StreamChunk, TokenUsage, ToolCallId } from '@deepseek-ai/dsh-llm'
import { DONE } from './sse.ts'
import type { WireChunk, WireUsage } from './types.ts'
@ -77,7 +78,7 @@ function closeBlock(block: OpenBlock): ContentBlock {
case 'reasoning': return { type: 'reasoning', text: block.text }
case 'tool-call': return {
type: 'tool-call',
id: ToolCallId(block.callId ?? ''),
id: brandString<ToolCallId>(block.callId ?? ''),
name: block.name ?? '',
arguments: block.text,
}
@ -172,7 +173,7 @@ export async function* translate(payloads: AsyncIterable<string>): AsyncGenerato
yield {
type: 'tool-call-delta',
index: block.index,
id: ToolCallId(block.callId ?? ''),
id: brandString<ToolCallId>(block.callId ?? ''),
...block.name !== undefined ? { name: block.name } : {},
argumentsDelta: fragment,
}

View file

@ -15,13 +15,12 @@ import type {
} from '@deepseek-ai/dsh-attachment'
import { credentialRef } from '@deepseek-ai/dsh-credentials'
import { LocalCredentialProvider } from '@deepseek-ai/dsh-credentials-local'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { FileSettingsProvider } from '@deepseek-ai/dsh-settings-file'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import { assemble } from './assemble.ts'
import { closeMockServers, mockServer, textEvents } from './mock-server.ts'
const NS = settingsNamespace('llm-deepseek')
const NS = 'llm-deepseek'
const KEY_REF = credentialRef('DEEPSEEK_API_KEY')
const IMAGE_REF: ImageAttachmentRef = {
attachmentId: AttachmentId(`sha256:${'a'.repeat(64)}`),

View file

@ -21,7 +21,6 @@ import AgentRegistry from '@deepseek-ai/dsh-agent'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import { credentialRef } from '@deepseek-ai/dsh-credentials'
import LocalCredentialProvider from '@deepseek-ai/dsh-credentials-local'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import FileSettingsProvider from '@deepseek-ai/dsh-settings-file'
import { getOrCreateAnonymousUserId } from '@deepseek-ai/dsh-anonymous-user-id'
import DeepSeekLlmApiExtensionRegistry from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
@ -31,7 +30,7 @@ import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import { assemble } from './assemble.ts'
import { closeMockServers, mockServer, textEvents } from './mock-server.ts'
const NS = settingsNamespace('llm-deepseek')
const NS = 'llm-deepseek'
const KEY_REF = credentialRef('DEEPSEEK_API_KEY')
let root: string | undefined

View file

@ -4,8 +4,9 @@
* @module dsh-llm-pi-ai/context
*/
import { ToolCallId, contentHasImage, LlmError, offloadedImageText, offloadRequestImagesWithPolicy, requestImageHandleText } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, GenerateOptions, ImageAttachmentAccessResolver, Message } from '@deepseek-ai/dsh-llm'
import { brandString } from '@deepseek-ai/dsh-brand'
import { contentHasImage, LlmError, offloadedImageText, offloadRequestImagesWithPolicy, requestImageHandleText } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, GenerateOptions, ImageAttachmentAccessResolver, Message, ToolCallId } from '@deepseek-ai/dsh-llm'
import type {
AttachmentId,
AttachmentStore,
@ -137,6 +138,19 @@ function piContext(options: GenerateOptions, messages: PiMessage[]): PiContext {
}
}
function appendAssistant(
message: Message,
messages: PiMessage[],
toolNames: Map<ToolCallId, string>,
onReplayDegrade?: (reason: string) => void,
): void {
const assistant = toPiAssistant(message, onReplayDegrade)
for (const block of assistant.content) {
if (block.type === 'toolCall') toolNames.set(brandString<ToolCallId>(block.id), block.name)
}
messages.push(assistant)
}
function textOnlyContext(options: GenerateOptions, onReplayDegrade?: (reason: string) => void): PiContext {
const toolNames = new Map<ToolCallId, string>()
const messages: PiMessage[] = []
@ -149,9 +163,7 @@ function textOnlyContext(options: GenerateOptions, onReplayDegrade?: (reason: st
continue
}
if (message.role === 'assistant') {
const assistant = toPiAssistant(message, onReplayDegrade)
for (const block of assistant.content) if (block.type === 'toolCall') toolNames.set(ToolCallId(block.id), block.name)
messages.push(assistant)
appendAssistant(message, messages, toolNames, onReplayDegrade)
continue
}
const text = flattenText(message)
@ -263,11 +275,7 @@ async function toPiContextWithImages(
continue
}
if (message.role === 'assistant') {
const assistant = toPiAssistant(message, onReplayDegrade)
for (const block of assistant.content) {
if (block.type === 'toolCall') toolNames.set(ToolCallId(block.id), block.name)
}
messages.push(assistant)
appendAssistant(message, messages, toolNames, onReplayDegrade)
continue
}
// user role: text + tool results (each result becomes its own message).

View file

@ -60,7 +60,8 @@ import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
import { assertUsableApiKey, LlmError, resolveImageAttachmentAccess } from '@deepseek-ai/dsh-llm'
import type { AdapterRegistrationHandle, DirectoryRegistrationHandle, LlmConfigurableProvider } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/dsh-fs'
import { deepEqualJson, installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
import { deepEqualJson } from '@deepseek-ai/dsh-util-values'
import { PiAiAdapter } from './adapter.ts'
import { authContextFrom, credentialStoreFrom } from './auth.ts'
import { catalogProviderIds } from './catalog.ts'
@ -88,7 +89,7 @@ export { supportedProtocols } from './provider.ts'
export const name = 'llm-pi-ai'
export const inject = ['llm']
const NS = settingsNamespace('llm-pi-ai')
const NS = 'llm-pi-ai'
/**
* The registry captures these per route; a change here must re-register.
@ -292,38 +293,40 @@ export function apply(ctx: Context, config: Config): void {
}
ensureRegistrationFacts()
installSettingsSection(ctx, NS, Config, config, {
// Refuse an unserviceable section where it is written: without this a
// schema-valid profile the adapter cannot serve would be stored and then
// silently disable every route in this namespace.
validate: assertServiceable,
setSource: (source) => {
current = source
},
onChange: () => {
// Named here rather than left to the settings watcher: `assertServiceable`
// cannot see the llm registry, so a profile claiming a route another
// adapter family owns is stored successfully and only fails at this swap.
// Without its own diagnostic that refusal reaches the operator as a
// generic "settings: watcher failed", naming neither the route nor why it
// is not serving. The previous routes keep serving either way.
try {
ensureRegistrationFacts()
} catch (error) {
ctx.logger.error('llm-pi-ai: keeping the previously registered routes after a refused update')
ctx.logger.error(error)
}
// The directory follows the profiles the registry accepted, so a route
// that failed to register is not advertised as configurable. A refused
// directory swap is contained here for the same reason the registry's
// is: the previous entries keep serving, and `directoryFacts` stays put
// so returning to a working configuration re-applies.
try {
ensureDirectory()
} catch (error) {
ctx.logger.error('llm-pi-ai: keeping the previous configurable-provider directory after a refused update')
ctx.logger.error(error)
}
},
ctx.inject(['settings'], (settingsCtx) => {
settingsCtx.settings.installSection(ctx, NS, Config, config, {
// Refuse an unserviceable section where it is written: without this a
// schema-valid profile the adapter cannot serve would be stored and then
// silently disable every route in this namespace.
validate: assertServiceable,
setSource: (source) => {
current = source
},
onChange: () => {
// Named here rather than left to the settings watcher: `assertServiceable`
// cannot see the llm registry, so a profile claiming a route another
// adapter family owns is stored successfully and only fails at this swap.
// Without its own diagnostic that refusal reaches the operator as a
// generic "settings: watcher failed", naming neither the route nor why it
// is not serving. The previous routes keep serving either way.
try {
ensureRegistrationFacts()
} catch (error) {
ctx.logger.error('llm-pi-ai: keeping the previously registered routes after a refused update')
ctx.logger.error(error)
}
// The directory follows the profiles the registry accepted, so a route
// that failed to register is not advertised as configurable. A refused
// directory swap is contained here for the same reason the registry's
// is: the previous entries keep serving, and `directoryFacts` stays put
// so returning to a working configuration re-applies.
try {
ensureDirectory()
} catch (error) {
ctx.logger.error('llm-pi-ai: keeping the previous configurable-provider directory after a refused update')
ctx.logger.error(error)
}
},
})
})
}

View file

@ -8,8 +8,9 @@
* @module dsh-llm-pi-ai/stream
*/
import { ToolCallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm'
import type { FinishReason, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm'
import { brandString } from '@deepseek-ai/dsh-brand'
import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm'
import type { FinishReason, StreamChunk, TokenUsage, ToolCallId } from '@deepseek-ai/dsh-llm'
import { isContextOverflow } from '@earendil-works/pi-ai'
import type { AssistantMessage, AssistantMessageEvent, Usage as PiUsage } from '@earendil-works/pi-ai'
import { toPiReplayState } from './replay.ts'
@ -182,7 +183,7 @@ export async function* toStreamChunks(
yield {
type: 'tool-call-delta',
index: event.contentIndex,
id: ToolCallId(known?.id ?? ''),
id: brandString<ToolCallId>(known?.id ?? ''),
...known?.name !== undefined && known.name.length > 0 ? { name: known.name } : {},
argumentsDelta: event.delta,
}
@ -194,7 +195,7 @@ export async function* toStreamChunks(
index: event.contentIndex,
block: {
type: 'tool-call',
id: ToolCallId(event.toolCall.id),
id: brandString<ToolCallId>(event.toolCall.id),
name: event.toolCall.name,
// pi-ai hands back the PARSED arguments; the harness vocabulary
// keeps the raw string.

View file

@ -6,7 +6,6 @@ import { Context } from '@deepseek-ai/cordis'
import LlmRuntime, { createUserMessage, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
import FileSettingsProvider from '@deepseek-ai/dsh-settings-file'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai'
import { PiAiAdapter } from '@deepseek-ai/dsh-llm-pi-ai'
import { getBuiltinModels } from '@earendil-works/pi-ai/providers/all'
@ -227,7 +226,7 @@ describe('hand-declared providers', () => {
// a written section, the plugin's own registration, and `ctx.llm`.
const dir = await home()
const ctx = await bootWithSettings(dir, {})
await ctx.settings.update(settingsNamespace('llm-pi-ai'), {
await ctx.settings.update('llm-pi-ai', {
providers: {
'acme-gateway': {
api: 'openai-completions',
@ -962,7 +961,7 @@ describe('compat switches', () => {
// and `Model.compat`.
const dir = await home()
const ctx = await bootWithSettings(dir, {})
await expect(ctx.settings.update(settingsNamespace('llm-pi-ai'), {
await expect(ctx.settings.update('llm-pi-ai', {
providers: {
'acme-gateway': {
api: 'openai-completions',
@ -981,7 +980,7 @@ describe('compat switches', () => {
const server = await mockServer([{ events: textEvents }])
const dir = await home()
const ctx = await bootWithSettings(dir, {})
await ctx.settings.update(settingsNamespace('llm-pi-ai'), {
await ctx.settings.update('llm-pi-ai', {
providers: {
'acme-gateway': {
apiKeyEnv: KEY_ENV,
@ -1152,7 +1151,7 @@ describe('configurable-provider directory', () => {
const before = ctx.llm.listConfigurableProviders().length
expect(before).toBeGreaterThan(30)
await ctx.settings.update(settingsNamespace('llm-pi-ai'), {
await ctx.settings.update('llm-pi-ai', {
providers: {
'deepseek-official': {
api: 'openai-completions',
@ -1174,7 +1173,7 @@ describe('configurable-provider directory', () => {
const ctx = await bootWithSettings(dir, {})
const catalogOnly = ctx.llm.listConfigurableProviders().length
await ctx.settings.update(settingsNamespace('llm-pi-ai'), {
await ctx.settings.update('llm-pi-ai', {
providers: {
'acme-gateway': {
displayName: 'Acme Gateway',
@ -1188,7 +1187,7 @@ describe('configurable-provider directory', () => {
expect(ctx.llm.listConfigurableProviders().find(entry => entry.provider === 'acme-gateway')?.displayName)
.toBe('Acme Gateway')
await ctx.settings.replace(settingsNamespace('llm-pi-ai'), {})
await ctx.settings.replace('llm-pi-ai', {})
expect(ctx.llm.listConfigurableProviders()).toHaveLength(catalogOnly)
})

View file

@ -6,14 +6,13 @@ import { join } from 'node:path'
import LlmRuntime, { LlmAdapter } from '@deepseek-ai/dsh-llm'
import { credentialRef } from '@deepseek-ai/dsh-credentials'
import { LocalCredentialProvider } from '@deepseek-ai/dsh-credentials-local'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { FileSettingsProvider } from '@deepseek-ai/dsh-settings-file'
import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai'
import AuthorizationService from '@deepseek-ai/dsh-authorization'
import { assemble } from './assemble.ts'
import { closeMockServers, mockServer, textEvents } from './mock-server.ts'
const NS = settingsNamespace('llm-pi-ai')
const NS = 'llm-pi-ai'
/** Minimal foreign adapter: only needs to own a route the pi-ai plugin then wants. */
class StubAdapter extends LlmAdapter {

View file

@ -11,6 +11,9 @@
{
"path": "../../util/launch-environment"
},
{
"path": "../../util/brand"
},
{
"path": "../../../vendor/cosmokit"
},

View file

@ -199,7 +199,7 @@ describe('BlockAssembler replay metadata', () => {
describe('assertNever', () => {
it('throws with diagnostics when a value escapes a closed union at runtime', async () => {
const { assertNever } = await import('@deepseek-ai/dsh-llm')
const { assertNever } = await import('@deepseek-ai/dsh-util-values')
expect(() => assertNever({ type: 'rogue' } as never, 'test-context'))
.toThrow('unreachable variant in test-context: {"type":"rogue"}')
expect(() => assertNever(undefined as never)).toThrow('unreachable variant: undefined')

View file

@ -5,7 +5,8 @@
*/
import { describe, expect, it } from 'vitest'
import { callConfigEquals, deepFreeze, isAgentLoopRequest, markAgentLoopRequest } from '../src/call-config.ts'
import { deepFreeze } from '@deepseek-ai/dsh-util-values'
import { callConfigEquals, isAgentLoopRequest, markAgentLoopRequest } from '../src/call-config.ts'
import { ReasoningEffortId } from '../src/brand.ts'
import type { GenerateOptions } from '../src/types.ts'

View file

@ -11,10 +11,11 @@ import { dirname, isAbsolute, join, parse } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { FiberState, type Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { Entry, EntryTree } from '@deepseek-ai/cordis-plugin-loader'
import type {} from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type { DeepSeekPluginPackageIdentity, DeepSeekPluginPackageInventoryExtension } from './types.ts'
import type {} from './types.ts'
@ -155,7 +156,7 @@ async function collectActivePluginPackages(
): Promise<DeepSeekPluginPackageIdentity[]> {
const entries = activeEntries(ctx.loader)
if (sessionId !== undefined && ctx.get('agentPresets') !== undefined) {
const agent = ctx.agents.get(SessionId(sessionId))
const agent = ctx.agents.get(brandString<SessionId>(sessionId))
if (agent !== undefined) {
// The optional peer is loaded only when its service is present. Its existing
// mount query keeps Loader internals off the public AgentPresets service.

View file

@ -6,8 +6,9 @@
import { Context, Service } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { BlockAssembler, deepFreeze } from '@deepseek-ai/dsh-llm'
import { BlockAssembler } from '@deepseek-ai/dsh-llm'
import type { LlmImageRequestPricing, Message, TokenUsage } from '@deepseek-ai/dsh-llm'
import { deepFreeze } from '@deepseek-ai/dsh-util-values'
import type { EpochHeader, Session, SessionEvent } from '@deepseek-ai/dsh-session'
import { canonicalHeader, headerEquals, isSurfaceEvent } from '@deepseek-ai/dsh-session'
// Type-only: activates the `ctx.sessionProjections` Context declaration.

View file

@ -12,7 +12,7 @@ import type {
LspRange,
} from '@deepseek-ai/dsh-lsp'
import { LspError } from '@deepseek-ai/dsh-lsp'
import { assertNever } from '@deepseek-ai/dsh-llm'
import { assertNever } from '@deepseek-ai/dsh-util-values'
import type {
WireHover,
WireLocation,

View file

@ -13,11 +13,10 @@
import type { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { assertNever } from '@deepseek-ai/dsh-llm'
import { LspError } from '@deepseek-ai/dsh-lsp'
import type {} from '@deepseek-ai/dsh-lsp'
import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt'
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import { assertNever } from '@deepseek-ai/dsh-util-values'
import {
DEFAULT_MAX_LOCATIONS,
DEFAULT_MAX_RESULT_CHARS,
@ -103,7 +102,7 @@ export function apply(ctx: Context, config: Config): void {
ctx.systemPrompt.section({
name: 'tool:lsp',
order: FIRST_PARTY_SECTION_ORDER.TOOL_LSP,
order: ctx.systemPrompt.getSectionOrder('TOOL_LSP'),
text: LSP_PROMPT_TEXT,
})

View file

@ -23,7 +23,8 @@ import type { AttachmentStore, ImageAttachmentRef, ImageMediaType, SaveImageAtta
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { ToolDefinition, ToolExecution, ToolExecutionResult } from '@deepseek-ai/dsh-tools'
import { assertSupportedJsonSchema } from '@deepseek-ai/dsh-tools'
import type { JsonSchemaNode, JsonValue } from '@deepseek-ai/dsh-tools'
import type { JsonSchemaNode } from '@deepseek-ai/dsh-tools'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
/** Resolved options relevant to tool bridging. */
export interface ToolBridgeOptions {

View file

@ -8,7 +8,8 @@ import { ToolCallId, LlmAdapter, LlmRuntime } from '@deepseek-ai/dsh-llm'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRuntime, { type JsonValue } from '@deepseek-ai/dsh-tools'
import ToolRuntime from '@deepseek-ai/dsh-tools'
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import type { PostToolDecision } from '@deepseek-ai/dsh-tools'
import { publicToolName, syncTools, type ToolBridgeOptions } from '@deepseek-ai/dsh-mcp-client/src/tools.ts'
import { createTransport } from '@deepseek-ai/dsh-mcp-client/src/transport.ts'

Some files were not shown because too many files have changed in this diff Show more