narraeon 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/protocol/appPreferences.js +25 -0
- package/dist/node/protocol/conversationObservation.js +67 -0
- package/dist/node/protocol/v1.js +186 -34
- package/dist/node/protocol/worldExtensions.js +16 -0
- package/dist/node/protocol/worldMaintenance.js +77 -0
- package/dist/node/protocol/worldPromptDiagnostics.js +1 -0
- package/dist/node/runtime/ConversationChanges.js +24 -0
- package/dist/node/runtime/V1Runtime.js +445 -121
- package/dist/node/runtime/artifact/FileNativeArtifactStore.js +326 -12
- package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +63 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +218 -0
- package/dist/node/runtime/config/AppPreferencesStore.js +46 -17
- package/dist/node/runtime/content/ContentTreeFingerprint.js +13 -0
- package/dist/node/runtime/content/ContentWorkspace.js +43 -13
- package/dist/node/runtime/content/CurrentTreeContentLibrary.js +128 -46
- package/dist/node/runtime/content/FileNativeContentTree.js +28 -10
- package/dist/node/runtime/content/PackageFollowups.js +80 -0
- package/dist/node/runtime/extension/FrontendExtensionBundle.js +19 -14
- package/dist/node/runtime/extension/PackageScriptPermissions.js +70 -0
- package/dist/node/runtime/extension/WorldExtensionControls.js +205 -0
- package/dist/node/runtime/extension/WorldExtensionRequests.js +64 -0
- package/dist/node/runtime/model/FileNativeModelAdapters.js +21 -466
- package/dist/node/runtime/model/ModelConnectionStore.js +13 -0
- package/dist/node/runtime/model/ModelHost.js +31 -1
- package/dist/node/runtime/model/ModelHostStream.js +51 -5
- package/dist/node/runtime/play/FileNativeContinuityCorrection.js +55 -45
- package/dist/node/runtime/play/FileNativePlayPresetStore.js +256 -238
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +118 -9
- package/dist/node/runtime/play/OrderedFollowups.js +71 -0
- package/dist/node/runtime/play/OrderedPlayPrompts.js +180 -0
- package/dist/node/runtime/play/PlayCallChain.js +814 -61
- package/dist/node/runtime/play/PlayContinuity.js +88 -0
- package/dist/node/runtime/play/PlayDocumentTools.js +205 -287
- package/dist/node/runtime/play/PlayFollowupRequests.js +34 -3
- package/dist/node/runtime/play/PlayPresetWorkbench.js +45 -54
- package/dist/node/runtime/play/PlayPromptRun.js +86 -0
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +670 -93
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +37 -21
- package/dist/node/runtime/prompt/PromptCompilationCodec.js +322 -0
- package/dist/node/runtime/prompt/WorldMaintenanceReport.js +251 -0
- package/dist/node/runtime/prompt/WorldMaterialCoverage.js +83 -0
- package/dist/node/runtime/prompt/WorldPromptDiagnostics.js +185 -0
- package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +1027 -0
- package/dist/node/runtime/setting/SettingAuthoringTransaction.js +1339 -0
- package/dist/node/runtime/setting/SettingImprovementSession.js +714 -0
- package/dist/node/runtime/world/FileNativeAuthorityV3.js +88 -1
- package/dist/node/runtime/world/FileNativeWorldStore.js +292 -27
- package/dist/node/runtime/world/PlayerViewBindings.js +30 -0
- package/dist/node/runtime/world/PlayerViewRenderer.js +27 -34
- package/dist/node/runtime/world/WorldDocumentMaintenance.js +174 -0
- package/dist/node/runtime/world/WorldDocumentStore.js +166 -96
- package/dist/node/runtime/world/WorldOperationCoordinator.js +103 -2
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +696 -0
- package/dist/node/runtime/world-revision/WorldRevisionAuthoringTransaction.js +380 -0
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +709 -0
- package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +494 -0
- package/dist/node/server/ConversationStream.js +109 -0
- package/dist/node/server/createServer.js +5 -1
- package/dist/node/shared/artifact-payload-contract.js +123 -0
- package/dist/node/shared/default-play-prompts.js +25 -45
- package/dist/node/shared/default-preset-host.js +114 -138
- package/dist/node/shared/default-setting-improvement-prompt.js +52 -38
- package/dist/node/shared/display-regex.js +114 -0
- package/dist/node/shared/first-party-action-choices.js +1 -6
- package/dist/node/shared/first-party-player-view.js +1 -6
- package/dist/node/shared/ordered-author-prompts.js +64 -0
- package/dist/node/shared/ordered-followups.js +42 -0
- package/dist/node/shared/ordered-play-prompts.js +73 -0
- package/dist/web/assets/index-DG85hpWC.css +1 -0
- package/dist/web/assets/index-DfxvCjb6.js +1207 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/node/runtime/model/SettingAuthorStream.js +0 -21
- package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +0 -1731
- package/dist/web/assets/index-D8YKfmNG.js +0 -1192
- package/dist/web/assets/index-Xa0s35CJ.css +0 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { appendFile, chmod, mkdir, stat } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
-
import { defaultAppLocale, } from "../../protocol/appPreferences.js";
|
|
5
4
|
import { hasCLIProxyThinkingSuffix, modelReasoningPolicyIssue, } from "../../protocol/modelConnections.js";
|
|
6
5
|
import { ModelHostBindingMismatchError, ModelHostCancelledError, ModelHostContinuationError, ModelHostFailureError, ModelHostOutcomeUnknownError, } from "./ModelHost.js";
|
|
7
6
|
import { renderPromptDeltaMessage, } from "../prompt/FileNativePromptCompiler.js";
|
|
8
|
-
import { aggregateAnthropicSettingStream, aggregateChatSettingStream, } from "./SettingAuthorStream.js";
|
|
9
7
|
import { aggregateAnthropicModelStream, aggregateChatModelStream, aggregateResponsesModelStream, } from "./ModelHostStream.js";
|
|
10
8
|
import { defaultRuntimeToolDefinitionStrategy, portableRuntimeToolInputSchema, } from "../prompt/FileNativeToolRegistry.js";
|
|
11
9
|
import { AiExchangeCapture, errorDescription, } from "./AiFailureLog.js";
|
|
@@ -347,9 +345,11 @@ function modelDiagnosticContext(request) {
|
|
|
347
345
|
return {
|
|
348
346
|
scope: request.requestId === "play_call_chain"
|
|
349
347
|
? "play_call_chain"
|
|
350
|
-
: request.operationId?.
|
|
351
|
-
? "
|
|
352
|
-
: "
|
|
348
|
+
: request.operationId?.startsWith("setting-") === true
|
|
349
|
+
? "setting_improvement"
|
|
350
|
+
: request.operationId?.includes(":followup:") === true
|
|
351
|
+
? "play_followup"
|
|
352
|
+
: "model_host",
|
|
353
353
|
...(request.requestId === undefined
|
|
354
354
|
? {}
|
|
355
355
|
: { requestId: request.requestId }),
|
|
@@ -376,15 +376,6 @@ function captureDelta(capture, downstream) {
|
|
|
376
376
|
downstream?.(delta);
|
|
377
377
|
};
|
|
378
378
|
}
|
|
379
|
-
function captureSettingDelta(capture, downstream) {
|
|
380
|
-
if (capture === undefined)
|
|
381
|
-
return downstream;
|
|
382
|
-
return (delta) => {
|
|
383
|
-
if (delta.kind === "reasoning")
|
|
384
|
-
capture.observeReasoning(delta.text);
|
|
385
|
-
downstream?.(delta);
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
379
|
async function capturedProviderOperation(failureLog, capture, operation) {
|
|
389
380
|
const observeResponse = (response) => capture?.captureResponse(response) ?? response;
|
|
390
381
|
try {
|
|
@@ -407,6 +398,10 @@ async function capturedProviderOperation(failureLog, capture, operation) {
|
|
|
407
398
|
message: error instanceof Error
|
|
408
399
|
? error.message
|
|
409
400
|
: "Provider request processing failed.",
|
|
401
|
+
...(error instanceof ModelHostFailureError &&
|
|
402
|
+
error.details !== undefined
|
|
403
|
+
? { details: error.details }
|
|
404
|
+
: {}),
|
|
410
405
|
error: errorDescription(error),
|
|
411
406
|
};
|
|
412
407
|
await failureLog.recordFailure({ exchange, failures: [failure] });
|
|
@@ -432,323 +427,6 @@ async function trace(kind, value) {
|
|
|
432
427
|
// Explicit acceptance diagnostics must never change provider semantics.
|
|
433
428
|
}
|
|
434
429
|
}
|
|
435
|
-
export class FileNativeSettingAuthorProvider {
|
|
436
|
-
#connection;
|
|
437
|
-
#fetch;
|
|
438
|
-
#failureLog;
|
|
439
|
-
#operationId;
|
|
440
|
-
#locale;
|
|
441
|
-
#exchange = 0;
|
|
442
|
-
constructor(connection, fetchImplementation = fetch, diagnostics) {
|
|
443
|
-
this.#connection = resolveConnection(connection);
|
|
444
|
-
this.#fetch = fetchImplementation;
|
|
445
|
-
this.#failureLog = diagnostics?.failureLog;
|
|
446
|
-
this.#operationId = diagnostics?.operationId;
|
|
447
|
-
this.#locale = diagnostics?.locale ?? defaultAppLocale;
|
|
448
|
-
}
|
|
449
|
-
async next(request) {
|
|
450
|
-
this.#exchange += 1;
|
|
451
|
-
const diagnosticContext = {
|
|
452
|
-
scope: "setting_improvement",
|
|
453
|
-
requestId: "setting_improvement",
|
|
454
|
-
...(this.#operationId === undefined
|
|
455
|
-
? {}
|
|
456
|
-
: { operationId: this.#operationId }),
|
|
457
|
-
requestAttempt: 1,
|
|
458
|
-
exchange: this.#exchange,
|
|
459
|
-
};
|
|
460
|
-
const tools = request.tools.map((name) => settingToolDefinition(name, this.#locale));
|
|
461
|
-
if (this.#connection.provider === "chat_completions") {
|
|
462
|
-
const body = {
|
|
463
|
-
model: this.#connection.modelId,
|
|
464
|
-
...chatReasoningRequest(this.#connection),
|
|
465
|
-
messages: chatSettingMessages(this.#connection, request.messages),
|
|
466
|
-
...(this.#connection.dialect === "cliproxyapi"
|
|
467
|
-
? {
|
|
468
|
-
prompt_cache_key: settingPromptCacheKey(this.#connection, request.messages, this.#operationId),
|
|
469
|
-
}
|
|
470
|
-
: {}),
|
|
471
|
-
...(tools.length === 0
|
|
472
|
-
? {}
|
|
473
|
-
: {
|
|
474
|
-
tools: tools.map((tool) => ({
|
|
475
|
-
type: "function",
|
|
476
|
-
function: {
|
|
477
|
-
name: tool.name,
|
|
478
|
-
description: tool.description,
|
|
479
|
-
parameters: tool.schema,
|
|
480
|
-
},
|
|
481
|
-
})),
|
|
482
|
-
}),
|
|
483
|
-
max_tokens: Math.min(request.maxOutputTokens, this.#connection.maxOutputTokens),
|
|
484
|
-
stream: true,
|
|
485
|
-
stream_options: { include_usage: true },
|
|
486
|
-
};
|
|
487
|
-
await trace("setting_chat_request", body);
|
|
488
|
-
const bodyJson = JSON.stringify(body);
|
|
489
|
-
const url = providerUrl(this.#connection);
|
|
490
|
-
const capture = providerCapture(this.#failureLog, this.#connection.provider, url, diagnosticContext, bodyJson);
|
|
491
|
-
const diagnosticDelta = captureSettingDelta(capture, request.onDelta);
|
|
492
|
-
return capturedProviderOperation(this.#failureLog, capture, async (observe) => {
|
|
493
|
-
const response = observe(await this.#fetch(url, {
|
|
494
|
-
method: "POST",
|
|
495
|
-
headers: {
|
|
496
|
-
Authorization: `Bearer ${this.#connection.apiKey}`,
|
|
497
|
-
"Content-Type": "application/json",
|
|
498
|
-
},
|
|
499
|
-
body: bodyJson,
|
|
500
|
-
}));
|
|
501
|
-
if (!response.ok)
|
|
502
|
-
throw await providerError(response);
|
|
503
|
-
if (response.body === null)
|
|
504
|
-
throw new Error("The authoring response is missing a streaming body");
|
|
505
|
-
const streamed = await aggregateChatSettingStream(response.body, diagnosticDelta);
|
|
506
|
-
capture?.setReasoning(streamed.reasoningContent);
|
|
507
|
-
await trace("setting_chat_response", streamed);
|
|
508
|
-
return {
|
|
509
|
-
role: "assistant",
|
|
510
|
-
content: streamed.content,
|
|
511
|
-
...(streamed.reasoningContent === ""
|
|
512
|
-
? {}
|
|
513
|
-
: { reasoningContent: streamed.reasoningContent }),
|
|
514
|
-
providerState: {
|
|
515
|
-
protocol: "chat_completions",
|
|
516
|
-
assistantMessage: cloneProviderValue(streamed.assistantMessage),
|
|
517
|
-
},
|
|
518
|
-
toolCalls: streamed.toolCalls.map((call) => ({
|
|
519
|
-
id: call.id,
|
|
520
|
-
name: call.name,
|
|
521
|
-
arguments: asArguments(parseArguments(call.arguments)),
|
|
522
|
-
})),
|
|
523
|
-
usage: settingAuthorUsage(normalizeChatUsage(streamed.usage)),
|
|
524
|
-
};
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
if (this.#connection.provider === "openai_responses") {
|
|
528
|
-
const response = await openAIResponsesRequest({
|
|
529
|
-
connection: this.#connection,
|
|
530
|
-
fetchImplementation: this.#fetch,
|
|
531
|
-
input: responsesSettingInput(this.#connection, request.messages),
|
|
532
|
-
tools: tools.map((tool) => ({
|
|
533
|
-
name: tool.name,
|
|
534
|
-
description: tool.description,
|
|
535
|
-
parameters: tool.schema,
|
|
536
|
-
})),
|
|
537
|
-
maxOutputTokens: Math.min(request.maxOutputTokens, this.#connection.maxOutputTokens),
|
|
538
|
-
promptCacheKey: settingPromptCacheKey(this.#connection, request.messages, this.#operationId),
|
|
539
|
-
tracePrefix: "setting_responses",
|
|
540
|
-
...(this.#failureLog === undefined
|
|
541
|
-
? {}
|
|
542
|
-
: { failureLog: this.#failureLog }),
|
|
543
|
-
diagnosticContext,
|
|
544
|
-
...(request.onDelta === undefined ? {} : { onDelta: request.onDelta }),
|
|
545
|
-
});
|
|
546
|
-
return {
|
|
547
|
-
role: "assistant",
|
|
548
|
-
content: response.text ?? "",
|
|
549
|
-
...(response.reasoningContent === undefined
|
|
550
|
-
? {}
|
|
551
|
-
: { reasoningContent: response.reasoningContent }),
|
|
552
|
-
providerState: response.providerState,
|
|
553
|
-
toolCalls: (response.toolCalls ?? []).map((call) => ({
|
|
554
|
-
...call,
|
|
555
|
-
arguments: asArguments(call.arguments),
|
|
556
|
-
})),
|
|
557
|
-
usage: settingAuthorUsage(response.usage),
|
|
558
|
-
...(response.diagnostics === undefined
|
|
559
|
-
? {}
|
|
560
|
-
: { diagnostics: response.diagnostics }),
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
const system = request.messages
|
|
564
|
-
.filter(({ role }) => role === "system")
|
|
565
|
-
.map(({ content }, index, messages) => ({
|
|
566
|
-
type: "text",
|
|
567
|
-
text: content,
|
|
568
|
-
...(index === messages.length - 1
|
|
569
|
-
? { cache_control: { type: "ephemeral" } }
|
|
570
|
-
: {}),
|
|
571
|
-
}));
|
|
572
|
-
const body = {
|
|
573
|
-
model: this.#connection.modelId,
|
|
574
|
-
...anthropicReasoningRequest(this.#connection),
|
|
575
|
-
...(system.length === 0 ? {} : { system }),
|
|
576
|
-
messages: anthropicSettingMessages(request.messages),
|
|
577
|
-
...(tools.length === 0
|
|
578
|
-
? {}
|
|
579
|
-
: {
|
|
580
|
-
tools: tools.map((tool) => ({
|
|
581
|
-
name: tool.name,
|
|
582
|
-
description: tool.description,
|
|
583
|
-
input_schema: tool.schema,
|
|
584
|
-
})),
|
|
585
|
-
}),
|
|
586
|
-
max_tokens: Math.min(request.maxOutputTokens, this.#connection.maxOutputTokens),
|
|
587
|
-
stream: true,
|
|
588
|
-
};
|
|
589
|
-
await trace("setting_anthropic_request", body);
|
|
590
|
-
const bodyJson = JSON.stringify(body);
|
|
591
|
-
const url = providerUrl(this.#connection);
|
|
592
|
-
const capture = providerCapture(this.#failureLog, this.#connection.provider, url, diagnosticContext, bodyJson);
|
|
593
|
-
const diagnosticDelta = captureSettingDelta(capture, request.onDelta);
|
|
594
|
-
return capturedProviderOperation(this.#failureLog, capture, async (observe) => {
|
|
595
|
-
const response = observe(await this.#fetch(url, {
|
|
596
|
-
method: "POST",
|
|
597
|
-
headers: {
|
|
598
|
-
"anthropic-version": "2023-06-01",
|
|
599
|
-
"x-api-key": this.#connection.apiKey,
|
|
600
|
-
"Content-Type": "application/json",
|
|
601
|
-
},
|
|
602
|
-
body: bodyJson,
|
|
603
|
-
}));
|
|
604
|
-
if (!response.ok)
|
|
605
|
-
throw await providerError(response);
|
|
606
|
-
if (response.body === null)
|
|
607
|
-
throw new Error("The authoring response is missing a streaming body");
|
|
608
|
-
const streamed = await aggregateAnthropicSettingStream(response.body, diagnosticDelta);
|
|
609
|
-
capture?.setReasoning(streamed.reasoningContent);
|
|
610
|
-
await trace("setting_anthropic_response", streamed);
|
|
611
|
-
return {
|
|
612
|
-
role: "assistant",
|
|
613
|
-
content: streamed.content,
|
|
614
|
-
...(streamed.reasoningContent === ""
|
|
615
|
-
? {}
|
|
616
|
-
: { reasoningContent: streamed.reasoningContent }),
|
|
617
|
-
providerState: {
|
|
618
|
-
protocol: "anthropic_messages",
|
|
619
|
-
content: cloneProviderValue(streamed.providerContent),
|
|
620
|
-
...(streamed.responseId === undefined
|
|
621
|
-
? {}
|
|
622
|
-
: { responseId: streamed.responseId }),
|
|
623
|
-
...(streamed.model === undefined ? {} : { model: streamed.model }),
|
|
624
|
-
...(streamed.stopReason === undefined
|
|
625
|
-
? {}
|
|
626
|
-
: { stopReason: streamed.stopReason }),
|
|
627
|
-
},
|
|
628
|
-
toolCalls: streamed.toolCalls.map((call) => ({
|
|
629
|
-
id: call.id,
|
|
630
|
-
name: call.name,
|
|
631
|
-
arguments: asArguments(structuredClone(call.arguments)),
|
|
632
|
-
})),
|
|
633
|
-
usage: settingAuthorUsage(normalizeAnthropicUsage(streamed.usage)),
|
|
634
|
-
};
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
// Setting authoring uses the same complete usage projection as play. Missing
|
|
639
|
-
// Provider usage stays unavailable instead of being counted as zero.
|
|
640
|
-
function settingAuthorUsage(usage) {
|
|
641
|
-
if (usage !== undefined)
|
|
642
|
-
return structuredClone(usage);
|
|
643
|
-
return usageRecord({
|
|
644
|
-
inputTokens: null,
|
|
645
|
-
uncachedInputTokens: null,
|
|
646
|
-
cacheReadTokens: null,
|
|
647
|
-
cacheWriteTokens: null,
|
|
648
|
-
reasoningTokens: null,
|
|
649
|
-
outputTokens: null,
|
|
650
|
-
totalTokens: null,
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
function settingToolDefinition(name, locale = defaultAppLocale) {
|
|
654
|
-
const descriptions = settingToolDescriptions[locale];
|
|
655
|
-
const schema = (properties, required) => ({
|
|
656
|
-
type: "object",
|
|
657
|
-
additionalProperties: false,
|
|
658
|
-
properties,
|
|
659
|
-
required,
|
|
660
|
-
});
|
|
661
|
-
const text = { type: "string", minLength: 1 };
|
|
662
|
-
const path = { type: "string", minLength: 1 };
|
|
663
|
-
const cursor = { type: "string", minLength: 1 };
|
|
664
|
-
const limit = { type: "integer", minimum: 1, maximum: 100 };
|
|
665
|
-
const definitions = {
|
|
666
|
-
setting_list: {
|
|
667
|
-
description: descriptions.setting_list,
|
|
668
|
-
schema: schema({
|
|
669
|
-
// World document paths accept any segment that is not "."/".."; an
|
|
670
|
-
// ASCII-only pattern here made directories the author had just
|
|
671
|
-
// created impossible to list back.
|
|
672
|
-
directory: {
|
|
673
|
-
type: "string",
|
|
674
|
-
pattern: "^world(?:/[^\\\\]+)?$",
|
|
675
|
-
},
|
|
676
|
-
limit,
|
|
677
|
-
cursor,
|
|
678
|
-
}, []),
|
|
679
|
-
},
|
|
680
|
-
setting_search: {
|
|
681
|
-
description: descriptions.setting_search,
|
|
682
|
-
schema: schema({
|
|
683
|
-
query: { type: "string", minLength: 1, maxLength: 256 },
|
|
684
|
-
within: path,
|
|
685
|
-
caseSensitive: { type: "boolean" },
|
|
686
|
-
limit,
|
|
687
|
-
cursor,
|
|
688
|
-
}, ["query"]),
|
|
689
|
-
},
|
|
690
|
-
setting_read: {
|
|
691
|
-
description: descriptions.setting_read,
|
|
692
|
-
schema: schema({
|
|
693
|
-
path,
|
|
694
|
-
maxBytes: { type: "integer", minimum: 4, maximum: 65_536 },
|
|
695
|
-
cursor,
|
|
696
|
-
}, ["path"]),
|
|
697
|
-
},
|
|
698
|
-
setting_write_file: {
|
|
699
|
-
description: descriptions.setting_write_file,
|
|
700
|
-
schema: schema({ path, contents: text }, ["path", "contents"]),
|
|
701
|
-
},
|
|
702
|
-
setting_patch: {
|
|
703
|
-
description: descriptions.setting_patch,
|
|
704
|
-
schema: schema({
|
|
705
|
-
document: text,
|
|
706
|
-
op: { type: "string", enum: ["add", "replace"] },
|
|
707
|
-
locator: {
|
|
708
|
-
type: "array",
|
|
709
|
-
minItems: 1,
|
|
710
|
-
items: { type: "string", minLength: 1 },
|
|
711
|
-
},
|
|
712
|
-
value: {},
|
|
713
|
-
}, ["document", "op", "locator", "value"]),
|
|
714
|
-
},
|
|
715
|
-
setting_move: {
|
|
716
|
-
description: descriptions.setting_move,
|
|
717
|
-
schema: schema({ from: text, to: path }, ["from", "to"]),
|
|
718
|
-
},
|
|
719
|
-
setting_preview_candidate: {
|
|
720
|
-
description: descriptions.setting_preview_candidate,
|
|
721
|
-
schema: schema({}, []),
|
|
722
|
-
},
|
|
723
|
-
setting_finish_candidate: {
|
|
724
|
-
description: descriptions.setting_finish_candidate,
|
|
725
|
-
schema: schema({}, []),
|
|
726
|
-
},
|
|
727
|
-
};
|
|
728
|
-
return { name, ...definitions[name] };
|
|
729
|
-
}
|
|
730
|
-
const settingToolDescriptions = {
|
|
731
|
-
en: {
|
|
732
|
-
setting_list: "List the world root or a world/ subdirectory from the current candidate-document snapshot. The root also lists specialized opening and control files. A pagination cursor is valid only for the same query against the candidate snapshot that produced it.",
|
|
733
|
-
setting_search: "Search literal source text in world documents from the current candidate snapshot. within may be a world/ directory, world/ document path, @short-ref, or document identity.",
|
|
734
|
-
setting_read: "Precisely read a world-document body from the current candidate snapshot by world/ path, @short-ref, or document identity. Read opening and control files through their exact dedicated paths. Write authorization is granted only after every page has been read; later candidate changes do not revoke it.",
|
|
735
|
-
setting_write_file: "Create or replace one complete candidate file. path is always a full logical path: a .yaml or .md world document under world/, control/frame.yaml, control/player-views.yaml, control/blocks/*.md, or the root opening.md. A new world document must include a complete body with its $document technical header. When replacing an existing world document, you may omit $document and write back the body returned by setting_read; Runtime preserves the existing title, summary, and aliases. Runtime always decides id and ref: it allocates them for a new document and preserves them for an existing one, regardless of values you supply. Existing world documents and an existing opening.md must be read completely before replacement.",
|
|
736
|
-
setting_patch: 'Patch one YAML map node by document id, @short-ref, or world/ logical path and revision. op must explicitly be add or replace. locator is a stable array of map keys, for example ["relationships","player","trust"]. The existing document must already have been read completely. Rewrite Markdown documents in full with setting_write_file.',
|
|
737
|
-
setting_move: "Move a completely read world document to a new world/ logical path by revision. Use this to repair catalog-directory association while preserving document content and identity.",
|
|
738
|
-
setting_preview_candidate: "Run mechanical content-tree checks and the real Prompt Preview against the isolated candidate, returning diagnostics for path association, slots, references, and material coverage. Call this after all final changes; the candidate may finish only after it passes. A failed preview is a normal iteration: repair the diagnostics and call it again.",
|
|
739
|
-
setting_finish_candidate: "Call only after setting_preview_candidate has passed for the final changes. It may immediately follow the passing preview in the same model response. It must be the last call in that response and takes no arguments.",
|
|
740
|
-
},
|
|
741
|
-
"zh-CN": {
|
|
742
|
-
setting_list: "通过当前候选文档快照列出 world 或 world/ 子目录;根目录同时显示 opening/control 等专用文件。分页 cursor 只能用于产生它的同一候选快照和相同查询。",
|
|
743
|
-
setting_search: "通过当前候选文档快照原文字面搜索 world 文档;within 可取 world/ 目录、world/ 文档路径、@短引用或文档身份。",
|
|
744
|
-
setting_read: "通过当前候选文档快照按 world/ 路径、@短引用或文档身份精确读取世界文档正文;opening/control 使用精确路径专用读取。只有读完全部分页才获得写授权,此后不因其他修改失效。",
|
|
745
|
-
setting_write_file: "创建或整份写入一个候选文件,path 一律是完整逻辑路径:world/ 下的 .yaml 或 .md 世界文档、control/frame.yaml、control/player-views.yaml、control/blocks/*.md,或根级 opening.md。新建世界文档写完整原文并带 $document 技术头;覆盖既有世界文档可以省略 $document,直接写回 setting_read 返回的正文,Runtime 会沿用原有 title、summary 和 aliases。id 和 ref 始终由 Runtime 决定——新建时分配,覆盖时保留原值,你写什么都不会报错。既有 world 文档和既有 opening.md 必须先完整读取。",
|
|
746
|
-
setting_patch: '通过 revision 按文档 ID、@短引用或 world/ 逻辑路径 patch 一个 YAML map 节点;op 必须明确选择 add(新增)或 replace(替换),locator 是稳定 map-key 数组,例如 ["关系","对玩家","信任"]。既有文档必须已完整读取。Markdown 文档请用 setting_write_file 整份重写。',
|
|
747
|
-
setting_move: "通过 revision 把已完整读取的 world 文档移动到新的 world/ 逻辑路径;用于修复 catalog 目录关联,保留文档内容和身份。",
|
|
748
|
-
setting_preview_candidate: "对当前隔离候选运行内容树机械检查和真实 Prompt Preview,并返回路径关联、slot、引用和材料覆盖诊断;所有最终修改后调用,通过后才可结束候选。未通过属于正常迭代,修复后重新调用即可。",
|
|
749
|
-
setting_finish_candidate: "setting_preview_candidate 已对最终修改返回自检通过后调用,可以紧接在同一模型响应的自检之后;必须是当前响应的最后一个调用,且不得携带参数。",
|
|
750
|
-
},
|
|
751
|
-
};
|
|
752
430
|
function items(request) {
|
|
753
431
|
return request.appended;
|
|
754
432
|
}
|
|
@@ -860,6 +538,9 @@ function responsesRequestBody(input) {
|
|
|
860
538
|
name: tool.name,
|
|
861
539
|
description: tool.description,
|
|
862
540
|
parameters: tool.parameters,
|
|
541
|
+
// Runtime schemas distinguish absent options from supplied values.
|
|
542
|
+
// Responses otherwise normalizes compatible schemas to strict mode.
|
|
543
|
+
strict: false,
|
|
863
544
|
})),
|
|
864
545
|
tool_choice: toolChoice,
|
|
865
546
|
}),
|
|
@@ -967,20 +648,11 @@ function promptCacheKey(connection, request) {
|
|
|
967
648
|
.update(`${connection.provider}\0${connection.dialect}\0${connection.modelId}\0${scope}`)
|
|
968
649
|
.digest("hex");
|
|
969
650
|
}
|
|
970
|
-
function settingPromptCacheKey(connection, messages, operationId) {
|
|
971
|
-
const firstAssistant = messages.findIndex(({ role }) => role === "assistant");
|
|
972
|
-
const initialPrefix = messages.slice(0, firstAssistant < 0 ? messages.length : firstAssistant);
|
|
973
|
-
const stablePrefixScope = JSON.stringify(initialPrefix);
|
|
974
|
-
const scope = connection.dialect === "cliproxyapi"
|
|
975
|
-
? (operationId ?? stablePrefixScope)
|
|
976
|
-
: stablePrefixScope;
|
|
977
|
-
return createHash("sha256")
|
|
978
|
-
.update(`${connection.provider}\0${connection.dialect}\0${connection.modelId}\0${scope}`)
|
|
979
|
-
.digest("hex");
|
|
980
|
-
}
|
|
981
651
|
function chatAppend(request) {
|
|
982
652
|
return items(request).flatMap((item) => {
|
|
983
|
-
if (item.kind === "player"
|
|
653
|
+
if (item.kind === "player" ||
|
|
654
|
+
item.kind === "user" ||
|
|
655
|
+
item.kind === "runtime_notice")
|
|
984
656
|
return [{ role: "user", content: item.text }];
|
|
985
657
|
if (item.kind === "prompt_delta")
|
|
986
658
|
return promptDeltaProviderMessages(item.logicalMessages);
|
|
@@ -1020,7 +692,9 @@ function anthropicAppend(request) {
|
|
|
1020
692
|
continue;
|
|
1021
693
|
}
|
|
1022
694
|
flushToolResults();
|
|
1023
|
-
if (item.kind === "player"
|
|
695
|
+
if (item.kind === "player" ||
|
|
696
|
+
item.kind === "user" ||
|
|
697
|
+
item.kind === "runtime_notice") {
|
|
1024
698
|
messages.push({ role: "user", content: item.text });
|
|
1025
699
|
continue;
|
|
1026
700
|
}
|
|
@@ -1043,7 +717,9 @@ function anthropicAppend(request) {
|
|
|
1043
717
|
}
|
|
1044
718
|
function responsesAppend(request) {
|
|
1045
719
|
return items(request).flatMap((item) => {
|
|
1046
|
-
if (item.kind === "player"
|
|
720
|
+
if (item.kind === "player" ||
|
|
721
|
+
item.kind === "user" ||
|
|
722
|
+
item.kind === "runtime_notice")
|
|
1047
723
|
return [{ role: "user", content: item.text }];
|
|
1048
724
|
if (item.kind === "prompt_delta")
|
|
1049
725
|
return promptDeltaProviderMessages(item.logicalMessages);
|
|
@@ -1173,122 +849,6 @@ async function providerError(response) {
|
|
|
1173
849
|
});
|
|
1174
850
|
return new ModelHostFailureError(`Provider request failed: ${response.status} ${text}`);
|
|
1175
851
|
}
|
|
1176
|
-
function chatSettingMessages(connection, messages) {
|
|
1177
|
-
const translated = messages.map(chatSettingMessage);
|
|
1178
|
-
if (connection.dialect !== "cliproxyapi")
|
|
1179
|
-
return translated;
|
|
1180
|
-
return attachCacheControlAtIndex(attachCacheControlToLastRole(translated, "system"), settingPrefixUserIndex(messages));
|
|
1181
|
-
}
|
|
1182
|
-
function chatSettingMessage(message) {
|
|
1183
|
-
if (message.role === "tool")
|
|
1184
|
-
return {
|
|
1185
|
-
role: "tool",
|
|
1186
|
-
tool_call_id: message.toolCallId,
|
|
1187
|
-
content: message.content,
|
|
1188
|
-
};
|
|
1189
|
-
if (message.role === "assistant") {
|
|
1190
|
-
const state = message.providerState;
|
|
1191
|
-
if (state?.protocol === "chat_completions" &&
|
|
1192
|
-
validChatAssistantMessage(state.assistantMessage))
|
|
1193
|
-
return cloneProviderValue(state.assistantMessage);
|
|
1194
|
-
throw continuationError("Chat Completions setting improvement");
|
|
1195
|
-
}
|
|
1196
|
-
return {
|
|
1197
|
-
role: message.role === "system" ? "system" : "user",
|
|
1198
|
-
content: message.content,
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
|
-
function anthropicSettingMessages(source) {
|
|
1202
|
-
const messages = [];
|
|
1203
|
-
const cacheableUserIndex = settingPrefixUserIndex(source);
|
|
1204
|
-
let toolResults = [];
|
|
1205
|
-
const flushToolResults = () => {
|
|
1206
|
-
if (toolResults.length === 0)
|
|
1207
|
-
return;
|
|
1208
|
-
messages.push({ role: "user", content: toolResults });
|
|
1209
|
-
toolResults = [];
|
|
1210
|
-
};
|
|
1211
|
-
for (const [index, message] of source.entries()) {
|
|
1212
|
-
if (message.role === "system")
|
|
1213
|
-
continue;
|
|
1214
|
-
if (message.role === "tool") {
|
|
1215
|
-
toolResults.push({
|
|
1216
|
-
type: "tool_result",
|
|
1217
|
-
tool_use_id: message.toolCallId,
|
|
1218
|
-
content: message.content,
|
|
1219
|
-
...(message.isError === true ? { is_error: true } : {}),
|
|
1220
|
-
});
|
|
1221
|
-
continue;
|
|
1222
|
-
}
|
|
1223
|
-
flushToolResults();
|
|
1224
|
-
if (message.role === "assistant") {
|
|
1225
|
-
if (message.providerState?.protocol !== "anthropic_messages" ||
|
|
1226
|
-
!validAnthropicContent(message.providerState.content))
|
|
1227
|
-
throw continuationError("Anthropic Messages setting improvement");
|
|
1228
|
-
messages.push({
|
|
1229
|
-
role: "assistant",
|
|
1230
|
-
content: cloneProviderValue(message.providerState.content),
|
|
1231
|
-
});
|
|
1232
|
-
continue;
|
|
1233
|
-
}
|
|
1234
|
-
messages.push({
|
|
1235
|
-
role: "user",
|
|
1236
|
-
content: index === cacheableUserIndex
|
|
1237
|
-
? [
|
|
1238
|
-
{
|
|
1239
|
-
type: "text",
|
|
1240
|
-
text: message.content,
|
|
1241
|
-
cache_control: { type: "ephemeral" },
|
|
1242
|
-
},
|
|
1243
|
-
]
|
|
1244
|
-
: message.content,
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
flushToolResults();
|
|
1248
|
-
return messages;
|
|
1249
|
-
}
|
|
1250
|
-
function responsesSettingInput(connection, messages) {
|
|
1251
|
-
const translated = messages.flatMap(responsesSettingMessage);
|
|
1252
|
-
if (connection.dialect !== "cliproxyapi")
|
|
1253
|
-
return translated;
|
|
1254
|
-
return attachCacheControlAtIndex(attachCacheControlToLastRole(translated, "system"), settingPrefixUserIndex(messages));
|
|
1255
|
-
}
|
|
1256
|
-
function settingPrefixUserIndex(messages) {
|
|
1257
|
-
const firstAssistant = messages.findIndex(({ role }) => role === "assistant");
|
|
1258
|
-
const end = firstAssistant < 0 ? messages.length : firstAssistant;
|
|
1259
|
-
let target = -1;
|
|
1260
|
-
for (let index = 0; index < end; index += 1)
|
|
1261
|
-
if (messages[index]?.role === "user")
|
|
1262
|
-
target = index;
|
|
1263
|
-
return target;
|
|
1264
|
-
}
|
|
1265
|
-
function attachCacheControlAtIndex(messages, index) {
|
|
1266
|
-
const result = cloneProviderValue(messages);
|
|
1267
|
-
if (index < 0 || !isRecord(result[index]))
|
|
1268
|
-
return result;
|
|
1269
|
-
result[index] = {
|
|
1270
|
-
...result[index],
|
|
1271
|
-
cache_control: { type: "ephemeral" },
|
|
1272
|
-
};
|
|
1273
|
-
return result;
|
|
1274
|
-
}
|
|
1275
|
-
function responsesSettingMessage(message) {
|
|
1276
|
-
if (message.role === "tool")
|
|
1277
|
-
return [
|
|
1278
|
-
{
|
|
1279
|
-
type: "function_call_output",
|
|
1280
|
-
call_id: message.toolCallId,
|
|
1281
|
-
output: message.content,
|
|
1282
|
-
},
|
|
1283
|
-
];
|
|
1284
|
-
if (message.role === "assistant" &&
|
|
1285
|
-
message.providerState?.protocol === "openai_responses" &&
|
|
1286
|
-
validResponsesOutput(message.providerState.output))
|
|
1287
|
-
return cloneResponseOutput(message.providerState.output);
|
|
1288
|
-
if (message.role === "assistant")
|
|
1289
|
-
throw continuationError("OpenAI Responses setting improvement");
|
|
1290
|
-
return [{ role: message.role, content: message.content }];
|
|
1291
|
-
}
|
|
1292
852
|
function responsesBootstrapMessage(message, preserveCacheControl = false) {
|
|
1293
853
|
return {
|
|
1294
854
|
role: message.role,
|
|
@@ -1679,11 +1239,6 @@ function signalWasAborted(signal) {
|
|
|
1679
1239
|
function unknownProviderResponse(label) {
|
|
1680
1240
|
return new ModelHostOutcomeUnknownError(`${label} was dispatched but its response shape could not be confirmed`);
|
|
1681
1241
|
}
|
|
1682
|
-
function asArguments(value) {
|
|
1683
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1684
|
-
throw new Error("Authoring tool arguments must be an object");
|
|
1685
|
-
return value;
|
|
1686
|
-
}
|
|
1687
1242
|
function isRecord(value) {
|
|
1688
1243
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1689
1244
|
}
|
|
@@ -2,6 +2,8 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { chmod, mkdir, open, readFile, rename, stat, unlink, } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { hasCLIProxyThinkingSuffix, modelReasoningPolicyIssue, modelProviderPresets, } from "../../protocol/modelConnections.js";
|
|
5
|
+
import { FileNativeModelHost, } from "./FileNativeModelAdapters.js";
|
|
6
|
+
import { equalModelHostBinding, ModelHostBindingMismatchError, } from "./ModelHost.js";
|
|
5
7
|
const libraryFileName = "model-connections-v1.json";
|
|
6
8
|
const maxModelListBytes = 1024 * 1024;
|
|
7
9
|
const maxStoredConnections = 64;
|
|
@@ -105,6 +107,17 @@ export class ModelConnectionStore {
|
|
|
105
107
|
throw new ModelConnectionUnavailableError();
|
|
106
108
|
return toBinding(connection);
|
|
107
109
|
}
|
|
110
|
+
/** Resolve a frozen logical operation without changing the active model. */
|
|
111
|
+
async bindMatching(binding) {
|
|
112
|
+
await this.#mutationTail;
|
|
113
|
+
const document = await this.#readDocument();
|
|
114
|
+
for (const connection of document.connections) {
|
|
115
|
+
const candidate = toBinding(connection);
|
|
116
|
+
if (equalModelHostBinding(new FileNativeModelHost(candidate).binding(), binding))
|
|
117
|
+
return candidate;
|
|
118
|
+
}
|
|
119
|
+
throw new ModelHostBindingMismatchError("No saved model connection matches this setting-improvement conversation");
|
|
120
|
+
}
|
|
108
121
|
async listModels(input) {
|
|
109
122
|
await this.#mutationTail;
|
|
110
123
|
const provider = validateProvider(input.provider);
|
|
@@ -23,14 +23,44 @@ export class ModelHostContinuationError extends Error {
|
|
|
23
23
|
this.name = "ModelHostContinuationError";
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
/** A deterministic provider failure: the operation may be failed safely. */
|
|
27
26
|
export class ModelHostFailureError extends Error {
|
|
28
27
|
kind = "failed";
|
|
28
|
+
details;
|
|
29
29
|
constructor(message, options) {
|
|
30
30
|
super(message, options);
|
|
31
31
|
this.name = "ModelHostFailureError";
|
|
32
|
+
if (options?.details !== undefined)
|
|
33
|
+
this.details = options.details;
|
|
32
34
|
}
|
|
33
35
|
}
|
|
36
|
+
/** A Provider has rejected the complete request because its context is full. */
|
|
37
|
+
export function modelHostFailureRequiresFreshContext(error) {
|
|
38
|
+
if (!(error instanceof ModelHostFailureError))
|
|
39
|
+
return false;
|
|
40
|
+
const evidence = [error.message, ...failureDetailStrings(error.details)]
|
|
41
|
+
.join("\n")
|
|
42
|
+
.toLocaleLowerCase("und");
|
|
43
|
+
return [
|
|
44
|
+
/context[_ -](?:length|window)[_ -](?:exceeded|overflow)/u,
|
|
45
|
+
/maximum context length/u,
|
|
46
|
+
/prompt[_ -](?:is[_ -])?too[_ -]long/u,
|
|
47
|
+
/too many input tokens/u,
|
|
48
|
+
/input (?:is )?too long/u,
|
|
49
|
+
].some((pattern) => pattern.test(evidence));
|
|
50
|
+
}
|
|
51
|
+
function failureDetailStrings(value, depth = 0, seen = new WeakSet()) {
|
|
52
|
+
if (typeof value === "string" || typeof value === "number")
|
|
53
|
+
return [String(value)];
|
|
54
|
+
if (value === null || typeof value !== "object" || depth >= 4)
|
|
55
|
+
return [];
|
|
56
|
+
if (seen.has(value))
|
|
57
|
+
return [];
|
|
58
|
+
seen.add(value);
|
|
59
|
+
const children = Array.isArray(value) ? value : Object.values(value);
|
|
60
|
+
return children
|
|
61
|
+
.slice(0, 64)
|
|
62
|
+
.flatMap((child) => failureDetailStrings(child, depth + 1, seen));
|
|
63
|
+
}
|
|
34
64
|
/** The provider outcome cannot be replayed without risking a duplicate response. */
|
|
35
65
|
export class ModelHostOutcomeUnknownError extends Error {
|
|
36
66
|
kind = "unknown";
|