@indexnetwork/protocol 4.4.1 → 4.5.0-rc.322.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.
Files changed (85) hide show
  1. package/dist/chat/chat.interrupt.classifier.d.ts.map +1 -1
  2. package/dist/chat/chat.interrupt.classifier.js +2 -2
  3. package/dist/chat/chat.interrupt.classifier.js.map +1 -1
  4. package/dist/chat/chat.suggester.d.ts.map +1 -1
  5. package/dist/chat/chat.suggester.js +2 -3
  6. package/dist/chat/chat.suggester.js.map +1 -1
  7. package/dist/chat/chat.summarizer.d.ts.map +1 -1
  8. package/dist/chat/chat.summarizer.js +11 -3
  9. package/dist/chat/chat.summarizer.js.map +1 -1
  10. package/dist/chat/chat.title.generator.d.ts.map +1 -1
  11. package/dist/chat/chat.title.generator.js +2 -2
  12. package/dist/chat/chat.title.generator.js.map +1 -1
  13. package/dist/contact/contact.inviter.d.ts.map +1 -1
  14. package/dist/contact/contact.inviter.js +2 -3
  15. package/dist/contact/contact.inviter.js.map +1 -1
  16. package/dist/context/context.generator.d.ts.map +1 -1
  17. package/dist/context/context.generator.js +2 -2
  18. package/dist/context/context.generator.js.map +1 -1
  19. package/dist/enrichment/enrichment.generator.d.ts +1 -1
  20. package/dist/enrichment/enrichment.generator.d.ts.map +1 -1
  21. package/dist/enrichment/enrichment.generator.js +3 -4
  22. package/dist/enrichment/enrichment.generator.js.map +1 -1
  23. package/dist/intent/intent.clarifier.d.ts.map +1 -1
  24. package/dist/intent/intent.clarifier.js +4 -5
  25. package/dist/intent/intent.clarifier.js.map +1 -1
  26. package/dist/intent/intent.indexer.d.ts.map +1 -1
  27. package/dist/intent/intent.indexer.js +2 -3
  28. package/dist/intent/intent.indexer.js.map +1 -1
  29. package/dist/intent/intent.inferrer.d.ts.map +1 -1
  30. package/dist/intent/intent.inferrer.js +2 -3
  31. package/dist/intent/intent.inferrer.js.map +1 -1
  32. package/dist/intent/intent.reconciler.d.ts.map +1 -1
  33. package/dist/intent/intent.reconciler.js +2 -3
  34. package/dist/intent/intent.reconciler.js.map +1 -1
  35. package/dist/intent/intent.verifier.d.ts.map +1 -1
  36. package/dist/intent/intent.verifier.js +2 -3
  37. package/dist/intent/intent.verifier.js.map +1 -1
  38. package/dist/negotiation/insight.generator.d.ts.map +1 -1
  39. package/dist/negotiation/insight.generator.js +2 -2
  40. package/dist/negotiation/insight.generator.js.map +1 -1
  41. package/dist/negotiation/negotiation.agent.js +2 -2
  42. package/dist/negotiation/negotiation.agent.js.map +1 -1
  43. package/dist/negotiation/negotiation.summarizer.d.ts +11 -0
  44. package/dist/negotiation/negotiation.summarizer.d.ts.map +1 -1
  45. package/dist/negotiation/negotiation.summarizer.js +13 -3
  46. package/dist/negotiation/negotiation.summarizer.js.map +1 -1
  47. package/dist/network/network.recommender.d.ts.map +1 -1
  48. package/dist/network/network.recommender.js +2 -3
  49. package/dist/network/network.recommender.js.map +1 -1
  50. package/dist/opportunity/feed/feed.categorizer.d.ts.map +1 -1
  51. package/dist/opportunity/feed/feed.categorizer.js +2 -3
  52. package/dist/opportunity/feed/feed.categorizer.js.map +1 -1
  53. package/dist/opportunity/opportunity.evaluator.d.ts.map +1 -1
  54. package/dist/opportunity/opportunity.evaluator.js +3 -4
  55. package/dist/opportunity/opportunity.evaluator.js.map +1 -1
  56. package/dist/opportunity/opportunity.presenter.d.ts.map +1 -1
  57. package/dist/opportunity/opportunity.presenter.js +3 -4
  58. package/dist/opportunity/opportunity.presenter.js.map +1 -1
  59. package/dist/opportunity/question.generator.d.ts.map +1 -1
  60. package/dist/opportunity/question.generator.js +17 -3
  61. package/dist/opportunity/question.generator.js.map +1 -1
  62. package/dist/premise/premise.analyzer.d.ts.map +1 -1
  63. package/dist/premise/premise.analyzer.js +2 -3
  64. package/dist/premise/premise.analyzer.js.map +1 -1
  65. package/dist/premise/premise.decomposer.d.ts.map +1 -1
  66. package/dist/premise/premise.decomposer.js +2 -3
  67. package/dist/premise/premise.decomposer.js.map +1 -1
  68. package/dist/premise/premise.indexer.d.ts.map +1 -1
  69. package/dist/premise/premise.indexer.js +2 -3
  70. package/dist/premise/premise.indexer.js.map +1 -1
  71. package/dist/questioner/questioner.agent.d.ts +2 -2
  72. package/dist/questioner/questioner.agent.d.ts.map +1 -1
  73. package/dist/questioner/questioner.agent.js +12 -4
  74. package/dist/questioner/questioner.agent.js.map +1 -1
  75. package/dist/shared/agent/model.config.d.ts +41 -2
  76. package/dist/shared/agent/model.config.d.ts.map +1 -1
  77. package/dist/shared/agent/model.config.js +98 -1
  78. package/dist/shared/agent/model.config.js.map +1 -1
  79. package/dist/shared/hyde/hyde.generator.d.ts.map +1 -1
  80. package/dist/shared/hyde/hyde.generator.js +2 -3
  81. package/dist/shared/hyde/hyde.generator.js.map +1 -1
  82. package/dist/shared/hyde/lens.inferrer.d.ts.map +1 -1
  83. package/dist/shared/hyde/lens.inferrer.js +2 -3
  84. package/dist/shared/hyde/lens.inferrer.js.map +1 -1
  85. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"negotiation.agent.js","sourceRoot":"/","sources":["negotiation/negotiation.agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { createModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\nimport { SystemNegotiationTurnSchema, FinalNegotiationTurnSchema, type NegotiationTurn, type UserNegotiationContext, type SeedAssessment } from \"./negotiation.state.js\";\nimport type { NegotiationUserAnswer } from \"../shared/interfaces/database.interface.js\";\n\nconst SYSTEM_PROMPT = `You are the Index Negotiator, an AI agent acting on behalf of {userName}. You represent their interests in a bilateral negotiation about a potential connection on a discovery network.\n\n{discoveryContext}\n{discoveryQueryContext}\nYour user's role in this connection: {role}\nNetwork context: {networkContext}\n\nYour job: Evaluate whether this connection genuinely serves {userName}'s interests given their role. Argue their case honestly — acknowledge weaknesses, but advocate for genuine fit.\n\nRules:\n- On the FIRST turn: Propose the connection case. Explain why it would benefit both parties. Set action to \"propose\".\n- On SUBSEQUENT turns: Evaluate the other agent's arguments. Either:\n - \"counter\" if you have specific objections but see potential\n - \"accept\" if the match genuinely benefits {userName}\n - \"reject\" if the match does not serve {userName}'s needs\n- Focus on concrete intent alignment, not vague overlap.\n- Do NOT reference internal system details like scores, pre-screens, or evaluator outputs.\n- suggestedRoles: \"agent\" = can help, \"patient\" = seeks help, \"peer\" = mutual benefit.\n{finalTurnInstruction}`;\n\nexport interface NegotiationAgentInput {\n ownUser: UserNegotiationContext;\n otherUser: UserNegotiationContext;\n indexContext: { networkId: string; prompt?: string };\n seedAssessment: SeedAssessment;\n history: NegotiationTurn[];\n isFinalTurn?: boolean;\n /** Whether ownUser is the party that initiated the discovery (searched/signalled). */\n isDiscoverer?: boolean;\n /** The explicit search query that triggered discovery (if any). Takes priority over background intents. */\n discoveryQuery?: string;\n /** Whether this negotiation is continuing a prior conversation with the same counterparty. */\n isContinuation?: boolean;\n /** User answers collected by the questioner between negotiation sessions. */\n userAnswers?: NegotiationUserAnswer[];\n}\n\nexport interface IndexNegotiatorConfig {\n /**\n * Hard ceiling on a single LLM turn round-trip, in ms. When the underlying\n * model.invoke call exceeds this, an AbortSignal cancels the request and the\n * promise rejects — the calling turn node catches the rejection and treats it\n * as a failed turn, so one slow upstream call cannot consume the whole\n * negotiate-phase budget.\n *\n * Defaults to `NEGOTIATOR_TURN_TIMEOUT_MS` env var when set, otherwise\n * `DEFAULT_TURN_TIMEOUT_MS`. Sized to clip the p99 tail on Gemini-2.5-Flash\n * (~20 s today on OpenRouter) without trimming p90 (~12 s).\n */\n turnTimeoutMs?: number;\n}\n\nconst DEFAULT_TURN_TIMEOUT_MS = 15_000;\n\n// Resolver-valid range is `(0, Number.MAX_SAFE_INTEGER]`. The upper bound is\n// the runtime ceiling: `AbortSignal.timeout(N)` throws when N is outside\n// `[0, Number.MAX_SAFE_INTEGER]`, so `Number.isFinite` alone isn't enough —\n// values like `1e30` pass finiteness but blow up at the AbortSignal call.\n// The lower bound (`n > 0`) is a design choice rather than a runtime\n// constraint: `AbortSignal.timeout(0)` is technically legal but would abort\n// every turn before the LLM produces a response, so we reject it and fall\n// back to the default just like any other invalid override.\nfunction isValidTimeoutMs(n: number): boolean {\n return Number.isFinite(n) && n > 0 && n <= Number.MAX_SAFE_INTEGER;\n}\n\nfunction resolveTurnTimeoutMs(override?: number): number {\n if (typeof override === \"number\" && isValidTimeoutMs(override)) return override;\n const envValue = process.env.NEGOTIATOR_TURN_TIMEOUT_MS;\n if (envValue) {\n const parsed = Number(envValue);\n if (isValidTimeoutMs(parsed)) return parsed;\n }\n return DEFAULT_TURN_TIMEOUT_MS;\n}\n\n/**\n * Unified system negotiation agent that advocates for its user.\n * Adapts behavior based on turn position (first turn = propose, subsequent = respond).\n * @remarks Uses structured output constrained to NegotiationTurnSchema (without question action).\n */\nexport class IndexNegotiator {\n private readonly turnTimeoutMs: number;\n\n constructor(config?: IndexNegotiatorConfig) {\n this.turnTimeoutMs = resolveTurnTimeoutMs(config?.turnTimeoutMs);\n }\n\n /**\n * Generate a negotiation turn.\n * @param input - User contexts, seed assessment, history, and final turn flag\n * @returns A structured NegotiationTurn\n * @throws If the per-turn timeout fires before the LLM responds.\n */\n async invoke(input: NegotiationAgentInput): Promise<NegotiationTurn> {\n const schema = input.isFinalTurn ? FinalNegotiationTurnSchema : SystemNegotiationTurnSchema;\n const model = createModel(\"negotiator\").withStructuredOutput(schema, { name: \"index_negotiator\" });\n\n const userName = input.ownUser.profile.name ?? \"your user\";\n const role = input.seedAssessment.valencyRole || \"peer\";\n const networkContext = input.indexContext.prompt || \"General discovery\";\n const finalTurnInstruction = input.isFinalTurn\n ? \"\\n\\nIMPORTANT: This is your FINAL turn. You MUST choose either 'accept' or 'reject'. No counter is allowed.\"\n : \"\";\n\n const otherName = input.otherUser.profile.name ?? \"the other user\";\n const discoveryContext = input.isDiscoverer\n ? `${userName} initiated this discovery — they are actively looking for connections. ${otherName} was identified as a potential match.`\n : `${otherName} initiated this discovery and found ${userName} as a potential match. You are representing the discovered party.`;\n\n const discoveryQueryContext = input.discoveryQuery\n ? `\\nDISCOVERY QUERY: ${userName} explicitly searched for \"${input.discoveryQuery}\".\nQUERY PRIORITY RULE: This search query is the PRIMARY criterion for this negotiation. Before evaluating intents or profile overlap, first answer: does ${otherName} satisfy the search query \"${input.discoveryQuery}\"?\n- If the query is a role or identity term (e.g. \"samurai\", \"investors\", \"designers\"): check whether ${otherName} IS that thing based on their profile. Subject-matter adjacency does not count (drawing samurai ≠ being a samurai, raising funding ≠ being an investor).\n- If ${otherName} does NOT satisfy the query: REJECT the match. Background intents cannot rescue a query mismatch.\n- If ${otherName} DOES satisfy the query: PROPOSE or ACCEPT the connection and evaluate fit normally using intents and profile data.`\n : '';\n\n const systemPrompt = SYSTEM_PROMPT\n .replace(/{userName}/g, userName)\n .replace(\"{discoveryContext}\", discoveryContext)\n .replace(\"{discoveryQueryContext}\", discoveryQueryContext)\n .replace(\"{role}\", role)\n .replace(\"{networkContext}\", networkContext)\n .replace(\"{finalTurnInstruction}\", finalTurnInstruction);\n\n const historyText = input.history.length > 0\n ? `\\n\\nNegotiation history:\\n${input.history.map((t, i) => {\n const msgPart = t.message ? ` — message: ${t.message}` : '';\n return `Turn ${i + 1}: ${t.action} — reasoning: ${t.assessment.reasoning}${msgPart}`;\n }).join(\"\\n\")}`\n : \"\";\n\n const continuationContext = input.isContinuation && input.history.length > 0\n ? `\\n\\n--- Prior dialogue with this counterparty ---\n${historyText}\n\n--- New signal under evaluation ---\n${input.discoveryQuery\n ? `Discovery query: \"${input.discoveryQuery}\"`\n : `Seed assessment: ${input.seedAssessment.reasoning}`\n}\n\nPolicy: You are continuing a prior dialogue. If this signal is materially the same as one you previously evaluated, you may resolve quickly. If materially different, evaluate on its own merits.`\n : '';\n\n const userAnswersContext = input.userAnswers && input.userAnswers.length > 0\n ? `\\n\\n--- ${userName}'s additional context (provided between sessions) ---\\n${input.userAnswers.map((a) => {\n const opts = Array.isArray(a.selectedOptions) ? a.selectedOptions : [];\n const parts = opts.length > 0 ? opts.join(', ') : '';\n const free = a.freeText ? (parts ? ` — ${a.freeText}` : a.freeText) : '';\n if (!parts && !free) return '';\n return `- ${parts}${free}`;\n }).filter(Boolean).join(\"\\n\")}\\n`\n : '';\n\n const discoveryQueryReminder = input.discoveryQuery\n ? `\\nREMINDER: ${userName} searched for \"${input.discoveryQuery}\". Evaluate ${otherName} against this query FIRST. If ${otherName} is not a \"${input.discoveryQuery}\", reject.\\n`\n : '';\n\n const intentsLabel = input.discoveryQuery ? 'Background intents (secondary to discovery query)' : 'Intents';\n\n const userMessage = `YOUR USER (${userName}):\nBio: ${input.ownUser.profile.bio ?? \"N/A\"}\nSkills: ${input.ownUser.profile.skills?.join(\", \") ?? \"N/A\"}\n${intentsLabel}:\n${input.ownUser.intents.map((i) => `- ${i.title}: ${i.description}`).join(\"\\n\")}\n\nOTHER USER (${otherName}):\nBio: ${input.otherUser.profile.bio ?? \"N/A\"}\nSkills: ${input.otherUser.profile.skills?.join(\", \") ?? \"N/A\"}\nIntents:\n${input.otherUser.intents.map((i) => `- ${i.title}: ${i.description}`).join(\"\\n\")}\n\nWhy this match was suggested: ${input.seedAssessment.reasoning}${input.isContinuation ? continuationContext : historyText}${userAnswersContext}\n${discoveryQueryReminder}\n${input.history.length === 0 && !input.isContinuation ? \"This is the opening turn. Propose the connection case.\" : \"Evaluate the latest arguments and respond.\"}`;\n\n const result = await invokeWithAbortSignal(\n model,\n [\n { role: \"system\", content: systemPrompt },\n { role: \"user\", content: userMessage },\n ],\n AbortSignal.timeout(this.turnTimeoutMs),\n );\n\n return result as NegotiationTurn;\n }\n}\n"]}
