solanapolis 1.0.0
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/README.md +518 -0
- package/bin/solanapolis.js +197 -0
- package/convex/_generated/api.d.ts +175 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +60 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/agent/conversation.ts +352 -0
- package/convex/agent/embeddingsCache.ts +110 -0
- package/convex/agent/memory.ts +450 -0
- package/convex/agent/schema.ts +53 -0
- package/convex/aiChat.ts +54 -0
- package/convex/aiTown/agent.ts +382 -0
- package/convex/aiTown/agentDescription.ts +27 -0
- package/convex/aiTown/agentInputs.ts +155 -0
- package/convex/aiTown/agentOperations.ts +178 -0
- package/convex/aiTown/conversation.ts +395 -0
- package/convex/aiTown/conversationMembership.ts +38 -0
- package/convex/aiTown/game.ts +371 -0
- package/convex/aiTown/ids.ts +32 -0
- package/convex/aiTown/inputHandler.ts +9 -0
- package/convex/aiTown/inputs.ts +25 -0
- package/convex/aiTown/insertInput.ts +20 -0
- package/convex/aiTown/location.ts +32 -0
- package/convex/aiTown/main.ts +154 -0
- package/convex/aiTown/movement.ts +189 -0
- package/convex/aiTown/player.ts +310 -0
- package/convex/aiTown/playerDescription.ts +35 -0
- package/convex/aiTown/schema.ts +79 -0
- package/convex/aiTown/world.ts +65 -0
- package/convex/aiTown/worldMap.ts +74 -0
- package/convex/chat.ts +79 -0
- package/convex/constants.ts +78 -0
- package/convex/convex.config.ts +6 -0
- package/convex/crons.ts +89 -0
- package/convex/engine/abstractGame.ts +199 -0
- package/convex/engine/historicalObject.ts +355 -0
- package/convex/engine/schema.ts +56 -0
- package/convex/http.ts +36 -0
- package/convex/init.ts +110 -0
- package/convex/messages.ts +53 -0
- package/convex/npcCarAgents.ts +415 -0
- package/convex/schema.ts +61 -0
- package/convex/streaming.ts +23 -0
- package/convex/testing.ts +202 -0
- package/convex/tsconfig.json +18 -0
- package/convex/util/FastIntegerCompression.ts +221 -0
- package/convex/util/assertNever.ts +4 -0
- package/convex/util/asyncMap.ts +20 -0
- package/convex/util/compression.ts +71 -0
- package/convex/util/geometry.ts +132 -0
- package/convex/util/isSimpleObject.ts +11 -0
- package/convex/util/llm.ts +724 -0
- package/convex/util/minheap.ts +38 -0
- package/convex/util/object.ts +22 -0
- package/convex/util/sleep.ts +3 -0
- package/convex/util/types.ts +33 -0
- package/convex/util/xxhash.ts +228 -0
- package/convex/world.ts +257 -0
- package/data/animations/campfire.json +45 -0
- package/data/animations/gentlesparkle.json +37 -0
- package/data/animations/gentlesplash.json +61 -0
- package/data/animations/gentlewaterfall.json +61 -0
- package/data/animations/windmill.json +78 -0
- package/data/characters.ts +121 -0
- package/data/convertMap.js +74 -0
- package/data/gentle.js +330 -0
- package/data/spritesheets/f1.ts +75 -0
- package/data/spritesheets/f2.ts +75 -0
- package/data/spritesheets/f3.ts +75 -0
- package/data/spritesheets/f4.ts +75 -0
- package/data/spritesheets/f5.ts +75 -0
- package/data/spritesheets/f6.ts +75 -0
- package/data/spritesheets/f7.ts +75 -0
- package/data/spritesheets/f8.ts +75 -0
- package/data/spritesheets/p1.ts +59 -0
- package/data/spritesheets/p2.ts +59 -0
- package/data/spritesheets/p3.ts +59 -0
- package/data/spritesheets/player.ts +59 -0
- package/data/spritesheets/types.ts +26 -0
- package/eslint.config.mjs +37 -0
- package/next.config.ts +7 -0
- package/package.json +85 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/helius-icon.svg +84 -0
- package/public/helius-logo.svg +85 -0
- package/public/next.svg +1 -0
- package/public/plane.glb +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/clear-city.ts +74 -0
- package/scripts/seed-wallets.ts +185 -0
- package/scripts/setup-webhook.ts +73 -0
- package/src/app/api/auth/callback/route.ts +6 -0
- package/src/app/api/auth/link-wallet/route.ts +6 -0
- package/src/app/api/auth/phantom/route.ts +6 -0
- package/src/app/api/broadcast-position/route.ts +59 -0
- package/src/app/api/leaderboard/route.ts +85 -0
- package/src/app/api/network-stats/route.ts +86 -0
- package/src/app/api/parcel-reward/route.ts +181 -0
- package/src/app/api/queue-status/route.ts +30 -0
- package/src/app/api/snapshots/route.ts +37 -0
- package/src/app/api/transactions/enhanced/route.ts +57 -0
- package/src/app/api/treasury/route.ts +83 -0
- package/src/app/api/wallet/[address]/balances/route.ts +124 -0
- package/src/app/api/wallet/[address]/identity/route.ts +32 -0
- package/src/app/api/wallet/[address]/route.ts +216 -0
- package/src/app/api/wallet/[address]/traded-tokens/route.ts +41 -0
- package/src/app/api/wallets/route.ts +68 -0
- package/src/app/api/webhooks/helius/route.ts +76 -0
- package/src/app/auth/callback/page.tsx +29 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +39 -0
- package/src/app/layout.tsx +43 -0
- package/src/app/page.tsx +16 -0
- package/src/components/AITownNPCs.tsx +206 -0
- package/src/components/ActivityFeed.tsx +189 -0
- package/src/components/AuthPanel.tsx +163 -0
- package/src/components/BeachScene.tsx +280 -0
- package/src/components/Building.tsx +138 -0
- package/src/components/CesiumFlight.tsx +1768 -0
- package/src/components/CesiumGlobe.tsx +616 -0
- package/src/components/CitizenCard.tsx +442 -0
- package/src/components/CitizenCardModal.tsx +153 -0
- package/src/components/CityGrid.tsx +313 -0
- package/src/components/CityLandmarks.tsx +427 -0
- package/src/components/CityScene.tsx +1289 -0
- package/src/components/CitySlotsBadge.tsx +68 -0
- package/src/components/CockpitHUD.tsx +460 -0
- package/src/components/ConvexWrapper.tsx +19 -0
- package/src/components/DubaiDistrict.tsx +630 -0
- package/src/components/FlightMiniMap.tsx +133 -0
- package/src/components/GameChat.tsx +383 -0
- package/src/components/GameHUD.tsx +393 -0
- package/src/components/Ground.tsx +14 -0
- package/src/components/HowItWorksModal.tsx +251 -0
- package/src/components/IngestionBanner.tsx +123 -0
- package/src/components/InstancedBuildings.tsx +316 -0
- package/src/components/InstancedCars.tsx +504 -0
- package/src/components/InstancedCityPlanes.tsx +259 -0
- package/src/components/InstancedHouses.tsx +246 -0
- package/src/components/InstancedLampPosts.tsx +201 -0
- package/src/components/InstancedResidentCars.tsx +357 -0
- package/src/components/InstancedRoadDashes.tsx +42 -0
- package/src/components/InstancedSkyscrapers.tsx +434 -0
- package/src/components/InstancedTrees.tsx +67 -0
- package/src/components/LeaderboardPanel.tsx +136 -0
- package/src/components/MultiplayerPlanes.tsx +128 -0
- package/src/components/NetworkStats.tsx +83 -0
- package/src/components/NewBuildingSpotlight.tsx +93 -0
- package/src/components/ParcelChallengeBanner.tsx +242 -0
- package/src/components/ParcelReward.tsx +191 -0
- package/src/components/Park.tsx +42 -0
- package/src/components/PhantomWrapper.tsx +22 -0
- package/src/components/PixelStreamViewer.tsx +335 -0
- package/src/components/PlaneMode.tsx +190 -0
- package/src/components/PlayerCar.tsx +211 -0
- package/src/components/PlayerPlane.tsx +255 -0
- package/src/components/ProjectileRenderer.tsx +249 -0
- package/src/components/QueueStatusBanner.tsx +86 -0
- package/src/components/RealPlayerTags.tsx +82 -0
- package/src/components/SceneLighting.tsx +382 -0
- package/src/components/SelectionBeam.tsx +59 -0
- package/src/components/SwapPanel.tsx +104 -0
- package/src/components/SwapParticles.tsx +237 -0
- package/src/components/TreasureGate.tsx +505 -0
- package/src/components/WalletPanel.tsx +421 -0
- package/src/components/WalletSearch.tsx +244 -0
- package/src/components/WelcomeOverlay.tsx +135 -0
- package/src/components/WindowTooltip.tsx +498 -0
- package/src/context/AuthContext.tsx +230 -0
- package/src/lib/bot-detection.ts +125 -0
- package/src/lib/building-math.ts +136 -0
- package/src/lib/building-shader.ts +253 -0
- package/src/lib/car-paths.ts +244 -0
- package/src/lib/car-system.ts +182 -0
- package/src/lib/city-constants.ts +29 -0
- package/src/lib/city-slots.ts +35 -0
- package/src/lib/city-zoning.ts +64 -0
- package/src/lib/collision-map.ts +147 -0
- package/src/lib/day-night.ts +252 -0
- package/src/lib/export-card.ts +28 -0
- package/src/lib/helius-webhook.ts +90 -0
- package/src/lib/helius.ts +74 -0
- package/src/lib/house-shader.ts +119 -0
- package/src/lib/mock-data.ts +56 -0
- package/src/lib/multiplayer-manager.ts +329 -0
- package/src/lib/plane-physics.ts +66 -0
- package/src/lib/player-car.ts +147 -0
- package/src/lib/player-plane.ts +200 -0
- package/src/lib/projectile-system.ts +272 -0
- package/src/lib/skyscraper-types.ts +52 -0
- package/src/lib/sound-engine.ts +464 -0
- package/src/lib/supabase-admin.ts +9 -0
- package/src/lib/supabase.ts +8 -0
- package/src/lib/swap-events.ts +70 -0
- package/src/middleware.ts +37 -0
- package/src/types/phantom.d.ts +16 -0
- package/src/types/wallet.ts +20 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Generated utilities for implementing server-side Convex query and mutation functions.
|
|
4
|
+
*
|
|
5
|
+
* THIS CODE IS AUTOMATICALLY GENERATED.
|
|
6
|
+
*
|
|
7
|
+
* To regenerate, run `npx convex dev`.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
actionGeneric,
|
|
13
|
+
httpActionGeneric,
|
|
14
|
+
queryGeneric,
|
|
15
|
+
mutationGeneric,
|
|
16
|
+
internalActionGeneric,
|
|
17
|
+
internalMutationGeneric,
|
|
18
|
+
internalQueryGeneric,
|
|
19
|
+
} from "convex/server";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Define a query in this Convex app's public API.
|
|
23
|
+
*
|
|
24
|
+
* This function will be allowed to read your Convex database and will be accessible from the client.
|
|
25
|
+
*
|
|
26
|
+
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
|
27
|
+
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
|
28
|
+
*/
|
|
29
|
+
export const query = queryGeneric;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Define a query that is only accessible from other Convex functions (but not from the client).
|
|
33
|
+
*
|
|
34
|
+
* This function will be allowed to read from your Convex database. It will not be accessible from the client.
|
|
35
|
+
*
|
|
36
|
+
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
|
37
|
+
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
|
38
|
+
*/
|
|
39
|
+
export const internalQuery = internalQueryGeneric;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Define a mutation in this Convex app's public API.
|
|
43
|
+
*
|
|
44
|
+
* This function will be allowed to modify your Convex database and will be accessible from the client.
|
|
45
|
+
*
|
|
46
|
+
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
|
47
|
+
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
|
48
|
+
*/
|
|
49
|
+
export const mutation = mutationGeneric;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
|
53
|
+
*
|
|
54
|
+
* This function will be allowed to modify your Convex database. It will not be accessible from the client.
|
|
55
|
+
*
|
|
56
|
+
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
|
57
|
+
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
|
58
|
+
*/
|
|
59
|
+
export const internalMutation = internalMutationGeneric;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Define an action in this Convex app's public API.
|
|
63
|
+
*
|
|
64
|
+
* An action is a function which can execute any JavaScript code, including non-deterministic
|
|
65
|
+
* code and code with side-effects, like calling third-party services.
|
|
66
|
+
* They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
|
|
67
|
+
* They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
|
|
68
|
+
*
|
|
69
|
+
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
|
70
|
+
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
|
71
|
+
*/
|
|
72
|
+
export const action = actionGeneric;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Define an action that is only accessible from other Convex functions (but not from the client).
|
|
76
|
+
*
|
|
77
|
+
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
|
78
|
+
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
|
79
|
+
*/
|
|
80
|
+
export const internalAction = internalActionGeneric;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Define an HTTP action.
|
|
84
|
+
*
|
|
85
|
+
* The wrapped function will be used to respond to HTTP requests received
|
|
86
|
+
* by a Convex deployment if the requests matches the path and method where
|
|
87
|
+
* this action is routed. Be sure to route your httpAction in `convex/http.js`.
|
|
88
|
+
*
|
|
89
|
+
* @param func - The function. It receives an {@link ActionCtx} as its first argument
|
|
90
|
+
* and a Fetch API `Request` object as its second.
|
|
91
|
+
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
|
|
92
|
+
*/
|
|
93
|
+
export const httpAction = httpActionGeneric;
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { v } from 'convex/values';
|
|
2
|
+
import { Id } from '../_generated/dataModel';
|
|
3
|
+
import { ActionCtx, internalQuery } from '../_generated/server';
|
|
4
|
+
import { LLMMessage, chatCompletion } from '../util/llm';
|
|
5
|
+
import * as memory from './memory';
|
|
6
|
+
import { api, internal } from '../_generated/api';
|
|
7
|
+
import * as embeddingsCache from './embeddingsCache';
|
|
8
|
+
import { GameId, conversationId, playerId } from '../aiTown/ids';
|
|
9
|
+
import { NUM_MEMORIES_TO_SEARCH } from '../constants';
|
|
10
|
+
|
|
11
|
+
const selfInternal = internal.agent.conversation;
|
|
12
|
+
|
|
13
|
+
export async function startConversationMessage(
|
|
14
|
+
ctx: ActionCtx,
|
|
15
|
+
worldId: Id<'worlds'>,
|
|
16
|
+
conversationId: GameId<'conversations'>,
|
|
17
|
+
playerId: GameId<'players'>,
|
|
18
|
+
otherPlayerId: GameId<'players'>,
|
|
19
|
+
): Promise<string> {
|
|
20
|
+
const { player, otherPlayer, agent, otherAgent, lastConversation } = await ctx.runQuery(
|
|
21
|
+
selfInternal.queryPromptData,
|
|
22
|
+
{
|
|
23
|
+
worldId,
|
|
24
|
+
playerId,
|
|
25
|
+
otherPlayerId,
|
|
26
|
+
conversationId,
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
const embedding = await embeddingsCache.fetch(
|
|
30
|
+
ctx,
|
|
31
|
+
`${player.name} is talking to ${otherPlayer.name}`,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const memories = await memory.searchMemories(
|
|
35
|
+
ctx,
|
|
36
|
+
player.id as GameId<'players'>,
|
|
37
|
+
embedding,
|
|
38
|
+
Number(process.env.NUM_MEMORIES_TO_SEARCH) || NUM_MEMORIES_TO_SEARCH,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const memoryWithOtherPlayer = memories.find(
|
|
42
|
+
(m) => m.data.type === 'conversation' && m.data.playerIds.includes(otherPlayerId),
|
|
43
|
+
);
|
|
44
|
+
const prompt = [
|
|
45
|
+
`You are ${player.name}, and you just started a conversation with ${otherPlayer.name}.`,
|
|
46
|
+
];
|
|
47
|
+
prompt.push(...agentPrompts(otherPlayer, agent, otherAgent ?? null));
|
|
48
|
+
prompt.push(...previousConversationPrompt(otherPlayer, lastConversation));
|
|
49
|
+
prompt.push(...relatedMemoriesPrompt(memories));
|
|
50
|
+
if (memoryWithOtherPlayer) {
|
|
51
|
+
prompt.push(
|
|
52
|
+
`Be sure to include some detail or question about a previous conversation in your greeting.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const lastPrompt = `${player.name} to ${otherPlayer.name}:`;
|
|
56
|
+
prompt.push(lastPrompt);
|
|
57
|
+
|
|
58
|
+
const { content } = await chatCompletion({
|
|
59
|
+
messages: [
|
|
60
|
+
{
|
|
61
|
+
role: 'system',
|
|
62
|
+
content: prompt.join('\n'),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
max_tokens: 300,
|
|
66
|
+
stop: stopWords(otherPlayer.name, player.name),
|
|
67
|
+
});
|
|
68
|
+
return trimContentPrefx(content, lastPrompt);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function trimContentPrefx(content: string, prompt: string) {
|
|
72
|
+
if (content.startsWith(prompt)) {
|
|
73
|
+
return content.slice(prompt.length).trim();
|
|
74
|
+
}
|
|
75
|
+
return content;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function continueConversationMessage(
|
|
79
|
+
ctx: ActionCtx,
|
|
80
|
+
worldId: Id<'worlds'>,
|
|
81
|
+
conversationId: GameId<'conversations'>,
|
|
82
|
+
playerId: GameId<'players'>,
|
|
83
|
+
otherPlayerId: GameId<'players'>,
|
|
84
|
+
): Promise<string> {
|
|
85
|
+
const { player, otherPlayer, conversation, agent, otherAgent } = await ctx.runQuery(
|
|
86
|
+
selfInternal.queryPromptData,
|
|
87
|
+
{
|
|
88
|
+
worldId,
|
|
89
|
+
playerId,
|
|
90
|
+
otherPlayerId,
|
|
91
|
+
conversationId,
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const started = new Date(conversation.created);
|
|
96
|
+
const embedding = await embeddingsCache.fetch(
|
|
97
|
+
ctx,
|
|
98
|
+
`What do you think about ${otherPlayer.name}?`,
|
|
99
|
+
);
|
|
100
|
+
const memories = await memory.searchMemories(ctx, player.id as GameId<'players'>, embedding, 3);
|
|
101
|
+
const prompt = [
|
|
102
|
+
`You are ${player.name}, and you're currently in a conversation with ${otherPlayer.name}.`,
|
|
103
|
+
`The conversation started at ${started.toLocaleString()}. It's now ${now.toLocaleString()}.`,
|
|
104
|
+
];
|
|
105
|
+
prompt.push(...agentPrompts(otherPlayer, agent, otherAgent ?? null));
|
|
106
|
+
prompt.push(...relatedMemoriesPrompt(memories));
|
|
107
|
+
prompt.push(
|
|
108
|
+
`Below is the current chat history between you and ${otherPlayer.name}.`,
|
|
109
|
+
`DO NOT greet them again. Do NOT use the word "Hey" too often. Your response should be brief and within 200 characters.`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const llmMessages: LLMMessage[] = [
|
|
113
|
+
{
|
|
114
|
+
role: 'system',
|
|
115
|
+
content: prompt.join('\n'),
|
|
116
|
+
},
|
|
117
|
+
...(await previousMessages(
|
|
118
|
+
ctx,
|
|
119
|
+
worldId,
|
|
120
|
+
player,
|
|
121
|
+
otherPlayer,
|
|
122
|
+
conversation.id as GameId<'conversations'>,
|
|
123
|
+
)),
|
|
124
|
+
];
|
|
125
|
+
const lastPrompt = `${player.name} to ${otherPlayer.name}:`;
|
|
126
|
+
llmMessages.push({ role: 'user', content: lastPrompt });
|
|
127
|
+
|
|
128
|
+
const { content } = await chatCompletion({
|
|
129
|
+
messages: llmMessages,
|
|
130
|
+
max_tokens: 300,
|
|
131
|
+
stop: stopWords(otherPlayer.name, player.name),
|
|
132
|
+
});
|
|
133
|
+
return trimContentPrefx(content, lastPrompt);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function leaveConversationMessage(
|
|
137
|
+
ctx: ActionCtx,
|
|
138
|
+
worldId: Id<'worlds'>,
|
|
139
|
+
conversationId: GameId<'conversations'>,
|
|
140
|
+
playerId: GameId<'players'>,
|
|
141
|
+
otherPlayerId: GameId<'players'>,
|
|
142
|
+
): Promise<string> {
|
|
143
|
+
const { player, otherPlayer, conversation, agent, otherAgent } = await ctx.runQuery(
|
|
144
|
+
selfInternal.queryPromptData,
|
|
145
|
+
{
|
|
146
|
+
worldId,
|
|
147
|
+
playerId,
|
|
148
|
+
otherPlayerId,
|
|
149
|
+
conversationId,
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
const prompt = [
|
|
153
|
+
`You are ${player.name}, and you're currently in a conversation with ${otherPlayer.name}.`,
|
|
154
|
+
`You've decided to leave the question and would like to politely tell them you're leaving the conversation.`,
|
|
155
|
+
];
|
|
156
|
+
prompt.push(...agentPrompts(otherPlayer, agent, otherAgent ?? null));
|
|
157
|
+
prompt.push(
|
|
158
|
+
`Below is the current chat history between you and ${otherPlayer.name}.`,
|
|
159
|
+
`How would you like to tell them that you're leaving? Your response should be brief and within 200 characters.`,
|
|
160
|
+
);
|
|
161
|
+
const llmMessages: LLMMessage[] = [
|
|
162
|
+
{
|
|
163
|
+
role: 'system',
|
|
164
|
+
content: prompt.join('\n'),
|
|
165
|
+
},
|
|
166
|
+
...(await previousMessages(
|
|
167
|
+
ctx,
|
|
168
|
+
worldId,
|
|
169
|
+
player,
|
|
170
|
+
otherPlayer,
|
|
171
|
+
conversation.id as GameId<'conversations'>,
|
|
172
|
+
)),
|
|
173
|
+
];
|
|
174
|
+
const lastPrompt = `${player.name} to ${otherPlayer.name}:`;
|
|
175
|
+
llmMessages.push({ role: 'user', content: lastPrompt });
|
|
176
|
+
|
|
177
|
+
const { content } = await chatCompletion({
|
|
178
|
+
messages: llmMessages,
|
|
179
|
+
max_tokens: 300,
|
|
180
|
+
stop: stopWords(otherPlayer.name, player.name),
|
|
181
|
+
});
|
|
182
|
+
return trimContentPrefx(content, lastPrompt);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function agentPrompts(
|
|
186
|
+
otherPlayer: { name: string },
|
|
187
|
+
agent: { identity: string; plan: string } | null,
|
|
188
|
+
otherAgent: { identity: string; plan: string } | null,
|
|
189
|
+
): string[] {
|
|
190
|
+
const prompt = [];
|
|
191
|
+
if (agent) {
|
|
192
|
+
prompt.push(`About you: ${agent.identity}`);
|
|
193
|
+
prompt.push(`Your goals for the conversation: ${agent.plan}`);
|
|
194
|
+
}
|
|
195
|
+
if (otherAgent) {
|
|
196
|
+
prompt.push(`About ${otherPlayer.name}: ${otherAgent.identity}`);
|
|
197
|
+
}
|
|
198
|
+
return prompt;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function previousConversationPrompt(
|
|
202
|
+
otherPlayer: { name: string },
|
|
203
|
+
conversation: { created: number } | null,
|
|
204
|
+
): string[] {
|
|
205
|
+
const prompt = [];
|
|
206
|
+
if (conversation) {
|
|
207
|
+
const prev = new Date(conversation.created);
|
|
208
|
+
const now = new Date();
|
|
209
|
+
prompt.push(
|
|
210
|
+
`Last time you chatted with ${
|
|
211
|
+
otherPlayer.name
|
|
212
|
+
} it was ${prev.toLocaleString()}. It's now ${now.toLocaleString()}.`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return prompt;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function relatedMemoriesPrompt(memories: memory.Memory[]): string[] {
|
|
219
|
+
const prompt = [];
|
|
220
|
+
if (memories.length > 0) {
|
|
221
|
+
prompt.push(`Here are some related memories in decreasing relevance order:`);
|
|
222
|
+
for (const memory of memories) {
|
|
223
|
+
prompt.push(' - ' + memory.description);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return prompt;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function previousMessages(
|
|
230
|
+
ctx: ActionCtx,
|
|
231
|
+
worldId: Id<'worlds'>,
|
|
232
|
+
player: { id: string; name: string },
|
|
233
|
+
otherPlayer: { id: string; name: string },
|
|
234
|
+
conversationId: GameId<'conversations'>,
|
|
235
|
+
) {
|
|
236
|
+
const llmMessages: LLMMessage[] = [];
|
|
237
|
+
const prevMessages = await ctx.runQuery(api.messages.listMessages, { worldId, conversationId });
|
|
238
|
+
for (const message of prevMessages) {
|
|
239
|
+
const author = message.author === player.id ? player : otherPlayer;
|
|
240
|
+
const recipient = message.author === player.id ? otherPlayer : player;
|
|
241
|
+
llmMessages.push({
|
|
242
|
+
role: 'user',
|
|
243
|
+
content: `${author.name} to ${recipient.name}: ${message.text}`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return llmMessages;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export const queryPromptData = internalQuery({
|
|
250
|
+
args: {
|
|
251
|
+
worldId: v.id('worlds'),
|
|
252
|
+
playerId,
|
|
253
|
+
otherPlayerId: playerId,
|
|
254
|
+
conversationId,
|
|
255
|
+
},
|
|
256
|
+
handler: async (ctx, args) => {
|
|
257
|
+
const world = await ctx.db.get(args.worldId);
|
|
258
|
+
if (!world) {
|
|
259
|
+
throw new Error(`World ${args.worldId} not found`);
|
|
260
|
+
}
|
|
261
|
+
const player = world.players.find((p) => p.id === args.playerId);
|
|
262
|
+
if (!player) {
|
|
263
|
+
throw new Error(`Player ${args.playerId} not found`);
|
|
264
|
+
}
|
|
265
|
+
const playerDescription = await ctx.db
|
|
266
|
+
.query('playerDescriptions')
|
|
267
|
+
.withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('playerId', args.playerId))
|
|
268
|
+
.first();
|
|
269
|
+
if (!playerDescription) {
|
|
270
|
+
throw new Error(`Player description for ${args.playerId} not found`);
|
|
271
|
+
}
|
|
272
|
+
const otherPlayer = world.players.find((p) => p.id === args.otherPlayerId);
|
|
273
|
+
if (!otherPlayer) {
|
|
274
|
+
throw new Error(`Player ${args.otherPlayerId} not found`);
|
|
275
|
+
}
|
|
276
|
+
const otherPlayerDescription = await ctx.db
|
|
277
|
+
.query('playerDescriptions')
|
|
278
|
+
.withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('playerId', args.otherPlayerId))
|
|
279
|
+
.first();
|
|
280
|
+
if (!otherPlayerDescription) {
|
|
281
|
+
throw new Error(`Player description for ${args.otherPlayerId} not found`);
|
|
282
|
+
}
|
|
283
|
+
const conversation = world.conversations.find((c) => c.id === args.conversationId);
|
|
284
|
+
if (!conversation) {
|
|
285
|
+
throw new Error(`Conversation ${args.conversationId} not found`);
|
|
286
|
+
}
|
|
287
|
+
const agent = world.agents.find((a) => a.playerId === args.playerId);
|
|
288
|
+
if (!agent) {
|
|
289
|
+
throw new Error(`Player ${args.playerId} not found`);
|
|
290
|
+
}
|
|
291
|
+
const agentDescription = await ctx.db
|
|
292
|
+
.query('agentDescriptions')
|
|
293
|
+
.withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('agentId', agent.id))
|
|
294
|
+
.first();
|
|
295
|
+
if (!agentDescription) {
|
|
296
|
+
throw new Error(`Agent description for ${agent.id} not found`);
|
|
297
|
+
}
|
|
298
|
+
const otherAgent = world.agents.find((a) => a.playerId === args.otherPlayerId);
|
|
299
|
+
let otherAgentDescription;
|
|
300
|
+
if (otherAgent) {
|
|
301
|
+
otherAgentDescription = await ctx.db
|
|
302
|
+
.query('agentDescriptions')
|
|
303
|
+
.withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('agentId', otherAgent.id))
|
|
304
|
+
.first();
|
|
305
|
+
if (!otherAgentDescription) {
|
|
306
|
+
throw new Error(`Agent description for ${otherAgent.id} not found`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const lastTogether = await ctx.db
|
|
310
|
+
.query('participatedTogether')
|
|
311
|
+
.withIndex('edge', (q) =>
|
|
312
|
+
q
|
|
313
|
+
.eq('worldId', args.worldId)
|
|
314
|
+
.eq('player1', args.playerId)
|
|
315
|
+
.eq('player2', args.otherPlayerId),
|
|
316
|
+
)
|
|
317
|
+
// Order by conversation end time descending.
|
|
318
|
+
.order('desc')
|
|
319
|
+
.first();
|
|
320
|
+
|
|
321
|
+
let lastConversation = null;
|
|
322
|
+
if (lastTogether) {
|
|
323
|
+
lastConversation = await ctx.db
|
|
324
|
+
.query('archivedConversations')
|
|
325
|
+
.withIndex('worldId', (q) =>
|
|
326
|
+
q.eq('worldId', args.worldId).eq('id', lastTogether.conversationId),
|
|
327
|
+
)
|
|
328
|
+
.first();
|
|
329
|
+
if (!lastConversation) {
|
|
330
|
+
throw new Error(`Conversation ${lastTogether.conversationId} not found`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
player: { name: playerDescription.name, ...player },
|
|
335
|
+
otherPlayer: { name: otherPlayerDescription.name, ...otherPlayer },
|
|
336
|
+
conversation,
|
|
337
|
+
agent: { identity: agentDescription.identity, plan: agentDescription.plan, ...agent },
|
|
338
|
+
otherAgent: otherAgent && {
|
|
339
|
+
identity: otherAgentDescription!.identity,
|
|
340
|
+
plan: otherAgentDescription!.plan,
|
|
341
|
+
...otherAgent,
|
|
342
|
+
},
|
|
343
|
+
lastConversation,
|
|
344
|
+
};
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
function stopWords(otherPlayer: string, player: string) {
|
|
349
|
+
// These are the words we ask the LLM to stop on. OpenAI only supports 4.
|
|
350
|
+
const variants = [`${otherPlayer} to ${player}`];
|
|
351
|
+
return variants.flatMap((stop) => [stop + ':', stop.toLowerCase() + ':']);
|
|
352
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { v } from 'convex/values';
|
|
2
|
+
import { ActionCtx, internalMutation, internalQuery } from '../_generated/server';
|
|
3
|
+
import { internal } from '../_generated/api';
|
|
4
|
+
import { Id } from '../_generated/dataModel';
|
|
5
|
+
import { fetchEmbeddingBatch } from '../util/llm';
|
|
6
|
+
|
|
7
|
+
const selfInternal = internal.agent.embeddingsCache;
|
|
8
|
+
|
|
9
|
+
export async function fetch(ctx: ActionCtx, text: string) {
|
|
10
|
+
const result = await fetchBatch(ctx, [text]);
|
|
11
|
+
return result.embeddings[0];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function fetchBatch(ctx: ActionCtx, texts: string[]) {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
|
|
17
|
+
const textHashes = await Promise.all(texts.map((text) => hashText(text)));
|
|
18
|
+
const results = new Array<number[]>(texts.length);
|
|
19
|
+
const cacheResults = await ctx.runQuery(selfInternal.getEmbeddingsByText, {
|
|
20
|
+
textHashes,
|
|
21
|
+
});
|
|
22
|
+
for (const { index, embedding } of cacheResults) {
|
|
23
|
+
results[index] = embedding;
|
|
24
|
+
}
|
|
25
|
+
const toWrite = [];
|
|
26
|
+
if (cacheResults.length < texts.length) {
|
|
27
|
+
const missingIndexes = [...results.keys()].filter((i) => !results[i]);
|
|
28
|
+
const missingTexts = missingIndexes.map((i) => texts[i]);
|
|
29
|
+
const response = await fetchEmbeddingBatch(missingTexts);
|
|
30
|
+
if (response.embeddings.length !== missingIndexes.length) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Expected ${missingIndexes.length} embeddings, got ${response.embeddings.length}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
for (let i = 0; i < missingIndexes.length; i++) {
|
|
36
|
+
const resultIndex = missingIndexes[i];
|
|
37
|
+
toWrite.push({
|
|
38
|
+
textHash: textHashes[resultIndex],
|
|
39
|
+
embedding: response.embeddings[i],
|
|
40
|
+
});
|
|
41
|
+
results[resultIndex] = response.embeddings[i];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (toWrite.length > 0) {
|
|
45
|
+
await ctx.runMutation(selfInternal.writeEmbeddings, { embeddings: toWrite });
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
embeddings: results,
|
|
49
|
+
hits: cacheResults.length,
|
|
50
|
+
ms: Date.now() - start,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function hashText(text: string) {
|
|
55
|
+
const textEncoder = new TextEncoder();
|
|
56
|
+
const buf = textEncoder.encode(text);
|
|
57
|
+
if (typeof crypto === 'undefined') {
|
|
58
|
+
// Ugly, ugly hax to get ESBuild to not try to bundle this node dependency.
|
|
59
|
+
const f = () => 'node:crypto';
|
|
60
|
+
const crypto = (await import(f())) as typeof import('crypto');
|
|
61
|
+
const hash = crypto.createHash('sha256');
|
|
62
|
+
hash.update(buf);
|
|
63
|
+
return hash.digest().buffer;
|
|
64
|
+
} else {
|
|
65
|
+
return await crypto.subtle.digest('SHA-256', buf);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const getEmbeddingsByText = internalQuery({
|
|
70
|
+
args: { textHashes: v.array(v.bytes()) },
|
|
71
|
+
handler: async (
|
|
72
|
+
ctx,
|
|
73
|
+
args,
|
|
74
|
+
): Promise<{ index: number; embeddingId: Id<'embeddingsCache'>; embedding: number[] }[]> => {
|
|
75
|
+
const out = [];
|
|
76
|
+
for (let i = 0; i < args.textHashes.length; i++) {
|
|
77
|
+
const textHash = args.textHashes[i];
|
|
78
|
+
const result = await ctx.db
|
|
79
|
+
.query('embeddingsCache')
|
|
80
|
+
.withIndex('text', (q) => q.eq('textHash', textHash))
|
|
81
|
+
.first();
|
|
82
|
+
if (result) {
|
|
83
|
+
out.push({
|
|
84
|
+
index: i,
|
|
85
|
+
embeddingId: result._id,
|
|
86
|
+
embedding: result.embedding,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const writeEmbeddings = internalMutation({
|
|
95
|
+
args: {
|
|
96
|
+
embeddings: v.array(
|
|
97
|
+
v.object({
|
|
98
|
+
textHash: v.bytes(),
|
|
99
|
+
embedding: v.array(v.float64()),
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
102
|
+
},
|
|
103
|
+
handler: async (ctx, args): Promise<Id<'embeddingsCache'>[]> => {
|
|
104
|
+
const ids = [];
|
|
105
|
+
for (const embedding of args.embeddings) {
|
|
106
|
+
ids.push(await ctx.db.insert('embeddingsCache', embedding));
|
|
107
|
+
}
|
|
108
|
+
return ids;
|
|
109
|
+
},
|
|
110
|
+
});
|