@semiont/jobs 0.5.9 → 0.5.10

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { JobId, UserId, ResourceId, EntityType, AnnotationId, Annotation, GatheredContext, TagSchema, Logger, EventBus, components, SupportedMediaType } from '@semiont/core';
1
+ import { JobId, UserId, ResourceId, EntityType, AnnotationId, GatheredContext, SupportedMediaType, TagSchema, Logger, EventBus, components } from '@semiont/core';
2
2
  import { SemiontProject } from '@semiont/core/node';
3
3
  import { InferenceClient } from '@semiont/inference';
4
4
 
@@ -74,10 +74,13 @@ interface DetectionParams {
74
74
  * Generation job parameters
75
75
  */
76
76
  interface GenerationParams {
77
- referenceId: AnnotationId;
78
- sourceResourceId: ResourceId;
79
- sourceResourceName: string;
80
- annotation: Annotation;
77
+ /**
78
+ * The unresolved reference an annotation-focus generation was triggered from.
79
+ * Absent for resource-focus generation (`yield.fromResource`). When present, the
80
+ * worker auto-binds the new resource to it; when absent, provenance is a minted
81
+ * source→derived reference annotation (Fork 2b).
82
+ */
83
+ referenceId?: AnnotationId;
81
84
  prompt?: string;
82
85
  title?: string;
83
86
  entityTypes?: EntityType[];
@@ -93,6 +96,13 @@ interface GenerationParams {
93
96
  temperature?: number;
94
97
  maxTokens?: number;
95
98
  storageUri?: string;
99
+ /**
100
+ * Requested media type of the generated resource's content. Default `text/markdown`.
101
+ * The generation worker produces `text/markdown` and `text/plain`; any other value
102
+ * fails the job (no silent fallback) — Semiont is multi-modal at the core, but
103
+ * generation coverage is text-only for now and the gap is surfaced, not hidden.
104
+ */
105
+ outputMediaType?: SupportedMediaType;
96
106
  }
97
107
  /**
98
108
  * Highlight detection job parameters
@@ -627,7 +637,7 @@ declare class AnnotationDetection {
627
637
  * generate German content from an English source resource. See
628
638
  * `types.ts` "Locale conventions" for the full discussion.
629
639
  */
630
- declare function generateResourceFromTopic(topic: string, entityTypes: string[], client: InferenceClient, logger: Logger, userPrompt?: string, locale?: string, context?: GatheredContext, temperature?: number, maxTokens?: number, sourceLanguage?: string): Promise<{
640
+ declare function generateResourceFromTopic(topic: string, entityTypes: string[], client: InferenceClient, logger: Logger, userPrompt?: string, locale?: string, context?: GatheredContext, temperature?: number, maxTokens?: number, sourceLanguage?: string, outputMediaType?: SupportedMediaType): Promise<{
631
641
  title: string;
632
642
  content: string;
633
643
  }>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { promises } from 'fs';
2
2
  import * as path from 'path';
3
- import { jobId, reconcileSelector, isObject, isString, getLocaleEnglishName, didToAgent, isArray } from '@semiont/core';
3
+ import { jobId, deriveViews, reconcileSelector, isObject, isString, getLocaleEnglishName, didToAgent, isArray } from '@semiont/core';
4
4
  import { generateAnnotationId } from '@semiont/event-sourcing';
5
5
 
6
6
  // src/fs-job-queue.ts
@@ -1068,7 +1068,7 @@ Example output:
1068
1068
  function getLanguageName(locale) {
1069
1069
  return getLocaleEnglishName(locale) || locale;
1070
1070
  }
1071
- async function generateResourceFromTopic(topic, entityTypes, client, logger, userPrompt, locale, context, temperature, maxTokens, sourceLanguage) {
1071
+ async function generateResourceFromTopic(topic, entityTypes, client, logger, userPrompt, locale, context, temperature, maxTokens, sourceLanguage, outputMediaType = "text/markdown") {
1072
1072
  logger.debug("Generating resource from topic", {
1073
1073
  topicPreview: topic.substring(0, 100),
1074
1074
  entityTypes,
@@ -1088,61 +1088,95 @@ IMPORTANT: Write the entire resource in ${getLanguageName(locale)}.` : "";
1088
1088
 
1089
1089
  The source resource and embedded context are in ${getLanguageName(sourceLanguage)}.` : "";
1090
1090
  let annotationSection = "";
1091
+ let contextSection = "";
1092
+ let resourceSection = "";
1093
+ let graphSection = "";
1091
1094
  if (context) {
1092
- const parts = [];
1093
- parts.push(`- Annotation motivation: ${context.annotation.motivation}`);
1094
- parts.push(`- Source resource: ${context.sourceResource.name}`);
1095
- const { motivation, body } = context.annotation;
1096
- if (motivation === "commenting" || motivation === "assessing") {
1097
- const bodyItem = Array.isArray(body) ? body[0] : body;
1098
- if (bodyItem && "value" in bodyItem && bodyItem.value) {
1099
- const label = motivation === "commenting" ? "Comment" : "Assessment";
1100
- parts.push(`- ${label}: ${bodyItem.value}`);
1095
+ const { focus } = context;
1096
+ const mainResourceId = focus.kind === "annotation" ? focus.sourceResource["@id"] : focus.resource["@id"];
1097
+ const focalAnnotationId = focus.kind === "annotation" ? focus.annotation.id : void 0;
1098
+ if (focus.kind === "annotation") {
1099
+ const parts = [];
1100
+ parts.push(`- Annotation motivation: ${focus.annotation.motivation}`);
1101
+ parts.push(`- Source resource: ${focus.sourceResource.name}`);
1102
+ const { motivation, body } = focus.annotation;
1103
+ if (motivation === "commenting" || motivation === "assessing") {
1104
+ const bodyItem = Array.isArray(body) ? body[0] : body;
1105
+ if (bodyItem && "value" in bodyItem && bodyItem.value) {
1106
+ const label = motivation === "commenting" ? "Comment" : "Assessment";
1107
+ parts.push(`- ${label}: ${bodyItem.value}`);
1108
+ }
1101
1109
  }
1102
- }
1103
- annotationSection = `
1110
+ annotationSection = `
1104
1111
 
1105
1112
  Annotation context:
1106
1113
  ${parts.join("\n")}`;
1107
- }
1108
- let contextSection = "";
1109
- if (context?.sourceContext) {
1110
- const { before, selected, after } = context.sourceContext;
1111
- contextSection = `
1114
+ if (focus.selected) {
1115
+ const { before, text, after } = focus.selected;
1116
+ contextSection = `
1112
1117
 
1113
1118
  Source document context:
1114
1119
  ---
1115
1120
  ${before ? `...${before}` : ""}
1116
- **[${selected}]**
1121
+ **[${text}]**
1117
1122
  ${after ? `${after}...` : ""}
1118
1123
  ---
1119
1124
  `;
1120
- }
1121
- let graphContextSection = "";
1122
- if (context?.graphContext) {
1123
- const gc = context.graphContext;
1124
- const connections = gc.connections ?? [];
1125
- const citedBy = gc.citedBy ?? [];
1126
- const parts = [];
1127
- if (connections.length > 0) {
1128
- const connList = connections.map((c) => `${c.resourceName}${c.entityTypes?.length ? ` (${c.entityTypes.join(", ")})` : ""}`).join(", ");
1129
- parts.push(`- Connected resources: ${connList}`);
1130
- }
1131
- if (gc.citedByCount && gc.citedByCount > 0) {
1132
- const citedNames = citedBy.map((c) => c.resourceName).join(", ");
1133
- parts.push(`- This resource is cited by ${gc.citedByCount} other resource${gc.citedByCount > 1 ? "s" : ""}${citedNames ? `: ${citedNames}` : ""}`);
1134
- }
1135
- if (gc.siblingEntityTypes && gc.siblingEntityTypes.length > 0) {
1136
- parts.push(`- Related entity types in this document: ${gc.siblingEntityTypes.join(", ")}`);
1137
- }
1138
- if (gc.inferredRelationshipSummary) {
1139
- parts.push(`- Relationship summary: ${gc.inferredRelationshipSummary}`);
1125
+ }
1126
+ } else {
1127
+ const RESOURCE_CONTENT_CAP = 4e3;
1128
+ const parts = [`- Resource: ${focus.resource.name}`];
1129
+ if (focus.summary) parts.push(`- Summary: ${focus.summary}`);
1130
+ if (focus.suggestedReferences && focus.suggestedReferences.length > 0) {
1131
+ parts.push(`- Suggested references: ${focus.suggestedReferences.join(", ")}`);
1132
+ }
1133
+ resourceSection = `
1134
+
1135
+ Resource context:
1136
+ ${parts.join("\n")}`;
1137
+ if (focus.content?.main) {
1138
+ resourceSection += `
1139
+
1140
+ Resource content:
1141
+ ---
1142
+ ${focus.content.main.slice(0, RESOURCE_CONTENT_CAP)}
1143
+ ---`;
1144
+ }
1145
+ const related = Object.entries(focus.content?.related ?? {});
1146
+ if (related.length > 0) {
1147
+ const blocks = related.map(([id, text]) => `[${id}]
1148
+ ${text.slice(0, RESOURCE_CONTENT_CAP)}`).join("\n\n");
1149
+ resourceSection += `
1150
+
1151
+ Related resource content:
1152
+ ---
1153
+ ${blocks}
1154
+ ---`;
1155
+ }
1140
1156
  }
1141
- if (parts.length > 0) {
1142
- graphContextSection = `
1157
+ if (mainResourceId) {
1158
+ const views = deriveViews(context.graph, mainResourceId, focalAnnotationId);
1159
+ const parts = [];
1160
+ if (views.connections.length > 0) {
1161
+ const connList = views.connections.map((c) => `${c.resourceName}${c.entityTypes.length ? ` (${c.entityTypes.join(", ")})` : ""}`).join(", ");
1162
+ parts.push(`- Connected resources: ${connList}`);
1163
+ }
1164
+ if (views.citedByCount > 0) {
1165
+ const citedNames = views.citedBy.map((c) => c.resourceName).join(", ");
1166
+ parts.push(`- This resource is cited by ${views.citedByCount} other resource${views.citedByCount > 1 ? "s" : ""}${citedNames ? `: ${citedNames}` : ""}`);
1167
+ }
1168
+ if (views.siblingEntityTypes.length > 0) {
1169
+ parts.push(`- Related entity types in this document: ${views.siblingEntityTypes.join(", ")}`);
1170
+ }
1171
+ if (context.inferredRelationshipSummary) {
1172
+ parts.push(`- Relationship summary: ${context.inferredRelationshipSummary}`);
1173
+ }
1174
+ if (parts.length > 0) {
1175
+ graphSection = `
1143
1176
 
1144
1177
  Knowledge graph context:
1145
1178
  ${parts.join("\n")}`;
1179
+ }
1146
1180
  }
1147
1181
  }
1148
1182
  let semanticContextSection = "";
@@ -1154,17 +1188,20 @@ ${parts.join("\n")}`;
1154
1188
  Related passages from the knowledge base:
1155
1189
  ${lines.join("\n")}`;
1156
1190
  }
1157
- const structureGuidance = finalMaxTokens >= 1e3 ? "organized into titled sections (## Section) with well-structured paragraphs" : "organized into well-structured paragraphs";
1191
+ const isPlainText = outputMediaType === "text/plain";
1192
+ const structureGuidance = !isPlainText && finalMaxTokens >= 1e3 ? "organized into titled sections (## Section) with well-structured paragraphs" : "organized into well-structured paragraphs";
1193
+ const formatRequirements = isPlainText ? `- Write the response as plain text \u2014 no formatting markup (no #, *, backticks, headings, or links)
1194
+ - Begin with the title on its own first line` : `- Start with a clear heading (# Title)
1195
+ - Use markdown formatting
1196
+ - Write the response as markdown`;
1158
1197
  const prompt = `Generate a concise, informative resource about "${topic}".
1159
1198
  ${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(", ")}.` : ""}
1160
- ${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${graphContextSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
1199
+ ${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${resourceSection}${graphSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
1161
1200
 
1162
1201
  Requirements:
1163
- - Start with a clear heading (# Title)
1164
1202
  - Aim for approximately ${finalMaxTokens} tokens of content, ${structureGuidance}
1165
1203
  - Be factual and informative
1166
- - Use markdown formatting
1167
- - Write the response as markdown`;
1204
+ ${formatRequirements}`;
1168
1205
  const parseResponse = (response2) => {
1169
1206
  let content = response2.trim();
1170
1207
  if (content.startsWith("```markdown") || content.startsWith("```md")) {
@@ -1483,6 +1520,13 @@ async function processTagJob(content, inferenceClient, params, userId, generator
1483
1520
  };
1484
1521
  }
1485
1522
  async function processGenerationJob(inferenceClient, params, onProgress, logger) {
1523
+ const GENERATABLE_MEDIA_TYPES = ["text/markdown", "text/plain"];
1524
+ const outputMediaType = params.outputMediaType ?? "text/markdown";
1525
+ if (!GENERATABLE_MEDIA_TYPES.includes(outputMediaType)) {
1526
+ throw new Error(
1527
+ `Unsupported outputMediaType for generation: ${outputMediaType}. Generation produces ${GENERATABLE_MEDIA_TYPES.join(" or ")}.`
1528
+ );
1529
+ }
1486
1530
  onProgress(20, "Fetching context...", "fetching");
1487
1531
  const title = params.title ?? "Untitled";
1488
1532
  const entityTypes = (params.entityTypes ?? []).map(String);
@@ -1497,13 +1541,14 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
1497
1541
  params.context,
1498
1542
  params.temperature,
1499
1543
  params.maxTokens,
1500
- params.sourceLanguage
1544
+ params.sourceLanguage,
1545
+ outputMediaType
1501
1546
  );
1502
1547
  onProgress(85, "Creating resource...", "creating");
1503
1548
  return {
1504
1549
  content: generated.content,
1505
1550
  title: generated.title ?? title,
1506
- format: "text/markdown",
1551
+ format: outputMediaType,
1507
1552
  result: {
1508
1553
  resourceId: "",
1509
1554
  resourceName: generated.title ?? title