1
+ {"version":3,"file":"negotiation.agent.js","sourceRoot":"/","sources":["negotiation/negotiation.agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\nimport { SystemNegotiationTurnSchema, FinalNegotiationTurnSchema, type NegotiationTurn, type UserNegotiationContext, type SeedAssessment } from \"./negotiation.state.js\";\nimport type { NegotiationUserAnswer } from \"../shared/interfaces/database.interface.js\";\n\nconst SYSTEM_PROMPT = `You are the Index Negotiator, an AI agent acting on behalf of {userName}. You represent their interests in a bilateral negotiation about a potential connection on a discovery network.\n\n{discoveryContext}\n{discoveryQueryContext}\nYour user's role in this connection: {role}\nNetwork context: {networkContext}\n\nYour job: Evaluate whether this connection genuinely serves {userName}'s interests given their role. Argue their case honestly — acknowledge weaknesses, but advocate for genuine fit.\n\nRules:\n- On the FIRST turn: Propose the connection case. Explain why it would benefit both parties. Set action to \"propose\".\n- On SUBSEQUENT turns: Evaluate the other agent's arguments. Either:\n - \"counter\" if you have specific objections but see potential\n - \"accept\" if the match genuinely benefits {userName}\n - \"reject\" if the match does not serve {userName}'s needs\n- Focus on concrete intent alignment, not vague overlap.\n- Do NOT reference internal system details like scores, pre-screens, or evaluator outputs.\n- suggestedRoles: \"agent\" = can help, \"patient\" = seeks help, \"peer\" = mutual benefit.\n{finalTurnInstruction}`;\n\nexport interface NegotiationAgentInput {\n ownUser: UserNegotiationContext;\n otherUser: UserNegotiationContext;\n indexContext: { networkId: string; prompt?: string };\n seedAssessment: SeedAssessment;\n history: NegotiationTurn[];\n isFinalTurn?: boolean;\n /** Whether ownUser is the party that initiated the discovery (searched/signalled). */\n isDiscoverer?: boolean;\n /** The explicit search query that triggered discovery (if any). Takes priority over background intents. */\n discoveryQuery?: string;\n /** Whether this negotiation is continuing a prior conversation with the same counterparty. */\n isContinuation?: boolean;\n /** User answers collected by the questioner between negotiation sessions. */\n userAnswers?: NegotiationUserAnswer[];\n}\n\nexport interface IndexNegotiatorConfig {\n /**\n * Hard ceiling on a single LLM turn round-trip, in ms. When the underlying\n * model.invoke call exceeds this, an AbortSignal cancels the request and the\n * promise rejects — the calling turn node catches the rejection and treats it\n * as a failed turn, so one slow upstream call cannot consume the whole\n * negotiate-phase budget.\n *\n * Defaults to `NEGOTIATOR_TURN_TIMEOUT_MS` env var when set, otherwise\n * `DEFAULT_TURN_TIMEOUT_MS`. Sized to clip the p99 tail on Gemini-2.5-Flash\n * (~20 s today on OpenRouter) without trimming p90 (~12 s).\n */\n turnTimeoutMs?: number;\n}\n\nconst DEFAULT_TURN_TIMEOUT_MS = 15_000;\n\n// Resolver-valid range is `(0, Number.MAX_SAFE_INTEGER]`. The upper bound is\n// the runtime ceiling: `AbortSignal.timeout(N)` throws when N is outside\n// `[0, Number.MAX_SAFE_INTEGER]`, so `Number.isFinite` alone isn't enough —\n// values like `1e30` pass finiteness but blow up at the AbortSignal call.\n// The lower bound (`n > 0`) is a design choice rather than a runtime\n// constraint: `AbortSignal.timeout(0)` is technically legal but would abort\n// every turn before the LLM produces a response, so we reject it and fall\n// back to the default just like any other invalid override.\nfunction isValidTimeoutMs(n: number): boolean {\n return Number.isFinite(n) && n > 0 && n <= Number.MAX_SAFE_INTEGER;\n}\n\nfunction resolveTurnTimeoutMs(override?: number): number {\n if (typeof override === \"number\" && isValidTimeoutMs(override)) return override;\n const envValue = process.env.NEGOTIATOR_TURN_TIMEOUT_MS;\n if (envValue) {\n const parsed = Number(envValue);\n if (isValidTimeoutMs(parsed)) return parsed;\n }\n return DEFAULT_TURN_TIMEOUT_MS;\n}\n\n/**\n * Unified system negotiation agent that advocates for its user.\n * Adapts behavior based on turn position (first turn = propose, subsequent = respond).\n * @remarks Uses structured output constrained to NegotiationTurnSchema (without question action).\n */\nexport class IndexNegotiator {\n private readonly turnTimeoutMs: number;\n\n constructor(config?: IndexNegotiatorConfig) {\n this.turnTimeoutMs = resolveTurnTimeoutMs(config?.turnTimeoutMs);\n }\n\n /**\n * Generate a negotiation turn.\n * @param input - User contexts, seed assessment, history, and final turn flag\n * @returns A structured NegotiationTurn\n * @throws If the per-turn timeout fires before the LLM responds.\n */\n async invoke(input: NegotiationAgentInput): Promise<NegotiationTurn> {\n const schema = input.isFinalTurn ? FinalNegotiationTurnSchema : SystemNegotiationTurnSchema;\n const model = createStructuredModel(\"negotiator\", schema, { name: \"index_negotiator\" });\n\n const userName = input.ownUser.profile.name ?? \"your user\";\n const role = input.seedAssessment.valencyRole || \"peer\";\n const networkContext = input.indexContext.prompt || \"General discovery\";\n const finalTurnInstruction = input.isFinalTurn\n ? \"\\n\\nIMPORTANT: This is your FINAL turn. You MUST choose either 'accept' or 'reject'. No counter is allowed.\"\n : \"\";\n\n const otherName = input.otherUser.profile.name ?? \"the other user\";\n const discoveryContext = input.isDiscoverer\n ? `${userName} initiated this discovery — they are actively looking for connections. ${otherName} was identified as a potential match.`\n : `${otherName} initiated this discovery and found ${userName} as a potential match. You are representing the discovered party.`;\n\n const discoveryQueryContext = input.discoveryQuery\n ? `\\nDISCOVERY QUERY: ${userName} explicitly searched for \"${input.discoveryQuery}\".\nQUERY PRIORITY RULE: This search query is the PRIMARY criterion for this negotiation. Before evaluating intents or profile overlap, first answer: does ${otherName} satisfy the search query \"${input.discoveryQuery}\"?\n- If the query is a role or identity term (e.g. \"samurai\", \"investors\", \"designers\"): check whether ${otherName} IS that thing based on their profile. Subject-matter adjacency does not count (drawing samurai ≠ being a samurai, raising funding ≠ being an investor).\n- If ${otherName} does NOT satisfy the query: REJECT the match. Background intents cannot rescue a query mismatch.\n- If ${otherName} DOES satisfy the query: PROPOSE or ACCEPT the connection and evaluate fit normally using intents and profile data.`\n : '';\n\n const systemPrompt = SYSTEM_PROMPT\n .replace(/{userName}/g, userName)\n .replace(\"{discoveryContext}\", discoveryContext)\n .replace(\"{discoveryQueryContext}\", discoveryQueryContext)\n .replace(\"{role}\", role)\n .replace(\"{networkContext}\", networkContext)\n .replace(\"{finalTurnInstruction}\", finalTurnInstruction);\n\n const historyText = input.history.length > 0\n ? `\\n\\nNegotiation history:\\n${input.history.map((t, i) => {\n const msgPart = t.message ? ` — message: ${t.message}` : '';\n return `Turn ${i + 1}: ${t.action} — reasoning: ${t.assessment.reasoning}${msgPart}`;\n }).join(\"\\n\")}`\n : \"\";\n\n const continuationContext = input.isContinuation && input.history.length > 0\n ? `\\n\\n--- Prior dialogue with this counterparty ---\n${historyText}\n\n--- New signal under evaluation ---\n${input.discoveryQuery\n ? `Discovery query: \"${input.discoveryQuery}\"`\n : `Seed assessment: ${input.seedAssessment.reasoning}`\n}\n\nPolicy: You are continuing a prior dialogue. If this signal is materially the same as one you previously evaluated, you may resolve quickly. If materially different, evaluate on its own merits.`\n : '';\n\n const userAnswersContext = input.userAnswers && input.userAnswers.length > 0\n ? `\\n\\n--- ${userName}'s additional context (provided between sessions) ---\\n${input.userAnswers.map((a) => {\n const opts = Array.isArray(a.selectedOptions) ? a.selectedOptions : [];\n const parts = opts.length > 0 ? opts.join(', ') : '';\n const free = a.freeText ? (parts ? ` — ${a.freeText}` : a.freeText) : '';\n if (!parts && !free) return '';\n return `- ${parts}${free}`;\n }).filter(Boolean).join(\"\\n\")}\\n`\n : '';\n\n const discoveryQueryReminder = input.discoveryQuery\n ? `\\nREMINDER: ${userName} searched for \"${input.discoveryQuery}\". Evaluate ${otherName} against this query FIRST. If ${otherName} is not a \"${input.discoveryQuery}\", reject.\\n`\n : '';\n\n const intentsLabel = input.discoveryQuery ? 'Background intents (secondary to discovery query)' : 'Intents';\n\n const userMessage = `YOUR USER (${userName}):\nBio: ${input.ownUser.profile.bio ?? \"N/A\"}\nSkills: ${input.ownUser.profile.skills?.join(\", \") ?? \"N/A\"}\n${intentsLabel}:\n${input.ownUser.intents.map((i) => `- ${i.title}: ${i.description}`).join(\"\\n\")}\n\nOTHER USER (${otherName}):\nBio: ${input.otherUser.profile.bio ?? \"N/A\"}\nSkills: ${input.otherUser.profile.skills?.join(\", \") ?? \"N/A\"}\nIntents:\n${input.otherUser.intents.map((i) => `- ${i.title}: ${i.description}`).join(\"\\n\")}\n\nWhy this match was suggested: ${input.seedAssessment.reasoning}${input.isContinuation ? continuationContext : historyText}${userAnswersContext}\n${discoveryQueryReminder}\n${input.history.length === 0 && !input.isContinuation ? \"This is the opening turn. Propose the connection case.\" : \"Evaluate the latest arguments and respond.\"}`;\n\n const result = await invokeWithAbortSignal(\n model,\n [\n { role: \"system\", content: systemPrompt },\n { role: \"user\", content: userMessage },\n ],\n AbortSignal.timeout(this.turnTimeoutMs),\n );\n\n return result as NegotiationTurn;\n }\n}\n"]}
@@ -1,3 +1,14 @@
1
+ /**
2
+ * NegotiationSummarizer — pure LLM pass that compresses one DiscoveryNegotiation
3
+ * (potentially several KB of turn reasoning + outcome reasoning) into a
4
+ * DiscoveryNegotiationDigest (≤256 bytes structured). The digest is what the decision-
5
+ * question generator consumes, replacing the raw negotiation blob.
6
+ *
7
+ * Why: a 10-candidate discovery turn used to produce a 60+ KB prompt for the
8
+ * question generator, which stalled upstream Gemini/OpenRouter routes and
9
+ * dropped the connection at ~3 minutes. Per-negotiation summarization caps
10
+ * the question-generator input at a fixed, predictable size.
11
+ */
1
12
  import { type DiscoveryNegotiationDigest } from "../shared/schemas/negotiation-digest.schema.js";
