merge: integrate remote and local main histories
# Conflicts: # .project-docs/20-architecture/module-map.md # .project-docs/30-worklog/current-state.md # .project-docs/50-evidence/evidence-index.md # src/pages/MyPlugins/index.tsx # tests/unit/pi-agent-server-process-real.test.ts # tests/unit/pi-managed-worker-opener.test.ts # tests/unit/plugin-marketplace-pages.test.tsx
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import type { CapabilityResultV1 } from './data-service';
|
||||
import type { ModelToolDetailsV1 } from './model-tools';
|
||||
import type { DevicePackageToolDetailsV1 } from './device-packages';
|
||||
|
||||
export type { CapabilityBillingReceiptV1, CapabilityResultV1 } from './data-service';
|
||||
export type { ModelToolDetailsV1 } from './model-tools';
|
||||
export type { DevicePackageToolDetailsV1 } from './device-packages';
|
||||
|
||||
export type ConversationThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high';
|
||||
export type ConversationThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'max';
|
||||
|
||||
export interface ProductModelRef {
|
||||
accountId: string;
|
||||
@@ -220,6 +224,8 @@ export type KnownToolDetails =
|
||||
| GameAssetsDetailsV1
|
||||
| RuntimeContextDetailsV1
|
||||
| CapabilityResultV1
|
||||
| ModelToolDetailsV1
|
||||
| DevicePackageToolDetailsV1
|
||||
| SubagentDetailsV1;
|
||||
|
||||
export interface ConversationToolNode {
|
||||
|
||||
@@ -8,6 +8,14 @@ import type {
|
||||
RuntimeContextDetailsV1,
|
||||
TaskStateDetailsV1,
|
||||
} from './coding-conversation-contracts';
|
||||
import type { ModelToolDetailsV1, ModelWebSearchErrorCode } from './model-tools';
|
||||
import {
|
||||
DEVICE_PACKAGE_TOOL_NAMES,
|
||||
type DevicePackageIndexV1,
|
||||
type DevicePackageRecordV1,
|
||||
type DevicePackageToolDetailsV1,
|
||||
type InstallPreviewV1,
|
||||
} from './device-packages';
|
||||
import { DATA_SERVICE_PI_TOOL_NAMES } from './data-service';
|
||||
|
||||
const PRODUCT_TOOL_NAMES = new Set([
|
||||
@@ -17,6 +25,8 @@ const PRODUCT_TOOL_NAMES = new Set([
|
||||
'task_state',
|
||||
'changed_file',
|
||||
'runtime_context',
|
||||
'web_search',
|
||||
...DEVICE_PACKAGE_TOOL_NAMES,
|
||||
...DATA_SERVICE_PI_TOOL_NAMES,
|
||||
]);
|
||||
|
||||
@@ -192,6 +202,11 @@ function capabilityBilling(value: unknown): CapabilityBillingReceiptV1 | null {
|
||||
? { mode: 'external_account', status: 'external' }
|
||||
: null;
|
||||
}
|
||||
if (billing.mode === 'platform_metered' && billing.status === 'receipt_unavailable') {
|
||||
return exactKeys(billing, ['mode', 'status'])
|
||||
? { mode: 'platform_metered', status: 'receipt_unavailable' }
|
||||
: null;
|
||||
}
|
||||
if (billing.mode !== 'platform_metered' || typeof billing.reserved_points !== 'string'
|
||||
|| !CAPABILITY_DECIMAL_PATTERN.test(billing.reserved_points)
|
||||
|| billing.reserved_points.length > 32) return null;
|
||||
@@ -234,6 +249,7 @@ function capabilityDetails(value: Record<string, unknown>): CapabilityResultV1 |
|
||||
|| value.schema !== 'makelore-capability.v1') return null;
|
||||
if (typeof value.plugin_id !== 'string' || value.plugin_id.length > 48
|
||||
|| !CAPABILITY_PLUGIN_ID_PATTERN.test(value.plugin_id)
|
||||
|| value.plugin_id === 'makelore.web-search'
|
||||
|| !text(value.plugin_version, 64)
|
||||
|| typeof value.capability_id !== 'string' || !CAPABILITY_ID_PATTERN.test(value.capability_id)
|
||||
|| typeof value.operation !== 'string' || !CAPABILITY_OPERATION_PATTERN.test(value.operation)
|
||||
@@ -293,6 +309,186 @@ function capabilityDetails(value: Record<string, unknown>): CapabilityResultV1 |
|
||||
};
|
||||
}
|
||||
|
||||
const MODEL_WEB_SEARCH_ERROR_CONTRACT = Object.freeze({
|
||||
model_web_search_unsupported: { httpStatus: 400, retryable: false },
|
||||
model_context_changed: { httpStatus: 409, retryable: false },
|
||||
model_web_search_rate_limited: { httpStatus: 429, retryable: false },
|
||||
model_web_search_unavailable: { httpStatus: 503, retryable: true },
|
||||
model_web_search_invalid_result: { httpStatus: 502, retryable: false },
|
||||
} satisfies Record<ModelWebSearchErrorCode, { httpStatus: number; retryable: boolean }>);
|
||||
|
||||
function modelToolSource(value: unknown): { title: string; url: string } | null {
|
||||
const source = record(value);
|
||||
const title = text(source?.title, 240);
|
||||
const rawUrl = text(source?.url, 2_048);
|
||||
if (!source || !exactKeys(source, ['title', 'url']) || !title || !rawUrl) return null;
|
||||
try {
|
||||
const url = new URL(rawUrl);
|
||||
if ((url.protocol !== 'http:' && url.protocol !== 'https:') || url.username || url.password) return null;
|
||||
return { title, url: url.toString() };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function modelToolDetails(value: Record<string, unknown>): ModelToolDetailsV1 | null {
|
||||
if (value.schema !== 'makelore-model-tool.v1' || value.tool !== 'web_search') return null;
|
||||
const modelId = text(value.modelId, 256);
|
||||
if (!modelId) return null;
|
||||
if (value.status === 'succeeded') {
|
||||
if (!exactKeys(value, [
|
||||
'schema', 'tool', 'status', 'modelId', 'answer', 'sources', 'sourceMode',
|
||||
])) return null;
|
||||
const answer = text(value.answer, 20_000);
|
||||
if (!answer || !Array.isArray(value.sources) || value.sources.length > 20
|
||||
|| (value.sourceMode !== 'structured' && value.sourceMode !== 'inline-or-structured')) return null;
|
||||
const sources = value.sources.map(modelToolSource);
|
||||
if (sources.some((source) => source === null)
|
||||
|| (value.sourceMode === 'structured' && sources.length === 0)) return null;
|
||||
return {
|
||||
schema: 'makelore-model-tool.v1',
|
||||
tool: 'web_search',
|
||||
status: 'succeeded',
|
||||
modelId,
|
||||
answer,
|
||||
sources: sources as Array<{ title: string; url: string }>,
|
||||
sourceMode: value.sourceMode,
|
||||
};
|
||||
}
|
||||
if (value.status !== 'failed'
|
||||
|| !exactKeys(value, ['schema', 'tool', 'status', 'modelId', 'error'])) return null;
|
||||
const error = record(value.error);
|
||||
const code = error?.code;
|
||||
const contract = typeof code === 'string'
|
||||
? MODEL_WEB_SEARCH_ERROR_CONTRACT[code as ModelWebSearchErrorCode]
|
||||
: undefined;
|
||||
const message = text(error?.message, 2_000);
|
||||
if (!error || !contract || !message
|
||||
|| !exactKeys(error, ['code', 'message', 'httpStatus', 'retryable'])
|
||||
|| error.httpStatus !== contract.httpStatus
|
||||
|| error.retryable !== contract.retryable) return null;
|
||||
return {
|
||||
schema: 'makelore-model-tool.v1',
|
||||
tool: 'web_search',
|
||||
status: 'failed',
|
||||
modelId,
|
||||
error: {
|
||||
code: code as ModelWebSearchErrorCode,
|
||||
message,
|
||||
httpStatus: contract.httpStatus as 400 | 409 | 429 | 502 | 503,
|
||||
retryable: contract.retryable,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function deviceSkillEntries(value: unknown): DevicePackageRecordV1['skillEntries'] | null {
|
||||
if (!Array.isArray(value) || value.length > 100) return null;
|
||||
const entries: Array<{ id: string; entryPath: string }> = [];
|
||||
for (const candidate of value) {
|
||||
const item = record(candidate);
|
||||
const id = text(item?.id, 128);
|
||||
const entryPath = text(item?.entryPath, 1_024);
|
||||
if (!item || !exactKeys(item, ['id', 'entryPath']) || !id || !entryPath) return null;
|
||||
entries.push({ id, entryPath });
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function devicePackageKind(value: unknown): DevicePackageRecordV1['kind'] | null {
|
||||
return value === 'skill-only' || value === 'pi-extension' || value === 'mixed' ? value : null;
|
||||
}
|
||||
|
||||
function devicePackagePreview(value: unknown): InstallPreviewV1 | null {
|
||||
const preview = record(value);
|
||||
if (!preview || !exactKeys(preview, [
|
||||
'schemaVersion', 'planId', 'expiresAt', 'requestedSource', 'resolvedSource',
|
||||
'packageId', 'displayName', 'resolvedVersion', 'kind', 'skillEntries',
|
||||
'extensionEntries', 'includesExecutableCode', 'ignoredLifecycleScripts', 'warnings',
|
||||
'scope',
|
||||
]) || preview.schemaVersion !== 1 || preview.scope !== 'device-parent-workers'
|
||||
|| typeof preview.includesExecutableCode !== 'boolean') return null;
|
||||
const planId = text(preview.planId, 128);
|
||||
const expiresAt = text(preview.expiresAt, 64);
|
||||
const requestedSource = text(preview.requestedSource, 2_048);
|
||||
const resolvedSource = text(preview.resolvedSource, 4_096);
|
||||
const packageId = text(preview.packageId, 128);
|
||||
const displayName = text(preview.displayName, 240);
|
||||
const resolvedVersion = text(preview.resolvedVersion, 128);
|
||||
const kind = devicePackageKind(preview.kind);
|
||||
const skillEntries = deviceSkillEntries(preview.skillEntries);
|
||||
const extensionEntries = strings(preview.extensionEntries, 100);
|
||||
const ignoredLifecycleScripts = strings(preview.ignoredLifecycleScripts, 20);
|
||||
const warnings = strings(preview.warnings, 20);
|
||||
if (!planId || !expiresAt || !requestedSource || !resolvedSource || !packageId
|
||||
|| !displayName || !resolvedVersion || !kind || !skillEntries || !extensionEntries
|
||||
|| !ignoredLifecycleScripts || !warnings) return null;
|
||||
return {
|
||||
schemaVersion: 1, planId, expiresAt, requestedSource, resolvedSource, packageId,
|
||||
displayName, resolvedVersion, kind, skillEntries, extensionEntries,
|
||||
includesExecutableCode: preview.includesExecutableCode,
|
||||
ignoredLifecycleScripts, warnings, scope: 'device-parent-workers',
|
||||
};
|
||||
}
|
||||
|
||||
function devicePackageRecord(value: unknown): DevicePackageRecordV1 | null {
|
||||
const item = record(value);
|
||||
if (!item || !exactKeys(item, [
|
||||
'schemaVersion', 'packageId', 'displayName', 'resolvedVersion', 'source', 'kind',
|
||||
'skillEntries', 'extensionEntries', 'enabled', 'confirmedExecutableCode', 'installedAt',
|
||||
]) || item.schemaVersion !== 1 || typeof item.enabled !== 'boolean'
|
||||
|| typeof item.confirmedExecutableCode !== 'boolean') return null;
|
||||
const source = record(item.source);
|
||||
const packageId = text(item.packageId, 128);
|
||||
const displayName = text(item.displayName, 240);
|
||||
const resolvedVersion = text(item.resolvedVersion, 128);
|
||||
const kind = devicePackageKind(item.kind);
|
||||
const installedAt = text(item.installedAt, 64);
|
||||
const skillEntries = deviceSkillEntries(item.skillEntries);
|
||||
const extensionEntries = strings(item.extensionEntries, 100);
|
||||
if (!source || !exactKeys(source, ['kind', 'requested', 'resolved'])
|
||||
|| !['npm', 'git', 'file'].includes(String(source.kind))
|
||||
|| !packageId || !displayName || !resolvedVersion || !kind || !installedAt
|
||||
|| !skillEntries || !extensionEntries) return null;
|
||||
const requested = text(source.requested, 2_048);
|
||||
const resolved = text(source.resolved, 4_096);
|
||||
if (!requested || !resolved) return null;
|
||||
return {
|
||||
schemaVersion: 1, packageId, displayName, resolvedVersion,
|
||||
source: { kind: source.kind as DevicePackageRecordV1['source']['kind'], requested, resolved },
|
||||
kind, skillEntries, extensionEntries, enabled: item.enabled,
|
||||
confirmedExecutableCode: item.confirmedExecutableCode, installedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function devicePackageIndex(value: unknown): DevicePackageIndexV1 | null {
|
||||
const index = record(value);
|
||||
if (!index || !exactKeys(index, ['schemaVersion', 'generation', 'packages'])
|
||||
|| index.schemaVersion !== 1 || !Number.isSafeInteger(index.generation)
|
||||
|| (index.generation as number) < 0 || !Array.isArray(index.packages)
|
||||
|| index.packages.length > 200) return null;
|
||||
const packages = index.packages.map(devicePackageRecord);
|
||||
if (packages.some((item) => item === null)) return null;
|
||||
return { schemaVersion: 1, generation: index.generation as number, packages: packages as DevicePackageRecordV1[] };
|
||||
}
|
||||
|
||||
function devicePackageDetails(value: Record<string, unknown>): DevicePackageToolDetailsV1 | null {
|
||||
if (value.schema !== 'makelore-device-package.v1' || value.success !== true
|
||||
|| !['prepare', 'commit', 'list', 'set_enabled', 'uninstall'].includes(String(value.operation))) return null;
|
||||
if (value.operation === 'prepare') {
|
||||
if (!exactKeys(value, ['schema', 'operation', 'success', 'preview'])) return null;
|
||||
const preview = devicePackagePreview(value.preview);
|
||||
return preview ? { schema: 'makelore-device-package.v1', operation: 'prepare', success: true, preview } : null;
|
||||
}
|
||||
if (!exactKeys(value, ['schema', 'operation', 'success', 'index'])) return null;
|
||||
const index = devicePackageIndex(value.index);
|
||||
return index ? {
|
||||
schema: 'makelore-device-package.v1',
|
||||
operation: value.operation as Exclude<DevicePackageToolDetailsV1['operation'], 'prepare'>,
|
||||
success: true,
|
||||
index,
|
||||
} : null;
|
||||
}
|
||||
|
||||
export function productToolDetails(value: unknown): Exclude<KnownToolDetails, { schema: 'subagent.v1' | 'write-lease.v1' }> | null {
|
||||
const details = record(value);
|
||||
if (!details) return null;
|
||||
@@ -305,6 +501,8 @@ export function productToolDetails(value: unknown): Exclude<KnownToolDetails, {
|
||||
if (details.schema === 'game-assets.v1') return gameAssetDetails(details);
|
||||
if (details.schema === 'runtime-context.v1') return runtimeContextDetails(details);
|
||||
if (details.schema === 'makelore-capability.v1') return capabilityDetails(details);
|
||||
if (details.schema === 'makelore-model-tool.v1') return modelToolDetails(details);
|
||||
if (details.schema === 'makelore-device-package.v1') return devicePackageDetails(details);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ const RUN_STATUSES = new Set([
|
||||
]);
|
||||
|
||||
const WORKER_STATUSES = new Set(['stopped', 'starting', 'ready', 'recovering', 'error']);
|
||||
const THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high']);
|
||||
const THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high', 'max']);
|
||||
const ERROR_CODES = new Set([
|
||||
'CODING_RUNTIME_START_FAILED',
|
||||
'CODING_RUNTIME_READY_TIMEOUT',
|
||||
|
||||
@@ -101,6 +101,21 @@ export const DATA_SERVICE_ADAPTER_ID = 'data-service' as const;
|
||||
export const DATA_SERVICE_PROJECT_SETTINGS_SURFACE = 'data-service' as const;
|
||||
export const DATA_SERVICE_PREVIEW_RUNTIME_SURFACE = 'data-service-v1' as const;
|
||||
|
||||
export const GAME_RESOURCE_PLUGIN_ID = 'makelore.game-resource' as const;
|
||||
export const GAME_RESOURCE_BUNDLED_RELEASE_ID = '00000000-0000-4000-8000-000000000105' as const;
|
||||
|
||||
export const CODE_OWNED_OPTIONAL_BUNDLED_RELEASES = Object.freeze({
|
||||
[GAME_RESOURCE_PLUGIN_ID]: Object.freeze({
|
||||
releaseId: GAME_RESOURCE_BUNDLED_RELEASE_ID,
|
||||
version: '1.0.0',
|
||||
channel: 'stable' as const,
|
||||
}),
|
||||
});
|
||||
|
||||
export function isCodeOwnedOptionalBundledPluginId(pluginId: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(CODE_OWNED_OPTIONAL_BUNDLED_RELEASES, pluginId);
|
||||
}
|
||||
|
||||
export const DATA_SERVICE_CAPABILITY_IDS = Object.freeze([
|
||||
'data-service.control',
|
||||
'data-service.documents',
|
||||
|
||||
@@ -100,6 +100,11 @@ export type CapabilityBillingReceiptV1 =
|
||||
actual_points: string;
|
||||
usage_amount?: number;
|
||||
unit?: string;
|
||||
}
|
||||
| {
|
||||
/** Main-only projection used when a possibly-dispatched receipt cannot be read. */
|
||||
mode: 'platform_metered';
|
||||
status: 'receipt_unavailable';
|
||||
};
|
||||
|
||||
export interface CapabilityResultV1<T = unknown> {
|
||||
|
||||
74
shared/device-packages.ts
Normal file
74
shared/device-packages.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
export type DevicePackageSourceKind = 'npm' | 'git' | 'file';
|
||||
export type DevicePackageKind = 'skill-only' | 'pi-extension' | 'mixed';
|
||||
|
||||
export interface DevicePackageSkillEntry {
|
||||
id: string;
|
||||
entryPath: string;
|
||||
}
|
||||
|
||||
export interface InstallPreviewV1 {
|
||||
schemaVersion: 1;
|
||||
planId: string;
|
||||
expiresAt: string;
|
||||
requestedSource: string;
|
||||
resolvedSource: string;
|
||||
packageId: string;
|
||||
displayName: string;
|
||||
resolvedVersion: string;
|
||||
kind: DevicePackageKind;
|
||||
skillEntries: readonly DevicePackageSkillEntry[];
|
||||
extensionEntries: readonly string[];
|
||||
includesExecutableCode: boolean;
|
||||
ignoredLifecycleScripts: readonly string[];
|
||||
warnings: readonly string[];
|
||||
scope: 'device-parent-workers';
|
||||
}
|
||||
|
||||
export interface DevicePackageRecordV1 {
|
||||
schemaVersion: 1;
|
||||
packageId: string;
|
||||
displayName: string;
|
||||
resolvedVersion: string;
|
||||
source: Readonly<{
|
||||
kind: DevicePackageSourceKind;
|
||||
requested: string;
|
||||
resolved: string;
|
||||
}>;
|
||||
kind: DevicePackageKind;
|
||||
skillEntries: readonly DevicePackageSkillEntry[];
|
||||
extensionEntries: readonly string[];
|
||||
enabled: boolean;
|
||||
confirmedExecutableCode: boolean;
|
||||
installedAt: string;
|
||||
}
|
||||
|
||||
export interface DevicePackageIndexV1 {
|
||||
schemaVersion: 1;
|
||||
generation: number;
|
||||
packages: readonly DevicePackageRecordV1[];
|
||||
}
|
||||
|
||||
export type DevicePackageToolOperation =
|
||||
| 'prepare'
|
||||
| 'commit'
|
||||
| 'list'
|
||||
| 'set_enabled'
|
||||
| 'uninstall';
|
||||
|
||||
export const DEVICE_PACKAGE_TOOL_NAMES = Object.freeze([
|
||||
'local_package_prepare',
|
||||
'local_package_commit',
|
||||
'local_package_list',
|
||||
'local_package_set_enabled',
|
||||
'local_package_uninstall',
|
||||
] as const);
|
||||
|
||||
export type DevicePackageToolName = typeof DEVICE_PACKAGE_TOOL_NAMES[number];
|
||||
|
||||
export type DevicePackageToolDetailsV1 = Readonly<{
|
||||
schema: 'makelore-device-package.v1';
|
||||
operation: DevicePackageToolOperation;
|
||||
success: true;
|
||||
preview?: InstallPreviewV1;
|
||||
index?: DevicePackageIndexV1;
|
||||
}>;
|
||||
@@ -1,6 +1,37 @@
|
||||
export type ImportedModelModality = 'text' | 'audio' | 'image' | 'pdf';
|
||||
export type ImportedVisionTokenEstimator = 'qwen-32px-grid';
|
||||
export type ImportedThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high';
|
||||
export type ImportedThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'max';
|
||||
export const IMPORTED_REASONING_EFFORTS = ['low', 'high', 'max'] as const;
|
||||
export type ImportedReasoningEffort = (typeof IMPORTED_REASONING_EFFORTS)[number];
|
||||
|
||||
export type ImportedModelWebSearchCapability = Readonly<{
|
||||
schemaVersion: 1;
|
||||
adapter: 'openai-responses' | 'bailian-responses' | 'bailian-chat-completions';
|
||||
supportsForcedSearch: true;
|
||||
sourceMode: 'structured' | 'inline-or-structured';
|
||||
billingAuthority: 'model-request';
|
||||
}>;
|
||||
|
||||
export interface ImportedModelCapability {
|
||||
reasoningEfforts: ImportedReasoningEffort[];
|
||||
reasoningCanDisable: boolean;
|
||||
webSearch?: ImportedModelWebSearchCapability;
|
||||
}
|
||||
|
||||
export type ImportedModelCapabilities = Record<string, ImportedModelCapability>;
|
||||
|
||||
export function thinkingLevelMapForImportedModelCapability(
|
||||
capability: ImportedModelCapability,
|
||||
): Partial<Record<ImportedThinkingLevel, string | null>> {
|
||||
return {
|
||||
...(capability.reasoningCanDisable ? {} : { off: null }),
|
||||
minimal: null,
|
||||
low: capability.reasoningEfforts.includes('low') ? 'low' : null,
|
||||
medium: null,
|
||||
high: capability.reasoningEfforts.includes('high') ? 'high' : null,
|
||||
max: capability.reasoningEfforts.includes('max') ? 'max' : null,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ImportedPiModelProfile {
|
||||
reasoning: boolean;
|
||||
@@ -62,14 +93,15 @@ export function getImportedModelProfile(rawModelId: string): ImportedModelProfil
|
||||
pi: {
|
||||
reasoning: true,
|
||||
thinkingLevelMap: {
|
||||
off: null,
|
||||
minimal: null,
|
||||
low: null,
|
||||
low: 'low',
|
||||
medium: null,
|
||||
high: 'high',
|
||||
max: 'max',
|
||||
},
|
||||
compat: {
|
||||
thinkingFormat: 'deepseek',
|
||||
supportsReasoningEffort: true,
|
||||
requiresReasoningContentOnAssistantMessages: true,
|
||||
},
|
||||
},
|
||||
|
||||
36
shared/model-tools.ts
Normal file
36
shared/model-tools.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export type ModelWebSearchErrorCode =
|
||||
| 'model_web_search_unsupported'
|
||||
| 'model_context_changed'
|
||||
| 'model_web_search_rate_limited'
|
||||
| 'model_web_search_unavailable'
|
||||
| 'model_web_search_invalid_result';
|
||||
|
||||
export type ModelWebSearchSourceV1 = Readonly<{
|
||||
title: string;
|
||||
url: string;
|
||||
}>;
|
||||
|
||||
export type ModelWebSearchSuccessV1 = Readonly<{
|
||||
schema: 'makelore-model-tool.v1';
|
||||
tool: 'web_search';
|
||||
status: 'succeeded';
|
||||
modelId: string;
|
||||
answer: string;
|
||||
sources: ReadonlyArray<ModelWebSearchSourceV1>;
|
||||
sourceMode: 'structured' | 'inline-or-structured';
|
||||
}>;
|
||||
|
||||
export type ModelWebSearchFailureV1 = Readonly<{
|
||||
schema: 'makelore-model-tool.v1';
|
||||
tool: 'web_search';
|
||||
status: 'failed';
|
||||
modelId: string;
|
||||
error: Readonly<{
|
||||
code: ModelWebSearchErrorCode;
|
||||
message: string;
|
||||
httpStatus: 400 | 409 | 429 | 502 | 503;
|
||||
retryable: boolean;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
export type ModelToolDetailsV1 = ModelWebSearchSuccessV1 | ModelWebSearchFailureV1;
|
||||
@@ -1,3 +1,11 @@
|
||||
import {
|
||||
IMPORTED_REASONING_EFFORTS,
|
||||
type ImportedModelCapabilities,
|
||||
type ImportedModelCapability,
|
||||
type ImportedModelWebSearchCapability,
|
||||
type ImportedReasoningEffort,
|
||||
} from './imported-model-profile';
|
||||
|
||||
export const NIANCODE_USER_MODEL_ACCOUNT_ID = 'niancode-user-models';
|
||||
export const NIANCODE_USER_MODEL_ACCOUNT_LABEL = 'Makelore Models';
|
||||
|
||||
@@ -23,6 +31,94 @@ export function normalizeImportedUserModelId(rawModel: string): string {
|
||||
: trimmed;
|
||||
}
|
||||
|
||||
function normalizeImportedModelCapability(value: unknown): ImportedModelCapability | null {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
||||
const record = value as Record<string, unknown>;
|
||||
const rawEfforts = record.reasoning_efforts ?? record.reasoningEfforts;
|
||||
const reasoningCanDisable = record.reasoning_can_disable ?? record.reasoningCanDisable;
|
||||
if (!Array.isArray(rawEfforts) || typeof reasoningCanDisable !== 'boolean') return null;
|
||||
const reasoningEfforts = IMPORTED_REASONING_EFFORTS.filter((effort) => (
|
||||
rawEfforts.some((candidate) => candidate === effort)
|
||||
)) as ImportedReasoningEffort[];
|
||||
if (reasoningEfforts.length === 0 && rawEfforts.length > 0) return null;
|
||||
const webSearch = normalizeImportedModelWebSearchCapability(
|
||||
record.web_search ?? record.webSearch,
|
||||
);
|
||||
return {
|
||||
reasoningEfforts,
|
||||
reasoningCanDisable,
|
||||
...(webSearch ? { webSearch } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeImportedModelWebSearchCapability(
|
||||
value: unknown,
|
||||
): ImportedModelWebSearchCapability | null {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
||||
const record = value as Record<string, unknown>;
|
||||
const schemaVersion = record.schema_version ?? record.schemaVersion;
|
||||
const rawAdapter = record.adapter;
|
||||
const supportsForcedSearch = record.supports_forced_search ?? record.supportsForcedSearch;
|
||||
const rawSourceMode = record.source_mode ?? record.sourceMode;
|
||||
const rawBillingAuthority = record.billing_authority ?? record.billingAuthority;
|
||||
const adapters: Record<string, ImportedModelWebSearchCapability['adapter']> = {
|
||||
openai_responses: 'openai-responses',
|
||||
'openai-responses': 'openai-responses',
|
||||
bailian_responses: 'bailian-responses',
|
||||
'bailian-responses': 'bailian-responses',
|
||||
bailian_chat_completions: 'bailian-chat-completions',
|
||||
'bailian-chat-completions': 'bailian-chat-completions',
|
||||
};
|
||||
const sourceModes: Record<string, ImportedModelWebSearchCapability['sourceMode']> = {
|
||||
structured: 'structured',
|
||||
inline_or_structured: 'inline-or-structured',
|
||||
'inline-or-structured': 'inline-or-structured',
|
||||
};
|
||||
const billingAuthorities: Record<string, ImportedModelWebSearchCapability['billingAuthority']> = {
|
||||
model_request: 'model-request',
|
||||
'model-request': 'model-request',
|
||||
};
|
||||
const adapter = typeof rawAdapter === 'string' ? adapters[rawAdapter] : undefined;
|
||||
const sourceMode = typeof rawSourceMode === 'string' ? sourceModes[rawSourceMode] : undefined;
|
||||
const billingAuthority = typeof rawBillingAuthority === 'string'
|
||||
? billingAuthorities[rawBillingAuthority]
|
||||
: undefined;
|
||||
if (
|
||||
schemaVersion !== 1
|
||||
|| !adapter
|
||||
|| supportsForcedSearch !== true
|
||||
|| !sourceMode
|
||||
|| !billingAuthority
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
adapter,
|
||||
supportsForcedSearch: true,
|
||||
sourceMode,
|
||||
billingAuthority,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeImportedModelCapabilities(
|
||||
value: unknown,
|
||||
modelIds?: readonly string[],
|
||||
): ImportedModelCapabilities | undefined {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
||||
const allowedModelIds = modelIds
|
||||
? new Set(modelIds.map(normalizeImportedUserModelId))
|
||||
: null;
|
||||
const result: ImportedModelCapabilities = {};
|
||||
for (const [rawModelId, rawCapability] of Object.entries(value as Record<string, unknown>)) {
|
||||
const modelId = normalizeImportedUserModelId(rawModelId);
|
||||
if (!modelId || (allowedModelIds && !allowedModelIds.has(modelId))) continue;
|
||||
const capability = normalizeImportedModelCapability(rawCapability);
|
||||
if (capability && !result[modelId]) result[modelId] = capability;
|
||||
}
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
export function selectUserModelRuntimeAccounts<T extends NianCodeUserModelAccountLike>(
|
||||
accounts: T[],
|
||||
): T[] {
|
||||
|
||||
Reference in New Issue
Block a user