feat(ai): add atm10 expert agent
This commit is contained in:
parent
04983f97c5
commit
5a950de0ba
30
.opencode/agent-context/atm10-expert/INDEX.md
Normal file
30
.opencode/agent-context/atm10-expert/INDEX.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# ATM10 Expert Context Index
|
||||||
|
|
||||||
|
Local context for the `atm10-expert` opencode agent.
|
||||||
|
|
||||||
|
## Target Pack
|
||||||
|
|
||||||
|
- ATM10 / All The Mods 10 version `7.0` (`0.7.0` is accepted as a user alias).
|
||||||
|
- Minecraft `1.21.1`.
|
||||||
|
- NeoForge `21.1.228`.
|
||||||
|
- CurseForge project id `925200`.
|
||||||
|
- Client pack file id `8091114`.
|
||||||
|
- Server pack file id `8094893`.
|
||||||
|
- Source: [AllTheMods/ATM-10](https://github.com/AllTheMods/ATM-10).
|
||||||
|
|
||||||
|
## Local Glossaries
|
||||||
|
|
||||||
|
- [CC:Tweaked glossary](glossaries/cc_glossary.md) - globals, modules, peripherals, events, and guides.
|
||||||
|
- [Advanced Peripherals glossary](glossaries/advanced_peripherals_glossary.md) - Advanced Peripherals 0.7 guides, peripherals, turtles, integrations, and changelog pages.
|
||||||
|
- [Create CC:Tweaked glossary](glossaries/create_cc_tweaked_glossary.md) - Create CC:Tweaked integration pages.
|
||||||
|
|
||||||
|
## Modpack Notes
|
||||||
|
|
||||||
|
- [ATM10 7.0 modpack](modpacks/atm10-7.0.md) - pack metadata and generated CurseForge mod ids.
|
||||||
|
|
||||||
|
## Update Workflow
|
||||||
|
|
||||||
|
- Use local glossaries first when answering in-game questions.
|
||||||
|
- Use web lookup when local context is missing, stale, or too shallow.
|
||||||
|
- When a useful durable documentation source is found, update or create a glossary and update this index.
|
||||||
|
- Refresh the mod id list with `./.opencode/agent-context/atm10-expert/scripts/fetch-atm10-7.0-mods.sh`.
|
||||||
20
.opencode/agent-context/atm10-expert/modpacks/atm10-7.0.md
Normal file
20
.opencode/agent-context/atm10-expert/modpacks/atm10-7.0.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# ATM10 7.0 Modpack
|
||||||
|
|
||||||
|
Known facts for ATM10 / All The Mods 10 version `7.0`.
|
||||||
|
|
||||||
|
## Pack Metadata
|
||||||
|
|
||||||
|
- Minecraft: `1.21.1`.
|
||||||
|
- NeoForge: `21.1.228`.
|
||||||
|
- CurseForge project id: `925200`.
|
||||||
|
- Client pack file id: `8091114`.
|
||||||
|
- Server pack file id: `8094893`.
|
||||||
|
- Source: [AllTheMods/ATM-10](https://github.com/AllTheMods/ATM-10).
|
||||||
|
|
||||||
|
## Mod List
|
||||||
|
|
||||||
|
The mod list is generated by [`../scripts/fetch-atm10-7.0-mods.sh`](../scripts/fetch-atm10-7.0-mods.sh).
|
||||||
|
|
||||||
|
Run it with `CURSEFORGE_API_KEY` to resolve project names through the CurseForge API. Without an API key, the generated table preserves raw project/file ids and API URLs for later enrichment.
|
||||||
|
|
||||||
|
Generated list: not fetched yet.
|
||||||
93
.opencode/agent-context/atm10-expert/scripts/fetch-atm10-7.0-mods.sh
Executable file
93
.opencode/agent-context/atm10-expert/scripts/fetch-atm10-7.0-mods.sh
Executable file
@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_ID=925200
|
||||||
|
FILE_ID=8091114
|
||||||
|
SERVER_FILE_ID=8094893
|
||||||
|
NEOFORGE_VERSION=21.1.228
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONTEXT_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
OUTPUT="${CONTEXT_DIR}/modpacks/atm10-7.0.md"
|
||||||
|
TMP_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "${TMP_DIR}"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||||||
|
printf 'missing required command: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_cmd curl
|
||||||
|
require_cmd jq
|
||||||
|
require_cmd unzip
|
||||||
|
|
||||||
|
ARCHIVE="${TMP_DIR}/atm10-${FILE_ID}.zip"
|
||||||
|
MANIFEST="${TMP_DIR}/manifest.json"
|
||||||
|
PROJECTS="${TMP_DIR}/projects.json"
|
||||||
|
|
||||||
|
download_url=''
|
||||||
|
if [ -n "${CURSEFORGE_API_KEY:-}" ]; then
|
||||||
|
download_url="$(curl -fsS \
|
||||||
|
-H "x-api-key: ${CURSEFORGE_API_KEY}" \
|
||||||
|
"https://api.curseforge.com/v1/mods/${PROJECT_ID}/files/${FILE_ID}/download-url" \
|
||||||
|
| jq -r '.data // empty')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${download_url}" ]; then
|
||||||
|
curl -fL --retry 3 -o "${ARCHIVE}" "${download_url}"
|
||||||
|
else
|
||||||
|
curl -fL --retry 3 -o "${ARCHIVE}" \
|
||||||
|
"https://www.curseforge.com/api/v1/mods/${PROJECT_ID}/files/${FILE_ID}/download"
|
||||||
|
fi
|
||||||
|
|
||||||
|
unzip -p "${ARCHIVE}" manifest.json > "${MANIFEST}"
|
||||||
|
|
||||||
|
if [ -n "${CURSEFORGE_API_KEY:-}" ]; then
|
||||||
|
mod_ids="$(jq -c '[.files[].projectID] | unique' "${MANIFEST}")"
|
||||||
|
curl -fsS \
|
||||||
|
-H "x-api-key: ${CURSEFORGE_API_KEY}" \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d "{\"modIds\":${mod_ids}}" \
|
||||||
|
'https://api.curseforge.com/v1/mods' > "${PROJECTS}"
|
||||||
|
else
|
||||||
|
printf '{"data":[]}\n' > "${PROJECTS}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '# ATM10 7.0 Modpack\n\n'
|
||||||
|
printf 'Generated from CurseForge client file `%s`.\n\n' "${FILE_ID}"
|
||||||
|
printf 'Last generated: `%s`.\n\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||||
|
printf '## Pack Metadata\n\n'
|
||||||
|
printf -- '- Minecraft: `%s`.\n' "$(jq -r '.minecraft.version // "1.21.1"' "${MANIFEST}")"
|
||||||
|
printf -- '- NeoForge: `%s`.\n' "${NEOFORGE_VERSION}"
|
||||||
|
printf -- '- CurseForge project id: `%s`.\n' "${PROJECT_ID}"
|
||||||
|
printf -- '- Client pack file id: `%s`.\n' "${FILE_ID}"
|
||||||
|
printf -- '- Server pack file id: `%s`.\n' "${SERVER_FILE_ID}"
|
||||||
|
printf -- '- Source: [AllTheMods/ATM-10](https://github.com/AllTheMods/ATM-10).\n\n'
|
||||||
|
printf '## Manifest\n\n'
|
||||||
|
jq -r '"- Name: `" + (.name // "unknown") + "`.", "- Version: `" + (.version // "unknown") + "`."' "${MANIFEST}"
|
||||||
|
printf '\n## Mods\n\n'
|
||||||
|
printf '| Project id | File id | Name | Reference |\n'
|
||||||
|
printf '| --- | --- | --- | --- |\n'
|
||||||
|
jq -r --slurpfile projects "${PROJECTS}" '
|
||||||
|
def project($id): (($projects[0].data // [])[]? | select(.id == $id)) // {};
|
||||||
|
def cell: tostring | gsub("\\|"; "\\\\|");
|
||||||
|
.files[]
|
||||||
|
| project(.projectID) as $project
|
||||||
|
| [
|
||||||
|
(.projectID | tostring),
|
||||||
|
(.fileID | tostring),
|
||||||
|
(($project.name // "unresolved") | cell),
|
||||||
|
(($project.links.websiteUrl // ("https://api.curseforge.com/v1/mods/" + (.projectID | tostring))) | cell)
|
||||||
|
]
|
||||||
|
| "| " + join(" | ") + " |"
|
||||||
|
' "${MANIFEST}"
|
||||||
|
} > "${OUTPUT}"
|
||||||
|
|
||||||
|
printf 'wrote %s\n' "${OUTPUT}"
|
||||||
61
.opencode/agent/atm10-expert.md
Normal file
61
.opencode/agent/atm10-expert.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
description: Answers in-game ATM10, ComputerCraft, CC:Tweaked, Advanced Peripherals, Create, and TrapOS user questions with concise player-facing replies; use for programs/ai.lua requests when opencc.agent is atm10-expert.
|
||||||
|
mode: primary
|
||||||
|
permission:
|
||||||
|
"*": deny
|
||||||
|
read: allow
|
||||||
|
glob: allow
|
||||||
|
grep: allow
|
||||||
|
webfetch: allow
|
||||||
|
websearch: allow
|
||||||
|
edit: ask
|
||||||
|
bash:
|
||||||
|
"*": ask
|
||||||
|
"just check": allow
|
||||||
|
computercraft-mcp-bridge_probe-computers: allow
|
||||||
|
computercraft-mcp-bridge_exec-lua: allow
|
||||||
|
---
|
||||||
|
|
||||||
|
You answer players using TrapOS `ai` from inside ATM10 / All The Mods 10.
|
||||||
|
|
||||||
|
Target environment:
|
||||||
|
|
||||||
|
- ATM10 / All The Mods 10 pack version `7.0`; accept `0.7.0` as the same user alias.
|
||||||
|
- Minecraft `1.21.1`.
|
||||||
|
- NeoForge `21.1.228`.
|
||||||
|
- ComputerCraft is CC:Tweaked in the ComputerCraft sandbox, not desktop Lua.
|
||||||
|
|
||||||
|
Response style:
|
||||||
|
|
||||||
|
- Keep replies short for in-game terminals. Prefer 1-4 concise lines.
|
||||||
|
- Default to practical, non-technical answers unless the user asks for details.
|
||||||
|
- Answer in the user's language; usually French when the prompt is French.
|
||||||
|
- When giving commands, make them directly runnable in CraftOS when possible.
|
||||||
|
- When giving code, keep it minimal and runnable. Avoid markdown fences unless the user clearly asks for a code block.
|
||||||
|
- If the prompt asks for raw Lua code only, output raw Lua only with no markdown or explanation.
|
||||||
|
|
||||||
|
Context workflow:
|
||||||
|
|
||||||
|
- Use `.opencode/agent-context/atm10-expert/INDEX.md` first.
|
||||||
|
- Prefer local glossaries and modpack notes before web lookup.
|
||||||
|
- Use web search or fetch only for current external facts, missing mod documentation, or stale local references.
|
||||||
|
- If you find a useful durable mod documentation source, ask before editing, then update or create a glossary and update `INDEX.md`.
|
||||||
|
|
||||||
|
MCP bridge safety:
|
||||||
|
|
||||||
|
- You may use the ComputerCraft MCP bridge only through `probe-computers` and `exec-lua`.
|
||||||
|
- Use `probe-computers` before `exec-lua` unless the target computer id is already clear from the hidden caller context or conversation.
|
||||||
|
- Treat `exec-lua` as privileged in-game execution. Prefer read-only inspection.
|
||||||
|
- Do not delete files, reboot or shut down computers, move turtles, change inventories, transmit network traffic, mutate peripherals, or run long loops unless the user explicitly asks for that specific effect.
|
||||||
|
- Keep `exec-lua` snippets small and bounded. Use short timeouts. Avoid blocking pulls, sleeps, infinite loops, and assumptions that a timeout stops code already running in ComputerCraft.
|
||||||
|
- `print()` and `write()` output is captured in MCP results. To intentionally write to the visible ComputerCraft screen, use terminal APIs such as `term.clear()`, `term.setCursorPos()`, and `term.write()`.
|
||||||
|
|
||||||
|
Caller context:
|
||||||
|
|
||||||
|
- TrapOS may prepend hidden caller context with the ComputerCraft computer id and label.
|
||||||
|
- Use that context to choose the right MCP target, but do not expose it unless it helps the user.
|
||||||
|
|
||||||
|
Repository scope:
|
||||||
|
|
||||||
|
- If the user asks for repo internals, answer only what is needed to unblock them.
|
||||||
|
- This agent is explicit and player-facing. Do not assume it is the default coding assistant.
|
||||||
@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
description: Answers in-game ComputerCraft and TrapOS user questions with concise, actionable replies; use for programs/ai.lua requests.
|
|
||||||
mode: primary
|
|
||||||
permission:
|
|
||||||
"*": deny
|
|
||||||
websearch: allow
|
|
||||||
computercraft-mcp-bridge_probe-computers: allow
|
|
||||||
computercraft-mcp-bridge_exec-lua: allow
|
|
||||||
---
|
|
||||||
|
|
||||||
You answer ComputerCraft / CC:Tweaked users from inside Minecraft through TrapOS `ai`.
|
|
||||||
|
|
||||||
Keep replies extremely concise. Prefer 1-4 short lines. Avoid long explanations, tables, and broad background.
|
|
||||||
|
|
||||||
Assume the user is reading on an in-game ComputerCraft terminal with limited space.
|
|
||||||
|
|
||||||
Use ComputerCraft Lua and CC:Tweaked behavior, not standard desktop Lua, unless explicitly asked otherwise.
|
|
||||||
|
|
||||||
When giving commands, make them directly runnable in CraftOS when possible.
|
|
||||||
|
|
||||||
When giving code, keep it minimal and runnable. Avoid markdown fences unless the user clearly asks for a code block.
|
|
||||||
|
|
||||||
You may use web search for current external facts and documentation. Keep searches focused and summarize only what unblocks the user.
|
|
||||||
|
|
||||||
You may use the ComputerCraft MCP bridge only through `probe-computers` and `exec-lua`.
|
|
||||||
|
|
||||||
Use `probe-computers` before `exec-lua` unless the target computer id is already clear from the conversation.
|
|
||||||
|
|
||||||
Treat `exec-lua` as privileged in-game execution. Prefer read-only inspection. Do not delete files, reboot or shut down computers, move turtles, change inventories, transmit network traffic, mutate peripherals, or run long loops unless the user explicitly asks for that specific effect.
|
|
||||||
|
|
||||||
Keep `exec-lua` snippets small and bounded. Use short timeouts. Avoid blocking pulls, sleeps, infinite loops, and assumptions that a timeout stops code already running in ComputerCraft.
|
|
||||||
|
|
||||||
`print()` and `write()` output is captured in MCP results. To intentionally write to the visible ComputerCraft screen, use terminal APIs such as `term.clear()`, `term.setCursorPos()`, and `term.write()`.
|
|
||||||
|
|
||||||
If the user asks for repo internals, answer only what is needed to unblock them.
|
|
||||||
|
|
||||||
If the prompt asks for raw Lua code only, output raw Lua only with no markdown or explanation.
|
|
||||||
109
ATM_EXPERT_AGENT_PLAN.md
Normal file
109
ATM_EXPERT_AGENT_PLAN.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# ATM Expert Agent Plan
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Replace `.opencode/agent/computercraft.md` with `atm10-expert`.
|
||||||
|
|
||||||
|
The agent targets ATM10 / All The Mods 10, Minecraft `1.21.1`, NeoForge, pack version `7.0` (`0.7.0` accepted as user alias).
|
||||||
|
|
||||||
|
## Known Facts
|
||||||
|
|
||||||
|
CurseForge project id: `925200`.
|
||||||
|
|
||||||
|
ATM10 `7.0` file id: `8091114`.
|
||||||
|
|
||||||
|
Server pack file id: `8094893`.
|
||||||
|
|
||||||
|
NeoForge version: `21.1.228`.
|
||||||
|
|
||||||
|
GitHub source: <https://github.com/AllTheMods/ATM-10>
|
||||||
|
|
||||||
|
## Files To Create
|
||||||
|
|
||||||
|
Create `.opencode/agent/atm10-expert.md`.
|
||||||
|
|
||||||
|
Create `.opencode/agent-context/atm10-expert/INDEX.md`.
|
||||||
|
|
||||||
|
Create `.opencode/agent-context/atm10-expert/glossaries/`.
|
||||||
|
|
||||||
|
Create `.opencode/agent-context/atm10-expert/scripts/`.
|
||||||
|
|
||||||
|
Create `.opencode/agent-context/atm10-expert/modpacks/atm10-7.0.md`.
|
||||||
|
|
||||||
|
## Files To Move
|
||||||
|
|
||||||
|
Move in-game glossaries only:
|
||||||
|
|
||||||
|
`docs/cc_glossary.md` to agent context.
|
||||||
|
|
||||||
|
`docs/create_cc_tweaked_glossary.md` to agent context.
|
||||||
|
|
||||||
|
`docs/advanced_peripherals_glossary.md` to agent context.
|
||||||
|
|
||||||
|
Keep `docs/craftos_pc_glossary.md` in `docs/`.
|
||||||
|
|
||||||
|
Update `docs/README.md` links.
|
||||||
|
|
||||||
|
## Agent Behavior
|
||||||
|
|
||||||
|
Delete old `computercraft` agent.
|
||||||
|
|
||||||
|
New agent answers players concisely for in-game terminals.
|
||||||
|
|
||||||
|
Use local glossaries first, then websearch/webfetch.
|
||||||
|
|
||||||
|
Default to non-technical answers.
|
||||||
|
|
||||||
|
Adapt language to the user, usually French.
|
||||||
|
|
||||||
|
If a new useful mod documentation source is found, update or create a glossary and update `INDEX.md`.
|
||||||
|
|
||||||
|
Keep replies short unless the user asks for detail.
|
||||||
|
|
||||||
|
Use MCP bridge carefully: probe first, execute only bounded/read-only Lua unless explicitly asked.
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
Allow local read/search and web lookup.
|
||||||
|
|
||||||
|
Keep MCP bridge `probe-computers` and `exec-lua`.
|
||||||
|
|
||||||
|
Make edit/bash approval-gated or tightly limited, because this agent is player-facing.
|
||||||
|
|
||||||
|
Allow `just check` for markdown validation.
|
||||||
|
|
||||||
|
## ATM10 Mod List Script
|
||||||
|
|
||||||
|
Add `.opencode/agent-context/atm10-expert/scripts/fetch-atm10-7.0-mods.sh`.
|
||||||
|
|
||||||
|
Script should download CurseForge file `8091114`.
|
||||||
|
|
||||||
|
Extract `manifest.json`.
|
||||||
|
|
||||||
|
Write raw project/file ids and best available names to `modpacks/atm10-7.0.md`.
|
||||||
|
|
||||||
|
If `CURSEFORGE_API_KEY` exists, resolve project names through the CurseForge API.
|
||||||
|
|
||||||
|
Without API key, preserve ids and URLs for later glossary enrichment.
|
||||||
|
|
||||||
|
## TrapOS AI Context
|
||||||
|
|
||||||
|
Update `programs/ai.lua` / `apis/libai.lua` so each prompt includes hidden caller context:
|
||||||
|
|
||||||
|
computer id.
|
||||||
|
|
||||||
|
computer label, if any.
|
||||||
|
|
||||||
|
This helps `atm10-expert` know which in-game computer is calling.
|
||||||
|
|
||||||
|
Add tests in `tests/ai.lua`.
|
||||||
|
|
||||||
|
Bump `packages/trapos-ai/ccpm.json` and `packages/index.json`.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Run `just check`.
|
||||||
|
|
||||||
|
Fix Lua lint and markdown link issues.
|
||||||
|
|
||||||
|
Restart opencode after agent/config changes.
|
||||||
@ -165,6 +165,35 @@ local function buildLuaOutputPrompt(userPrompt, output)
|
|||||||
}, '\n');
|
}, '\n');
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function readOsValue(osLib, name)
|
||||||
|
if type(osLib) ~= 'table' or type(osLib[name]) ~= 'function' then
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
local ok, value = pcall(osLib[name]);
|
||||||
|
if not ok then return nil; end
|
||||||
|
return value;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function buildPromptWithCallerContext(prompt, osLib)
|
||||||
|
local lines = {
|
||||||
|
'<caller-context hidden="true">',
|
||||||
|
'Use this context silently to identify the in-game ComputerCraft caller.',
|
||||||
|
};
|
||||||
|
local computerId = readOsValue(osLib, 'getComputerID');
|
||||||
|
local computerLabel = readOsValue(osLib, 'getComputerLabel');
|
||||||
|
if computerId ~= nil then
|
||||||
|
lines[#lines + 1] = 'computer id: ' .. tostring(computerId);
|
||||||
|
end
|
||||||
|
if not isBlank(computerLabel) then
|
||||||
|
lines[#lines + 1] = 'computer label: ' .. tostring(computerLabel);
|
||||||
|
end
|
||||||
|
lines[#lines + 1] = '</caller-context>';
|
||||||
|
lines[#lines + 1] = '';
|
||||||
|
lines[#lines + 1] = 'User prompt:';
|
||||||
|
lines[#lines + 1] = prompt;
|
||||||
|
return table.concat(lines, '\n');
|
||||||
|
end
|
||||||
|
|
||||||
local function sessionTime(session)
|
local function sessionTime(session)
|
||||||
if type(session) ~= 'table' or type(session.time) ~= 'table' then
|
if type(session) ~= 'table' or type(session.time) ~= 'table' then
|
||||||
return 0;
|
return 0;
|
||||||
@ -179,6 +208,7 @@ local function createAi(opts)
|
|||||||
local settingsLib = opts.settings or settings;
|
local settingsLib = opts.settings or settings;
|
||||||
local eventloopFactory = opts.eventloop or require('/apis/eventloop');
|
local eventloopFactory = opts.eventloop or require('/apis/eventloop');
|
||||||
local nowFunc = opts.now or nowSeconds;
|
local nowFunc = opts.now or nowSeconds;
|
||||||
|
local osLib = opts.os or os;
|
||||||
|
|
||||||
local api = {};
|
local api = {};
|
||||||
|
|
||||||
@ -566,15 +596,20 @@ local function createAi(opts)
|
|||||||
log('reusing session ' .. sessionId);
|
log('reusing session ' .. sessionId);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local promptWithContext = prompt;
|
||||||
|
if options.includeCallerContext ~= false then
|
||||||
|
promptWithContext = buildPromptWithCallerContext(prompt, osLib);
|
||||||
|
end
|
||||||
|
|
||||||
if not (cfg.providerID and cfg.modelID) then
|
if not (cfg.providerID and cfg.modelID) then
|
||||||
log('provider/model unset; using blocking message endpoint');
|
log('provider/model unset; using blocking message endpoint');
|
||||||
return askBlocking(cfg, sessionId, prompt, persist, sessionSettingKey, log);
|
return askBlocking(cfg, sessionId, promptWithContext, persist, sessionSettingKey, log);
|
||||||
end
|
end
|
||||||
|
|
||||||
local messageId = options.messageId or createMessageId();
|
local messageId = options.messageId or createMessageId();
|
||||||
log('sending async prompt ' .. messageId);
|
log('sending async prompt ' .. messageId);
|
||||||
local body, code = doPost(cfg, '/session/' .. sessionId .. '/prompt_async',
|
local body, code = doPost(cfg, '/session/' .. sessionId .. '/prompt_async',
|
||||||
buildPromptBody(cfg, messageId, prompt));
|
buildPromptBody(cfg, messageId, promptWithContext));
|
||||||
if not body then return false, code; end
|
if not body then return false, code; end
|
||||||
if code == 404 then
|
if code == 404 then
|
||||||
return handleMissingSession(persist, sessionSettingKey);
|
return handleMissingSession(persist, sessionSettingKey);
|
||||||
@ -760,6 +795,8 @@ local function createAi(opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function api.ping(options)
|
function api.ping(options)
|
||||||
|
options = options or {};
|
||||||
|
options.includeCallerContext = false;
|
||||||
return api.ask(PING_PROMPT, options);
|
return api.ask(PING_PROMPT, options);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,8 @@ Start here when looking up ComputerCraft-related APIs, CraftOS-PC behavior, peri
|
|||||||
|
|
||||||
## Indexes
|
## Indexes
|
||||||
|
|
||||||
- [`cc_glossary.md`](cc_glossary.md) - CC:Tweaked globals, modules, peripherals, events, and guides.
|
|
||||||
- [`craftos_pc_glossary.md`](craftos_pc_glossary.md) - CraftOS-PC emulator setup, CLI flags, mounting, peripheral emulation, troubleshooting, and related references.
|
- [`craftos_pc_glossary.md`](craftos_pc_glossary.md) - CraftOS-PC emulator setup, CLI flags, mounting, peripheral emulation, troubleshooting, and related references.
|
||||||
- [`advanced_peripherals_glossary.md`](advanced_peripherals_glossary.md) - Advanced Peripherals 0.7 guides, peripherals, turtles, integrations, and changelog pages.
|
- [`../.opencode/agent-context/atm10-expert/INDEX.md`](../.opencode/agent-context/atm10-expert/INDEX.md) - ATM10 in-game agent context, including CC:Tweaked, Advanced Peripherals, and Create CC:Tweaked glossaries.
|
||||||
- [`create_cc_tweaked_glossary.md`](create_cc_tweaked_glossary.md) - Create CC:Tweaked integration pages.
|
|
||||||
- [`opencode_server_guide.md`](opencode_server_guide.md) - Running `opencode serve` for the TrapOS `ai` client.
|
- [`opencode_server_guide.md`](opencode_server_guide.md) - Running `opencode serve` for the TrapOS `ai` client.
|
||||||
- [`ingame-trapos-ai-mcp-guide.md`](ingame-trapos-ai-mcp-guide.md) - Concise in-game checklist for installing TrapOS, connecting `ai`, and linking MCP.
|
- [`ingame-trapos-ai-mcp-guide.md`](ingame-trapos-ai-mcp-guide.md) - Concise in-game checklist for installing TrapOS, connecting `ai`, and linking MCP.
|
||||||
- [`opencode_api.md`](opencode_api.md) - Minimal opencode HTTP API reference used by TrapOS.
|
- [`opencode_api.md`](opencode_api.md) - Minimal opencode HTTP API reference used by TrapOS.
|
||||||
|
|||||||
@ -52,7 +52,7 @@ set opencc.model_id claude-opus-4-7
|
|||||||
Optional agent setting for the in-game ComputerCraft assistant:
|
Optional agent setting for the in-game ComputerCraft assistant:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
set opencc.agent computercraft
|
set opencc.agent atm10-expert
|
||||||
```
|
```
|
||||||
|
|
||||||
Test it:
|
Test it:
|
||||||
|
|||||||
@ -88,7 +88,7 @@ Send a message and wait for the AI reply (blocking). Returns when the assistant
|
|||||||
"parts": [
|
"parts": [
|
||||||
{ "type": "text", "text": "your prompt here" }
|
{ "type": "text", "text": "your prompt here" }
|
||||||
],
|
],
|
||||||
"agent": "computercraft",
|
"agent": "atm10-expert",
|
||||||
"model": { "providerID": "anthropic", "modelID": "claude-opus-4-7" }
|
"model": { "providerID": "anthropic", "modelID": "claude-opus-4-7" }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -153,7 +153,7 @@ Fire-and-forget variant. Returns `204` immediately. Include `messageID` in the r
|
|||||||
"parts": [
|
"parts": [
|
||||||
{ "type": "text", "text": "your prompt here" }
|
{ "type": "text", "text": "your prompt here" }
|
||||||
],
|
],
|
||||||
"agent": "computercraft",
|
"agent": "atm10-expert",
|
||||||
"model": { "providerID": "anthropic", "modelID": "claude-opus-4-7" }
|
"model": { "providerID": "anthropic", "modelID": "claude-opus-4-7" }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@ -107,7 +107,7 @@ set opencc.username myuser
|
|||||||
Optional — select an opencode agent for requests from this computer:
|
Optional — select an opencode agent for requests from this computer:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
set opencc.agent computercraft
|
set opencc.agent atm10-expert
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional — scope `ai sessions` to a specific opencode project directory. If omitted, `ai sessions` falls back to the directory recorded on the saved `opencc.session_id` when the unscoped list is empty:
|
Optional — scope `ai sessions` to a specific opencode project directory. If omitted, `ai sessions` falls back to the directory recorded on the saved `opencc.session_id` when the unscoped list is empty:
|
||||||
@ -144,7 +144,7 @@ ai ping -- ping, reuses existing session
|
|||||||
ai "explain what a turtle is"
|
ai "explain what a turtle is"
|
||||||
ai new "start a fresh topic" -- forget current session, start fresh
|
ai new "start a fresh topic" -- forget current session, start fresh
|
||||||
ai sessions -- list all server sessions with their IDs
|
ai sessions -- list all server sessions with their IDs
|
||||||
ai --agent computercraft "what peripherals are attached?"
|
ai --agent atm10-expert "what peripherals are attached?"
|
||||||
ai --verbose ping -- show HTTP/session diagnostics
|
ai --verbose ping -- show HTTP/session diagnostics
|
||||||
ai --help
|
ai --help
|
||||||
ai --version
|
ai --version
|
||||||
|
|||||||
@ -49,7 +49,7 @@ local function printUsage()
|
|||||||
print(' opencc.password (Basic Auth password)');
|
print(' opencc.password (Basic Auth password)');
|
||||||
print(' opencc.session_id (auto-managed)');
|
print(' opencc.session_id (auto-managed)');
|
||||||
print(' opencc.directory (optional session list scope)');
|
print(' opencc.directory (optional session list scope)');
|
||||||
print(' opencc.agent (e.g. computercraft)');
|
print(' opencc.agent (e.g. atm10-expert)');
|
||||||
print(' opencc.provider_id (e.g. anthropic)');
|
print(' opencc.provider_id (e.g. anthropic)');
|
||||||
print(' opencc.model_id (e.g. claude-opus-4-7)');
|
print(' opencc.model_id (e.g. claude-opus-4-7)');
|
||||||
print(' opencc.timeout_seconds (per HTTP call, max 60)');
|
print(' opencc.timeout_seconds (per HTTP call, max 60)');
|
||||||
|
|||||||
82
tests/ai.lua
82
tests/ai.lua
@ -21,6 +21,13 @@ local function fakeSettings(initial)
|
|||||||
};
|
};
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function fakeOs(computerId, computerLabel)
|
||||||
|
return {
|
||||||
|
getComputerID = function() return computerId; end,
|
||||||
|
getComputerLabel = function() return computerLabel; end,
|
||||||
|
};
|
||||||
|
end
|
||||||
|
|
||||||
local function fakeAsyncSettings(initial)
|
local function fakeAsyncSettings(initial)
|
||||||
local values = {
|
local values = {
|
||||||
['opencc.server_url'] = 'http://host',
|
['opencc.server_url'] = 'http://host',
|
||||||
@ -429,7 +436,7 @@ testlib.test('ask falls back to blocking message when model is unset', function(
|
|||||||
testlib.assertEquals(#httpStub.getCalls, 0);
|
testlib.assertEquals(#httpStub.getCalls, 0);
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('ask sends exact prompt text', function()
|
testlib.test('ask wraps prompt with caller context', function()
|
||||||
local httpStub = fakeHttp(
|
local httpStub = fakeHttp(
|
||||||
{ messageResp('reply') },
|
{ messageResp('reply') },
|
||||||
{}
|
{}
|
||||||
@ -438,14 +445,43 @@ testlib.test('ask sends exact prompt text', function()
|
|||||||
['opencc.server_url'] = 'http://host',
|
['opencc.server_url'] = 'http://host',
|
||||||
['opencc.session_id'] = 'ses_1',
|
['opencc.session_id'] = 'ses_1',
|
||||||
});
|
});
|
||||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
local ai = createAi({
|
||||||
|
http = httpStub,
|
||||||
|
settings = settingsStub,
|
||||||
|
os = fakeOs(42, 'storage-main'),
|
||||||
|
});
|
||||||
|
|
||||||
ai.ask('my prompt');
|
ai.ask('my prompt');
|
||||||
|
|
||||||
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||||
testlib.assertEquals(#body.parts, 1);
|
testlib.assertEquals(#body.parts, 1);
|
||||||
testlib.assertEquals(body.parts[1].type, 'text');
|
testlib.assertEquals(body.parts[1].type, 'text');
|
||||||
testlib.assertEquals(body.parts[1].text, 'my prompt');
|
testlib.assertTrue(string.find(body.parts[1].text, '<caller-context hidden="true">', 1, true) ~= nil);
|
||||||
|
testlib.assertTrue(string.find(body.parts[1].text, 'computer id: 42', 1, true) ~= nil);
|
||||||
|
testlib.assertTrue(string.find(body.parts[1].text, 'computer label: storage-main', 1, true) ~= nil);
|
||||||
|
testlib.assertTrue(string.find(body.parts[1].text, 'User prompt:\nmy prompt', 1, true) ~= nil);
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('ask omits blank caller label', function()
|
||||||
|
local httpStub = fakeHttp(
|
||||||
|
{ messageResp('reply') },
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
local settingsStub = fakeSettings({
|
||||||
|
['opencc.server_url'] = 'http://host',
|
||||||
|
['opencc.session_id'] = 'ses_1',
|
||||||
|
});
|
||||||
|
local ai = createAi({
|
||||||
|
http = httpStub,
|
||||||
|
settings = settingsStub,
|
||||||
|
os = fakeOs(7, ''),
|
||||||
|
});
|
||||||
|
|
||||||
|
ai.ask('my prompt');
|
||||||
|
|
||||||
|
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||||
|
testlib.assertTrue(string.find(body.parts[1].text, 'computer id: 7', 1, true) ~= nil);
|
||||||
|
testlib.assertEquals(string.find(body.parts[1].text, 'computer label:', 1, true), nil);
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('ask includes agent from settings', function()
|
testlib.test('ask includes agent from settings', function()
|
||||||
@ -456,14 +492,14 @@ testlib.test('ask includes agent from settings', function()
|
|||||||
local settingsStub = fakeSettings({
|
local settingsStub = fakeSettings({
|
||||||
['opencc.server_url'] = 'http://host',
|
['opencc.server_url'] = 'http://host',
|
||||||
['opencc.session_id'] = 'ses_1',
|
['opencc.session_id'] = 'ses_1',
|
||||||
['opencc.agent'] = 'computercraft',
|
['opencc.agent'] = 'atm10-expert',
|
||||||
});
|
});
|
||||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||||
|
|
||||||
ai.ask('hello');
|
ai.ask('hello');
|
||||||
|
|
||||||
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||||
testlib.assertEquals(body.agent, 'computercraft');
|
testlib.assertEquals(body.agent, 'atm10-expert');
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('ask option agent overrides settings', function()
|
testlib.test('ask option agent overrides settings', function()
|
||||||
@ -478,10 +514,10 @@ testlib.test('ask option agent overrides settings', function()
|
|||||||
});
|
});
|
||||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||||
|
|
||||||
ai.ask('hello', { agent = 'computercraft' });
|
ai.ask('hello', { agent = 'atm10-expert' });
|
||||||
|
|
||||||
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||||
testlib.assertEquals(body.agent, 'computercraft');
|
testlib.assertEquals(body.agent, 'atm10-expert');
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('ask omits blank agent setting', function()
|
testlib.test('ask omits blank agent setting', function()
|
||||||
@ -624,7 +660,7 @@ testlib.test('ask includes agent in async prompts', function()
|
|||||||
);
|
);
|
||||||
local settingsStub = fakeAsyncSettings({
|
local settingsStub = fakeAsyncSettings({
|
||||||
['opencc.session_id'] = 'ses_1',
|
['opencc.session_id'] = 'ses_1',
|
||||||
['opencc.agent'] = 'computercraft',
|
['opencc.agent'] = 'atm10-expert',
|
||||||
});
|
});
|
||||||
local elFactory = fakeEventloopFactory();
|
local elFactory = fakeEventloopFactory();
|
||||||
local ai = createAi({
|
local ai = createAi({
|
||||||
@ -638,7 +674,35 @@ testlib.test('ask includes agent in async prompts', function()
|
|||||||
|
|
||||||
testlib.assertTrue(ok);
|
testlib.assertTrue(ok);
|
||||||
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||||
testlib.assertEquals(body.agent, 'computercraft');
|
testlib.assertEquals(body.agent, 'atm10-expert');
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('ask includes caller context in async prompts', function()
|
||||||
|
local httpStub = fakeHttp(
|
||||||
|
{ asyncResp() },
|
||||||
|
{
|
||||||
|
messageListResp({ assistantMessage('msg_1', 'reply', true) }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
local settingsStub = fakeAsyncSettings({
|
||||||
|
['opencc.session_id'] = 'ses_1',
|
||||||
|
});
|
||||||
|
local elFactory = fakeEventloopFactory();
|
||||||
|
local ai = createAi({
|
||||||
|
http = httpStub,
|
||||||
|
settings = settingsStub,
|
||||||
|
now = function() return 10; end,
|
||||||
|
eventloop = elFactory,
|
||||||
|
os = fakeOs(99, 'factory'),
|
||||||
|
});
|
||||||
|
|
||||||
|
local ok = ai.ask('status?', { messageId = 'msg_1' });
|
||||||
|
|
||||||
|
testlib.assertTrue(ok);
|
||||||
|
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||||
|
testlib.assertTrue(string.find(body.parts[1].text, 'computer id: 99', 1, true) ~= nil);
|
||||||
|
testlib.assertTrue(string.find(body.parts[1].text, 'computer label: factory', 1, true) ~= nil);
|
||||||
|
testlib.assertTrue(string.find(body.parts[1].text, 'User prompt:\nstatus?', 1, true) ~= nil);
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('ask polls async message until completion', function()
|
testlib.test('ask polls async message until completion', function()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user