2
13
  import type { DiscoveryNegotiation } from "../opportunity/question.prompt.js";
3
14
  export declare class NegotiationSummarizer {
@@ -1 +1 @@
1
- {"version":3,"file":"negotiation.summarizer.d.ts","sourceRoot":"/","sources":["negotiation/negotiation.summarizer.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAoC,KAAK,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AACnI,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAoC9E,qBAAa,qBAAqB;IAChC,OAAO,CAAC,KAAK,CAAiD;;IASxD,SAAS,CACb,WAAW,EAAE,oBAAoB,EACjC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC;CA8B9C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,oBAAoB,GAAG,0BAA0B,CAqBvF"}
1
+ {"version":3,"file":"negotiation.summarizer.d.ts","sourceRoot":"/","sources":["negotiation/negotiation.summarizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EAAoC,KAAK,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AACnI,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAoC9E,qBAAa,qBAAqB;IAChC,OAAO,CAAC,KAAK,CAA2C;;IAQlD,SAAS,CACb,WAAW,EAAE,oBAAoB,EACjC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC;CA8B9C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,oBAAoB,GAAG,0BAA0B,CAqBvF"}
@@ -1,5 +1,16 @@
1
+ /**
2
+ * NegotiationSummarizer — pure LLM pass that compresses one DiscoveryNegotiation
3
+ * (potentially several KB of turn reasoning + outcome reasoning) into a
4
+ * DiscoveryNegotiationDigest (≤256 bytes structured). The digest is what the decision-
5
+ * question generator consumes, replacing the raw negotiation blob.
6
+ *
7
+ * Why: a 10-candidate discovery turn used to produce a 60+ KB prompt for the
8
+ * question generator, which stalled upstream Gemini/OpenRouter routes and
9
+ * dropped the connection at ~3 minutes. Per-negotiation summarization caps
10
+ * the question-generator input at a fixed, predictable size.
11
+ */
1
12
  import { HumanMessage, SystemMessage } from "@langchain/core/messages";
2
- import { createModel } from "../shared/agent/model.config.js";
13
+ import { createStructuredModel } from "../shared/agent/model.config.js";
3
14
  import { invokeWithAbortSignal } from "../shared/agent/model-signal.js";
4
15
  import { protocolLogger } from "../shared/observability/protocol.logger.js";
5
16
  import { DiscoveryNegotiationDigestSchema } from "../shared/schemas/negotiation-digest.schema.js";
@@ -33,8 +44,7 @@ function buildUserPrompt(n) {
33
44
  }
34
45
  export class NegotiationSummarizer {
35
46
  constructor() {
36
- const llm = createModel("negotiationSummarizer");
37
- this.model = llm.withStructuredOutput(DiscoveryNegotiationDigestSchema, {
47
+ this.model = createStructuredModel("negotiationSummarizer", DiscoveryNegotiationDigestSchema, {
38
48
  name: "negotiation_digest",
39
49
  });
40
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"negotiation.summarizer.js","sourceRoot":"/","sources":["negotiation/negotiation.summarizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NegotiationSummarizer — pure LLM pass that compresses one DiscoveryNegotiation\n * (potentially several KB of turn reasoning + outcome reasoning) into a\n * DiscoveryNegotiationDigest (≤256 bytes structured). The digest is what the decision-\n * question generator consumes, replacing the raw negotiation blob.\n *\n * Why: a 10-candidate discovery turn used to produce a 60+ KB prompt for the\n * question generator, which stalled upstream Gemini/OpenRouter routes and\n * dropped the connection at ~3 minutes. Per-negotiation summarization caps\n * the question-generator input at a fixed, predictable size.\n */\nimport type { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\n\nimport { createModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { DiscoveryNegotiationDigestSchema, type DiscoveryNegotiationDigest } from \"../shared/schemas/negotiation-digest.schema.js\";\nimport type { DiscoveryNegotiation } from \"../opportunity/question.prompt.js\";\n\nconst logger = protocolLogger(\"NegotiationSummarizer\");\n\nconst SYSTEM_PROMPT = `You summarize one negotiation between a seeker's agent and a candidate's agent into a structured digest for downstream decision-question generation.\n\nOutput requirements (ALL fields — set optional fields to null when not applicable):\n- counterpartyHint: copy the input field verbatim, truncated to ≤120 chars.\n- indexContext: copy the input field verbatim, truncated to ≤120 chars.\n- outcomeRole: \"opportunity\" when hasOpportunity=true, else \"no-opportunity\".\n- outcomeReason: when outcomeRole=\"no-opportunity\", set to \"turn_cap\" / \"timeout\" if the input's reason matches; else \"rejected\" (if a turn explicitly rejected) or \"stalled\". When outcomeRole=\"opportunity\", set to null.\n- keyTake: ONE sentence (≤180 chars). Describe the decisive moment, the alignment gap, or the recurring signal — anything a question generator could use to formulate a clarifying question. NOT a generic outcome restatement. Anchor in a concrete detail from the negotiation if possible (a role mismatch, a missing input the candidate flagged, a pivot one party suggested).\n- suggestedRoles: when both parties agreed on roles (the last turn's suggestedRoles), copy them from input. Otherwise set to null.\n\nDo not include candidate identity, PII, names, or IDs. Reuse only counterpartyHint and indexContext as provided.`;\n\nfunction buildUserPrompt(n: DiscoveryNegotiation): string {\n const turnLines = n.turns\n .map(\n (t, i) =>\n `[${i + 1}] action=${t.action} roles=${t.suggestedRoles.ownUser}↔${t.suggestedRoles.otherUser} reasoning=${t.reasoning}`,\n )\n .join(\"\\n\");\n return [\n `counterpartyHint: ${n.counterpartyHint}`,\n `indexContext: ${n.indexContext}`,\n `turns (${n.turns.length}):`,\n turnLines || \"(no turns)\",\n `outcome.hasOpportunity: ${n.outcome.hasOpportunity}`,\n `outcome.reasoning: ${n.outcome.reasoning}`,\n n.outcome.reason ? `outcome.reason: ${n.outcome.reason}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nexport class NegotiationSummarizer {\n private model: ReturnType<ChatOpenAI[\"withStructuredOutput\"]>;\n\n constructor() {\n const llm = createModel(\"negotiationSummarizer\");\n this.model = llm.withStructuredOutput(DiscoveryNegotiationDigestSchema, {\n name: \"negotiation_digest\",\n });\n }\n\n async summarize(\n negotiation: DiscoveryNegotiation,\n options?: { signal?: AbortSignal },\n ): Promise<DiscoveryNegotiationDigest | null> {\n const user = buildUserPrompt(negotiation);\n let raw: unknown;\n try {\n raw = await invokeWithAbortSignal(\n this.model,\n [new SystemMessage(SYSTEM_PROMPT), new HumanMessage(user)],\n options?.signal,\n );\n } catch (err) {\n const aborted = options?.signal?.aborted ?? false;\n if (aborted) {\n logger.info(\"NegotiationSummarizer aborted by signal\", {\n reason: options?.signal?.reason instanceof Error ? options.signal.reason.message : String(options?.signal?.reason ?? \"unknown\"),\n });\n } else {\n logger.warn(\"NegotiationSummarizer LLM call failed\", {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return null;\n }\n\n const parsed = DiscoveryNegotiationDigestSchema.safeParse(raw);\n if (!parsed.success) {\n logger.warn(\"NegotiationSummarizer parse failed\", { error: parsed.error.message });\n return null;\n }\n return parsed.data;\n }\n}\n\n/**\n * Deterministic fallback when the LLM summarizer fails or isn't wired. Produces\n * a minimal but valid digest so the question generator still has *some* signal.\n */\nexport function buildFallbackDigest(n: DiscoveryNegotiation): DiscoveryNegotiationDigest {\n const outcomeRole = n.outcome.hasOpportunity ? \"opportunity\" : \"no-opportunity\";\n const lastTurn = n.turns[n.turns.length - 1];\n const keyTakeRaw =\n n.outcome.reasoning && n.outcome.reasoning.trim().length > 0\n ? n.outcome.reasoning\n : `${n.turns.length} turn(s); ended ${outcomeRole}`;\n return {\n counterpartyHint: n.counterpartyHint.slice(0, 120),\n indexContext: n.indexContext.slice(0, 120),\n outcomeRole,\n outcomeReason:\n outcomeRole === \"no-opportunity\"\n ? ((n.outcome.reason ?? \"stalled\") as \"turn_cap\" | \"timeout\" | \"stalled\")\n : null,\n keyTake: keyTakeRaw.slice(0, 180),\n suggestedRoles:\n lastTurn?.action === \"accept\" && lastTurn.suggestedRoles\n ? lastTurn.suggestedRoles\n : null,\n };\n}\n"]}
1
+ {"version":3,"file":"negotiation.summarizer.js","sourceRoot":"/","sources":["negotiation/negotiation.summarizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NegotiationSummarizer — pure LLM pass that compresses one DiscoveryNegotiation\n * (potentially several KB of turn reasoning + outcome reasoning) into a\n * DiscoveryNegotiationDigest (≤256 bytes structured). The digest is what the decision-\n * question generator consumes, replacing the raw negotiation blob.\n *\n * Why: a 10-candidate discovery turn used to produce a 60+ KB prompt for the\n * question generator, which stalled upstream Gemini/OpenRouter routes and\n * dropped the connection at ~3 minutes. Per-negotiation summarization caps\n * the question-generator input at a fixed, predictable size.\n */\n\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\n\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { DiscoveryNegotiationDigestSchema, type DiscoveryNegotiationDigest } from \"../shared/schemas/negotiation-digest.schema.js\";\nimport type { DiscoveryNegotiation } from \"../opportunity/question.prompt.js\";\n\nconst logger = protocolLogger(\"NegotiationSummarizer\");\n\nconst SYSTEM_PROMPT = `You summarize one negotiation between a seeker's agent and a candidate's agent into a structured digest for downstream decision-question generation.\n\nOutput requirements (ALL fields — set optional fields to null when not applicable):\n- counterpartyHint: copy the input field verbatim, truncated to ≤120 chars.\n- indexContext: copy the input field verbatim, truncated to ≤120 chars.\n- outcomeRole: \"opportunity\" when hasOpportunity=true, else \"no-opportunity\".\n- outcomeReason: when outcomeRole=\"no-opportunity\", set to \"turn_cap\" / \"timeout\" if the input's reason matches; else \"rejected\" (if a turn explicitly rejected) or \"stalled\". When outcomeRole=\"opportunity\", set to null.\n- keyTake: ONE sentence (≤180 chars). Describe the decisive moment, the alignment gap, or the recurring signal — anything a question generator could use to formulate a clarifying question. NOT a generic outcome restatement. Anchor in a concrete detail from the negotiation if possible (a role mismatch, a missing input the candidate flagged, a pivot one party suggested).\n- suggestedRoles: when both parties agreed on roles (the last turn's suggestedRoles), copy them from input. Otherwise set to null.\n\nDo not include candidate identity, PII, names, or IDs. Reuse only counterpartyHint and indexContext as provided.`;\n\nfunction buildUserPrompt(n: DiscoveryNegotiation): string {\n const turnLines = n.turns\n .map(\n (t, i) =>\n `[${i + 1}] action=${t.action} roles=${t.suggestedRoles.ownUser}↔${t.suggestedRoles.otherUser} reasoning=${t.reasoning}`,\n )\n .join(\"\\n\");\n return [\n `counterpartyHint: ${n.counterpartyHint}`,\n `indexContext: ${n.indexContext}`,\n `turns (${n.turns.length}):`,\n turnLines || \"(no turns)\",\n `outcome.hasOpportunity: ${n.outcome.hasOpportunity}`,\n `outcome.reasoning: ${n.outcome.reasoning}`,\n n.outcome.reason ? `outcome.reason: ${n.outcome.reason}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nexport class NegotiationSummarizer {\n private model: ReturnType<typeof createStructuredModel>;\n\n constructor() {\n this.model = createStructuredModel(\"negotiationSummarizer\", DiscoveryNegotiationDigestSchema, {\n name: \"negotiation_digest\",\n });\n }\n\n async summarize(\n negotiation: DiscoveryNegotiation,\n options?: { signal?: AbortSignal },\n ): Promise<DiscoveryNegotiationDigest | null> {\n const user = buildUserPrompt(negotiation);\n let raw: unknown;\n try {\n raw = await invokeWithAbortSignal(\n this.model,\n [new SystemMessage(SYSTEM_PROMPT), new HumanMessage(user)],\n options?.signal,\n );\n } catch (err) {\n const aborted = options?.signal?.aborted ?? false;\n if (aborted) {\n logger.info(\"NegotiationSummarizer aborted by signal\", {\n reason: options?.signal?.reason instanceof Error ? options.signal.reason.message : String(options?.signal?.reason ?? \"unknown\"),\n });\n } else {\n logger.warn(\"NegotiationSummarizer LLM call failed\", {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return null;\n }\n\n const parsed = DiscoveryNegotiationDigestSchema.safeParse(raw);\n if (!parsed.success) {\n logger.warn(\"NegotiationSummarizer parse failed\", { error: parsed.error.message });\n return null;\n }\n return parsed.data;\n }\n}\n\n/**\n * Deterministic fallback when the LLM summarizer fails or isn't wired. Produces\n * a minimal but valid digest so the question generator still has *some* signal.\n */\nexport function buildFallbackDigest(n: DiscoveryNegotiation): DiscoveryNegotiationDigest {\n const outcomeRole = n.outcome.hasOpportunity ? \"opportunity\" : \"no-opportunity\";\n const lastTurn = n.turns[n.turns.length - 1];\n const keyTakeRaw =\n n.outcome.reasoning && n.outcome.reasoning.trim().length > 0\n ? n.outcome.reasoning\n : `${n.turns.length} turn(s); ended ${outcomeRole}`;\n return {\n counterpartyHint: n.counterpartyHint.slice(0, 120),\n indexContext: n.indexContext.slice(0, 120),\n outcomeRole,\n outcomeReason:\n outcomeRole === \"no-opportunity\"\n ? ((n.outcome.reason ?? \"stalled\") as \"turn_cap\" | \"timeout\" | \"stalled\")\n : null,\n keyTake: keyTakeRaw.slice(0, 180),\n suggestedRoles:\n lastTurn?.action === \"accept\" && lastTurn.suggestedRoles\n ? lastTurn.suggestedRoles\n : null,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"network.recommender.d.ts","sourceRoot":"/","sources":["network/network.recommender.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,eAAO,MAAM,8BAA8B;;;;;;;;;EAOzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAItF,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,yBAAyB,EAAE,CAAC;CACvC;AAgCD;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAiD;;IAS9D;;;;;OAKG;IAEU,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC;CAoC9F"}
1
+ {"version":3,"file":"network.recommender.d.ts","sourceRoot":"/","sources":["network/network.recommender.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,eAAO,MAAM,8BAA8B;;;;;;;;;EAOzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAItF,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,yBAAyB,EAAE,CAAC;CACvC;AAgCD;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAA2C;;IAQxD;;;;;OAKG;IAEU,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC;CAoC9F"}
@@ -11,7 +11,7 @@ import { HumanMessage, SystemMessage } from "@langchain/core/messages";
11
11
  import { z } from "zod";
12
12
  import { log } from "../shared/observability/log.js";
13
13
  import { Timed } from "../shared/observability/performance.js";
14
- import { createModel } from "../shared/agent/model.config.js";
14
+ import { createStructuredModel } from "../shared/agent/model.config.js";
15
15
  import { invokeWithAbortSignal } from "../shared/agent/model-signal.js";
16
16
  // ─── Response schema ───────────────────────────────────────────────────────────
17
17
  export const NetworkRecommenderOutputSchema = z.object({
@@ -58,8 +58,7 @@ OUTPUT RULES:
58
58
  */
59
59
  export class NetworkRecommender {
60
60
  constructor() {
61
- const model = createModel("networkRecommender");
62
- this.model = model.withStructuredOutput(NetworkRecommenderOutputSchema, {
61
+ this.model = createStructuredModel("networkRecommender", NetworkRecommenderOutputSchema, {
63
62
  name: "network_recommender",
64
63
  });
65
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"network.recommender.js","sourceRoot":"/","sources":["network/network.recommender.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\n\nimport { log } from \"../shared/observability/log.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { createModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\n// ─── Response schema ───────────────────────────────────────────────────────────\n\nexport const NetworkRecommenderOutputSchema = z.object({\n rankedNetworkIds: z\n .array(z.string())\n .describe(\"Network IDs ordered from most to least relevant for this user. Include all provided network IDs.\"),\n reasoning: z\n .string()\n .describe(\"One-sentence explanation of the top recommendation.\"),\n});\n\nexport type NetworkRecommenderOutput = z.infer<typeof NetworkRecommenderOutputSchema>;\n\n// ─── Input types ──────────────────────────────────────────────────────────────\n\nexport interface NetworkRecommenderNetwork {\n networkId: string;\n renderedContext: string;\n}\n\nexport interface NetworkRecommenderInput {\n /** The user's global user_context paragraph (profile-replacing identity text). */\n userContext: string;\n networks: NetworkRecommenderNetwork[];\n}\n\n// ─── Logger ───────────────────────────────────────────────────────────────────\n\nconst logger = log.lib.from(\"NetworkRecommender\");\n\n// ─── System prompt ────────────────────────────────────────────────────────────\n\nconst systemPrompt = `\nYou are a community matching agent for a social discovery network.\n\nTASK:\nGiven a user's profile and a list of communities, rank the communities from most to least relevant for this user.\nReturn ALL provided community IDs in ranked order.\n\nINPUTS:\n1. User Context: a synthesized paragraph describing the user (background, interests, skills, location, goals).\n2. Communities: a list of communities, each with an ID and a description.\n\nSCORING FACTORS (in priority order):\n1. Thematic alignment — do the community's topics match the user's interests and skills?\n2. Geographic relevance — does the user's location match the community's focus (if any)?\n3. Professional fit — does the community's purpose match the user's professional background?\n\nOUTPUT RULES:\n- Return ALL community IDs in your ranked list (no omissions).\n- If context is insufficient to differentiate, preserve original order.\n- Keep reasoning brief (one sentence about the top recommendation).\n`;\n\n// ─── Agent class ──────────────────────────────────────────────────────────────\n\n/**\n * LLM-based agent that ranks public communities against a user's profile.\n * Used during onboarding step 6 to surface the most relevant communities first.\n *\n * Follows the IntentIndexer pattern: `withStructuredOutput`, `invokeWithAbortSignal`,\n * null-on-error fallback. `createModel` is called inside the constructor (not at\n * module level) so that importing this file does not require OPENROUTER_API_KEY to\n * be set — tests that import `createNetworkTools` without a live LLM env are unaffected.\n */\nexport class NetworkRecommender {\n private model: ReturnType<ChatOpenAI[\"withStructuredOutput\"]>;\n\n constructor() {\n const model = createModel(\"networkRecommender\");\n this.model = model.withStructuredOutput(NetworkRecommenderOutputSchema, {\n name: \"network_recommender\",\n });\n }\n\n /**\n * Ranks the provided networks by relevance to the user's profile.\n *\n * @param input - User profile and list of networks with rendered context.\n * @returns Ranked network IDs and one-sentence reasoning, or null on error.\n */\n @Timed()\n public async invoke(input: NetworkRecommenderInput): Promise<NetworkRecommenderOutput | null> {\n if (input.networks.length === 0) return null;\n\n logger.verbose(\"[NetworkRecommender.invoke] Ranking communities\", {\n networkCount: input.networks.length,\n });\n\n const networkList = input.networks\n .map((n, i) => `### Community ${i + 1} (ID: ${n.networkId})\\n${n.renderedContext}`)\n .join(\"\\n\\n\");\n\n const userSection = input.userContext.trim() || \"(not provided)\";\n\n const prompt = `## User Context\\n${userSection}\\n\\n## Communities to Rank\\n${networkList}`;\n\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(prompt),\n ];\n\n try {\n const result = await invokeWithAbortSignal(this.model, messages);\n const parsed = NetworkRecommenderOutputSchema.safeParse(result);\n if (!parsed.success) {\n logger.error(\"[NetworkRecommender] Schema validation failed\", { error: parsed.error });\n return null;\n }\n logger.verbose(\"[NetworkRecommender.invoke] Ranking complete\", {\n top: parsed.data.rankedNetworkIds[0],\n });\n return parsed.data;\n } catch (error) {\n logger.error(\"[NetworkRecommender] Error during execution\", { error });\n return null;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"network.recommender.js","sourceRoot":"/","sources":["network/network.recommender.ts"],"names":[],"mappings":"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","sourcesContent":["import { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\n\nimport { log } from \"../shared/observability/log.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\n// ─── Response schema ───────────────────────────────────────────────────────────\n\nexport const NetworkRecommenderOutputSchema = z.object({\n rankedNetworkIds: z\n .array(z.string())\n .describe(\"Network IDs ordered from most to least relevant for this user. Include all provided network IDs.\"),\n reasoning: z\n .string()\n .describe(\"One-sentence explanation of the top recommendation.\"),\n});\n\nexport type NetworkRecommenderOutput = z.infer<typeof NetworkRecommenderOutputSchema>;\n\n// ─── Input types ──────────────────────────────────────────────────────────────\n\nexport interface NetworkRecommenderNetwork {\n networkId: string;\n renderedContext: string;\n}\n\nexport interface NetworkRecommenderInput {\n /** The user's global user_context paragraph (profile-replacing identity text). */\n userContext: string;\n networks: NetworkRecommenderNetwork[];\n}\n\n// ─── Logger ───────────────────────────────────────────────────────────────────\n\nconst logger = log.lib.from(\"NetworkRecommender\");\n\n// ─── System prompt ────────────────────────────────────────────────────────────\n\nconst systemPrompt = `\nYou are a community matching agent for a social discovery network.\n\nTASK:\nGiven a user's profile and a list of communities, rank the communities from most to least relevant for this user.\nReturn ALL provided community IDs in ranked order.\n\nINPUTS:\n1. User Context: a synthesized paragraph describing the user (background, interests, skills, location, goals).\n2. Communities: a list of communities, each with an ID and a description.\n\nSCORING FACTORS (in priority order):\n1. Thematic alignment — do the community's topics match the user's interests and skills?\n2. Geographic relevance — does the user's location match the community's focus (if any)?\n3. Professional fit — does the community's purpose match the user's professional background?\n\nOUTPUT RULES:\n- Return ALL community IDs in your ranked list (no omissions).\n- If context is insufficient to differentiate, preserve original order.\n- Keep reasoning brief (one sentence about the top recommendation).\n`;\n\n// ─── Agent class ──────────────────────────────────────────────────────────────\n\n/**\n * LLM-based agent that ranks public communities against a user's profile.\n * Used during onboarding step 6 to surface the most relevant communities first.\n *\n * Follows the IntentIndexer pattern: `withStructuredOutput`, `invokeWithAbortSignal`,\n * null-on-error fallback. `createModel` is called inside the constructor (not at\n * module level) so that importing this file does not require OPENROUTER_API_KEY to\n * be set — tests that import `createNetworkTools` without a live LLM env are unaffected.\n */\nexport class NetworkRecommender {\n private model: ReturnType<typeof createStructuredModel>;\n\n constructor() {\n this.model = createStructuredModel(\"networkRecommender\", NetworkRecommenderOutputSchema, {\n name: \"network_recommender\",\n });\n }\n\n /**\n * Ranks the provided networks by relevance to the user's profile.\n *\n * @param input - User profile and list of networks with rendered context.\n * @returns Ranked network IDs and one-sentence reasoning, or null on error.\n */\n @Timed()\n public async invoke(input: NetworkRecommenderInput): Promise<NetworkRecommenderOutput | null> {\n if (input.networks.length === 0) return null;\n\n logger.verbose(\"[NetworkRecommender.invoke] Ranking communities\", {\n networkCount: input.networks.length,\n });\n\n const networkList = input.networks\n .map((n, i) => `### Community ${i + 1} (ID: ${n.networkId})\\n${n.renderedContext}`)\n .join(\"\\n\\n\");\n\n const userSection = input.userContext.trim() || \"(not provided)\";\n\n const prompt = `## User Context\\n${userSection}\\n\\n## Communities to Rank\\n${networkList}`;\n\n const messages = [\n new SystemMessage(systemPrompt),\n new HumanMessage(prompt),\n ];\n\n try {\n const result = await invokeWithAbortSignal(this.model, messages);\n const parsed = NetworkRecommenderOutputSchema.safeParse(result);\n if (!parsed.success) {\n logger.error(\"[NetworkRecommender] Schema validation failed\", { error: parsed.error });\n return null;\n }\n logger.verbose(\"[NetworkRecommender.invoke] Ranking complete\", {\n top: parsed.data.rankedNetworkIds[0],\n });\n return parsed.data;\n } catch (error) {\n logger.error(\"[NetworkRecommender] Error during execution\", { error });\n return null;\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"feed.categorizer.d.ts","sourceRoot":"/","sources":["opportunity/feed/feed.categorizer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAyB3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC,CAAC;AAgFF,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,KAAK,CAAiD;;IAO9D;;OAEG;IAEG,UAAU,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAoD5E"}
1
+ {"version":3,"file":"feed.categorizer.d.ts","sourceRoot":"/","sources":["opportunity/feed/feed.categorizer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAyB3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC,CAAC;AAgFF,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,KAAK,CAA2C;;IAMxD;;OAEG;IAEG,UAAU,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAoD5E"}
@@ -19,7 +19,7 @@ import { z } from 'zod';
19
19
  import { getIconNamesForPrompt, DEFAULT_HOME_SECTION_ICON } from '../../shared/ui/lucide.icon-catalog.js';
20
20
  import { protocolLogger } from '../../shared/observability/protocol.logger.js';
21
21
  import { Timed } from "../../shared/observability/performance.js";
22
- import { createModel } from "../../shared/agent/model.config.js";
22
+ import { createStructuredModel } from "../../shared/agent/model.config.js";
23
23
  import { invokeWithAbortSignal } from "../../shared/agent/model-signal.js";
24
24
  const logger = protocolLogger('HomeCategorizer');
25
25
  const categorizationSchema = z.object({
@@ -98,8 +98,7 @@ function reconcileSections(sections, maxIndex) {
98
98
  }
99
99
  export class HomeCategorizerAgent {
100
100
  constructor() {
101
- const llm = createModel("homeCategorizer");
102
- this.model = llm.withStructuredOutput(categorizationSchema, { name: 'home_sections' });
101
+ this.model = createStructuredModel("homeCategorizer", categorizationSchema, { name: 'home_sections' });
103
102
  }
104
103
  /**
105
104
  * Categorize presenter-produced cards into 1–5 sections with CTA-style titles and icons.
@@ -1 +1 @@
1
- {"version":3,"file":"feed.categorizer.js","sourceRoot":"/","sources":["opportunity/feed/feed.categorizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Home Categorizer Agent\n *\n * Takes a list of presenter-produced opportunity cards and returns dynamic sections\n * with CTA-style titles and Lucide icon names. Used by the home graph after\n * generateCardText.\n */\n\nimport type { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage, SystemMessage } from '@langchain/core/messages';\nimport { z } from 'zod';\n\nimport type { HomeSectionProposal } from './feed.state.js';\nimport { getIconNamesForPrompt, DEFAULT_HOME_SECTION_ICON } from '../../shared/ui/lucide.icon-catalog.js';\nimport { protocolLogger } from '../../shared/observability/protocol.logger.js';\nimport { Timed } from \"../../shared/observability/performance.js\";\nimport { createModel } from \"../../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger('HomeCategorizer');\n\nconst categorizationSchema = z.object({\n sections: z.array(\n z.object({\n id: z.string().describe('Short kebab-case id for the section'),\n title: z\n .string()\n .describe(\n 'Call-to-action style section heading (e.g. REACH OUT NOW, YOUR MOVE, CONNECTIONS WAITING FOR YOU)'\n ),\n subtitle: z.string().nullable().describe('Optional one-line description; use null if none'),\n iconName: z.string().describe('Lucide icon name in kebab-case from the allowed list'),\n itemIndices: z.array(z.number()).describe('Indices of cards in this section (0-based)'),\n })\n ),\n});\n\nexport type CategorizerInputItem = {\n index: number;\n headline?: string;\n mainText: string;\n name: string;\n viewerRole?: string;\n opportunityStatus?: string;\n};\n\nexport type CategorizerResult = {\n sections: HomeSectionProposal[];\n};\n\nconst UNCATEGORIZED_SECTION_ID = 'uncategorized';\nconst UNCATEGORIZED_SECTION_TITLE = 'UNCATEGORIZED OPPORTUNITIES';\n\n/** Single-section fallback grouping every card under one generic heading. */\nfunction buildFallbackResult(cards: CategorizerInputItem[]): CategorizerResult {\n return {\n sections: [\n {\n id: 'opportunities',\n title: 'OPPORTUNITIES',\n iconName: DEFAULT_HOME_SECTION_ICON,\n itemIndices: cards.map((c) => c.index),\n },\n ],\n };\n}\n\n/**\n * Build card summaries for the categorizer prompt.\n */\nfunction buildCardSummaries(cards: CategorizerInputItem[]): string {\n return cards\n .map((c) => {\n const role = c.viewerRole ?? 'party';\n const status = c.opportunityStatus ?? 'pending';\n return `[${c.index}] role=${role}; status=${status}; ${c.headline ?? c.mainText.slice(0, 60)}... (${c.name})`;\n })\n .join('\\n');\n}\n\nfunction reconcileSections(sections: HomeSectionProposal[], maxIndex: number): HomeSectionProposal[] {\n const assigned = new Set<number>();\n\n const normalizedSections = sections.map((section) => {\n const uniqueIndices = Array.from(\n new Set(section.itemIndices.filter((index) => index >= 0 && index <= maxIndex))\n ).sort((a, b) => a - b);\n\n for (const index of uniqueIndices) {\n assigned.add(index);\n }\n\n return {\n ...section,\n itemIndices: uniqueIndices,\n };\n });\n\n const missingIndices: number[] = [];\n for (let index = 0; index <= maxIndex; index += 1) {\n if (!assigned.has(index)) {\n missingIndices.push(index);\n }\n }\n\n if (missingIndices.length === 0) {\n return normalizedSections;\n }\n\n const fallbackSection = normalizedSections.find((section) => section.id === UNCATEGORIZED_SECTION_ID);\n if (fallbackSection) {\n fallbackSection.itemIndices = Array.from(\n new Set([...fallbackSection.itemIndices, ...missingIndices])\n ).sort((a, b) => a - b);\n return normalizedSections;\n }\n\n return [\n ...normalizedSections,\n {\n id: UNCATEGORIZED_SECTION_ID,\n title: UNCATEGORIZED_SECTION_TITLE,\n iconName: DEFAULT_HOME_SECTION_ICON,\n itemIndices: missingIndices,\n },\n ];\n}\n\nexport class HomeCategorizerAgent {\n private model: ReturnType<ChatOpenAI['withStructuredOutput']>;\n\n constructor() {\n const llm = createModel(\"homeCategorizer\");\n this.model = llm.withStructuredOutput(categorizationSchema, { name: 'home_sections' });\n }\n\n /**\n * Categorize presenter-produced cards into 1–5 sections with CTA-style titles and icons.\n */\n @Timed()\n async categorize(cards: CategorizerInputItem[]): Promise<CategorizerResult> {\n if (cards.length === 0) {\n return { sections: [] };\n }\n const iconList = getIconNamesForPrompt();\n const cardSummaries = buildCardSummaries(cards);\n const maxIndex = cards.length - 1;\n\n const systemPrompt = `You are organizing connection opportunities into dynamic sections for a user's home feed.\n\nGiven a list of opportunity cards (each with index, headline/summary, and name), group them into 1–5 sections.\n\nEach section must have:\n- id: short kebab-case identifier (e.g. waiting-for-action, your-perspective, connector)\n- title: CALL-TO-ACTION style section heading in uppercase. Write as an action or invitation, not a neutral label. Examples: REACH OUT NOW, YOUR MOVE, CONNECTIONS WAITING FOR YOU, INTROS READY TO SEND, PEOPLE YOU SHOULD MEET\n- subtitle: optional one-line description\n- iconName: exactly ONE icon from this list (use the name as-is): ${iconList}\n- itemIndices: array of card indices that belong in this section (each index 0 to ${maxIndex} at most once)\n\nRules:\n- Use only icon names from the list above.\n- Every card index must appear in exactly one section.\n- Section titles must read as calls-to-action or invitations, not passive labels.\n- If a card has role=introducer and status=pending, prioritize grouping those cards under a connector curation section with a CTA title like \"YOU'RE THE CONNECTOR THEY NEED\", \"MAKE THE INTRO\", or \"DECIDE IF IT'S A GOOD MATCH\".`;\n\n const userContent = `Cards to categorize:\\n${cardSummaries}\\n\\nOutput sections with id, title (CTA-style), subtitle (optional), iconName, and itemIndices.`;\n\n try {\n const result = await invokeWithAbortSignal(this.model, [\n new SystemMessage(systemPrompt),\n new HumanMessage(userContent),\n ]);\n const parsed = categorizationSchema.safeParse(result);\n if (!parsed.success) {\n logger.warn('HomeCategorizer parse failed', { error: parsed.error });\n return buildFallbackResult(cards);\n }\n const sections: HomeSectionProposal[] = parsed.data.sections.map((s) => ({\n id: s.id,\n title: s.title,\n subtitle: s.subtitle ?? undefined,\n iconName: s.iconName,\n itemIndices: s.itemIndices.filter((i) => i >= 0 && i <= maxIndex),\n }));\n const reconciledSections = reconcileSections(sections, maxIndex);\n return { sections: reconciledSections };\n } catch (e) {\n const err = e instanceof Error ? { message: e.message, name: e.name } : String(e);\n logger.error('HomeCategorizer categorize failed', { error: err });\n return buildFallbackResult(cards);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"feed.categorizer.js","sourceRoot":"/","sources":["opportunity/feed/feed.categorizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Home Categorizer Agent\n *\n * Takes a list of presenter-produced opportunity cards and returns dynamic sections\n * with CTA-style titles and Lucide icon names. Used by the home graph after\n * generateCardText.\n */\n\nimport { HumanMessage, SystemMessage } from '@langchain/core/messages';\nimport { z } from 'zod';\n\nimport type { HomeSectionProposal } from './feed.state.js';\nimport { getIconNamesForPrompt, DEFAULT_HOME_SECTION_ICON } from '../../shared/ui/lucide.icon-catalog.js';\nimport { protocolLogger } from '../../shared/observability/protocol.logger.js';\nimport { Timed } from \"../../shared/observability/performance.js\";\nimport { createStructuredModel } from \"../../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger('HomeCategorizer');\n\nconst categorizationSchema = z.object({\n sections: z.array(\n z.object({\n id: z.string().describe('Short kebab-case id for the section'),\n title: z\n .string()\n .describe(\n 'Call-to-action style section heading (e.g. REACH OUT NOW, YOUR MOVE, CONNECTIONS WAITING FOR YOU)'\n ),\n subtitle: z.string().nullable().describe('Optional one-line description; use null if none'),\n iconName: z.string().describe('Lucide icon name in kebab-case from the allowed list'),\n itemIndices: z.array(z.number()).describe('Indices of cards in this section (0-based)'),\n })\n ),\n});\n\nexport type CategorizerInputItem = {\n index: number;\n headline?: string;\n mainText: string;\n name: string;\n viewerRole?: string;\n opportunityStatus?: string;\n};\n\nexport type CategorizerResult = {\n sections: HomeSectionProposal[];\n};\n\nconst UNCATEGORIZED_SECTION_ID = 'uncategorized';\nconst UNCATEGORIZED_SECTION_TITLE = 'UNCATEGORIZED OPPORTUNITIES';\n\n/** Single-section fallback grouping every card under one generic heading. */\nfunction buildFallbackResult(cards: CategorizerInputItem[]): CategorizerResult {\n return {\n sections: [\n {\n id: 'opportunities',\n title: 'OPPORTUNITIES',\n iconName: DEFAULT_HOME_SECTION_ICON,\n itemIndices: cards.map((c) => c.index),\n },\n ],\n };\n}\n\n/**\n * Build card summaries for the categorizer prompt.\n */\nfunction buildCardSummaries(cards: CategorizerInputItem[]): string {\n return cards\n .map((c) => {\n const role = c.viewerRole ?? 'party';\n const status = c.opportunityStatus ?? 'pending';\n return `[${c.index}] role=${role}; status=${status}; ${c.headline ?? c.mainText.slice(0, 60)}... (${c.name})`;\n })\n .join('\\n');\n}\n\nfunction reconcileSections(sections: HomeSectionProposal[], maxIndex: number): HomeSectionProposal[] {\n const assigned = new Set<number>();\n\n const normalizedSections = sections.map((section) => {\n const uniqueIndices = Array.from(\n new Set(section.itemIndices.filter((index) => index >= 0 && index <= maxIndex))\n ).sort((a, b) => a - b);\n\n for (const index of uniqueIndices) {\n assigned.add(index);\n }\n\n return {\n ...section,\n itemIndices: uniqueIndices,\n };\n });\n\n const missingIndices: number[] = [];\n for (let index = 0; index <= maxIndex; index += 1) {\n if (!assigned.has(index)) {\n missingIndices.push(index);\n }\n }\n\n if (missingIndices.length === 0) {\n return normalizedSections;\n }\n\n const fallbackSection = normalizedSections.find((section) => section.id === UNCATEGORIZED_SECTION_ID);\n if (fallbackSection) {\n fallbackSection.itemIndices = Array.from(\n new Set([...fallbackSection.itemIndices, ...missingIndices])\n ).sort((a, b) => a - b);\n return normalizedSections;\n }\n\n return [\n ...normalizedSections,\n {\n id: UNCATEGORIZED_SECTION_ID,\n title: UNCATEGORIZED_SECTION_TITLE,\n iconName: DEFAULT_HOME_SECTION_ICON,\n itemIndices: missingIndices,\n },\n ];\n}\n\nexport class HomeCategorizerAgent {\n private model: ReturnType<typeof createStructuredModel>;\n\n constructor() {\n this.model = createStructuredModel(\"homeCategorizer\", categorizationSchema, { name: 'home_sections' });\n }\n\n /**\n * Categorize presenter-produced cards into 1–5 sections with CTA-style titles and icons.\n */\n @Timed()\n async categorize(cards: CategorizerInputItem[]): Promise<CategorizerResult> {\n if (cards.length === 0) {\n return { sections: [] };\n }\n const iconList = getIconNamesForPrompt();\n const cardSummaries = buildCardSummaries(cards);\n const maxIndex = cards.length - 1;\n\n const systemPrompt = `You are organizing connection opportunities into dynamic sections for a user's home feed.\n\nGiven a list of opportunity cards (each with index, headline/summary, and name), group them into 1–5 sections.\n\nEach section must have:\n- id: short kebab-case identifier (e.g. waiting-for-action, your-perspective, connector)\n- title: CALL-TO-ACTION style section heading in uppercase. Write as an action or invitation, not a neutral label. Examples: REACH OUT NOW, YOUR MOVE, CONNECTIONS WAITING FOR YOU, INTROS READY TO SEND, PEOPLE YOU SHOULD MEET\n- subtitle: optional one-line description\n- iconName: exactly ONE icon from this list (use the name as-is): ${iconList}\n- itemIndices: array of card indices that belong in this section (each index 0 to ${maxIndex} at most once)\n\nRules:\n- Use only icon names from the list above.\n- Every card index must appear in exactly one section.\n- Section titles must read as calls-to-action or invitations, not passive labels.\n- If a card has role=introducer and status=pending, prioritize grouping those cards under a connector curation section with a CTA title like \"YOU'RE THE CONNECTOR THEY NEED\", \"MAKE THE INTRO\", or \"DECIDE IF IT'S A GOOD MATCH\".`;\n\n const userContent = `Cards to categorize:\\n${cardSummaries}\\n\\nOutput sections with id, title (CTA-style), subtitle (optional), iconName, and itemIndices.`;\n\n try {\n const result = await invokeWithAbortSignal(this.model, [\n new SystemMessage(systemPrompt),\n new HumanMessage(userContent),\n ]);\n const parsed = categorizationSchema.safeParse(result);\n if (!parsed.success) {\n logger.warn('HomeCategorizer parse failed', { error: parsed.error });\n return buildFallbackResult(cards);\n }\n const sections: HomeSectionProposal[] = parsed.data.sections.map((s) => ({\n id: s.id,\n title: s.title,\n subtitle: s.subtitle ?? undefined,\n iconName: s.iconName,\n itemIndices: s.itemIndices.filter((i) => i >= 0 && i <= maxIndex),\n }));\n const reconciledSections = reconcileSections(sections, maxIndex);\n return { sections: reconciledSections };\n } catch (e) {\n const err = e instanceof Error ? { message: e.message, name: e.name } : String(e);\n logger.error('HomeCategorizer categorize failed', { error: err });\n return buildFallbackResult(cards);\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.evaluator.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.evaluator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"opportunity.evaluator.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.evaluator.ts"],"names":[],"mappings":"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"}
@@ -13,11 +13,10 @@ import { z } from "zod";
13
13
  import { protocolLogger } from "../shared/observability/protocol.logger.js";
14
14
  import { Timed } from "../shared/observability/performance.js";
15
15
  import { stripUuids } from "./opportunity.presentation.js";
16
- import { createModel } from "../shared/agent/model.config.js";
16
+ import { createStructuredModel } from "../shared/agent/model.config.js";
17
17
  import { invokeWithAbortSignal } from "../shared/agent/model-signal.js";
18
18
  import { renderOpportunityEvidenceForPrompt } from './opportunity.evidence.js';
19
19
  const logger = protocolLogger("OpportunityEvaluator");
20
- const model = createModel("opportunityEvaluator");
21
20
  // ──────────────────────────────────────────────────────────────
22
21
  // 1. SYSTEM PROMPT
23
22
  // ──────────────────────────────────────────────────────────────
@@ -186,10 +185,10 @@ const entityBundleResponseFormat = z.object({
186
185
  });
187
186
  export class OpportunityEvaluator {
188
187
  constructor(options) {
189
- this.model = model.withStructuredOutput(responseFormat, {
188
+ this.model = createStructuredModel("opportunityEvaluator", responseFormat, {
190
189
  name: "opportunity_evaluator"
191
190
  });
192
- this.entityBundleModel = options?.entityBundleModel ?? model.withStructuredOutput(entityBundleResponseFormat, {
191
+ this.entityBundleModel = options?.entityBundleModel ?? createStructuredModel("opportunityEvaluator", entityBundleResponseFormat, {
193
192
  name: "opportunity_evaluator_entity_bundle"
194
193
  });
195
194
  }